diff --git a/F1:F103/FX3U/Readme.md b/F1:F103/FX3U/Readme.md index 54e3260..a9e7217 100644 --- a/F1:F103/FX3U/Readme.md +++ b/F1:F103/FX3U/Readme.md @@ -7,6 +7,9 @@ You can see pinout table in file `hardware.c`. ## Serial protocol (each string ends with '\n'). +(TODO: add new) + + ``` commands format: parameter[number][=setter] parameter [CAN idx] - help @@ -49,11 +52,11 @@ Value in square brackets is CAN bus command code. All data in little-endian format! -BIT - MEANING +BYTE - MEANING 0, 1 - (uint16_t) - command code (value in square brackets upper); -2 - (uint8_t) - parameter number (e.g. ADC channel or X/Y channel number), 0..127 [ORed with 0x80 for setter]; +2 - (uint8_t) - parameter number (e.g. ADC channel or X/Y channel number), 0..126 [ORed with 0x80 for setter], 127 means "no parameter"; 3 - (uint8_t) - error code (only when device answers for requests); @@ -73,3 +76,7 @@ BIT - MEANING 5 - `ERR_CANTRUN` - can't run given command due to bad parameters or other reason. + +## MODBUS-RTU protocol + +(TODO) \ No newline at end of file diff --git a/F1:F103/FX3U/can.c b/F1:F103/FX3U/can.c index b851111..0b59e32 100644 --- a/F1:F103/FX3U/can.c +++ b/F1:F103/FX3U/can.c @@ -236,6 +236,7 @@ void CAN_proc(){ CAN_status CAN_send(CAN_message *message){ if(!message) return CAN_ERR; + IWDG->KR = IWDG_REFRESH; uint8_t *msg = message->data; uint8_t len = message->length; uint16_t target_id = message->ID; diff --git a/F1:F103/FX3U/canproto.c b/F1:F103/FX3U/canproto.c index 6304cd1..e0e6c16 100644 --- a/F1:F103/FX3U/canproto.c +++ b/F1:F103/FX3U/canproto.c @@ -20,7 +20,7 @@ #include "canproto.h" #include "flash.h" #include "hardware.h" -#include "proto.h" +#include "modbusrtu.h" #include "strfunc.h" #include "usart.h" @@ -41,49 +41,53 @@ static errcodes reset(CAN_message _U_ *msg){ // get/set Tms static errcodes time_getset(CAN_message *msg){ if(ISSETTER(msg->data)){ - Tms = *(uint32_t*)&msg->data[4]; - }else FIXDL(msg); - *(uint32_t*)&msg->data[4] = Tms; + Tms = MSGP_GET_U32(msg); + } + FIXDL(msg); + MSGP_SET_U32(msg, Tms); return ERR_OK; } // get MCU T static errcodes mcut(CAN_message *msg){ FIXDL(msg); - *(int32_t*)&msg->data[4] = getMCUtemp(); + MSGP_SET_U32(msg, getMCUtemp()); return ERR_OK; } // get ADC raw values static errcodes adcraw(CAN_message *msg){ FIXDL(msg); - uint8_t no = msg->data[2] & ~SETTER_FLAG; + uint8_t no = PARVAL(msg->data); if(no >= ADC_CHANNELS) return ERR_BADPAR; - *(uint32_t*)&msg->data[4] = getADCval(no); + MSGP_SET_U32(msg, getADCval(no)); return ERR_OK; } // set common CAN ID / get CAN IN in static errcodes canid(CAN_message *msg){ if(ISSETTER(msg->data)){ - the_conf.CANIDin = the_conf.CANIDout = *(uint32_t*)&msg->data[4]; + the_conf.CANIDin = the_conf.CANIDout = (uint16_t)MSGP_GET_U32(msg); CAN_reinit(0); // setup with new ID - }else FIXDL(msg); - *(uint32_t*)&msg->data[4] = the_conf.CANIDin; + } + FIXDL(msg); + MSGP_SET_U32(msg, the_conf.CANIDin); return ERR_OK; } // get/set input CAN ID static errcodes canidin(CAN_message *msg){ if(ISSETTER(msg->data)){ - the_conf.CANIDin = *(uint32_t*)&msg->data[4]; + the_conf.CANIDin = (uint16_t)MSGP_GET_U32(msg); CAN_reinit(0); // setup with new ID - }else FIXDL(msg); - *(uint32_t*)&msg->data[4] = the_conf.CANIDin; + } + FIXDL(msg); + MSGP_SET_U32(msg, the_conf.CANIDin); return ERR_OK; } // get/set output CAN ID static errcodes canidout(CAN_message *msg){ if(ISSETTER(msg->data)){ - the_conf.CANIDout = *(uint32_t*)&msg->data[4]; - }else FIXDL(msg); - *(uint32_t*)&msg->data[4] = the_conf.CANIDout; + the_conf.CANIDout = (uint16_t)MSGP_GET_U32(msg); + } + FIXDL(msg); + MSGP_SET_U32(msg, the_conf.CANIDout); return ERR_OK; } @@ -101,32 +105,42 @@ static errcodes erasestor(CAN_message _U_ *msg){ // relay management static errcodes relay(CAN_message *msg){ uint8_t no = OUTMAX+1; - if(msg->length > 2) no = msg->data[2] & ~SETTER_FLAG; + if(msg->length > 2){ + uint8_t chnl = PARVAL(msg->data); + if(chnl != NO_PARNO) no = chnl; + } if(ISSETTER(msg->data)){ - if(set_relay(no, *(uint32_t*)&msg->data[4]) < 0) return ERR_BADPAR; - }else FIXDL(msg); + if(set_relay(no, MSGP_GET_U32(msg)) < 0) return ERR_BADPAR; + } + FIXDL(msg); int rval = get_relay(no); if(rval < 0) return ERR_BADPAR; - *(uint32_t*)&msg->data[4] = (uint32_t)rval; + MSGP_SET_U32(msg, (uint32_t)rval); return ERR_OK; } // get current ESW status static errcodes esw(CAN_message *msg){ uint8_t no = INMAX+1; - if(msg->length > 2) no = msg->data[2] & ~SETTER_FLAG; + if(msg->length > 2){ + uint8_t chnl = PARVAL(msg->data); + if(chnl != NO_PARNO) no = chnl; + } int val = get_esw(no); if(val < 0) return ERR_BADPAR; - *(uint32_t*)&msg->data[4] = (uint32_t) val; + MSGP_SET_U32(msg, (uint32_t)val); FIXDL(msg); return ERR_OK; } // bounce-free ESW get status static errcodes eswg(CAN_message *msg){ uint8_t no = INMAX+1; - if(msg->length > 2) no = msg->data[2] & ~SETTER_FLAG; + if(msg->length > 2){ + uint8_t chnl = PARVAL(msg->data); + if(chnl != NO_PARNO) no = chnl; + } uint32_t curval = get_ab_esw(); - if(no > INMAX) *(uint32_t*)&msg->data[4] = curval; - else *(uint32_t*)&msg->data[4] = (curval & (1< INMAX) MSGP_SET_U32(msg, curval); + else MSGP_SET_U32(msg, (curval & (1<data; - uint32_t *ptr = NULL; - switch(idx){ - case CMD_CANSPEED: ptr = &the_conf.CANspeed; CAN_reinit(*(uint32_t*)&msg->data[4]); break; - case CMD_BOUNCE: ptr = &the_conf.bouncetime; break; - case CMD_USARTSPEED: ptr = &the_conf.usartspeed; break; + uint16_t cmd = *(uint16_t*)msg->data; + uint32_t *ptr = NULL, val; + switch(cmd){ + case CMD_CANSPEED: ptr = &the_conf.CANspeed; CAN_reinit(MSGP_GET_U32(msg)); break; + case CMD_BOUNCE: ptr = &the_conf.bouncetime; break; + case CMD_USARTSPEED: ptr = &the_conf.usartspeed; break; + case CMD_INCHNLS: val = inchannels(); ptr = &val; break; + case CMD_OUTCHNLS: val = outchannels(); ptr = &val; break; + case CMD_MODBUSID: ptr = &the_conf.modbusID; break; default: break; } if(!ptr) return ERR_CANTRUN; // unknown error if(ISSETTER(msg->data)){ - *ptr = *(uint32_t*)&msg->data[4]; - }else FIXDL(msg); - *(uint32_t*)&msg->data[4] = *ptr; + if(cmd == CMD_INCHNLS || cmd == CMD_OUTCHNLS) return ERR_CANTRUN; // can't set getter-only + *ptr = MSGP_GET_U32(msg); + } + FIXDL(msg); + MSGP_SET_U32(msg, *ptr); return ERR_OK; } -/* // common bitflag setter/getter +// without parno - all flags, with - flag with number N static errcodes flagsetget(CAN_message *msg){ - uint16_t idx = *(uint16_t*)msg->data; - uint8_t bit = 32; - switch(idx){ - case CMD_ENCISSSI: bit = FLAGBIT(ENC_IS_SSI); break; - case CMD_EMULPEP: bit = FLAGBIT(EMULATE_PEP); break; - default: break; + uint8_t idx = NO_PARNO; + if(msg->length > 2){ + idx = PARVAL(msg->data); + if(idx != NO_PARNO && idx > MAX_FLAG_BITNO) return ERR_BADPAR; } - if(bit > 31) return ERR_CANTRUN; // unknown error if(ISSETTER(msg->data)){ - if(msg->data[4]) the_conf.flags |= 1<data[4] = (the_conf.flags & (1<length; ++i){ usart_send(uhex2str(msg->data[i])); usart_putchar(' '); } - //for(int i = msg->length-1; i < 8; ++i) msg->data[i] = 0; newline(); #endif if(datalen < 2){ diff --git a/F1:F103/FX3U/canproto.h b/F1:F103/FX3U/canproto.h index dbaf54a..07278cc 100644 --- a/F1:F103/FX3U/canproto.h +++ b/F1:F103/FX3U/canproto.h @@ -25,9 +25,11 @@ #define SETTER_FLAG (0x80) #define ISSETTER(data) ((data[2] & SETTER_FLAG)) // parameter number 127 means there no parameter number at all (don't need paremeter or get all) -#define NO_PARNO (0x1f) +#define NO_PARNO (0x7f) // base value of parameter (even if it is a setter) #define PARBASE(x) (x & 0x7f) +// get parameter value of msg->data +#define PARVAL(data) (data[2] & 0x7f) // make error for CAN answer #define FORMERR(m, err) do{m->data[3] = err; if(m->length < 4) m->length = 4;}while(0) @@ -43,6 +45,19 @@ typedef enum{ ERR_AMOUNT // amount of error codes } errcodes; +// set command bytes in CAN message +#define MSG_SET_CMD(msg, cmd) do{*((uint16_t*)msg.data) = (cmd);}while(0) +#define MSGP_SET_CMD(msg, cmd) do{*((uint16_t*)msg->data) = (cmd);}while(0) +// set error +#define MSG_SET_ERR(msg, err) do{msg.data[3] = (err);}while(0) +#define MSGP_SET_ERR(msg, err) do{msg->data[3] = (err);}while(0) +// set uint32_t data +#define MSG_SET_U32(msg, d) do{*((uint32_t*)(&msg.data[4])) = (d);}while(0) +#define MSGP_SET_U32(msg, d) do{*((uint32_t*)(&msg->data[4])) = (d);}while(0) +// get uint32_t data +#define MSG_GET_U32(msg) (*(uint32_t*)&msg.data[4]) +#define MSGP_GET_U32(msg) (*(uint32_t*)&msg->data[4]) + // CAN commands indexes enum{ CMD_PING, // just ping @@ -62,6 +77,11 @@ enum{ CMD_BOUNCE, // get/set bounce constant (ms) CMD_USARTSPEED, // get/set USART1 speed (if encoder on RS-422) CMD_LED, // onboard LED + CMD_FLAGS, // flags setter/getter + CMD_INCHNLS, // all bits set are active supported IN channels + CMD_OUTCHNLS, // all bits set are active supported OUT channels + CMD_MODBUSID, // set/get modbus slave ID (or 0 if master) + CMD_MODBUSSPEED,// speed of modbus interface // should be the last: CMD_AMOUNT // amount of CAN commands }; diff --git a/F1:F103/FX3U/flash.c b/F1:F103/FX3U/flash.c index 41d9bd3..25765d4 100644 --- a/F1:F103/FX3U/flash.c +++ b/F1:F103/FX3U/flash.c @@ -18,6 +18,7 @@ #include "stm32f1.h" +#include "modbusrtu.h" #include "flash.h" #include "strfunc.h" #include "usart.h" @@ -34,6 +35,8 @@ static uint32_t maxCnum = 1024 / sizeof(user_conf); // can't use blocksize here ,.CANIDout = 2 \ ,.usartspeed = 115200 \ ,.bouncetime = 50 \ + ,.modbusID = MODBUS_MASTER_ID \ + ,.modbusspeed = 9600 \ } static int write2flash(const void*, const void*, uint32_t); diff --git a/F1:F103/FX3U/flash.h b/F1:F103/FX3U/flash.h index 2238c8c..c9a7f8d 100644 --- a/F1:F103/FX3U/flash.h +++ b/F1:F103/FX3U/flash.h @@ -23,6 +23,15 @@ #define FLASH_SIZE_REG ((uint32_t)0x1FFFF7E0) #define FLASH_SIZE *((uint16_t*)FLASH_SIZE_REG) +// maximal bit number of flags +#define MAX_FLAG_BITNO (0) +typedef union{ + uint32_t u32; + struct{ + uint32_t sw_send_relay_cmd; // switching ESW state will send also CMD_RELAY command with CANID_OUT + }; +} confflags_t; + /* * struct to save user configurations */ @@ -30,10 +39,13 @@ typedef struct __attribute__((aligned(4))){ uint16_t userconf_sz; // "magick number" uint16_t CANIDin; // CAN bus device ID for input commands uint16_t CANIDout; // -//- for output signals - uint16_t reserved; + uint16_t reserved; // added for 32-bit align uint32_t bouncetime; // anti-bounce timeout (ms) uint32_t usartspeed; // RS-232 speed uint32_t CANspeed; // CAN bus speed + confflags_t flags; // different flags + uint32_t modbusID; // MODBUS-RTU ID (0 for master) + uint32_t modbusspeed; // Speed of modbus interface } user_conf; extern user_conf the_conf; diff --git a/F1:F103/FX3U/fx3u.bin b/F1:F103/FX3U/fx3u.bin index d00a77f..67a2774 100755 Binary files a/F1:F103/FX3U/fx3u.bin and b/F1:F103/FX3U/fx3u.bin differ diff --git a/F1:F103/FX3U/fx3u.files b/F1:F103/FX3U/fx3u.files index accf1c5..96b4695 100644 --- a/F1:F103/FX3U/fx3u.files +++ b/F1:F103/FX3U/fx3u.files @@ -10,6 +10,10 @@ flash.h hardware.c hardware.h main.c +modbusproto.c +modbusproto.h +modbusrtu.c +modbusrtu.h proto.c proto.h strfunc.c diff --git a/F1:F103/FX3U/hardware.c b/F1:F103/FX3U/hardware.c index d81ebce..5e3f893 100644 --- a/F1:F103/FX3U/hardware.c +++ b/F1:F103/FX3U/hardware.c @@ -20,10 +20,12 @@ #include "canproto.h" #include "flash.h" #include "hardware.h" +/* #ifdef EBUG #include "strfunc.h" -#include "uchar.h" +#include "usart.h" #endif +*/ /* pinout: @@ -257,11 +259,18 @@ void proc_esw(){ if(oldesw != ESW_ab_values){ //usart_send("esw="); usart_send(u2str(ESW_ab_values)); newline(); CAN_message msg = {.ID = the_conf.CANIDout, .length = 8}; - msg.data[0] = CMD_GETESW; - *((uint32_t*)(&msg.data[4])) = ESW_ab_values; + MSG_SET_CMD(msg, CMD_GETESW); + MSG_SET_U32(msg, ESW_ab_values); uint32_t Tstart = Tms; while(Tms - Tstart < SEND_TIMEOUT_MS){ - if(CAN_OK == CAN_send(&msg)) return; + if(CAN_OK == CAN_send(&msg)) break; + } + if(the_conf.flags.sw_send_relay_cmd){ // send also CMD_RELAY + MSG_SET_CMD(msg, CMD_RELAY); + Tstart = Tms; + while(Tms - Tstart < SEND_TIMEOUT_MS){ + if(CAN_OK == CAN_send(&msg)) break; + } } } } diff --git a/F1:F103/FX3U/hardware.h b/F1:F103/FX3U/hardware.h index 8248f82..dd43573 100644 --- a/F1:F103/FX3U/hardware.h +++ b/F1:F103/FX3U/hardware.h @@ -33,6 +33,9 @@ // max number of in/out pins #define INMAX (15) #define OUTMAX (11) +// and 8-multiple +#define INMAXBYTES (2) +#define OUTMAXBYTES (2) // onboard LED - PD10 #define LEDPORT GPIOD diff --git a/F1:F103/FX3U/main.c b/F1:F103/FX3U/main.c index 1ce3a98..a3d79d2 100644 --- a/F1:F103/FX3U/main.c +++ b/F1:F103/FX3U/main.c @@ -20,6 +20,8 @@ #include "can.h" #include "flash.h" #include "hardware.h" +#include "modbusproto.h" +#include "modbusrtu.h" #include "proto.h" #include "strfunc.h" @@ -30,17 +32,31 @@ void sys_tick_handler(void){ ++Tms; } +TRUE_INLINE void parsemodbus(){ + if(the_conf.modbusID != MODBUS_MASTER_ID){ // slave + modbus_request req; + if(1 == modbus_get_request(&req)) + parse_modbus_request(&req); + }else{ // master + modbus_response res; + if(1 == modbus_get_response(&res)) + parse_modbus_response(&res); + } +} + int main(void){ uint32_t lastT = 0; CAN_message *can_mesg; StartHSE(); + RCC->CSR |= RCC_CSR_RMVF; // remove reset flags SysTick_Config(72000); flashstorage_init(); gpio_setup(); // should be run before other peripherial setup adc_setup(); usart_setup(the_conf.usartspeed); CAN_setup(the_conf.CANspeed); - RCC->CSR |= RCC_CSR_RMVF; // remove reset flags + modbus_setup(the_conf.modbusspeed); + #ifndef EBUG iwdg_setup(); #endif @@ -80,6 +96,7 @@ int main(void){ int g = usart_getline(&str); if(g < 0) usart_send("USART IN buffer overflow!\n"); else if(g > 0) cmd_parser(str); + parsemodbus(); } return 0; } diff --git a/F1:F103/FX3U/modbusproto.c b/F1:F103/FX3U/modbusproto.c new file mode 100644 index 0000000..cacab57 --- /dev/null +++ b/F1:F103/FX3U/modbusproto.c @@ -0,0 +1,261 @@ +/* + * This file is part of the fx3u project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "adc.h" +#include "flash.h" +#include "hardware.h" +#include "modbusproto.h" +#include "modbusrtu.h" +#include "strfunc.h" +#include "usart.h" + +// send error +static void senderr(modbus_request *r, uint8_t exception){ + modbus_response resp = {.ID = the_conf.modbusID, .Fcode = r->Fcode | MODBUS_RESPONSE_ERRMARK, .datalen = exception}; + modbus_send_response(&resp); +} + +// relays status +TRUE_INLINE void readcoil(modbus_request *r){ + // starting number shoul be 0 + if(r->startreg){ + senderr(r, ME_ILLEGAL_ADDRESS); + return; + } + // amount of bytes: should be 8-multiple + int amount = r->regno >> 3; + if(amount == 0 || (r->regno & 7) || amount > OUTMAXBYTES){ + senderr(r, ME_ILLEGAL_VALUE); + return; + } + uint8_t bytes[OUTMAXBYTES] = {0}; + int curidx = OUTMAXBYTES; + int vals = get_relay(OUTMAX+1); + for(int i = 0; i < amount; ++i){ + bytes[--curidx] = vals & 0xff; + vals >>= 8; + } + modbus_response resp = {.Fcode = r->Fcode, .ID = the_conf.modbusID, .data = bytes+curidx, .datalen = amount}; + modbus_send_response(&resp); +} + +// input status +TRUE_INLINE void readdiscr(modbus_request *r){ + if(r->startreg){ + senderr(r, ME_ILLEGAL_ADDRESS); + return; + } + // amount of bytes: should be 8-multiple + int amount = r->regno >> 3; + if(amount == 0 || (r->regno & 7) || amount > INMAXBYTES){ + senderr(r, ME_ILLEGAL_VALUE); + return; + } + uint8_t bytes[INMAXBYTES] = {0}; + int curidx = INMAXBYTES - 1; + int vals = get_esw(INMAX+1); + for(int i = 0; i < amount; ++i){ + bytes[--curidx] = vals & 0xff; + vals >>= 8; + } + modbus_response resp = {.Fcode = r->Fcode, .ID = the_conf.modbusID, .data = bytes+curidx, .datalen = amount}; + modbus_send_response(&resp); +} + +// registers: read only by one! +// !!! To simplify code I suppose that all registers read are 32-bit! +TRUE_INLINE void readreg(modbus_request *r){ + if(r->regno != 1){ + senderr(r, ME_ILLEGAL_VALUE); + return; + } + uint32_t regval; + switch(r->startreg){ + case MR_TIME: + regval = Tms; + break; + case MR_LED: + regval = LED(-1); + break; + case MR_INCHANNELS: + regval = inchannels(); + break; + case MR_OUTCHANNELS: + regval = outchannels(); + break; + default: + senderr(r, ME_ILLEGAL_ADDRESS); + return; + } + regval = __builtin_bswap32(regval); + modbus_response resp = {.Fcode = r->Fcode, .ID = the_conf.modbusID, .data = (uint8_t*)®val, .datalen = 4}; + modbus_send_response(&resp); +} + +// read ADC values; startreg = N of starting sensor, regno = N values +TRUE_INLINE void readadc(modbus_request *r){ + if(r->startreg >= ADC_CHANNELS){ + senderr(r, ME_ILLEGAL_ADDRESS); + return; + } + int nlast = r->regno + r->startreg; + if(r->regno == 0 || nlast > ADC_CHANNELS){ + senderr(r, ME_ILLEGAL_VALUE); + return; + } + uint16_t vals[ADC_CHANNELS]; + for(int i = r->startreg; i < nlast; ++i){ + uint16_t v = getADCval(i); + vals[i] = __builtin_bswap16(v); + } + modbus_response resp = {.Fcode = r->Fcode, .ID = the_conf.modbusID, .data = (uint8_t*) vals, .datalen = r->regno * 2}; + modbus_send_response(&resp); +} + +TRUE_INLINE void writecoil(modbus_request *r){ + if(r->startreg > OUTMAX || set_relay(r->startreg, r->regno) < 0){ + senderr(r, ME_ILLEGAL_ADDRESS); + return; + } + modbus_send_request(r); // answer with same data +} + +TRUE_INLINE void writereg(modbus_request *r){ + switch(r->startreg){ + case MR_LED: + LED(r->regno); + break; + case MR_RESET: + NVIC_SystemReset(); + break; + default: + senderr(r, ME_ILLEGAL_ADDRESS); + return; + } + modbus_send_request(r); +} + +// support ONLY write to ALL! +// data - by bits, like in readcoil +TRUE_INLINE void writecoils(modbus_request *r){ + if(r->startreg){ + senderr(r, ME_ILLEGAL_ADDRESS); + return; + } + int amount = r->regno; + if(amount == 0 || amount > OUTMAX || r->datalen > 4){ + senderr(r, ME_ILLEGAL_VALUE); + return; + } + uint32_t v = 0; + for(int i = 0; i < amount; ++i){ + v |= r->data[i]; + v <<= 8; + } + if(set_relay(OUTMAX+1, v) < 0){ + senderr(r, ME_NACK); + return; + } + r->datalen = 0; + modbus_send_request(r); +} + +// amount of registers should be equal 1, data should have 4 bytes +TRUE_INLINE void writeregs(modbus_request *r){ + if(r->datalen != 4 || r->regno != 1){ + senderr(r, ME_ILLEGAL_VALUE); + return; + } + uint32_t val = __builtin_bswap32(*(uint32_t*)r->data); + switch(r->startreg){ + case MR_TIME: + Tms = val; + break; + default: + senderr(r, ME_ILLEGAL_ADDRESS); + return; + } + r->datalen = 0; + modbus_send_request(r); +} + +// get request as slave and send answer about some registers +// DO NOT ANSWER for broadcasting requests! +// Understand only requests with codes <= 6 +void parse_modbus_request(modbus_request *r){ + if(!r) return; + switch(r->Fcode){ + case MC_READ_COIL: + readcoil(r); + break; + case MC_READ_DISCRETE: + readdiscr(r); + break; + case MC_READ_HOLDING_REG: + readreg(r); + break; + case MC_READ_INPUT_REG: + readadc(r); + break; + case MC_WRITE_COIL: + writecoil(r); + break; + case MC_WRITE_REG: + writereg(r); + break; + case MC_WRITE_MUL_COILS: + writecoils(r); + break; + case MC_WRITE_MUL_REGS: // write uint32_t as 2 registers + writeregs(r); + break; + default: + senderr(r, ME_ILLEGAL_FUNCION); + } +} + +// get responce as master and show it on terminal +void parse_modbus_response(modbus_response *r){ + if(!r) return; + if(r->Fcode & MODBUS_RESPONSE_ERRMARK){ // error - three bytes + usart_send("MODBUS ERR (ID="); + printuhex(r->ID); + usart_send(", Fcode="); + printuhex(r->Fcode & ~MODBUS_RESPONSE_ERRMARK); + usart_send(") > "); + printuhex(r->datalen); + newline(); + return; + } + // regular answer + usart_send("MODBUS RESP (ID="); + printuhex(r->ID); + usart_send(", Fcode="); + printuhex(r->Fcode & ~MODBUS_RESPONSE_ERRMARK); + usart_send(", Datalen="); + printu(r->datalen); + usart_send(") > "); + if(r->datalen){ + for(int i = 0; i < r->datalen; ++i){ + //uint16_t nxt = r->data[i] >> 8 | r->data[i] << 8; + printuhex(r->data[i]); + usart_putchar(' '); + } + } + newline(); +} diff --git a/F1:F103/FX3U/modbusproto.h b/F1:F103/FX3U/modbusproto.h new file mode 100644 index 0000000..db458e6 --- /dev/null +++ b/F1:F103/FX3U/modbusproto.h @@ -0,0 +1,48 @@ +/* + * This file is part of the fx3u project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "modbusrtu.h" +#include "strfunc.h" + +/* +MODBUS PROTOCOL: +uint32_t translates as two uint16_t in big-endian format + +MC_READ_COIL - coil values (by bits) +MC_READ_DISCRETE - discrete -//- +MC_READ_HOLDING_REG - read time/led/inch/outch +MC_READ_INPUT_REG - read ADC channel (regno is ADC ch number) +MC_WRITE_COIL - change single relay +MC_WRITE_REG - set/reset LED, restart MCU +MC_WRITE_MUL_COILS - change several relays +MC_WRITE_MUL_REGS - change Tms +*/ + +// holding registers list (comment - corresponding CANbus command) +typedef enum{ + MR_RESET, // CMD_RESET + MR_TIME, // CMD_TIME + MR_LED, // CMD_LED + MR_INCHANNELS, // CMD_INCHNLS + MR_OUTCHANNELS, // CMD_OUTCHNLS +} modbus_registers; + +void parse_modbus_request(modbus_request *r); +void parse_modbus_response(modbus_response *r); diff --git a/F1:F103/FX3U/modbusrtu.c b/F1:F103/FX3U/modbusrtu.c new file mode 100644 index 0000000..a207571 --- /dev/null +++ b/F1:F103/FX3U/modbusrtu.c @@ -0,0 +1,238 @@ +/* + * This file is part of the fx3u project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "modbusrtu.h" +#include "flash.h" +#include "strfunc.h" + +#ifdef EBUG +#include "usart.h" +#endif + +#include +#include // memcpy + +static volatile int modbus_txrdy = 1; +static volatile int idatalen[2] = {0,0}; // received data line length (including '\n') + +static volatile int modbus_rdy = 0 // received data ready + ,dlen = 0 // length of data (including '\n') in current buffer + ,bufovr = 0 // input buffer overfull +; + +static int rbufno = 0, tbufno = 0; // current rbuf/tbuf numbers +static uint8_t rbuf[2][MODBUSBUFSZI], tbuf[2][MODBUSBUFSZO]; // receive & transmit buffers +static uint8_t *recvdata = NULL; + +#define packCRC(d, l) *((uint16_t*) &d[l]) + +// calculate CRC for given data +static uint16_t getCRC(uint8_t *data, int l){ + uint16_t crc = 0xFFFF; + for(int pos = 0; pos < l; ++pos){ + crc ^= (uint16_t)data[pos]; + for(int i = 8; i; --i){ + if((crc & 1)){ + crc >>= 1; + crc ^= 0xA001; + }else crc >>= 1; + } + } + // CRC have swapped bytes, so we can just send it as *((uint16_t*)&data[x]) = CRC + return crc; +} + +/** + * return length of received data without CRC or -1 if buffer overflow or bad CRC + */ +int modbus_receive(uint8_t **packet){ + if(!modbus_rdy) return 0; + if(bufovr){ + DBG("Modbus buffer overflow\n"); + bufovr = 0; + modbus_rdy = 0; + return -1; + } + *packet = recvdata; + modbus_rdy = 0; + int x = dlen - 2; + dlen = 0; + uint16_t chk = getCRC(recvdata, x); + if(packCRC(recvdata, x) != chk){ + DBG("Bad CRC\n"); + return -1; + } + return x; +} + +// send current tbuf +static int senddata(int l){ + uint32_t tmout = 1600000; + while(!modbus_txrdy){ + IWDG->KR = IWDG_REFRESH; + if(--tmout == 0) return 0; + }; // wait for previos buffer transmission + modbus_txrdy = 0; + DMA2_Channel5->CCR &= ~DMA_CCR_EN; + DMA2_Channel5->CMAR = (uint32_t) tbuf[tbufno]; // mem + DMA2_Channel5->CNDTR = l + 2; // + CRC + DMA2_Channel5->CCR |= DMA_CCR_EN; + tbufno = !tbufno; + return l; +} + +// transmit raw data with length l; amount of bytes (without CRC) sent +int modbus_send(uint8_t *data, int l){ + if(l < 1) return 0; + if(l > MODBUSBUFSZO - 2) return -1; + memcpy(tbuf[tbufno], data, l); + packCRC(tbuf[tbufno], l) = getCRC(data, l); + return senddata(l); +} + +// send request: return the same as modbus_receive() +int modbus_send_request(modbus_request *r){ + uint8_t *curbuf = tbuf[tbufno]; + int n = 6; + *curbuf++ = r->ID; + *curbuf++ = r->Fcode; + *curbuf++ = r->startreg >> 8; // H + *curbuf++ = (uint8_t) r->startreg; // L + *curbuf++ = r->regno >> 8; // H + *curbuf = (uint8_t) r->regno; // L + // if r->datalen == 0 - this is responce for request with fcode > 4 + if((r->Fcode == MC_WRITE_MUL_COILS || r->Fcode == MC_WRITE_MUL_REGS) && r->datalen){ // request with data + *(++curbuf) = r->datalen; + memcpy(curbuf, r->data, r->datalen); + n += r->datalen; + } + return senddata(n); +} + +// return -1 in case of error, 0 if no data received, 1 if got good packet +int modbus_get_request(modbus_request* r){ + if(!r) return -1; + uint8_t *pack; + int l = modbus_receive(&pack); + if(l < 1) return l; + if(l < 6) return -1; // not a request + // check "broadcasting" and common requests + if(*pack && *pack != the_conf.modbusID) return 0; // alien request + r->ID = *pack++; + r->Fcode = *pack++; + r->startreg = pack[0] << 8 | pack[1]; + r->regno = pack[2] << 8 | pack[3]; + if(l > 6){ // request with data + if(r->Fcode != MC_WRITE_MUL_COILS && r->Fcode != MC_WRITE_MUL_REGS) return -1; // bad request + r->datalen = pack[4]; + if(r->datalen > l-6) r->datalen = l-6; // fix if data bytes less than field + r->data = pack + 5; + }else{ + r->datalen = 0; + r->data = NULL; + } + return 1; +} + +// send responce: return the same as modbus_receive() +int modbus_send_response(modbus_response *r){ + uint8_t *curbuf = tbuf[tbufno]; + int len = 3; // packet data length without CRC + *curbuf++ = r->ID; + *curbuf++ = r->Fcode; + *curbuf++ = r->datalen; + if(0 == (r->Fcode & MODBUS_RESPONSE_ERRMARK)){ // data + len += r->datalen; + if(len > MODBUSBUFSZO - 2) return -1; // too much data + memcpy(curbuf, r->data, r->datalen); + } + return senddata(len); +} + +// get recponce; warning: all data is a pointer to last rbuf, it could be corrupted after next reading +int modbus_get_response(modbus_response* r){ + if(!r) return -1; + uint8_t *pack; + int l = modbus_receive(&pack); + if(l < 1) return l; + if(l < 3) return -1; // not a responce + r->ID = *pack++; + r->Fcode = *pack++; + r->datalen = *pack++; + // error + if(r->Fcode & MODBUS_RESPONSE_ERRMARK) r->data = NULL; + else{ + // wrong datalen - fix + if(r->datalen != l-3){ r->datalen = l-3; } + r->data = pack; + } + return 1; +} + +// USART4: PC10 - Tx, PC11 - Rx +void modbus_setup(uint32_t speed){ + uint32_t tmout = 16000000; + // PA9 - Tx, PA10 - Rx + RCC->APB1ENR |= RCC_APB1ENR_UART4EN; + RCC->AHBENR |= RCC_AHBENR_DMA2EN; + GPIOA->CRH = (GPIOA->CRH & ~(CRH(9,0xf)|CRH(10,0xf))) | + CRH(9, CNF_AFPP|MODE_NORMAL) | CRH(10, CNF_FLINPUT|MODE_INPUT); + // UART4 Tx DMA - Channel5 (Rx - channel 3) + DMA2_Channel5->CPAR = (uint32_t) &UART4->DR; // periph + DMA2_Channel5->CCR |= DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_TCIE; // 8bit, mem++, mem->per, transcompl irq + // Tx CNDTR set @ each transmission due to data size + NVIC_SetPriority(DMA2_Channel4_5_IRQn, 2); + NVIC_EnableIRQ(DMA2_Channel4_5_IRQn); + NVIC_SetPriority(UART4_IRQn, 2); + // setup uart4 + UART4->BRR = 36000000 / speed; // APB1 is 36MHz + UART4->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE; // 1start,8data,nstop; enable Rx,Tx,USART + while(!(UART4->SR & USART_SR_TC)){if(--tmout == 0) break;} // polling idle frame Transmission + UART4->SR = 0; // clear flags + UART4->CR1 |= USART_CR1_RXNEIE | USART_CR1_IDLEIE; // allow Rx and IDLE IRQ + UART4->CR3 = USART_CR3_DMAT; // enable DMA Tx + NVIC_EnableIRQ(UART4_IRQn); +} + +void uart4_isr(){ + if(UART4->SR & USART_SR_IDLE){ // idle - end of frame + modbus_rdy = 1; + dlen = idatalen[rbufno]; + recvdata = rbuf[rbufno]; + // prepare other buffer + rbufno = !rbufno; + idatalen[rbufno] = 0; + (void) UART4->DR; // clear IDLE flag by reading DR + } + if(UART4->SR & USART_SR_RXNE){ // RX not emty - receive next char + uint8_t rb = UART4->DR; // clear RXNE flag + if(idatalen[rbufno] < MODBUSBUFSZI){ // put next char into buf + rbuf[rbufno][idatalen[rbufno]++] = rb; + }else{ // buffer overrun + bufovr = 1; + idatalen[rbufno] = 0; + } + } +} + +void dma2_channel4_5_isr(){ + if(DMA2->ISR & DMA_ISR_TCIF5){ // Tx + DMA2->IFCR = DMA_IFCR_CTCIF5; // clear TC flag + modbus_txrdy = 1; + } +} diff --git a/F1:F103/FX3U/modbusrtu.h b/F1:F103/FX3U/modbusrtu.h new file mode 100644 index 0000000..85b5a5f --- /dev/null +++ b/F1:F103/FX3U/modbusrtu.h @@ -0,0 +1,80 @@ +/* + * This file is part of the fx3u project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +// input and output buffers size +#define MODBUSBUFSZI (64) +#define MODBUSBUFSZO (64) + +#define MODBUS_MASTER_ID (0) +#define MODBUS_MAX_ID (247) + +// some exceptions +typedef enum{ + ME_ILLEGAL_FUNCION = 1, // The function code received in the request is not an authorized action for the slave. + ME_ILLEGAL_ADDRESS = 2, // The data address received by the slave is not an authorized address for the slave. + ME_ILLEGAL_VALUE = 3, // The value in the request data field is not an authorized value for the slave. + ME_SLAVE_FAILURE = 4, // The slave fails to perform a requested action because of an unrecoverable error. + ME_ACK = 5, // The slave accepts the request but needs a long time to process it. + ME_SLAVE_BUSY = 6, // The slave is busy processing another command. + ME_NACK = 7, // The slave cannot perform the programming request sent by the master. + ME_PARITY_ERROR = 8, // Memory parity error: slave is almost dead. +} modbus_exceptions; + +// common function codes; "by bits" means that output data length = (requested+15)/16 +typedef enum{ + MC_READ_COIL = 1, // read output[s] state (by bits) + MC_READ_DISCRETE = 2, // read input[s] state (by bits!) + MC_READ_HOLDING_REG = 3, + MC_READ_INPUT_REG = 4, + MC_WRITE_COIL = 5, + MC_WRITE_REG = 6, + MC_WRITE_MUL_COILS = 0xF, + MC_WRITE_MUL_REGS = 0x10, +} modbus_fcode; + +// modbus master request (without CRC) +typedef struct{ + uint8_t ID; // slave ID + uint8_t Fcode; // functional code + uint16_t startreg; // started register or single register address + uint16_t regno; // number of registers or data to write + uint8_t datalen;// data for 0xf/0x10 + uint8_t *data; +} modbus_request; + +// responce->Fcode & RESPONCE_ERRMARK -> error packet +#define MODBUS_RESPONSE_ERRMARK (0x80) + +// modbus slave responce (without CRC) +typedef struct{ + uint8_t ID; // slave ID + uint8_t Fcode; // functional code + uint8_t datalen; // length of data in BYTES! (or exception code) + uint8_t *data; // data (or NULL for error packet); BE CAREFUL: all data is big-endian! +} modbus_response; + +void modbus_setup(uint32_t speed); +int modbus_receive(uint8_t **packet); +int modbus_get_request(modbus_request* r); +int modbus_get_response(modbus_response* r); +int modbus_send(uint8_t *data, int l); +int modbus_send_request(modbus_request *r); +int modbus_send_response(modbus_response *r); diff --git a/F1:F103/FX3U/proto.c b/F1:F103/FX3U/proto.c index cfc53d7..0703c8f 100644 --- a/F1:F103/FX3U/proto.c +++ b/F1:F103/FX3U/proto.c @@ -22,6 +22,7 @@ #include "flash.h" #include "hardware.h" #include "proto.h" +#include "modbusrtu.h" #include "strfunc.h" #include "usart.h" #include "version.inc" @@ -38,6 +39,8 @@ typedef enum{ TCMD_CANSEND, // send CAN message TCMD_CANSNIFFER, // sniff all CAN messages TCMD_CANBUSERRPRNT, // pring all errors of bus + TCMD_SW_SEND_RELAY, // change of IN will send also command to change OUT + TCMD_MODBUS_SEND, // send raw modbus data (CRC added auto) TCMD_AMOUNT } text_cmd; @@ -56,6 +59,7 @@ static const funcdescr funclist[] = { {NULL, 0, "CAN bus commands"}, {"canbuserr", -TCMD_CANBUSERRPRNT, "print all CAN bus errors (a lot of if not connected)"}, {"cansniff", -TCMD_CANSNIFFER, "switch CAN sniffer mode"}, + {"s", -TCMD_CANSEND, "send CAN message: ID 0..8 data bytes"}, {NULL, 0, "Configuration"}, {"bounce", CMD_BOUNCE, "set/get anti-bounce timeout (ms, max: 1000)"}, {"canid", CMD_CANID, "set both (in/out) CAN ID / get in CAN ID"}, @@ -64,7 +68,11 @@ static const funcdescr funclist[] = { {"canspeed", CMD_CANSPEED, "get/set CAN speed (bps)"}, {"dumpconf", -TCMD_DUMPCONF, "dump current configuration"}, {"eraseflash", CMD_ERASESTOR, "erase all flash storage"}, + {"flags", CMD_FLAGS, "set/get configuration flags (as one U32 without parameter or Nth bit with)"}, + {"modbusid", CMD_MODBUSID, "set/get modbus slave ID (1..247) or set it master (0)"}, + {"modbusspeed", CMD_MODBUSSPEED, "set/get modbus speed (1200..115200)"}, {"saveconf", CMD_SAVECONF, "save configuration"}, + {"sw_send_relay", -TCMD_SW_SEND_RELAY, "change of IN will send also command to change OUT with `canidout`"}, {"usartspeed", CMD_USARTSPEED, "get/set USART1 speed"}, {NULL, 0, "IN/OUT"}, {"adc", CMD_ADCRAW, "get raw ADC values for given channel"}, @@ -73,9 +81,11 @@ static const funcdescr funclist[] = { {"led", CMD_LED, "work with onboard LED"}, {"relay", CMD_RELAY, "get/set relay state (0 - off, 1 - on)"}, {NULL, 0, "Other commands"}, + {"inchannels", CMD_INCHNLS, "get u32 with bits set on supported IN channels"}, {"mcutemp", CMD_MCUTEMP, "get MCU temperature (*10degrC)"}, + {"modbus", -TCMD_MODBUS_SEND, "send modbus request, format: slaveID Fcode startReg numRegs"}, + {"outchannels", CMD_OUTCHNLS, "get u32 with bits set on supported OUT channels"}, {"reset", CMD_RESET, "reset MCU"}, - {"s", -TCMD_CANSEND, "send CAN message: ID 0..8 data bytes"}, {"time", CMD_TIME, "get/set time (1ms, 32bit)"}, {"wdtest", -TCMD_WDTEST, "test watchdog"}, {NULL, 0, NULL} // last record @@ -83,7 +93,7 @@ static const funcdescr funclist[] = { static void printhelp(){ const funcdescr *c = funclist; - usart_send("https://github.com/eddyem/stm32samples/tree/master/F1:F103/FX3U#" BUILD_NUMBER " @ " BUILD_DATE "\n"); + usart_send("https://github.com/eddyem/stm32samples/tree/master/F1:F103/FX3U build #" BUILD_NUMBER " @ " BUILD_DATE "\n"); usart_send("commands format: parameter[number][=setter]\n"); usart_send("parameter [CAN idx] - help\n"); usart_send("--------------------------\n"); @@ -107,9 +117,9 @@ static void printhelp(){ } } -/*********** START of all common functions list (for `textfunctions`) ***********/ +/*********** START of all text functions list ***********/ -static errcodes cansnif(const char *str){ +static errcodes cansnif(const char *str, text_cmd _U_ cmd){ uint32_t U; if(str){ if(*str == '=') str = omit_spaces(str + 1); @@ -122,7 +132,7 @@ static errcodes cansnif(const char *str){ return ERR_OK; } -static errcodes canbuserr(const char *str){ +static errcodes canbuserr(const char *str, text_cmd _U_ cmd){ uint32_t U; if(str){ if(*str == '=') str = omit_spaces(str + 1); @@ -135,21 +145,20 @@ static errcodes canbuserr(const char *str){ return ERR_OK; } -static errcodes wdtest(const char _U_ *str){ +static errcodes wdtest(const char _U_ *str, text_cmd _U_ cmd){ usart_send("Wait for reboot\n"); usart_transmit(); while(1){nop();} return ERR_OK; } -/* + // names of bit flags (ordered from LSE of[0]) static const char * const bitfields[] = { - "encisSSI", - "emulatePEP", + "sw_send_relay_cmd", NULL -};*/ +}; -static errcodes dumpconf(const char _U_ *str){ +static errcodes dumpconf(const char _U_ *str, text_cmd _U_ cmd){ #ifdef EBUG uint32_t sz = FLASH_SIZE*1024; usart_send("flashsize="); printu(sz); usart_putchar('/'); @@ -167,21 +176,23 @@ static errcodes dumpconf(const char _U_ *str){ usart_send(float2str(the_conf.adcmul[i], 3)); }*/ usart_send("\nusartspeed="); printu(the_conf.usartspeed); + usart_send("\nmodbus_id="); printu(the_conf.modbusID); + usart_send("\nmodbusspeed="); printu(the_conf.modbusspeed); usart_send("\nbouncetime="); printu(the_conf.bouncetime); - /* const char * const *p = bitfields; int bit = 0; + usart_send("\nflags="); usart_putchar('='); printuhex(the_conf.flags.u32); while(*p){ - newline(); - usart_send(*p); usart_putchar('='); usart_putchar((the_conf.flags & (1< 31) break; + newline(); usart_putchar(' '); + usart_send(*p); usart_putchar('='); usart_putchar((the_conf.flags.u32 & (1< MAX_FLAG_BITNO) break; ++p; - }*/ + } newline(); return ERR_OK; } -static errcodes cansend(const char *txt){ +static errcodes cansend(const char *txt, text_cmd _U_ cmd){ CAN_message canmsg; bzero(&canmsg, sizeof(canmsg)); int ctr = -1; @@ -221,10 +232,70 @@ static errcodes cansend(const char *txt){ return ERR_CANTRUN; } -/************ END of all common functions list (for `textfunctions`) ************/ +// change configuration flags by one +static errcodes confflags(const char _U_ *str, text_cmd cmd){ + if(str){ + if(*str == '=') str = omit_spaces(str + 1); + if(*str != '0' && *str != '1') return ERR_BADVAL; + uint8_t val = *str - '0'; + switch(cmd){ + case TCMD_SW_SEND_RELAY: + the_conf.flags.sw_send_relay_cmd = val; + break; + default: + return ERR_BADCMD; + } + } + uint8_t val = 0, idx = 0; + switch(cmd){ + case TCMD_SW_SEND_RELAY: + val = the_conf.flags.sw_send_relay_cmd; + idx = 0; + break; + default: + return ERR_BADCMD; + } + usart_send(bitfields[idx]); usart_putchar('='); usart_putchar('0' + val); + newline(); + return ERR_OK; +} + +// format: slaveID Fcode startReg numRegs +static errcodes modbussend(const char *txt, text_cmd _U_ cmd){ + modbus_request req = {0}; + uint32_t N = 0; + const char *n = getnum(txt, &N); + if(n == txt || N > MODBUS_MAX_ID){ + usart_send("Need slave ID (0..247)\n"); + return ERR_WRONGLEN; + } + req.ID = N; + txt = n; n = getnum(txt, &N); + if(n == txt || N > 127 || N == 0){ + usart_send("Need function code (1..127)\n"); + return ERR_WRONGLEN; + } + req.Fcode = N; + txt = n; n = getnum(txt, &N); + if(n == txt){ + usart_send("Need starting register address\n"); + return ERR_WRONGLEN; + } + req.startreg = N; + txt = n; n = getnum(txt, &N); + if(n == txt || N == 0){ + usart_send("Need registers amount\n"); + return ERR_WRONGLEN; + } + req.regno = N; + if(modbus_send_request(&req) < 1) return ERR_CANTRUN; + return ERR_OK; +} + +/************ END of all text functions list ************/ // in `textfn` arg `str` is the rest of input string (spaces-omitted) after command -typedef errcodes (*textfn)(const char *str); +typedef errcodes (*textfn)(const char *str, text_cmd cmd); static textfn textfunctions[TCMD_AMOUNT] = { [TCMD_PROHIBITED] = NULL, [TCMD_WDTEST] = wdtest, @@ -232,6 +303,8 @@ static textfn textfunctions[TCMD_AMOUNT] = { [TCMD_CANSEND] = cansend, [TCMD_CANSNIFFER] = cansnif, [TCMD_CANBUSERRPRNT] = canbuserr, + [TCMD_SW_SEND_RELAY] = confflags, + [TCMD_MODBUS_SEND] = modbussend, }; static const char* const errors_txt[ERR_AMOUNT] = { @@ -254,9 +327,9 @@ static void errtext(errcodes e){ * @param txt - buffer with commands & data */ void cmd_parser(const char *str){ + errcodes ecode = ERR_BADCMD; if(!str || !*str) goto ret; char cmd[MAXCMDLEN + 1]; - errcodes ecode = ERR_BADCMD; int idx = CMD_AMOUNT; const funcdescr *c = funclist; int l = 0; @@ -279,7 +352,7 @@ void cmd_parser(const char *str){ } str = omit_spaces(ptr); if(idx < 0){ // text-only function - ecode = textfunctions[-idx](str); + ecode = textfunctions[-idx](str, -idx); goto ret; } // common CAN/text function: we need to form 8-byte data buffer diff --git a/F1:F103/FX3U/usart.c b/F1:F103/FX3U/usart.c index 7dffdeb..9c70504 100644 --- a/F1:F103/FX3U/usart.c +++ b/F1:F103/FX3U/usart.c @@ -23,7 +23,7 @@ extern volatile uint32_t Tms; static volatile int idatalen[2] = {0,0}; // received data line length (including '\n') static volatile int odatalen[2] = {0,0}; -volatile int usart_txrdy = 1; // transmission done +static volatile int usart_txrdy = 1; // transmission done static volatile int usart_linerdy = 0 // received data ready ,dlen = 0 // length of data (including '\n') in current buffer @@ -31,7 +31,7 @@ static volatile int usart_linerdy = 0 // received data ready ; -int rbufno = 0, tbufno = 0; // current rbuf/tbuf numbers +static int rbufno = 0, tbufno = 0; // current rbuf/tbuf numbers static char rbuf[2][UARTBUFSZI], tbuf[2][UARTBUFSZO]; // receive & transmit buffers static char *recvdata = NULL; diff --git a/F1:F103/FX3U/usart.h b/F1:F103/FX3U/usart.h index ef767df..3b296e7 100644 --- a/F1:F103/FX3U/usart.h +++ b/F1:F103/FX3U/usart.h @@ -18,6 +18,8 @@ #pragma once +#include + // input and output buffers size #define UARTBUFSZI (64) #define UARTBUFSZO (128) @@ -25,8 +27,6 @@ #define usartrx() (usart_linerdy) #define usartovr() (usart_bufovr) -extern volatile int usart_txrdy; - int usart_transmit(); void usart_setup(uint32_t speed); int usart_getline(char **line); diff --git a/F1:F103/FX3U/version.inc b/F1:F103/FX3U/version.inc index 627e72b..91e8782 100644 --- a/F1:F103/FX3U/version.inc +++ b/F1:F103/FX3U/version.inc @@ -1,2 +1,2 @@ -#define BUILD_NUMBER "56" -#define BUILD_DATE "2024-06-04" +#define BUILD_NUMBER "63" +#define BUILD_DATE "2024-09-19" diff --git a/snippets/strfunc.c b/snippets/strfunc.c index 4c6495c..c5ea3ac 100644 --- a/snippets/strfunc.c +++ b/snippets/strfunc.c @@ -1,6 +1,5 @@ /* - * This file is part of the i2cscan project. - * Copyright 2023 Edward V. Emelianov . + * Copyright 2024 Edward V. Emelianov . * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,15 +15,16 @@ * along with this program. If not, see . */ -#include - +#include +#include +#include +#include "usb.h" /** * @brief hexdump - dump hex array by 16 bytes in string - * @param sendfun - function to send data * @param arr - array to dump * @param len - length of `arr` */ -void hexdump(int (*sendfun)(const char *s), uint8_t *arr, uint16_t len){ +void hexdump(uint8_t *arr, uint16_t len){ char buf[52], *bptr = buf; for(uint16_t l = 0; l < len; ++l, ++arr){ for(int16_t j = 1; j > -1; --j){ @@ -35,14 +35,14 @@ void hexdump(int (*sendfun)(const char *s), uint8_t *arr, uint16_t len){ if(l % 16 == 15){ *bptr++ = '\n'; *bptr = 0; - sendfun(buf); + USB_sendstr(buf); bptr = buf; }else *bptr++ = ' '; } if(bptr != buf){ *bptr++ = '\n'; *bptr = 0; - sendfun(buf); + USB_sendstr(buf); } } @@ -60,8 +60,11 @@ static char *_2str(uint32_t val, uint8_t minus){ *(--bufptr) = '0'; }else{ while(val){ - *(--bufptr) = val % 10 + '0'; - val /= 10; + uint32_t x = val / 10; + *(--bufptr) = (val - 10*x) + '0'; + val = x; + //*(--bufptr) = val % 10 + '0'; + //val /= 10; } } if(minus) *(--bufptr) = '-'; @@ -113,12 +116,12 @@ char *uhex2str(uint32_t val){ * @param buf - string * @return - pointer to first character in `buf` > ' ' */ -char *omit_spaces(const char *buf){ +const char *omit_spaces(const char *buf){ while(*buf){ if(*buf > ' ') break; ++buf; } - return (char*)buf; + return buf; } /** @@ -127,7 +130,7 @@ char *omit_spaces(const char *buf){ * @param N - number read * @return Next non-number symbol. In case of overflow return `buf` and N==0xffffffff */ -static char *getdec(const char *buf, uint32_t *N){ +static const char *getdec(const char *buf, uint32_t *N){ char *start = (char*)buf; uint32_t num = 0; while(*buf){ @@ -144,11 +147,11 @@ static char *getdec(const char *buf, uint32_t *N){ ++buf; } *N = num; - return (char*)buf; + return buf; } // read hexadecimal number (without 0x prefix!) -static char *gethex(const char *buf, uint32_t *N){ - char *start = (char*)buf; +static const char *gethex(const char *buf, uint32_t *N){ + const char *start = buf; uint32_t num = 0; while(*buf){ char c = *buf; @@ -173,11 +176,11 @@ static char *gethex(const char *buf, uint32_t *N){ ++buf; } *N = num; - return (char*)buf; + return buf; } // read octal number (without 0 prefix!) -static char *getoct(const char *buf, uint32_t *N){ - char *start = (char*)buf; +static const char *getoct(const char *buf, uint32_t *N){ + const char *start = (char*)buf; uint32_t num = 0; while(*buf){ char c = *buf; @@ -193,11 +196,11 @@ static char *getoct(const char *buf, uint32_t *N){ ++buf; } *N = num; - return (char*)buf; + return buf; } // read binary number (without b prefix!) -static char *getbin(const char *buf, uint32_t *N){ - char *start = (char*)buf; +static const char *getbin(const char *buf, uint32_t *N){ + const char *start = (char*)buf; uint32_t num = 0; while(*buf){ char c = *buf; @@ -213,7 +216,7 @@ static char *getbin(const char *buf, uint32_t *N){ ++buf; } *N = num; - return (char*)buf; + return buf; } /** @@ -223,9 +226,9 @@ static char *getbin(const char *buf, uint32_t *N){ * @return pointer to first non-number symbol in buf * (if it is == buf, there's no number or if *N==0xffffffff there was overflow) */ -char *getnum(const char *txt, uint32_t *N){ - char *nxt = NULL; - char *s = omit_spaces(txt); +const char *getnum(const char *txt, uint32_t *N){ + const char *nxt = NULL; + const char *s = omit_spaces(txt); if(*s == '0'){ // hex, oct or 0 if(s[1] == 'x' || s[1] == 'X'){ // hex nxt = gethex(s+2, N); @@ -246,3 +249,101 @@ char *getnum(const char *txt, uint32_t *N){ } return nxt; } + +// get signed integer +const char *getint(const char *txt, int32_t *I){ + const char *s = omit_spaces(txt); + int32_t sign = 1; + uint32_t U; + if(*s == '-'){ + sign = -1; + ++s; + } + const char *nxt = getnum(s, &U); + if(nxt == s) return txt; + if(U & 0x80000000) return txt; // overfull + *I = sign * (int32_t)U; + return nxt; +} + +int mystrlen(const char *txt){ + if(!txt) return 0; + int r = 0; + while(*txt++) ++r; + return r; +} + +/* +void mymemcpy(char *dest, const char *src, int len){ + if(len < 1) return; + while(len--) *dest++ = *src++; +} +*/ + +// be careful: if pow10 would be bigger you should change str[] size! +static const float pwr10[] = {1.f, 10.f, 100.f, 1000.f, 10000.f}; +static const float rounds[] = {0.5f, 0.05f, 0.005f, 0.0005f, 0.00005f}; +#define P10L (sizeof(pwr10)/sizeof(uint32_t) - 1) +char * float2str(float x, uint8_t prec){ + static char str[16] = {0}; // -117.5494E-36\0 - 14 symbols max! + if(prec > P10L) prec = P10L; + if(isnan(x)){ memcpy(str, "NAN", 4); return str;} + else{ + int i = isinf(x); + if(i){memcpy(str, "-INF", 5); if(i == 1) return str+1; else return str;} + } + char *s = str + 14; // go to end of buffer + uint8_t minus = 0; + if(x < 0){ + x = -x; + minus = 1; + } + int pow = 0; // xxxEpow + // now convert float to 1.xxxE3y + while(x > 1000.f){ + x /= 1000.f; + pow += 3; + } + if(x > 0.) while(x < 1.){ + x *= 1000.f; + pow -= 3; + } + // print Eyy + if(pow){ + uint8_t m = 0; + if(pow < 0){pow = -pow; m = 1;} + while(pow){ + register int p10 = pow/10; + *s-- = '0' + (pow - 10*p10); + pow = p10; + } + if(m) *s-- = '-'; + *s-- = 'E'; + } + // now our number is in [1, 1000] + uint32_t units; + if(prec){ + units = (uint32_t) x; + uint32_t decimals = (uint32_t)((x-units+rounds[prec])*pwr10[prec]); + // print decimals + while(prec){ + register int d10 = decimals / 10; + *s-- = '0' + (decimals - 10*d10); + decimals = d10; + --prec; + } + // decimal point + *s-- = '.'; + }else{ // without decimal part + units = (uint32_t) (x + 0.5); + } + // print main units + if(units == 0) *s-- = '0'; + else while(units){ + register uint32_t u10 = units / 10; + *s-- = '0' + (units - 10*u10); + units = u10; + } + if(minus) *s-- = '-'; + return s+1; +} diff --git a/snippets/strfunc.h b/snippets/strfunc.h index 79d90fb..d49c8bf 100644 --- a/snippets/strfunc.h +++ b/snippets/strfunc.h @@ -1,6 +1,5 @@ /* - * This file is part of the i2cscan project. - * Copyright 2023 Edward V. Emelianov . + * Copyright 2024 Edward V. Emelianov . * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,9 +17,15 @@ #pragma once -void hexdump(int (*sendfun)(const char *s), uint8_t *arr, uint16_t len); -char *u2str(uint32_t val); -char *i2str(int32_t i); -char *uhex2str(uint32_t val); -char *getnum(const char *txt, uint32_t *N); -char *omit_spaces(const char *buf); +#include + +void hexdump(int ifno, uint8_t *arr, uint16_t len); +const char *u2str(uint32_t val); +const char *i2str(int32_t i); +const char *uhex2str(uint32_t val); +const char *getnum(const char *txt, uint32_t *N); +const char *omit_spaces(const char *buf); +const char *getint(const char *txt, int32_t *I); +int mystrlen(const char *txt); +//void mymemcpy(char *dest, const char *src, int len); +char *float2str(float x, uint8_t prec); diff --git a/snippets/usb_pl2303/usbhw.h b/snippets/usb_pl2303/usbhw.h index 9944700..1ca0482 100644 --- a/snippets/usb_pl2303/usbhw.h +++ b/snippets/usb_pl2303/usbhw.h @@ -124,6 +124,10 @@ typedef struct { __IO uint32_t FNR; __IO uint32_t DADDR; __IO uint32_t BTABLE; +#ifdef STM32F0 + __IO uint32_t LPMCSR; + __IO uint32_t BCDR; +#endif } USB_TypeDef; // F303 D/E have 2x16 access scheme