/********************************************************************\ Name: hvr_400.c Created by: Stefan Ritt Contents: Application specific (user) part of Midas Slow Control Bus protocol for HVR_300 High Voltage Regulator Memory usage: Program code: 0x0000 - 0x31FF (check after each compile!) Upgrade code: 0x3200 - 0x3811 EEPROM page: 0x3A00 - 0x3BFF (one page @ 512 bytes) 0x3C00 - 0x3DFF cannot be erased ??? $Id$ \********************************************************************/ #include #include #include #include "mscbemb.h" extern bit FREEZE_MODE; extern bit DEBUG_MODE; /* number of HV channels */ #define N_HV_CHN 4 /* declare number of sub-addresses to framework */ unsigned char idata _n_sub_addr = N_HV_CHN; char code node_name[] = "HVR-400"; /* maximum current im micro Ampere */ #define MAX_CURRENT 2000 /* maximum voltage in Volt */ #define MAX_VOLTAGE 2500 /* calculate voltage divider */ #define DIVIDER ((41E6 + 33E3) / 33E3) /* current resistor */ #define RCURR_HC 56E3 #define RCURR_LC 1E6 /* current multiplier */ #define CUR_MULT_HC 15 #define CUR_MULT_LC 807 // (g=1+50k/62) at INA114 /* delay for opto-couplers in us */ #define OPT_DELAY 300 /* configuration jumper */ sbit JU0 = P3 ^ 4; // negative module if forced to zero sbit JU1 = P3 ^ 2; // low current module if forced to zero sbit JU2 = P3 ^ 1; /* AD7718 pins */ sbit ADC_NRES = P1 ^ 7; // !Reset sbit ADC_SCLK = P1 ^ 5; // Serial Clock sbit ADC_NCS = P1 ^ 3; // !Chip select sbit ADC_NRDY = P1 ^ 0; // !Ready sbit ADC_DOUT = P1 ^ 1; // Data out sbit ADC_DIN = P1 ^ 4; // Data in /* LTC2600 pins */ sbit DAC_NCS = P1 ^ 2; // !Chip select sbit DAC_SCK = P1 ^ 5; // Serial Clock sbit DAC_CLR = P1 ^ 6; // Clear sbit DAC_DIN = P1 ^ 4; // Data in /* AD7718 registers */ #define REG_STATUS 0 #define REG_MODE 1 #define REG_CONTROL 2 #define REG_FILTER 3 #define REG_ADCDATA 4 #define REG_ADCOFFSET 5 #define REG_ADCGAIN 6 #define REG_IOCONTROL 7 #define ADC_SF_VALUE 27 // SF value for 50Hz rejection, 2 Hz total update rate //#define ADC_SF_VALUE 82 // SF value for 50Hz rejection, 1 Hz total update rate unsigned char idata chn_bits[N_HV_CHN]; #define DEMAND_CHANGED (1<<0) #define RAMP_UP (1<<1) #define RAMP_DOWN (1<<2) #define CUR_LIMIT_CHANGED (1<<3) float xdata u_actual[N_HV_CHN]; unsigned long xdata t_ramp[N_HV_CHN]; unsigned long xdata trip_time[N_HV_CHN]; /*---- Define variable parameters returned to CMD_GET_INFO command ----*/ /* data buffer (mirrored in EEPROM) */ /* CSR control bits */ #define CONTROL_HV_ON (1<<0) #define CONTROL_REGULATION (1<<1) #define CONTROL_IDLE (1<<2) /* CSR status bits */ #define STATUS_NEGATIVE (1<<0) #define STATUS_LOWCUR (1<<1) #define STATUS_RAMP_UP (1<<2) #define STATUS_RAMP_DOWN (1<<3) #define STATUS_VLIMIT (1<<4) #define STATUS_ILIMIT (1<<5) #define STATUS_RILIMIT (1<<6) struct { unsigned char control; float u_demand; float u_meas; float i_meas; unsigned char status; unsigned char trip_cnt; float ramp_up; float ramp_down; float u_limit; float i_limit; float ri_limit; unsigned char trip_max; unsigned char trip_time; float adc_gain; float adc_offset; float dac_gain; float dac_offset; float cur_vgain; float cur_gain; float cur_offset; float u_dac; } xdata user_data[N_HV_CHN]; MSCB_INFO_VAR code vars[] = { 1, UNIT_BYTE, 0, 0, 0, "Control", &user_data[0].control, // 0 4, UNIT_VOLT, 0, 0, MSCBF_FLOAT, "Udemand", &user_data[0].u_demand, // 1 4, UNIT_VOLT, 0, 0, MSCBF_FLOAT, "Umeas", &user_data[0].u_meas, // 2 4, UNIT_AMPERE, PRFX_MICRO, 0, MSCBF_FLOAT, "Imeas", &user_data[0].i_meas, // 3 1, UNIT_BYTE, 0, 0, 0, "Status", &user_data[0].status, // 4 1, UNIT_COUNT, 0, 0, 0, "TripCnt", &user_data[0].trip_cnt, // 5 4, UNIT_VOLT, 0, 0, MSCBF_FLOAT, "RampUp", &user_data[0].ramp_up, // 6 4, UNIT_VOLT, 0, 0, MSCBF_FLOAT, "RampDown",&user_data[0].ramp_down, // 7 4, UNIT_VOLT, 0, 0, MSCBF_FLOAT, "Ulimit", &user_data[0].u_limit, // 8 4, UNIT_AMPERE, PRFX_MICRO, 0, MSCBF_FLOAT, "Ilimit", &user_data[0].i_limit, // 9 4, UNIT_AMPERE, PRFX_MICRO, 0, MSCBF_FLOAT, "RIlimit", &user_data[0].ri_limit, // 10 1, UNIT_COUNT, 0, 0, 0, "TripMax", &user_data[0].trip_max, // 11 1, UNIT_SECOND, 0, 0, 0, "TripTime",&user_data[0].trip_time, // 12 /* calibration constants */ 4, UNIT_FACTOR, 0, 0, MSCBF_FLOAT | MSCBF_HIDDEN, "ADCgain", &user_data[0].adc_gain, // 13 4, UNIT_VOLT, 0, 0, MSCBF_FLOAT | MSCBF_HIDDEN, "ADCofs", &user_data[0].adc_offset, // 14 4, UNIT_FACTOR, 0, 0, MSCBF_FLOAT | MSCBF_HIDDEN, "DACgain", &user_data[0].dac_gain, // 15 4, UNIT_VOLT, 0, 0, MSCBF_FLOAT | MSCBF_HIDDEN, "DACofs", &user_data[0].dac_offset, // 16 4, UNIT_FACTOR, 0, 0, MSCBF_FLOAT | MSCBF_HIDDEN, "CURvgain",&user_data[0].cur_vgain, // 17 4, UNIT_FACTOR, 0, 0, MSCBF_FLOAT | MSCBF_HIDDEN, "CURgain", &user_data[0].cur_gain, // 18 4, UNIT_AMPERE, PRFX_MICRO, 0, MSCBF_FLOAT | MSCBF_HIDDEN, "CURofs", &user_data[0].cur_offset, // 19 4, UNIT_VOLT, 0, 0, MSCBF_FLOAT | MSCBF_HIDDEN, "UDAC", &user_data[0].u_dac, // 20 0 }; MSCB_INFO_VAR *variables = vars; /********************************************************************\ Application specific init and inout/output routines \********************************************************************/ void write_dac(unsigned char channel, unsigned short value) reentrant; void write_adc(unsigned char a, unsigned char d); void user_write(unsigned char index) reentrant; void ramp_hv(unsigned char channel); /*---- User init function ------------------------------------------*/ extern SYS_INFO idata sys_info; void user_init(unsigned char init) { unsigned char i; P2MDOUT = 0xF0; // P2.4-7: enable Push/Pull for LEDs P3MDOUT = 0; /* initial nonzero EEPROM values */ if (init) { memset(user_data, 0, sizeof(user_data)); for (i=0 ; i MAX_VOLTAGE) user_data[i].u_limit = MAX_VOLTAGE; if (user_data[i].i_limit > MAX_CURRENT && user_data[i].i_limit != 9999) user_data[i].i_limit = MAX_CURRENT; u_actual[i] = 0; t_ramp[i] = time(); } /* set default group address */ if (sys_info.group_addr == 0xFFFF) sys_info.group_addr = 400; /* jumper as input */ JU0 = 1; JU1 = 1; JU2 = 1; /* read node configuration */ for (i=0 ; i MAX_VOLTAGE) user_data[a].u_limit = MAX_VOLTAGE; chn_bits[a] |= DEMAND_CHANGED; } /* check current limit */ if (index == 9) { if (user_data[a].i_limit > MAX_CURRENT && user_data[a].i_limit != 9999) user_data[a].i_limit = MAX_CURRENT; chn_bits[a] |= CUR_LIMIT_CHANGED; } } /*---- User read function ------------------------------------------*/ unsigned char user_read(unsigned char index) { if (index); return 0; } /*---- User function called vid CMD_USER command -------------------*/ unsigned char user_func(unsigned char *data_in, unsigned char *data_out) { if (data_in || data_out); return 0; } /*---- DAC functions -----------------------------------------------*/ unsigned char code dac_index[] = {3, 2, 1, 0 }; void write_dac(unsigned char channel, unsigned short value) reentrant { unsigned char i, m, b; /* do mapping */ channel = dac_index[channel % 4]; DAC_NCS = 0; // chip select delay_us(OPT_DELAY); watchdog_refresh(1); // command 3: write and update for (i=0,m=8 ; i<4 ; i++) { DAC_SCK = 0; DAC_DIN = (3 & m) > 0; delay_us(OPT_DELAY); DAC_SCK = 1; delay_us(OPT_DELAY); m >>= 1; watchdog_refresh(1); } // channel address for (i=0,m=8 ; i<4 ; i++) { DAC_SCK = 0; DAC_DIN = (channel & m) > 0; delay_us(OPT_DELAY); DAC_SCK = 1; delay_us(OPT_DELAY); m >>= 1; watchdog_refresh(1); } // MSB b = value >> 8; for (i=0,m=0x80 ; i<8 ; i++) { DAC_SCK = 0; DAC_DIN = (b & m) > 0; delay_us(OPT_DELAY); DAC_SCK = 1; delay_us(OPT_DELAY); m >>= 1; watchdog_refresh(1); } // LSB b = value & 0xFF; for (i=0,m=0x80 ; i<8 ; i++) { DAC_SCK = 0; DAC_DIN = (b & m) > 0; delay_us(OPT_DELAY); DAC_SCK = 1; delay_us(OPT_DELAY); m >>= 1; watchdog_refresh(1); } DAC_NCS = 1; // remove chip select delay_us(OPT_DELAY); watchdog_refresh(1); } /*---- ADC functions -----------------------------------------------*/ void write_adc(unsigned char a, unsigned char d) { unsigned char i, m; /* write to communication register */ ADC_NCS = 0; delay_us(OPT_DELAY); watchdog_refresh(1); /* write zeros to !WEN and R/!W */ for (i=0 ; i<4 ; i++) { ADC_SCLK = 0; delay_us(OPT_DELAY); ADC_DIN = 0; delay_us(OPT_DELAY); ADC_SCLK = 1; delay_us(OPT_DELAY); watchdog_refresh(1); } /* register address */ for (i=0,m=8 ; i<4 ; i++) { ADC_SCLK = 0; delay_us(OPT_DELAY); ADC_DIN = (a & m) > 0; delay_us(OPT_DELAY); ADC_SCLK = 1; delay_us(OPT_DELAY); m >>= 1; watchdog_refresh(1); } ADC_NCS = 1; delay_us(OPT_DELAY); watchdog_refresh(1); /* write to selected data register */ ADC_NCS = 0; delay_us(OPT_DELAY); watchdog_refresh(1); for (i=0,m=0x80 ; i<8 ; i++) { ADC_SCLK = 0; delay_us(OPT_DELAY); ADC_DIN = (d & m) > 0; delay_us(OPT_DELAY); ADC_SCLK = 1; delay_us(OPT_DELAY); m >>= 1; watchdog_refresh(1); } ADC_NCS = 1; delay_us(OPT_DELAY); watchdog_refresh(1); } void read_adc8(unsigned char a, unsigned char *d) { unsigned char i, m; /* write to communication register */ ADC_NCS = 0; delay_us(OPT_DELAY); watchdog_refresh(1); /* write zero to !WEN and one to R/!W */ for (i=0 ; i<4 ; i++) { ADC_SCLK = 0; delay_us(OPT_DELAY); ADC_DIN = (i == 1); delay_us(OPT_DELAY); ADC_SCLK = 1; delay_us(OPT_DELAY); watchdog_refresh(1); } /* register address */ for (i=0,m=8 ; i<4 ; i++) { ADC_SCLK = 0; delay_us(OPT_DELAY); ADC_DIN = (a & m) > 0; delay_us(OPT_DELAY); ADC_SCLK = 1; delay_us(OPT_DELAY); m >>= 1; watchdog_refresh(1); } ADC_NCS = 1; delay_us(OPT_DELAY); watchdog_refresh(1); /* read from selected data register */ ADC_NCS = 0; delay_us(OPT_DELAY); watchdog_refresh(1); for (i=0,m=0x80,*d=0 ; i<8 ; i++) { ADC_SCLK = 0; delay_us(OPT_DELAY); if (ADC_DOUT) *d |= m; ADC_SCLK = 1; delay_us(OPT_DELAY); m >>= 1; watchdog_refresh(1); } ADC_NCS = 1; delay_us(OPT_DELAY); watchdog_refresh(1); } void read_adc24(unsigned char a, unsigned long *d) { unsigned char i, m; /* write to communication register */ ADC_NCS = 0; delay_us(OPT_DELAY); watchdog_refresh(1); /* write zero to !WEN and one to R/!W */ for (i=0 ; i<4 ; i++) { ADC_SCLK = 0; delay_us(OPT_DELAY); ADC_DIN = (i == 1); delay_us(OPT_DELAY); ADC_SCLK = 1; delay_us(OPT_DELAY); watchdog_refresh(1); } /* register address */ for (i=0,m=8 ; i<4 ; i++) { ADC_SCLK = 0; delay_us(OPT_DELAY); ADC_DIN = (a & m) > 0; delay_us(OPT_DELAY); ADC_SCLK = 1; delay_us(OPT_DELAY); m >>= 1; watchdog_refresh(1); } ADC_NCS = 1; delay_us(OPT_DELAY); watchdog_refresh(1); /* read from selected data register */ ADC_NCS = 0; delay_us(OPT_DELAY); watchdog_refresh(1); for (i=0,*d=0 ; i<24 ; i++) { *d <<= 1; ADC_SCLK = 0; delay_us(OPT_DELAY); delay_us(OPT_DELAY); *d |= ADC_DOUT; ADC_SCLK = 1; delay_us(OPT_DELAY); watchdog_refresh(1); } ADC_NCS = 1; delay_us(OPT_DELAY); watchdog_refresh(1); } unsigned char code adc_index[8] = {7, 6, 5, 4, 3, 2, 1, 0 }; unsigned char adc_read(unsigned char channel, float *value) { unsigned long d, start_time; unsigned char i; /* start conversion */ channel = adc_index[channel % 8]; write_adc(REG_CONTROL, channel << 4 | 0x0F); // adc_chn, +2.56V range write_adc(REG_MODE, 2); // single conversion start_time = time(); while (ADC_NRDY) { yield(); for (i=0 ; i 30) { /* reset ADC */ ADC_NRES = 0; delay_ms(100); ADC_NRES = 1; delay_ms(300); write_adc(REG_FILTER, ADC_SF_VALUE); return 0; } } read_adc24(REG_ADCDATA, &d); /* convert to volts */ *value = ((float)d / (1l<<24)) * 2.56; /* round result to 6 digits */ *value = floor(*value*1E6+0.5)/1E6; return 1; } /*------------------------------------------------------------------*/ void set_hv(unsigned char channel, float value) reentrant { unsigned short d; /* check for limit */ if (value > user_data[channel].u_limit) { value = user_data[channel].u_limit; user_data[channel].status |= STATUS_VLIMIT; } else user_data[channel].status &= ~STATUS_VLIMIT; /* apply correction */ value = value * user_data[channel].dac_gain + user_data[channel].dac_offset; if (value < 0) value = 0; /* convert HV to voltage */ value = value / DIVIDER; /* convert to DAC units */ d = (unsigned short) ((value / 2.5 * 65536) + 0.5); /* write dac */ write_dac(channel, d); } /*------------------------------------------------------------------*/ void read_hv(unsigned char channel) { float xdata hv; /* read voltage channel */ if (!adc_read(channel*2, &hv)) return; /* convert to HV */ hv *= DIVIDER; /* apply calibration */ hv = hv * user_data[channel].adc_gain + user_data[channel].adc_offset; /* 0.01 resolution */ hv = floor(hv * 100) / 100.0; led_mode(channel, !(hv > 10)); DISABLE_INTERRUPTS; user_data[channel].u_meas = hv; ENABLE_INTERRUPTS; } /*------------------------------------------------------------------*/ void check_current(unsigned char channel) { if (user_data[channel].i_meas > user_data[channel].i_limit && user_data[channel].i_limit != 9999) { if (trip_time[channel] == 0) trip_time[channel] = time(); /* zero output voltage */ set_hv(channel, 0); u_actual[channel] = 0; user_data[channel].u_dac = 0; /* stop possible ramping */ chn_bits[channel] &= ~DEMAND_CHANGED; chn_bits[channel] &= ~RAMP_UP; chn_bits[channel] &= ~RAMP_DOWN; user_data[channel].status &= ~(STATUS_RAMP_UP | STATUS_RAMP_DOWN); /* raise trip flag */ if ((user_data[channel].status & STATUS_ILIMIT) == 0) { user_data[channel].status |= STATUS_ILIMIT; user_data[channel].trip_cnt++; } } /* check for trip recovery */ if ((user_data[channel].status & STATUS_ILIMIT) && user_data[channel].trip_cnt < user_data[channel].trip_max && time() >= trip_time[channel] + user_data[channel].trip_time*100) { /* clear trip */ user_data[channel].status &= ~STATUS_ILIMIT; trip_time[channel] = 0; /* force ramp up */ chn_bits[channel] |= DEMAND_CHANGED; } if (user_data[channel].status & STATUS_ILIMIT) led_blink(channel, 3, 100); } /*------------------------------------------------------------------*/ void read_current(unsigned char channel) { float xdata current; /* read current channel */ if (!adc_read(channel*2+1, ¤t)) return; /* correct opamp gain, divider & curr. resist, microamp */ if (user_data[0].status & STATUS_LOWCUR) current = current / CUR_MULT_LC * DIVIDER / RCURR_LC * 1E6; else current = current / CUR_MULT_HC * DIVIDER / RCURR_HC * 1E6; /* correct for unbalanced voltage dividers */ current -= user_data[channel].cur_vgain * user_data[channel].u_meas; /* correct for offset */ current -= user_data[channel].cur_offset; /* calibrate gain */ current = current * user_data[channel].cur_gain; /* 1 uA resolution */ current = floor(current + 0.5); DISABLE_INTERRUPTS; user_data[channel].i_meas = current; ENABLE_INTERRUPTS; } /*------------------------------------------------------------------*/ void ramp_hv(unsigned char channel) { unsigned char delta; /* only process ramping when HV is on and not tripped */ if ((user_data[channel].control & CONTROL_HV_ON) && !(user_data[channel].status & STATUS_ILIMIT)) { if (chn_bits[channel] & DEMAND_CHANGED) { /* start ramping */ if (user_data[channel].u_demand > u_actual[channel] && user_data[channel].ramp_up > 0) { /* ramp up */ chn_bits[channel] |= RAMP_UP; chn_bits[channel] &= ~RAMP_DOWN; user_data[channel].status |= STATUS_RAMP_UP; user_data[channel].status &= ~STATUS_RAMP_DOWN; user_data[channel].status &= ~STATUS_RILIMIT; chn_bits[channel] &= ~DEMAND_CHANGED; } if (user_data[channel].u_demand < u_actual[channel] && user_data[channel].ramp_down > 0) { /* ramp down */ chn_bits[channel] &= ~RAMP_UP; chn_bits[channel] |= RAMP_DOWN; user_data[channel].status &= ~STATUS_RAMP_UP; user_data[channel].status |= STATUS_RAMP_DOWN; user_data[channel].status &= ~STATUS_RILIMIT; chn_bits[channel] &= ~DEMAND_CHANGED; } /* remember start time */ t_ramp[channel] = time(); } /* ramp up */ if (chn_bits[channel] & RAMP_UP) { delta = time() - t_ramp[channel]; if (user_data[channel].i_meas > user_data[channel].ri_limit) user_data[channel].status |= STATUS_RILIMIT; else user_data[channel].status &= ~STATUS_RILIMIT; if (delta && user_data[channel].i_meas < user_data[channel].ri_limit) { u_actual[channel] += (float) user_data[channel].ramp_up * delta / 100.0; user_data[channel].u_dac = u_actual[channel]; if (u_actual[channel] >= user_data[channel].u_demand) { /* finish ramping */ u_actual[channel] = user_data[channel].u_demand; user_data[channel].u_dac = u_actual[channel]; chn_bits[channel] &= ~RAMP_UP; user_data[channel].status &= ~STATUS_RAMP_UP; user_data[channel].status &= ~STATUS_RILIMIT; } set_hv(channel, u_actual[channel]); t_ramp[channel] = time(); } } /* ramp down */ if (chn_bits[channel] & RAMP_DOWN) { delta = time() - t_ramp[channel]; if (delta) { u_actual[channel] -= (float) user_data[channel].ramp_down * delta / 100.0; user_data[channel].u_dac = u_actual[channel]; if (u_actual[channel] <= user_data[channel].u_demand) { /* finish ramping */ u_actual[channel] = user_data[channel].u_demand; user_data[channel].u_dac = u_actual[channel]; chn_bits[channel] &= ~RAMP_DOWN; user_data[channel].status &= ~STATUS_RAMP_DOWN; } set_hv(channel, u_actual[channel]); t_ramp[channel] = time(); } } } watchdog_refresh(1); } /*------------------------------------------------------------------*/ void regulation(unsigned char channel) { /* only if HV on and not ramping */ if ((user_data[channel].control & CONTROL_HV_ON) && (chn_bits[channel] & (RAMP_UP | RAMP_DOWN)) == 0 && !(user_data[channel].status & STATUS_ILIMIT)) { if (user_data[channel].control & CONTROL_REGULATION) { u_actual[channel] = user_data[channel].u_demand; /* correct if difference is at least half a LSB */ if (fabs(user_data[channel].u_demand - user_data[channel].u_meas) / DIVIDER / 2.5 * 65536 > 0.5) { user_data[channel].u_dac += user_data[channel].u_demand - user_data[channel].u_meas; /* only allow +-2V fine regulation range */ if (user_data[channel].u_dac < user_data[channel].u_demand - 2) user_data[channel].u_dac = user_data[channel].u_demand - 2; if (user_data[channel].u_dac > user_data[channel].u_demand + 2) user_data[channel].u_dac = user_data[channel].u_demand + 2; chn_bits[channel] &= ~DEMAND_CHANGED; set_hv(channel, user_data[channel].u_dac); } } else { /* set voltage directly */ if (chn_bits[channel] & DEMAND_CHANGED) { u_actual[channel] = user_data[channel].u_demand; user_data[channel].u_dac = user_data[channel].u_demand; set_hv(channel, user_data[channel].u_demand); chn_bits[channel] &= ~DEMAND_CHANGED; } } } /* if HV switched off, set DAC to zero */ if (!(user_data[channel].control & CONTROL_HV_ON) && (chn_bits[channel] & DEMAND_CHANGED)) { user_data[channel].u_dac = 0; u_actual[channel] = 0; set_hv(channel, 0); chn_bits[channel] &= ~DEMAND_CHANGED; } watchdog_refresh(1); } /*------------------------------------------------------------------*/ #ifdef NOT_RELIABLE void read_temperature(void) { float temperature; unsigned char i; REF0CN = 0x0E; // temperature sensor on AD0EN = 1; // turn on ADC /* set MUX */ AMX0P = 0x1E; // Temp sensor AMX0N = 0x1F; // GND DISABLE_INTERRUPTS; AD0INT = 0; AD0BUSY = 1; while (AD0INT == 0); ENABLE_INTERRUPTS; temperature = ADC0L + (ADC0H & 0x03) * 256; /* convert to mV */ temperature = temperature / 1023.0 * 3000; /* convert to deg. C */ temperature = (temperature - 897) / 3.35; /* offset correction using prototype */ temperature -= 27; /* strip fractional part */ temperature = (int) (temperature + 0.5); for (i=0 ; i