From 13de6632704020a540c63ced55667e685dbcca38 Mon Sep 17 00:00:00 2001 From: Edward Emelianov Date: Wed, 2 Feb 2022 14:37:05 +0300 Subject: [PATCH] add some features --- STM32/TSYS_controller/Makefile | 1 + STM32/TSYS_controller/Readme.md | 7 + STM32/TSYS_controller/can_process.c | 35 ++++- STM32/TSYS_controller/can_process.h | 3 + STM32/TSYS_controller/main.c | 31 +++-- STM32/TSYS_controller/proto.c | 42 +++++- STM32/TSYS_controller/proto.h | 1 + STM32/TSYS_controller/sensors_manage.c | 174 ++++++++++++++++--------- STM32/TSYS_controller/sensors_manage.h | 3 +- STM32/TSYS_controller/tsys01.bin | Bin 21900 -> 22924 bytes STM32/TSYS_controller/version.inc | 5 +- 11 files changed, 220 insertions(+), 82 deletions(-) diff --git a/STM32/TSYS_controller/Makefile b/STM32/TSYS_controller/Makefile index 0701667..2081386 100644 --- a/STM32/TSYS_controller/Makefile +++ b/STM32/TSYS_controller/Makefile @@ -111,6 +111,7 @@ $(STARTUP): $(INC_DIR)/startup/vector.c $(VERSION_FILE): *.[ch] @echo " Generate version: $(NEXTVER) for date $(BUILDDATE)" @sed -i "s/#define BUILD_NUMBER.*/#define BUILD_NUMBER \"$(NEXTVER)\"/" $(VERSION_FILE) + @sed -i "s/#define BUILDNO.*/#define BUILDNO $(NEXTVER)/" $(VERSION_FILE) @sed -i "s/#define BUILD_DATE.*/#define BUILD_DATE \"$(BUILDDATE)\"/" $(VERSION_FILE) $(OBJDIR)/proto.o: proto.c $(VERSION_FILE) diff --git a/STM32/TSYS_controller/Readme.md b/STM32/TSYS_controller/Readme.md index 0feff0f..2824aaa 100644 --- a/STM32/TSYS_controller/Readme.md +++ b/STM32/TSYS_controller/Readme.md @@ -5,6 +5,7 @@ USART speed 115200. Code for ../../kicad/stm32 ## Serial interface commands (ends with '\n'), small letter for only local processing: - **0...7** send message to Nth controller, not broadcast (after number should be CAN command) +- **@** set/reset debug mode - **a** get raw ADC values - **B** send dummy CAN messages to broadcast address - **b** get/set CAN bus baudrate @@ -15,12 +16,15 @@ USART speed 115200. Code for ../../kicad/stm32 - **Ff** turn sensors off - **g** group (sniffer) CAN mode (print to USB terminal all incoming CAN messages with alien IDs) - **Hh** switch I2C to high speed (100kHz) +- **Ii** (re)init sensors - **Jj** get MCU temperature - **Kk** get values of U and I - **Ll** switch I2C to low speed (default, 10kHz) - **Mm** change master id to 0 (**m**) / broadcast (**M**) +- **N** get build number - **Oo** turn onboard diagnostic LEDs **O**n or **o**ff (both commands are local!) - **P** ping everyone over CAN +- **Qq** get system time - **Rr** reinit I2C - **s** send CAN message (format: ID data[0..8], dec, 0x - hex, 0b - binary) - **Tt** start single temperature measurement @@ -72,6 +76,9 @@ First (number zero) byte of every sequence is command mark (0xA5) or data mark ( - `CMD_GETUIVAL` (13) request to get values of V12, V5, I12 and V3.3 - `CMD_GETUIVAL0` (14) answer with values of V12 and V5 - `CMD_GETUIVAL1` (15) answer with values of I12 and V3.3 +- `CMD_REINIT_SENSORS` (16) (re)init all sensors (discover all and get calibrated data) +- `CMD_GETBUILDNO` (17) get by CAN firmware build number (uint32_t, littleendian, starting from byte #4) +- `CMD_SYSTIME` (18) get system time in ms (uint32_t, littleendian, starting from byte #4) ### Dummy commands for test purposes - `CMD_DUMMY0` = 0xDA, diff --git a/STM32/TSYS_controller/can_process.c b/STM32/TSYS_controller/can_process.c index e6aa695..291cda0 100644 --- a/STM32/TSYS_controller/can_process.c +++ b/STM32/TSYS_controller/can_process.c @@ -25,6 +25,7 @@ #include "can_process.h" #include "proto.h" #include "sensors_manage.h" +#include "version.inc" extern volatile uint32_t Tms; // timestamp data // id of master - all data will be sent to it @@ -97,13 +98,16 @@ void can_messages_proc(){ SEND(" "); printuhex(can_mesg->data[ctr]); } - newline(); sendbuf(); + newline(); } // don't process alien messages if(can_mesg->ID != CANID && can_mesg->ID != BCAST_ID) return; int16_t t; + uint32_t U32; if(data[0] == COMMAND_MARK){ // process commands if(len < 2) return; + // master shouldn't react to broadcast commands! + if(can_mesg->ID == BCAST_ID && CANID == MASTER_ID) return; switch(data[1]){ case CMD_DUMMY0: case CMD_DUMMY1: @@ -158,6 +162,19 @@ void can_messages_proc(){ case CMD_GETUIVAL: senduival(); break; + case CMD_REINIT_SENSORS: + sensors_init(); + break; + case CMD_GETBUILDNO: + b[1] = 0; + *((uint32_t*)&b[2]) = BUILDNO; + can_send_data(b, 6); + break; + case CMD_SYSTIME: + b[1] = 0; + *((uint32_t*)&b[2]) = Tms; + can_send_data(b, 6); + break; } }else if(data[0] == DATA_MARK){ // process received data char Ns = '0' + data[1]; @@ -216,6 +233,20 @@ void can_messages_proc(){ case CMD_GETUIVAL1: // I12 and V3.3 showui("I12_", "V33_", data); break; + case CMD_GETBUILDNO: + addtobuf("BUILDNO"); + bufputchar(Ns); + bufputchar('='); + U32 = *((uint32_t*)&data[4]); + printu(U32); + break; + case CMD_SYSTIME: + addtobuf("SYSTIME"); + bufputchar(Ns); + bufputchar('='); + U32 = *((uint32_t*)&data[4]); + printu(U32); + break; default: SEND("UNKNOWN_DATA"); } @@ -264,7 +295,7 @@ CAN_status can_send_data(uint8_t *data, uint8_t len){ * @return next number or -1 if all data sent */ int8_t send_temperatures(int8_t N){ - if(N < 0 || Controller_address == 0) return -1; + if(N < 0 || Controller_address == 0) return -1; // don't need to send Master's data over CAN bus int a, p; uint8_t can_data[4]; int8_t retn = N; diff --git a/STM32/TSYS_controller/can_process.h b/STM32/TSYS_controller/can_process.h index 2775140..d78fc4b 100644 --- a/STM32/TSYS_controller/can_process.h +++ b/STM32/TSYS_controller/can_process.h @@ -47,6 +47,9 @@ typedef enum{ CMD_GETUIVAL, // request to get values of V12, V5, I12 and V3.3 CMD_GETUIVAL0, // answer with values of V12 and V5 CMD_GETUIVAL1, // answer with values of I12 and V3.3 + CMD_REINIT_SENSORS, // (re)init sensors + CMD_GETBUILDNO, // request for firmware build number + CMD_SYSTIME, // get system time // dummy commands for test purposes CMD_DUMMY0 = 0xDA, CMD_DUMMY1 = 0xAD diff --git a/STM32/TSYS_controller/main.c b/STM32/TSYS_controller/main.c index affe161..64a12ff 100644 --- a/STM32/TSYS_controller/main.c +++ b/STM32/TSYS_controller/main.c @@ -66,7 +66,7 @@ static void iwdg_setup(){ } int main(void){ - uint32_t lastT = 0, lastS = 0; + uint32_t lastT = 0, lastS = 0, lastB = 0; uint8_t gotmeasurement = 0; char inbuf[256]; sysreset(); @@ -80,6 +80,7 @@ int main(void){ CAN_setup(0); // setup with default 250kbaud RCC->CSR |= RCC_CSR_RMVF; // remove reset flags USB_setup(); + sensors_init(); iwdg_setup(); while (1){ @@ -90,10 +91,19 @@ int main(void){ // send dummy command to noone to test CAN bus //can_send_cmd(NOONE_ID, CMD_DUMMY0); } - if(lastS > Tms || Tms - lastS > 5){ // run sensors proc. once per 5ms + if(lastS != Tms){ // run sensors proc. once per 1ms sensors_process(); lastS = Tms; + if(SENS_SLEEPING == Sstate){ // show temperature @ each sleeping occurence + if(!gotmeasurement){ + gotmeasurement = 1; + showtemperature(); + } + }else{ + if(SENS_WAITING == Sstate) gotmeasurement = 0; + } } + usb_proc(); can_proc(); CAN_status stat = CAN_get_status(); if(stat == CAN_FIFO_OVERRUN){ @@ -104,27 +114,22 @@ int main(void){ canerror = 1; } can_messages_proc(); - if(SENS_SLEEPING == Sstate){ // show temperature @ each sleeping occurence - if(!gotmeasurement){ - gotmeasurement = 1; - showtemperature(); - } - }else{ - gotmeasurement = 0; - } - usb_proc(); + IWDG->KR = IWDG_REFRESH; uint8_t r = 0; if((r = USB_receive(inbuf, 255))){ inbuf[r] = 0; cmd_parser(inbuf, 1); } - if(usartrx()){ // usart1 received data, store in in buffer + if(usartrx()){ // usart1 received data, store it in buffer char *txt = NULL; r = usart_getline(&txt); txt[r] = 0; cmd_parser(txt, 0); } - sendbuf(); + if(lastB - Tms > 99){ // run `sendbuf` each 100ms + sendbuf(); + lastB = Tms; + } } return 0; } diff --git a/STM32/TSYS_controller/proto.c b/STM32/TSYS_controller/proto.c index 716d530..4226277 100644 --- a/STM32/TSYS_controller/proto.c +++ b/STM32/TSYS_controller/proto.c @@ -34,9 +34,10 @@ #include "version.inc" extern volatile uint8_t canerror; +extern volatile uint32_t Tms; static char buff[UARTBUFSZ+1], *bptr = buff; -static uint8_t blen = 0, USBcmd = 0; +static uint8_t blen = 0, USBcmd = 0, debugmode = 0; // LEDs are OFF by default uint8_t noLED = #ifdef EBUG @@ -183,7 +184,6 @@ static CAN_message *parseCANmsg(char *txt){ return NULL; } SEND("Message parsed OK\n"); - sendbuf(); canmsg.length = (uint8_t) ctr; return &canmsg; } @@ -207,7 +207,6 @@ void cmd_parser(char *txt, uint8_t isUSB){ USBcmd = isUSB; int16_t L = strlen(txt), ID = BCAST_ID; char _1st = txt[0]; - sendbuf(); if(_1st >= '0' && _1st < '8'){ // send command to Nth controller, not broadcast if(L == 3){ // with '\n' at end! ID = (CAN_ID_PREFIX & CAN_ID_MASK) | (_1st - '0'); @@ -217,6 +216,13 @@ void cmd_parser(char *txt, uint8_t isUSB){ } } switch(_1st){ + case '@': + debugmode = !debugmode; + SEND("DEBUG mode "); + if(debugmode) SEND("ON"); + else SEND("OFF"); + newline(); + break; case 'a': showADCvals(); break; @@ -259,6 +265,12 @@ void cmd_parser(char *txt, uint8_t isUSB){ case 'h': i2c_setup(HIGH_SPEED); break; + case 'I': + CANsend(ID, CMD_REINIT_SENSORS, _1st); + break; + case 'i': + sensors_init(); + break; case 'J': CANsend(ID, CMD_GETMCUTEMP, _1st); break; @@ -283,6 +295,9 @@ void cmd_parser(char *txt, uint8_t isUSB){ case 'm': CANsend(ID, CMD_CHANGE_MASTER, _1st); break; + case 'N': + CANsend(ID, CMD_GETBUILDNO, _1st); + break; case 'O': noLED = 0; SEND("LED on\n"); @@ -296,6 +311,12 @@ void cmd_parser(char *txt, uint8_t isUSB){ case 'P': CANsend(ID, CMD_PING, _1st); break; + case 'Q': + CANsend(ID, CMD_SYSTIME, _1st); + break; + case 'q': + SEND("SYSTIME0="); printu(Tms); newline(); + break; case 'R': CANsend(ID, CMD_REINIT_I2C, _1st); break; @@ -354,6 +375,7 @@ void cmd_parser(char *txt, uint8_t isUSB){ SEND( "ALL little letters - without CAN messaging\n" "0..7 - send command to given controller (0 - this) instead of broadcast\n" + "@ - set/reset debug mode\n" "a - get raw ADC values\n" "B - send broadcast CAN dummy message\n" "b - get/set CAN bus baudrate\n" @@ -364,24 +386,26 @@ void cmd_parser(char *txt, uint8_t isUSB){ "Ff- turn oFf sensors\n" "g - group (sniffer) CAN mode\n" "Hh- high I2C speed\n" + "Ii- (re)init sensors\n" "Jj- get MCU temperature\n" "Kk- get U/I values\n" "Ll- low I2C speed\n" "Mm- change master id to 0 (m) / broadcast (M)\n" + "N - get build number\n" "Oo- turn onboard diagnostic LEDs *O*n or *o*ff (both commands are local)\n" "P - ping everyone over CAN\n" + "Qq- get system time\n" "Rr- reinit I2C\n" "s - send CAN message\n" "Tt- start temperature measurement\n" "u - unique ID (default) CAN mode\n" "Vv- very low I2C speed\n" "Xx- Start themperature scan\n" - "Yy- get sensors state over CAN\n" + "Yy- get sensors state\n" "z - check CAN status for errors\n" ); break; } - sendbuf(); } // print 32bit unsigned int @@ -493,3 +517,11 @@ char *getnum(char *txt, int32_t *N){ } return getdec(txt, N); } + +// show message in debug mode +void mesg(char *txt){ + if(!debugmode) return; + addtobuf("[DBG] "); + addtobuf(txt); + bufputchar('\n'); +} diff --git a/STM32/TSYS_controller/proto.h b/STM32/TSYS_controller/proto.h index 6909bce..48e544f 100644 --- a/STM32/TSYS_controller/proto.h +++ b/STM32/TSYS_controller/proto.h @@ -46,5 +46,6 @@ void printu(uint32_t val); void printuhex(uint32_t val); void sendbuf(); char *getnum(char *txt, int32_t *N); +void mesg(char *txt); #endif // __PROTO_H__ diff --git a/STM32/TSYS_controller/sensors_manage.c b/STM32/TSYS_controller/sensors_manage.c index 63e485f..fbac396 100644 --- a/STM32/TSYS_controller/sensors_manage.c +++ b/STM32/TSYS_controller/sensors_manage.c @@ -56,6 +56,8 @@ static const char *statenames[] = { ,[SENS_OVERCURNT_OFF] = "offbyovercurrent" }; +static uint8_t getcoeff(uint8_t i); + const char *sensors_get_statename(SensorsState x){ if(x >= SENS_STATE_CNT) return "wrongstate"; return statenames[x]; @@ -77,7 +79,9 @@ const double mul[5] = {-1.5e-2, 1., -2., 4., -2.}; */ static uint16_t calc_t(uint32_t t, int i){ uint16_t *coeff = coefficients[curr_mul_addr][i]; - if(coeff[0] == 0) return BAD_TEMPERATURE; // what is with coeffs? + if(coeff[0] == 0){ + if(!getcoeff(i)) return BAD_TEMPERATURE; // what is with coeffs? + } if(t < 600000 || t > 30000000) return BAD_TEMPERATURE; // wrong value - too small or too large int j; double d = (double)t/256., tmp = 0.; @@ -92,28 +96,39 @@ static uint16_t calc_t(uint32_t t, int i){ // turn off sensors' power void sensors_off(){ + mesg("Turn off sensors"); MUL_OFF(); // turn off multiplexers SENSORS_OFF(); // turn off sensors' power Sstate = SENS_OFF; } /** - * if all OK with current, turn ON sensors' power and change state to "initing" + * if all OK with current, turn ON sensors' power */ -void sensors_on(){ - sens_present[0] = sens_present[1] = 0; +static int sensors_on(){ + mesg("Turn on sensors"); curr_mul_addr = 0; - Nsens_present = 0; MUL_OFF(); if(SENSORS_OVERCURNT()){ + mesg("OVERCURRENT!"); SENSORS_OFF(); Sstate = (++overcurnt_ctr > 32) ? SENS_OVERCURNT_OFF : SENS_OVERCURNT; + return FALSE; }else{ + mesg("Powered on"); SENSORS_ON(); - Sstate = SENS_INITING; + return TRUE; } } +// init sensors +void sensors_init(){ + sens_present[0] = sens_present[1] = 0; + overcurnt_ctr = 0; + Nsens_present = 0; + if(sensors_on()) Sstate = SENS_INITING; +} + /** * start measurement if sensors are sleeping, * turn ON if they were OFF @@ -127,7 +142,10 @@ void sensors_start(){ break; case SENS_OFF: overcurnt_ctr = 0; - sensors_on(); + if(sensors_on()) Sstate = SENS_START_MSRMNT; + break; + case SENS_OVERCURNT_OFF: + sensors_init(); break; default: break; @@ -142,47 +160,56 @@ static void count_sensors(){ ++Nsens_present; B &= (B - 1); } +/* +SEND("count_sensors(): "); +printu(Nsens_present); +newline(); +*/ } /** - * All procedures return 1 if they change current state due to error & 0 if all OK + * All procedures return TRUE if all OK or FALSE if failed and need to start scan again */ // procedure call each time @ resetting static uint8_t resetproc(){ - uint8_t i, ctr = 0; + uint8_t i; for(i = 0; i < 2; ++i){ if(write_i2c(Taddr[i], TSYS01_RESET)){ - ++ctr; sens_present[i] |= 1< MUL_MAX_ADDRESS){ // scan is over curr_mul_addr = 0; - return 1; + return TRUE; } } - return 0; + return FALSE; } // print coefficients @debug console @@ -277,10 +311,8 @@ void showcoeffs(){ // print temperatures @debug console void showtemperature(){ int a, p; - if(Nsens_present == 0){ - return; - } - if(Ntemp_measured == 0){ + if(Nsens_present == 0 || Ntemp_measured == 0){ + SEND("No sensors found"); return; } for(a = 0; a <= MUL_MAX_ADDRESS; ++a){ @@ -313,32 +345,39 @@ void sensors_process(){ Sstate = (++overcurnt_ctr > 32) ? SENS_OVERCURNT_OFF : SENS_OVERCURNT; return; } - switch (Sstate){ + switch(Sstate){ case SENS_INITING: // initialisation (restart I2C) + mesg("SENS_INITING"); i2c_setup(CURRENT_SPEED); Sstate = SENS_RESETING; lastSensT = Tms; - overcurnt_ctr = 0; + NsentOverCAN = -1; break; case SENS_RESETING: // reset & discovery procedure + if(NsentOverCAN == -1){ + mesg("SENS_RESETING"); + NsentOverCAN = 0; + } if(Tms - lastSensT > POWERUP_TIME){ - overcurnt_ctr = 0; if(sensors_scan(resetproc)){ count_sensors(); // get total amount of sensors if(Nsens_present){ Sstate = SENS_GET_COEFFS; }else{ // no sensors found + mesg("No sensors found"); sensors_off(); } } } break; case SENS_GET_COEFFS: // get coefficients + mesg("SENS_GET_COEFFS"); if(sensors_scan(getcoefsproc)){ - Sstate = SENS_START_MSRMNT; + Sstate = SENS_SLEEPING; // sleep after got coefficients } break; case SENS_START_MSRMNT: // send all sensors command to start measurements + mesg("SENS_START_MSRMNT"); if(sensors_scan(msrtempproc)){ lastSensT = Tms; Sstate = SENS_WAITING; @@ -346,33 +385,50 @@ void sensors_process(){ } break; case SENS_WAITING: // wait for end of conversion + mesg("SENS_WAITING"); if(Tms - lastSensT > CONV_TIME){ + NsentOverCAN = -1; Sstate = SENS_GATHERING; } break; case SENS_GATHERING: // scan all sensors, get thermal data & calculate temperature + if(NsentOverCAN < 0){ + mesg("SENS_SLEEPING"); + NsentOverCAN = 0; + } if(sensors_scan(gettempproc)){ lastSensT = Tms; NsentOverCAN = 0; - Sstate = SENS_SLEEPING; + Sstate = SENS_SENDING_DATA; } break; - case SENS_SLEEPING: // wait for `SLEEP_TIME` till next measurements + case SENS_SENDING_DATA: + mesg("SENS_SENDING_DATA"); NsentOverCAN = send_temperatures(NsentOverCAN); // call sending T process - if(NsentOverCAN == -1){ + if(NsentOverCAN < 0){ // all data sent -> sleep + Sstate = SENS_SLEEPING; + /* if(Nsens_present != Ntemp_measured){ // restart sensors only after measurements sent + mesg("restart"); i2c_setup(CURRENT_SPEED); sensors_on(); - } - if(sensors_scan_mode){ // sleep until next measurement start - if(Tms - lastSensT > SLEEP_TIME){ - Sstate = SENS_START_MSRMNT; - } + }*/ + } + break; + case SENS_SLEEPING: // wait for `SLEEP_TIME` till next measurements in scan mode + if(NsentOverCAN < 0){ + mesg("SENS_SLEEPING"); + NsentOverCAN = 0; + } + if(sensors_scan_mode){ // sleep until next measurement start + if(Tms - lastSensT > SLEEP_TIME){ + Sstate = SENS_START_MSRMNT; } } break; case SENS_OVERCURNT: // try to reinit all after overcurrent - sensors_on(); + mesg("SENS_OVERCURNT"); + if(sensors_on()) Sstate = SENS_SLEEPING; break; default: // do nothing break; diff --git a/STM32/TSYS_controller/sensors_manage.h b/STM32/TSYS_controller/sensors_manage.h index 37b7c18..aea0f15 100644 --- a/STM32/TSYS_controller/sensors_manage.h +++ b/STM32/TSYS_controller/sensors_manage.h @@ -46,6 +46,7 @@ typedef enum{ ,SENS_OFF // 7 sensors' power is off by external command ,SENS_OVERCURNT // 8 overcurrent detected @ any stage ,SENS_OVERCURNT_OFF // 9 sensors' power is off due to continuous overcurrent + ,SENS_SENDING_DATA // A send data over CAN bus ,SENS_STATE_CNT } SensorsState; @@ -60,7 +61,7 @@ const char *sensors_get_statename(SensorsState x); void sensors_process(); void sensors_off(); -void sensors_on(); +void sensors_init(); void sensors_start(); void showcoeffs(); void showtemperature(); diff --git a/STM32/TSYS_controller/tsys01.bin b/STM32/TSYS_controller/tsys01.bin index b5e88b7ad328682cfd81f808f8de146a6f40bc84..fff6c1c4723dcd96e1907f77200559545b43842e 100755 GIT binary patch delta 6719 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 L1Z={lRnz_r*`?w@ delta 5663 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