/********************************************************************\

  Name:         mu3e_hv2.c
  Created by:   Stefan Ritt


  Contents:     Application specific (user) part of
                Midas Slow Control Bus protocol 
                for Mu3e Positive Hich Current HV box

\********************************************************************/

#include <stdio.h>
#include <stdlib.h>             // for atof()
#include <string.h>
#include <math.h>
#include "mscbemb.h"

extern bit FREEZE_MODE;
extern bit DEBUG_MODE;

char code node_name[] = "HV2";

/* declare number of sub-addresses to framework */
unsigned char idata _n_sub_addr = 1;

bit flush_flag;

sbit SCK         = P0^0;  // Serial Clock (output)
sbit MISO        = P0^1;  // Master In / Slave Out (input)
sbit MOSI        = P0^2;  // Master Out / Slave In (output)

sbit NCS_ADC_CUR = P0^3;  // /CS DAC
sbit NCS_ADC_HV  = P1^3;  // /CS HV ADC
sbit NCS_DAC     = P1^5;  // /CS DAC
#define DIVIDER     ((230000+10000)/(10000))

#define R_SHUNT_N 10000
#define R_SHUNT_P  1000

// delay for opto-coupler in microseconds
#define OPT_DELAY 1

/*---- Define variable parameters returned to CMD_GET_INFO command ----*/

/* data buffer (mirrored in EEPROM) */

struct {
   float hv_0;
   float hv_1;
   float hv_2;
   float hv_3;
   float hv_0_meas;
   float hv_1_meas;
   float hv_2_meas;
   float hv_3_meas;
   float i_0;
   float i_1;
   float i_2;
   float i_3;
   float hv_max;
} xdata user_data;


MSCB_INFO_VAR code vars[] = {
   4, UNIT_VOLT,            0, 0,    MSCBF_FLOAT,                "HV0",     &user_data.hv_0,
   4, UNIT_VOLT,            0, 0,    MSCBF_FLOAT,                "HV1",     &user_data.hv_1,
   4, UNIT_VOLT,            0, 0,    MSCBF_FLOAT,                "HV2",     &user_data.hv_2,
   4, UNIT_VOLT,            0, 0,    MSCBF_FLOAT,                "HV3",     &user_data.hv_3,

   4, UNIT_VOLT,            0, 0,    MSCBF_FLOAT,                "HV0Meas", &user_data.hv_0_meas,
   4, UNIT_VOLT,            0, 0,    MSCBF_FLOAT,                "HV1Meas", &user_data.hv_1_meas,
   4, UNIT_VOLT,            0, 0,    MSCBF_FLOAT,                "HV2Meas", &user_data.hv_2_meas,
   4, UNIT_VOLT,            0, 0,    MSCBF_FLOAT,                "HV3Meas", &user_data.hv_3_meas,
   
   4, UNIT_AMPERE, PRFX_MILLI, 0,    MSCBF_FLOAT,                "I0",      &user_data.i_0,
   4, UNIT_AMPERE, PRFX_MILLI, 0,    MSCBF_FLOAT,                "I1",      &user_data.i_1,
   4, UNIT_AMPERE, PRFX_MILLI, 0,    MSCBF_FLOAT,                "I2",      &user_data.i_2,
   4, UNIT_AMPERE, PRFX_MILLI, 0,    MSCBF_FLOAT,                "I3",      &user_data.i_3,
   
   4, UNIT_VOLT,            0, 0,    MSCBF_FLOAT | MSCBF_HIDDEN, "HVMax",   &user_data.hv_max,
   0
};

MSCB_INFO_VAR *variables = vars;
unsigned char xdata update_data[4];

/********************************************************************\

  Application specific init and inout/output routines

\********************************************************************/

void user_write(unsigned char index) reentrant;
void set_hv(unsigned char channel, float value);

/*---- User init function ------------------------------------------*/

extern SYS_INFO idata sys_info;

void user_init(unsigned char init)
{
   if (init) {
      user_data.hv_max = 60;
   }
   
   user_data.hv_0 = 0;
   user_data.hv_1 = 0;
   user_data.hv_2 = 0;
   user_data.hv_3 = 0;

   NCS_ADC_CUR = 1;  // /CS DAC
   NCS_ADC_HV  = 1;  // /CS HV ADC
   NCS_DAC     = 1;  // /CS DAC

   SFRPAGE = LEGACY_PAGE;
   P0MDOUT = 0x5D; // 0x5D; // SCLK, MOSI, CS_DAC, DI, DE_RE push-pull     0 1 0 1  1 1 0 1
   P1MDOUT = 0x68; // 0x68; // CS_HV_ADC, CS_DAC, LED push-pull            0 1 1 0  1 0 0 0

   update_data[0] = 1;
   update_data[1] = 1;
   update_data[2] = 1;
   update_data[3] = 1;
}


/*---- User write function -----------------------------------------*/

/* buffers in mscbmain.c */
extern unsigned char xdata in_buf[300], out_buf[300];

#pragma NOAREGS

void user_write(unsigned char index) reentrant
{
   if (index == 0) {
      if (user_data.hv_0 < 0)
         user_data.hv_0 = 0;
      if (user_data.hv_0 > user_data.hv_max)
         user_data.hv_0 = user_data.hv_max;
      update_data[0] = 1;
   }

   if (index == 1) {
      if (user_data.hv_1 < 0)
         user_data.hv_1 = 0;
      if (user_data.hv_1 > user_data.hv_max)
         user_data.hv_1 = user_data.hv_max;
      update_data[1] = 1;
   }

   if (index == 2) {
      if (user_data.hv_2 < 0)
         user_data.hv_2 = 0;
      if (user_data.hv_2 > user_data.hv_max)
         user_data.hv_2 = user_data.hv_max;
      update_data[2] = 1;
   }

   if (index == 3) {
      if (user_data.hv_3 < 0)
         user_data.hv_3 = 0;
      if (user_data.hv_3 > user_data.hv_max)
         user_data.hv_3 = user_data.hv_max;
      update_data[3] = 1;
   }

   if (index == 12)
      if (user_data.hv_max > 60)
         user_data.hv_max = 60;
      
}

/*---- User read function ------------------------------------------*/

unsigned char user_read(unsigned char index)
{
   if (index == 0);
   return 0;
}

/*---- User function called vid CMD_USER command -------------------*/

unsigned char user_func(unsigned char *data_in, unsigned char *data_out)
{
   /* echo input data */
   data_out[0] = data_in[0];
   data_out[1] = data_in[1];

   return 2;
}

/*---- DAC functions -----------------------------------------------*/

void write_dac(unsigned short channel, unsigned short value)
{
unsigned char i, m, b;
   
   NCS_DAC = 0; // chip select
   delay_us(OPT_DELAY);
   watchdog_refresh(1);
   
   // command 3: write and update
   b = (channel & 0x03) | (0x03 << 4);
   for (i=0,m=0x80 ; i<8 ; i++) {
      SCK = 0;
      MOSI = (b & m) > 0;
      delay_us(OPT_DELAY);
      SCK = 1;
      delay_us(OPT_DELAY);
      m >>= 1;
      watchdog_refresh(1);
   }

   // MSB
   b = value >> 8;
   for (i=0,m=0x80 ; i<8 ; i++) {
      SCK = 0;
      MOSI = (b & m) > 0;
      delay_us(OPT_DELAY);
      SCK = 1;
      delay_us(OPT_DELAY);
      m >>= 1;
      watchdog_refresh(1);
   }

   // LSB
   b = value & 0xFF;
   for (i=0,m=0x80 ; i<8 ; i++) {
      SCK = 0;
      MOSI = (b & m) > 0;
      delay_us(OPT_DELAY);
      SCK = 1;
      delay_us(OPT_DELAY);
      m >>= 1;
      watchdog_refresh(1);
   }

   NCS_DAC = 1; // remove CS
   delay_us(OPT_DELAY);
}

/*---- HV ADC functions --------------------------------------------*/

unsigned char ltc2486_cmd[] = {
   0xB0,
   0xB8,
   0xB1,
   0xB9
};

unsigned char read_adc_hv(float *v)
{
static unsigned char channel = 0;
unsigned char i, cmd1, cmd2, sign;
unsigned long d;
float f;
static unsigned int last = 0;

   // only measure once every 250 ms
   if (time() < last+25)
      return 0;
   last = time();
   
   SCK = 0;
   MOSI = 1;
   NCS_ADC_HV = 0; // chip select
   delay_us(OPT_DELAY);
   watchdog_refresh(1);
   
   if (MISO == 1) { // check /EOC
      NCS_ADC_HV = 1; // remove chip select
      delay_us(OPT_DELAY);
      return 0;
   }
   
   cmd1 = ltc2486_cmd[(channel+1) % 4];
   cmd2 = 0x80;
   
   MOSI = (cmd1 & 0x80) > 0 ? 1 : 0;
   cmd1 <<= 1;
   
   for (i=0,d=0 ; i<24 ; i++) {
      delay_us(OPT_DELAY);
      SCK = 1;
      d = (d << 1) | MISO;
      delay_us(OPT_DELAY);
      SCK = 0;
      if (i < 8) {
         MOSI = (cmd1 & 0x80) > 0 ? 1 : 0;
         cmd1 <<= 1;
      } else {
         MOSI = (cmd2 & 0x80) > 0 ? 1 : 0;
         cmd2 <<= 1;
      }
   }
   
   SCK = 1;
   MOSI = 1;   
   NCS_ADC_HV = 1; // remove CS
   
   if ((d >> 20) == 0x03) {        // overrange
      f = 9.999;
   } else if ((d >> 20) == 0x00) { // underrange
      f = -9.999;
   } else {
      sign = ((d >> 21) & 0x01);   // extract MSB
      d = (d & 0x000FFFFF);        // isolate the result
      d = d >> 4;                  // remove last 4 bits (always zero)
      f = ((float)d / (float)(1l<<16)) * 1.25; // convert raw value into voltage
      if (sign)
         f += 1.25;                // correct for SIGN set
   }

   // convert voltage to high voltage
   f *= DIVIDER;
   
   // round to three digits
   f = floor(f * 1000 + 0.5) / 1000;
   
   // channels are reversed
   v[3-channel] = f;

   // increment channel
   channel = (channel + 1) % 4;
   
   return 1;
}

/*---- Current ADC functions ---------------------------------------*/

unsigned char read_adc_current(float *v)
{
static unsigned char channel = 0;
unsigned char i, cmd1, cmd2, sign;
unsigned long d;
float f;
static unsigned int last = 0;

   // only measure once every 250 ms
   if (time() < last+25)
      return 0;
   last = time();
   
   SCK = 0;
   MOSI = 1;
   NCS_ADC_CUR = 0; // chip select
   delay_us(OPT_DELAY);
   watchdog_refresh(1);
   
   if (MISO == 1) { // check /EOC
      NCS_ADC_CUR = 1; // remove chip select
      delay_us(OPT_DELAY);
      return 0;
   }
   
   cmd1 = ltc2486_cmd[(channel+1) % 4];
   cmd2 = 0x80;
   
   MOSI = (cmd1 & 0x80) > 0 ? 1 : 0;
   cmd1 <<= 1;
   
   for (i=0,d=0 ; i<24 ; i++) {
      delay_us(OPT_DELAY);
      SCK = 1;
      d = (d << 1) | MISO;
      delay_us(OPT_DELAY);
      SCK = 0;
      if (i < 8) {
         MOSI = (cmd1 & 0x80) > 0 ? 1 : 0;
         cmd1 <<= 1;
      } else {
         MOSI = (cmd2 & 0x80) > 0 ? 1 : 0;
         cmd2 <<= 1;
      }
   }
   
   SCK = 1;
   MOSI = 1;   
   NCS_ADC_CUR = 1; // remove CS
   
   if ((d >> 20) == 0x03) {        // overrange
      f = 2.501;
   } else if ((d >> 20) == 0x01) { // underrange
      f = -0.001;
   } else {
      sign = ((d >> 21) & 0x01);   // extract MSB
      d = (d & 0x000FFFFF);        // isolate the result
      d = d >> 4;                  // remove last 4 bits (always zero)
      f = ((float)d / (float)(1l<<16)) * 1.25; // convert raw value into voltage
      if (sign)
         f += 1.25;                // correct for SIGN set
   }

   // convert output voltage to input voltage of INA293B2
   // f = f / 50.0; // 50 V / V
   
   // convert voltage to current in mA
   // f = f / 2 * 1000;
   
   // round to three digits
   // f = floor(f * 1000 + 0.5) / 1000;
   
   // channels are reversed
   v[3-channel] = f;

   // increment channel
   channel = (channel + 1) % 4;
   
   return 1;
}

/*---- User loop function ------------------------------------------*/

void set_hv(unsigned char channel, float voltage)
{
   float d;
   d = voltage/60.0; // scale voltage from 0...60 to 0...2.5
   if (d < 0)
      d = 0;
   if (d > 1)
      d = 1;
   
   write_dac(3-channel, d * 65535); // channels are reversed
}

void user_loop(void)
{
unsigned char i;
   
   for (i=0 ; i<4 ; i++) {
      if (update_data[i]) {
         set_hv(i, *(((float *)&user_data.hv_0)+i));   
         update_data[i] = 0;
      }
   }
   
   read_adc_hv(&user_data.hv_0_meas); 
   read_adc_current(&user_data.i_0);   
}
