/** * Copyright, 2015, SoftPLC Corp. ************************************************************************* * * This module provides data processing functions associated with the * TDK EZA2500-32048 DC-DC converter. */ /* BECAUSE THE PROGRAM IS SUBJECT TO CHANGE BY LICENSEE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. */ #include "tdk.h" #include #include #if defined(DEBUG) // #define STATE_TRACKING true // FSM state tracking switch #define STATE_TRACKING false #else #define STATE_TRACKING false #endif #define COMPORT 5 #define STARTUP_DELAY (2000 * 1000) // 2 secs expressed in usecs #define REPLY_TIMEOUT (100 * 1000) // 100 msecs expressed in usecs /// The time between the scan cycled reads #define INTER_READ_DELAY (1000 * 1000) // in usecs #define START_FRAME_CMD 0x05 #define START_FRAME_REP 0x02 #define TDK_1_RS485_ADDR 0x01 #define TDK_2_RS485_ADDR 0x02 #define PLC_RS485_ADDR 0x03 // serial command codes #define CMD_CHANGE_OP_MODE 0x00 #define CMD_READ_STATUS 0x01 #define CMD_READ_METER1 0x09 #define CMD_READ_METER2 0x0a #define CMD_ALARM_CLEAR 0x12 #define CMD_ALARM_STATUS 0x12 #define CMD_BAT_CONF2 0x16 #define CMD_CV_CHARGE 0x18 #define CMD_CV_DCHARGE 0x1A #define CMD_PROTECTION_LIMITS 0x1C #define CMD_BAT_CONF1 0x1D // BCF Def #define BCF_BAT_OVERCHARGE (1<<0) // bit 0 = battery overcharge protection #define BCF_BAT_OVERDCHARGE (1<<1) // bit 1 = battery overdchg protection #define BCF_BAT_BUS_RAMP_UP (1<<2) // bit 2 = battery bus ramping up #define DEFAULT_BCF \ ( BCF_BAT_OVERCHARGE \ | BCF_BAT_OVERDCHARGE \ | BCF_BAT_BUS_RAMP_UP \ ) // Modes of Operation #define TDK_WAIT_MODE 0x00 #define TDK_GRID_AUTONOMY_DCHG 0x34 // Math defines // Q is used to calc. data resolution, ex: Q13 = 2^13 // See function from_Q() and to_Q() #define Q_7 (1<<7) // 128 = 2^7 #define Q_10 (1<<10) #define Q_11 (1<<11) // 2048 = 2^11 #define Q_13 (1<<13) // 8192 = 2^13 #define Q_14 (1<<14) // 16384 = 2^14 #define DEFAULT_DROOP_RATE 0.02 // fraction of 1.0, range: 0.0 - 3.999 #define DEFAULT_DLB_DEVIATION 1.0 // in volts #define DEFAULT_LV_V_LO_LIM 33.0 // in volts #define DEFAULT_HV_V_LO_LIM 261.0 // in volts #define DEFAULT_LV_V_HI_LIM 47.0 // in volts #define DEFAULT_HV_V_HI_LIM 394.0 // in volts #define DEFAULT_HV_CURRENT_LIM 8.0 // in amps, range: 0.0 - 8.5162 inline void set_bit( PLCINT& aWord, int aBitNum, bool aValue ) { if( aValue ) aWord |= (1<------------------------------------------------------- /** * Enum TDK_STATES * is the set of states for a single DCDC converter RS-485 slave node * "communications state machine". */ enum TDK_STATES { TDK_END, TDK_WAIT, // INTER_READ_DELAY and Tx any "WRITES" // READS TDK_TX_READ_STATUS, TDK_RX_READ_STATUS, TDK_TX_READ_ALARMS, TDK_RX_READ_ALARMS, TDK_TX_READ_METER1, TDK_RX_READ_METER1, TDK_TX_READ_METER2, TDK_RX_READ_METER2, // WRITES TDK_TX_CHANGE_OP_MODE, TDK_RX_CHANGE_OP_MODE, TDK_TX_WRITE_BAT_CONF1, TDK_RX_WRITE_BAT_CONF1, TDK_TX_WRITE_BAT_CONF2, TDK_RX_WRITE_BAT_CONF2, TDK_TX_WRITE_CV_DCHARGE, TDK_RX_WRITE_CV_DCHARGE, TDK_TX_WRITE_CV_CHARGE, TDK_RX_WRITE_CV_CHARGE, TDK_TX_WRITE_PROTECTION, TDK_RX_WRITE_PROTECTION, TDK_TX_ALARM_CLEAR, TDK_RX_ALARM_CLEAR, }; // State to textual name mapper static const char* tdk_state( int aState ) { switch( aState ) { case TDK_TX_READ_STATUS: return "TX_READ_STATUS"; case TDK_RX_READ_STATUS: return "RX_READ_STATUS"; case TDK_TX_READ_ALARMS: return "TX_READ_ALARMS"; case TDK_RX_READ_ALARMS: return "RX_READ_ALARMS"; case TDK_TX_READ_METER1: return "TX_READ_METER1"; case TDK_RX_READ_METER1: return "RX_READ_METER1"; case TDK_TX_READ_METER2: return "TX_READ_METER2"; case TDK_RX_READ_METER2: return "RX_READ_METER2"; case TDK_TX_CHANGE_OP_MODE: return "TX_CHANGE_OP_MODE"; case TDK_RX_CHANGE_OP_MODE: return "RX_CHANGE_OP_MODE"; case TDK_TX_WRITE_BAT_CONF1: return "TX_WRITE_BAT_CONF1"; case TDK_RX_WRITE_BAT_CONF1: return "RX_WRITE_BAT_CONF1"; case TDK_TX_WRITE_BAT_CONF2: return "TX_WRITE_BAT_CONF2"; case TDK_RX_WRITE_BAT_CONF2: return "RX_WRITE_BAT_CONF2"; case TDK_TX_WRITE_CV_DCHARGE: return "TX_WRITE_CV_DCHARGE"; case TDK_RX_WRITE_CV_DCHARGE: return "RX_WRITE_CV_DCHARGE"; case TDK_TX_WRITE_CV_CHARGE: return "TX_WRITE_CV_CHARGE"; case TDK_RX_WRITE_CV_CHARGE: return "RX_WRITE_CV_CHARGE"; case TDK_TX_WRITE_PROTECTION: return "TX_WRITE_PROTECTION"; case TDK_RX_WRITE_PROTECTION: return "RX_WRITE_PROTECTION"; case TDK_TX_ALARM_CLEAR: return "TX_ALARM_CLEAR"; case TDK_RX_ALARM_CLEAR: return "RX_ALARM_CLEAR"; case TDK_END: return "END"; case TDK_WAIT: return "WAIT"; default: return "UNKNOWN!!"; } } /** * Classs TDK * extends FSM to bring in functions Tx(), Rx(), Tx_*() and Rx_*(), * and associates the port number and slave number with this FSM. */ class TDK : public FSM { public: TDK( int aComPortNumber, int aSlaveUnitNumber, const char* aStateTagOrAddress, const char* aTagFloatReads, const char* aTagFloatWrites, const char* aTagIntReads, const char* aTagIntWrites, bool doStateTracking, TLM* aTLM = TLM___ ) : FSM( aStateTagOrAddress, tdk_state, doStateTracking, aTLM ), com_port( aComPortNumber ), slave_num( aSlaveUnitNumber ), dt_fr( aTagFloatReads ), dt_fw( aTagFloatWrites ), dt_ir( aTagIntReads ), dt_iw( aTagIntWrites ) { // expand it once to worst worst case size. rx_buf.reserve( 100 ); } /** * Enum RX_STATES * is the set of sub-states that we go through when receiving a reply. * These are sub-states and are not exposed to clients of this class. The * normal FSM::SetState() is not used to manage these substates. */ enum RX_STATES { RX_SDF, RX_LEN, RX_AD1, RX_AD2, RX_CMD, RX_DATA, RX_CSUM0, RX_CSUM1, }; /** * Function show_rx_state * is a rx_state to textual name mapper for the private RX_STATES enum */ static const char* show_rx_state( int aState ) { switch( aState ) { case RX_SDF: return "RX_SDF"; case RX_LEN: return "RX_LEN"; case RX_AD1: return "RX_AD1"; case RX_AD2: return "RX_AD2"; case RX_CMD: return "RX_CMD"; case RX_DATA: return "RX_DATA"; case RX_CSUM0: return "RX_CSUM0"; case RX_CSUM1: return "RX_CSUM1"; default: return "UNKNOWN!!"; } } /** * Function Rx * is re-enterable and assembles bytes from the serial port until a total * valid reply is received, either an ACK or a NAK. The result is * in rx_buf. * * @return int - 0 if not done, * 1 if got ACK, * 2 if got NAK, * 3 if got checksum error */ int Rx(); /** * Function Tx * is a lean wrapper around COMwrite that also clears the receive buffers * before sending in preparation of getting a reply. */ void Tx( const uint8_t* aOutBuf, int aSendCount ); void Tx_READ_METER1() { uint8_t dout[20]; // Request Converter1 meter1 status inquiry 10-1 dout[0] = START_FRAME_CMD; // 0x05 dout[1] = 5-5; // 0x00 dout[2] = slave_num; dout[3] = PLC_RS485_ADDR; // 0x03 dout[4] = CMD_READ_METER1; // 0x09 int csum = checksum( dout, 4 ); U16PutLE( &dout[5], csum ); Tx( dout, 7 ); } void Rx_READ_METER1() { if( !length_check( 23 ) ) return; int16_t VG = U16GetLE( &rx_buf[5] ); int16_t IG = U16GetLE( &rx_buf[7] ); int16_t VB = U16GetLE( &rx_buf[9] ); int16_t IB = U16GetLE( &rx_buf[11] ); int16_t WG = U16GetLE( &rx_buf[13] ); int16_t WB = U16GetLE( &rx_buf[15] ); // int16_t IC = U16GetLE( &rx_buf[17] ); int16_t TMP = U16GetLE( &rx_buf[19] ); // from table on page P.33/41: dt_fr->Grid_Voltage = from_Q( VG, 380, Q_14 ); dt_fr->Grid_Current = from_Q( IG, 7.8125, Q_13 ); dt_fr->Battery_Voltage = from_Q( VB, 48, Q_14 ); dt_fr->Battery_Current = from_Q( IB, 52.08, Q_13 ); dt_fr->Grid_Watts = from_Q( WG, 2500, Q_11 ); dt_fr->Battery_Watts = from_Q( WB, 2500, Q_11 ); dt_fr->Inverter_Temp = from_Q( TMP, 1, Q_7 ); #if defined(DEBUG) && 0 printf( "%d.Grid_Voltage:%g\n", slave_num, dt_fr->Grid_Voltage ); printf( "%d.Grid_Current:%g\n", slave_num, dt_fr->Grid_Current ); printf( "%d.Grid_Watts:%g\n", slave_num, dt_fr->Grid_Watts ); printf( "%d.Battery_Voltage:%g\n", slave_num, dt_fr->Battery_Voltage ); printf( "%d.Battery_Current:%g\n", slave_num, dt_fr->Battery_Current ); printf( "%d.Battery_Watts:%g\n", slave_num, dt_fr->Battery_Watts ); printf( "%d.Inverter_Temp:%g\n", slave_num, dt_fr->Inverter_Temp ); #endif } void Tx_READ_METER2() { uint8_t dout[20]; // Request Converter1 meter1 status inquiry 10-1 dout[0] = START_FRAME_CMD; dout[1] = 5-5; dout[2] = slave_num; dout[3] = PLC_RS485_ADDR; dout[4] = CMD_READ_METER2; int csum = checksum( dout, 4 ); U16PutLE( &dout[5], csum ); Tx( dout, 7 ); } void Rx_READ_METER2() { if( !length_check( 23 ) ) return; dt_fr->V5S = from_Q( U16GetLE( &rx_buf[9] ), 1, Q_10 ), dt_ir->FAN1 = U16GetLE( &rx_buf[11] ); dt_ir->FAN2 = U16GetLE( &rx_buf[13] ); dt_ir->FAN3 = U16GetLE( &rx_buf[15] ); dt_ir->FAN4 = U16GetLE( &rx_buf[17] ); dt_ir->FAN5 = U16GetLE( &rx_buf[19] ); } void Tx_READ_STATUS() { uint8_t dout[20]; // Request Converter1 status inquiry 2-1 dout[0] = START_FRAME_CMD; dout[1] = 5-5; dout[2] = slave_num; dout[3] = PLC_RS485_ADDR; dout[4] = CMD_READ_STATUS; int csum = checksum( dout, 4 ); U16PutLE( &dout[5], csum ); Tx( dout, 7 ); } void Rx_READ_STATUS() { if( !length_check( 11 ) ) return; int cst = U16GetLE( &rx_buf[5] ); // int ext = U16GetLE( &rx_buf[7] ); switch( cst & 3 ) { case 0: dt_ir->Running_State = RS_OFF_STATE; break; case 1: dt_ir->Running_State = RS_CHARGE_STATE; break; case 2: dt_ir->Running_State = RS_DISCHARGE_STATE; break; default: dt_ir->Running_State = RS_UNKNOWN; break; } switch( (cst >> 2) & 3 ) { case 0: dt_ir->Alarm_State = AS_NO_ALARM; break; case 1: dt_ir->Alarm_State = AS_LIGHT_ALARM; break; case 2: dt_ir->Alarm_State = AS_HEAVY_ALARM; break; default: dt_ir->Alarm_State = AS_UNKNOWN; printf( "%s: slave:%d unknown Alarm_State:%d\n", TLM___->Name(), slave_num, (cst >> 2) & 3 ); break; } switch( (cst >> 4) & 7 ) { case 0: dt_ir->Op_Mode = OP_WAITING; break; case 1: dt_ir->Op_Mode = OP_HETERONOMY_CV; break; case 3: dt_ir->Op_Mode = OP_GRID_AUTONOMY; break; case 4: dt_ir->Op_Mode = OP_BATTERY_AUTONOMY; break; default: dt_ir->Op_Mode = OP_UNKNOWN; printf( "%s: slave:%d unknown Op_Mode:%d\n", TLM___->Name(), slave_num, (cst >> 4) & 7 ); break; } set_bit( dt_ir->Alarms, ALRM_VOLTAGE_UPPER_LIMIT_WARNING, cst & (1<<9) ); set_bit( dt_ir->Alarms, ALRM_VOLTAGE_LOWER_LIMIT_WARNING, cst & (1<<10) ); } void Tx_READ_ALARMS() { uint8_t dout[20]; // Request Converter1 alarm inquiry 9-1 dout[0] = START_FRAME_CMD; dout[1] = 5-5; dout[2] = slave_num; dout[3] = PLC_RS485_ADDR; dout[4] = CMD_ALARM_STATUS; int csum = checksum( dout, 4 ); U16PutLE( &dout[5], csum ); Tx( dout, 7 ); } void Rx_READ_ALARMS() { if( !length_check( 7 ) ) return; int alarm1 = U16GetLE( &rx_buf[5] ); // int alarm2 = U16GetLE( &rx_buf[7] ); // int alarm3 = U16GetLE( &rx_buf[9] ); switch( alarm1 & 7 ) { case 0: dt_ir->Battery = BATTERY_NO_ALARM; break; case 1: dt_ir->Battery = BATTERY_OVERV; break; case 2: dt_ir->Battery = BATTERY_UNDERV; break; case 4: dt_ir->Battery = BATTERY_OVERCURRENT; break; default: dt_ir->Battery = BATTERY_UNKNOWN; printf( "%s: unknown Battery state:%d\n", TLM___->Name(), alarm1 & 7 ); break; } switch( (alarm1 >> 3) & 7 ) { case 0: dt_ir->Grid = GRID_NO_ALARM; break; case 1: dt_ir->Grid = GRID_OVERV; break; case 2: dt_ir->Grid = GRID_UNDERV; break; case 4: dt_ir->Grid = GRID_OVERCURRENT; break; default: dt_ir->Grid = GRID_UNKNOWN; printf( "%s: unknown Grid state:%d\n", TLM___->Name(), (alarm1 >> 3) & 7 ); break; } PLCINT alrms = 0; // clear upper bits, maybe set lower bits: set_bit( alrms, ALRM_HW_OVERCURRENT, alarm1 & 0x0040 ); set_bit( alrms, ALRM_WAITING_EXTERNAL, alarm1 & 0x0080 ); set_bit( alrms, ALRM_PRI_HEATSINK_TEMP, alarm1 & 0x0200 ); set_bit( alrms, ALRM_SEC_HEATSINK_TEMP, alarm1 & 0x0400 ); set_bit( alrms, ALRM_DEVICE_TEMP, alarm1 & 0x0800 ); set_bit( alrms, ALRM_BATTERY_EMPTY, alarm1 & 0x2000 ); set_bit( alrms, ALRM_SYSTEM_FAULT, alarm1 & 0x8000 ); dt_ir->Alarms = alrms; } void Tx_CHANGE_OP_MODE() { uint8_t dout[20]; // Operation Notice Command 1-4 dout[0] = START_FRAME_CMD; dout[1] = 7-5; dout[2] = slave_num; dout[3] = PLC_RS485_ADDR; dout[4] = CMD_CHANGE_OP_MODE; dout[5] = dt_iw->Write_Op_Mode; dout[6] = 0; int csum = checksum( dout, 6 ); U16PutLE( &dout[7], csum ); Tx( dout, 9 ); } void Rx_CHANGE_OP_MODE() { // clear the trigger bit for this specific write. set_bit( dt_iw->Write_Trigger, WT_CHANGE_OP_MODE, false ); } void Tx_WRITE_BAT_CONF1() { uint8_t dout[20]; // write battery config parameter 1 command 7-4 dout[0] = START_FRAME_CMD; dout[1] = 15-5; dout[2] = slave_num; dout[3] = PLC_RS485_ADDR; dout[4] = CMD_BAT_CONF1; // from TDK manual, page P.28/41 int BCF = DEFAULT_BCF; int CVB = to_Q( dt_fw->CVB, 48.0, Q_14 ); int DLB = to_Q( DEFAULT_LV_V_LO_LIM + DEFAULT_DLB_DEVIATION, 48.0, Q_14 ); // = LV V limit + 1V int CDB = to_Q( DEFAULT_DLB_DEVIATION, 48.0, Q_14 ); int DDB = to_Q( DEFAULT_DLB_DEVIATION, 48.0, Q_14 ); #if defined(DEBUG) && 1 // && 0 printf( "%d.BCF:%02x\n", slave_num, BCF ); printf( "%d.iCVB:%04x fCVB:%g\n", slave_num, CVB, dt_fw->CVB ); printf( "%d.iDLB:%04x fDLB:%g\n", slave_num, DLB, DEFAULT_LV_V_LO_LIM + DEFAULT_DLB_DEVIATION ); printf( "%d.iCDB:%04x fCDB:%g\n", slave_num, CDB, DEFAULT_DLB_DEVIATION ); printf( "%d.iDDB:%04x fDDB:%g\n", slave_num, DDB, DEFAULT_DLB_DEVIATION ); #endif U16PutLE( &dout[5], BCF ); U16PutLE( &dout[7], CVB ); U16PutLE( &dout[9], DLB ); U16PutLE( &dout[11], CDB ); U16PutLE( &dout[13], DDB ); int csum = checksum( dout, 14 ); U16PutLE( &dout[15], csum ); Tx( dout, 17 ); } void Rx_WRITE_BAT_CONF1() { // clear my trigger bit set_bit( dt_iw->Write_Trigger, WT_WRITE_BAT_CONF1, false ); } void Tx_WRITE_BAT_CONF2() { uint8_t dout[20]; // write battery config parameter 1 command 7-4 dout[0] = START_FRAME_CMD; dout[1] = 9-5; dout[2] = slave_num; dout[3] = PLC_RS485_ADDR; dout[4] = CMD_BAT_CONF2; // from TDK manual, page P.30/41 int CCR = to_Q( dt_fw->CCR, 48, Q_14 ); int DDR = to_Q( dt_fw->DDR, 48, Q_14 ); U16PutLE( &dout[5], CCR ); U16PutLE( &dout[7], DDR ); int csum = checksum( dout, 8 ); U16PutLE( &dout[9], csum ); Tx( dout, 11 ); } void Rx_WRITE_BAT_CONF2() { // clear my trigger bit set_bit( dt_iw->Write_Trigger, WT_WRITE_BAT_CONF2, false ); } void Tx_WRITE_CV_DCHARGE() { uint8_t dout[20]; // CV Discharging Command 4-4 dout[0] = START_FRAME_CMD; dout[1] = 9-5; dout[2] = slave_num; dout[3] = PLC_RS485_ADDR; dout[4] = CMD_CV_DCHARGE; // see page P.22/41 int DVG = to_Q( dt_fw->DVG, 380, Q_14 ); int DRG = to_Q( DEFAULT_DROOP_RATE, 1, Q_13 ); U16PutLE( &dout[5], DVG ); U16PutLE( &dout[7], DRG ); int csum = checksum( dout, 8 ); U16PutLE( &dout[9], csum ); Tx( dout, 11 ); } void Rx_WRITE_CV_DCHARGE() { // clear my trigger bit set_bit( dt_iw->Write_Trigger, WT_CV_DCHARGE, false ); } void Tx_WRITE_CV_CHARGE() { uint8_t dout[20]; dout[0] = START_FRAME_CMD; dout[1] = 9-5; dout[2] = slave_num; dout[3] = PLC_RS485_ADDR; dout[4] = CMD_CV_CHARGE; // see page P.20/41 int CVB = to_Q( dt_fw->CVB, 48, Q_14 ); int DRB = to_Q( DEFAULT_DROOP_RATE, 1, Q_13 ); U16PutLE( &dout[5], CVB ); U16PutLE( &dout[7], DRB ); int csum = checksum( dout, 8 ); U16PutLE( &dout[9], csum ); Tx( dout, 11 ); } void Rx_WRITE_CV_CHARGE() { // clear my trigger bit set_bit( dt_iw->Write_Trigger, WT_CV_CHARGE, false ); } void Tx_WRITE_PROTECTION() { uint8_t dout[20]; // Protection Limits Command 6-4 dout[0] = START_FRAME_CMD; dout[1] = 17-5; dout[2] = slave_num; dout[3] = PLC_RS485_ADDR; dout[4] = CMD_PROTECTION_LIMITS; // see page P.26/41 int CIB = to_Q( dt_fw->CIB, 52.08, Q_13 ); int DIG = to_Q( DEFAULT_HV_CURRENT_LIM, 7.8125, Q_13 ); int UBV = to_Q( DEFAULT_LV_V_LO_LIM, 48, Q_14 ); int UGV = to_Q( DEFAULT_HV_V_LO_LIM, 380, Q_14 ); int OBV = to_Q( DEFAULT_LV_V_HI_LIM, 48, Q_14 ); int OGV = to_Q( DEFAULT_HV_V_HI_LIM, 380, Q_14 ); U16PutLE( &dout[5], CIB ); U16PutLE( &dout[7], DIG ); U16PutLE( &dout[9], UBV ); U16PutLE( &dout[11], UGV ); U16PutLE( &dout[13], OBV ); U16PutLE( &dout[15], OGV ); #if defined(DEBUG) && 1 printf( "%d.iCIB:%04x fCIB:%g\n", slave_num, CIB, dt_fw->CIB ); printf( "%d.iDIG:%04x fDIG:%g\n", slave_num, DIG, DEFAULT_HV_CURRENT_LIM ); printf( "%d.iUBV:%04x fUBV:%g\n", slave_num, UBV, DEFAULT_LV_V_LO_LIM ); printf( "%d:iUGV:%04x fOGV:%g\n", slave_num, OGV, DEFAULT_HV_V_LO_LIM ); printf( "%d.iOBV:%04x fOBV:%g\n", slave_num, OBV, DEFAULT_LV_V_HI_LIM ); printf( "%d.iOGV:%04x fOGV:%g\n", slave_num, OGV, DEFAULT_HV_V_HI_LIM ); #endif int csum = checksum( dout, 16 ); U16PutLE( &dout[17], csum ); Tx( dout, 19 ); } void Rx_WRITE_PROTECTION() { // clear my trigger bit set_bit( dt_iw->Write_Trigger, WT_WRITE_PROTECTION, false ); } void Tx_ALARM_CLEAR() { uint8_t dout[20]; // Operation Notice Command 1-4 dout[0] = START_FRAME_CMD; dout[1] = 7-5; dout[2] = slave_num; dout[3] = PLC_RS485_ADDR; dout[4] = CMD_ALARM_CLEAR; dout[5] = 0; dout[6] = 0; int csum = checksum( dout, 6 ); U16PutLE( &dout[7], csum ); Tx( dout, 9 ); } void Rx_ALARM_CLEAR() { // clear my trigger bit set_bit( dt_iw->Write_Trigger, WT_ALARM_CLEAR, false ); } void WaitState( int aNextReadState ) { SetState( TDK_WAIT ); wait_state_start_time = timeAtStateEntry; wait_next_read_state = aNextReadState; } /** * Function Waiting * is called between the scan cycled reads. We pause in here for INTER_READ_DELAY * usecs and while waiting we test the various write trigger bits in Write_Trigger. */ void Waiting() { // D(printf("Write_Trigger[%d]:%04x\n", slave_num, dt_iw->Write_Trigger );) // Test expiration time first, not last, so that we cannot get hung in // the TDK_WAIT state if somebody stands on a trigger bit. if( DwellTime() >= INTER_READ_DELAY ) { SetState( wait_next_read_state ); } // While waiting between reads, see if there are any writes in the // Write_Trigger. If so, send those now, and return to TDK_WAIT ASAP // until INTER_READ_DELAY has transpired. else if( test_bit( dt_iw->Write_Trigger, WT_CHANGE_OP_MODE ) ) { D(printf( "%s: %d.WT_CHANGE_OP_MODE\n", __func__, slave_num );) SetState( TDK_TX_CHANGE_OP_MODE ); } else if( test_bit( dt_iw->Write_Trigger, WT_WRITE_BAT_CONF1 ) ) { D(printf( "%s: %d.WT_WRITE_BAT_CONF1\n", __func__, slave_num );) SetState( TDK_TX_WRITE_BAT_CONF1 ); } else if( test_bit( dt_iw->Write_Trigger, WT_WRITE_BAT_CONF2 ) ) { D(printf( "%s: %d.WT_WRITE_BAT_CONF2\n", __func__, slave_num );) SetState( TDK_TX_WRITE_BAT_CONF2 ); } else if( test_bit( dt_iw->Write_Trigger, WT_CV_DCHARGE ) ) { D(printf( "%s: %d.WT_CV_DCHARGE\n", __func__, slave_num );) SetState( TDK_TX_WRITE_CV_DCHARGE ); } else if( test_bit( dt_iw->Write_Trigger, WT_CV_CHARGE ) ) { D(printf( "%s: %d.WT_CV_CHARGE\n", __func__, slave_num );) SetState( TDK_TX_WRITE_CV_CHARGE ); } else if( test_bit( dt_iw->Write_Trigger, WT_WRITE_PROTECTION ) ) { D(printf( "%s: %d.WT_WRITE_PROTECTION\n", __func__, slave_num );) SetState( TDK_TX_WRITE_PROTECTION ); } else if( test_bit( dt_iw->Write_Trigger, WT_ALARM_CLEAR ) ) { D(printf( "%s: %d.WT_ALARM_CLEAR\n", __func__, slave_num );) SetState( TDK_TX_ALARM_CLEAR ); } } void Resume() { SetState( TDK_WAIT ); // continue waiting, but use a starting time of that at original // entry to TDK_WAIT. This way the total wait time is not beyond // the original intended duration, and also so that a sequence of writes // do not starve the reads. timeAtStateEntry = wait_state_start_time; } // Call this in RUN mode only, from TLM::FirstScan() typically void Init() { dt_ir->Tx_Count = 0; dt_ir->Rx_Count = 0; dt_ir->Rx_Timeouts = 0; dt_ir->Rx_Checksum_Errors = 0; dt_ir->Rx_NAK_Count = 0; SetState( TDK_TX_READ_STATUS ); } /** * Function Churn * services this FSM in a cooperative fashion. When this FSM reaches the * TDK_END state, it returns true, otherwise false. */ bool Churn(); private: int com_port; int slave_num; typedef std::vector u8_buf; u8_buf rx_buf; unsigned rx_len; // discovered in reply int rx_state; // for Rx() only uint16_t rx_csum; // only 16 bits so rollovers get lost like in frame int wait_next_read_state; USECS wait_state_start_time; // datatable memory windows: DT_PTR dt_fr; DT_PTR dt_fw; DT_PTR dt_ir; DT_PTR dt_iw; // check reply length and printf() anything unexpected bool length_check( int aExpectedReplyLength ); }; bool TDK::Churn() { int reply; // 0 => none, 1 => ack, 2 => nak, 3 => csum error switch( (TDK_STATES) State() ) { // commands ----------------------------------------------------------- case TDK_TX_READ_STATUS: Tx_READ_STATUS(); SetState( TDK_RX_READ_STATUS ); break; case TDK_TX_READ_ALARMS: Tx_READ_ALARMS(); SetState( TDK_RX_READ_ALARMS ); break; case TDK_TX_READ_METER1: Tx_READ_METER1(); SetState( TDK_RX_READ_METER1 ); break; case TDK_TX_READ_METER2: Tx_READ_METER2(); SetState( TDK_RX_READ_METER2 ); break; case TDK_TX_CHANGE_OP_MODE: Tx_CHANGE_OP_MODE(); SetState( TDK_RX_CHANGE_OP_MODE ); break; case TDK_TX_WRITE_BAT_CONF1: Tx_WRITE_BAT_CONF1(); SetState( TDK_RX_WRITE_BAT_CONF1 ); break; case TDK_TX_WRITE_BAT_CONF2: Tx_WRITE_BAT_CONF2(); SetState( TDK_RX_WRITE_BAT_CONF2 ); break; case TDK_TX_WRITE_CV_DCHARGE: Tx_WRITE_CV_DCHARGE(); SetState( TDK_RX_WRITE_CV_DCHARGE ); break; case TDK_TX_WRITE_CV_CHARGE: Tx_WRITE_CV_CHARGE(); SetState( TDK_RX_WRITE_CV_CHARGE ); break; case TDK_TX_WRITE_PROTECTION: Tx_WRITE_PROTECTION(); SetState( TDK_RX_WRITE_PROTECTION ); break; case TDK_TX_ALARM_CLEAR: Tx_ALARM_CLEAR(); SetState( TDK_RX_ALARM_CLEAR ); break; // Replies ---------------------------------------------------------- case TDK_RX_READ_STATUS: case TDK_RX_READ_ALARMS: case TDK_RX_READ_METER1: case TDK_RX_READ_METER2: case TDK_RX_CHANGE_OP_MODE: case TDK_RX_WRITE_BAT_CONF1: case TDK_RX_WRITE_BAT_CONF2: case TDK_RX_WRITE_CV_DCHARGE: case TDK_RX_WRITE_CV_CHARGE: case TDK_RX_WRITE_PROTECTION: case TDK_RX_ALARM_CLEAR: reply = Rx(); if( reply == 1 ) { switch( State() ) { // Replies to Reads call WaitState() case TDK_RX_READ_STATUS: Rx_READ_STATUS(); WaitState( TDK_TX_READ_ALARMS ); break; case TDK_RX_READ_ALARMS: Rx_READ_ALARMS(); WaitState( TDK_TX_READ_METER1 ); break; case TDK_RX_READ_METER1: Rx_READ_METER1(); WaitState( TDK_TX_READ_METER2 ); break; case TDK_RX_READ_METER2: Rx_READ_METER2(); WaitState( TDK_END ); break; // Replies to a WRITE sent from Waiting() will call Resume() case TDK_RX_CHANGE_OP_MODE: Rx_CHANGE_OP_MODE(); Resume(); break; case TDK_RX_WRITE_BAT_CONF1: Rx_WRITE_BAT_CONF1(); Resume(); break; case TDK_RX_WRITE_BAT_CONF2: Rx_WRITE_BAT_CONF2(); Resume(); break; case TDK_RX_WRITE_CV_DCHARGE: Rx_WRITE_CV_DCHARGE(); Resume(); break; case TDK_RX_WRITE_CV_CHARGE: Rx_WRITE_CV_CHARGE(); Resume(); break; case TDK_RX_WRITE_PROTECTION: Rx_WRITE_PROTECTION(); Resume(); break; case TDK_RX_ALARM_CLEAR: Rx_ALARM_CLEAR(); Resume(); break; } // returning true causes super_fsm to toggle between TDKs. this // can only be done after valid replies or at TDK_END, otherwise // we'd end up with two outstanding cmds on the wire. If this is // disabled here, then super_fsm toggles only from TDK_END state. #if 1 return true; #endif } else if( reply > 0 ) { // reply == 2 or 3, NAK or csum error WaitState( TDK_END ); return true; } else if( DwellTime() >= REPLY_TIMEOUT ) { // Too long in the state and no reply, recover here. Cable is // disconnected or slave not operational. ++dt_ir->Rx_Timeouts; WaitState( TDK_END ); return true; } break; case TDK_END: // cycle back to STATUS state, but return true SetState( TDK_TX_READ_STATUS ); return true; case TDK_WAIT: Waiting(); break; // safety: default: printf( "%s: TDK: slave %d in unhandled state:%d\n", TLM___->Name(), slave_num, State() ); SetState( TDK_TX_READ_STATUS ); } return false; } //#define NO_CABLE // I used this for testing without slaves. Leave undefined typically int TDK::Rx() { #if defined(NO_CABLE) return 1; // debugging without a TDK #else int cc; while( (cc = COMgetc( com_port )) != -1 ) { uint8_t b = (uint8_t) cc; // D(printf( "%s:%02x\n", show_rx_state( rx_state ), b );) switch( rx_state ) { case RX_SDF: if( b == START_FRAME_REP ) // 0x02 { rx_buf.push_back( b ); rx_state = RX_LEN; } // else stay in state RX_SDF waiting for 0x02 break; case RX_LEN: rx_buf.push_back( b ); rx_len = b; rx_state = RX_AD1; break; case RX_AD1: rx_buf.push_back( b ); rx_csum += b; rx_state = RX_AD2; break; case RX_AD2: rx_buf.push_back( b ); rx_csum += b; rx_state = RX_CMD; break; case RX_CMD: rx_buf.push_back( b ); rx_csum += b; if( rx_len ) rx_state = RX_DATA; else rx_state = RX_CSUM0; break; case RX_DATA: rx_buf.push_back( b ); rx_csum += b; if( --rx_len == 0 ) rx_state = RX_CSUM0; break; case RX_CSUM0: rx_buf.push_back( b ); rx_state = RX_CSUM1; break; case RX_CSUM1: rx_buf.push_back( b ); D(dump( "rep:", &rx_buf[0], rx_buf.size() );) rx_state = RX_SDF; { int csum_ndx = rx_buf.size() - 2; uint16_t csum = U16GetLE( &rx_buf[csum_ndx] ); if( csum == rx_csum ) { if( !( 0x80 & rx_buf[4] ) ) { ++dt_ir->Rx_Count; return 1; // ACK } else { printf( "%s: got NAK ERCD:%04x from slave_num:%d to cmd:%02x\n", TLM___->Name(), U16GetLE( &rx_buf[5] ), slave_num, rx_buf[4] & 0x7f ); ++dt_ir->Rx_NAK_Count; return 2; // NAK } } else { printf( "%s: bad csum:%04x != rx_csum:%04x from slave_num:%d to cmd:%02x\n", TLM___->Name(), csum, rx_csum, slave_num, rx_buf[4] & 0x7f ); ++dt_ir->Rx_Checksum_Errors; return 3; // csum error } } break; default: rx_state = RX_SDF; } } return 0; #endif } void TDK::Tx( const uint8_t* aOutBuf, int aSendCount ) { COMrcvclear( com_port ); rx_state = RX_SDF; rx_csum = 0; rx_buf.clear(); D(dump( "cmd:", aOutBuf, aSendCount );) ++dt_ir->Tx_Count; COMwrite( com_port, (const char*) aOutBuf, aSendCount ); } bool TDK::length_check( int aExpectedReplyLength ) { #if !defined(NO_CABLE) if( (int) rx_buf.size() != aExpectedReplyLength ) { printf( "%s: got a %d byte reply to cmd:%02x, was expecting %d bytes.\n", TLM___->Name(), (int) rx_buf.size(), rx_buf[4], aExpectedReplyLength ); return false; } #endif return true; } // Define two DCDC converter comm engines: static TDK dcdc1( COMPORT, TDK_1_RS485_ADDR, "DCDC1_STATE", "DCDC1_FLOAT_READS", // assign tag to FLOAT dt file "DCDC1_FLOAT_WRITES", // assign tag to FLOAT dt file "DCDC1_INT_READS", "DCDC1_INT_WRITES", STATE_TRACKING ); static TDK dcdc2( COMPORT, TDK_2_RS485_ADDR, "DCDC2_STATE", "DCDC2_FLOAT_READS", // assign tag to FLOAT dt file "DCDC2_FLOAT_WRITES", // assign tag to FLOAT dt file "DCDC2_INT_READS", "DCDC2_INT_WRITES", STATE_TRACKING ); //---------------------------------------------------------------- // This FSM is a timing manager so that the subsidiary FSMs: dcdc1, dcdc2 can // be delayed, toggled, and sequenced as needed. Remember there can only be // one outstanding transaction per serial port. This is a contraint on when // we can bounce between the two DC/DC converters managed here. We can toggle // between commands, but not when expecting a reply. enum SU_STATES { SU_START_DELAY, SU_CHURN1, // spinning the dcdc1 FSM SU_CHURN2, // spinning the dcdc2 FSM SU_IDLE, }; // State to textual name mapper. static const char* show_super( int aState ) { switch( aState ) { case SU_START_DELAY: return "START_DELAY"; case SU_CHURN1: return "CHURN1"; case SU_CHURN2: return "CHURN2"; case SU_IDLE: return "IDLE"; default: return "UNKNOWN!!"; } } static FSM super_fsm( "TDK_SUPER_FSM", show_super, STATE_TRACKING ); void TLM::Init( req_init* req ) { printf( "%s: " __DATE__ " initializing...\n", Name() ); // open the serial port int result = COMsetup( COMPORT, 0, NULL, // sizeof(rcvBuf), rcvBuf, 0, NULL, // sizeof(xmitBuf), xmitBuf, 19200, // int baudrate, 'N', // int parity, 1, // int stop 8 // int databits ); if( result != 0 ) { throw ERROR( result, // no need for TLM name here. "unable to open com port %d. ec=%d", COMPORT, result ); } } void TLM::DeInstall() { COMdisable( COMPORT ); printf( "%s: deinstalling.\n", Name() ); } void TLM::FirstScan() { // reset the 3 state machines all to a known starting state. super_fsm.SetState( SU_START_DELAY ); dcdc1.Init(); dcdc2.Init(); } void TLM::Scan() { // Since we return from the subsidiary FSMs with outstanding serial commands // (commands not yet replied to) we can only continue servicing the SAME // DCDC until that reply has been received or timed out. This limits when // we can transition between the DCDC FSM Churn() functions. The return // value of TDK::Churn() is designed to give us a clue here. switch( super_fsm.State() ) { case SU_START_DELAY: if( super_fsm.DwellTime() > STARTUP_DELAY ) super_fsm.SetState( SU_CHURN1 ); break; case SU_CHURN1: if( dcdc1.Churn() ) { super_fsm.SetState( SU_CHURN2 ); } break; case SU_CHURN2: if( dcdc2.Churn() ) { super_fsm.SetState( SU_CHURN1 ); } break; case SU_IDLE: // not needed yet default: super_fsm.SetState( SU_CHURN1 ); break; } }