From 22a205001a9c2f4ad8b9081ece9ac6632890328e Mon Sep 17 00:00:00 2001 From: Edward Emelianov Date: Thu, 19 Sep 2024 17:32:05 +0300 Subject: [PATCH] add modbus (not tested yet) --- F1:F103/FX3U/Readme.md | 11 +- F1:F103/FX3U/can.c | 1 + F1:F103/FX3U/canproto.c | 122 ++++++++++------- F1:F103/FX3U/canproto.h | 22 ++- F1:F103/FX3U/flash.c | 3 + F1:F103/FX3U/flash.h | 14 +- F1:F103/FX3U/fx3u.bin | Bin 10000 -> 14576 bytes F1:F103/FX3U/fx3u.files | 4 + F1:F103/FX3U/hardware.c | 17 ++- F1:F103/FX3U/hardware.h | 3 + F1:F103/FX3U/main.c | 19 ++- F1:F103/FX3U/modbusproto.c | 261 ++++++++++++++++++++++++++++++++++++ F1:F103/FX3U/modbusproto.h | 48 +++++++ F1:F103/FX3U/modbusrtu.c | 238 ++++++++++++++++++++++++++++++++ F1:F103/FX3U/modbusrtu.h | 80 +++++++++++ F1:F103/FX3U/proto.c | 115 +++++++++++++--- F1:F103/FX3U/usart.c | 4 +- F1:F103/FX3U/usart.h | 4 +- F1:F103/FX3U/version.inc | 4 +- snippets/strfunc.c | 153 +++++++++++++++++---- snippets/strfunc.h | 21 +-- snippets/usb_pl2303/usbhw.h | 4 + 22 files changed, 1030 insertions(+), 118 deletions(-) create mode 100644 F1:F103/FX3U/modbusproto.c create mode 100644 F1:F103/FX3U/modbusproto.h create mode 100644 F1:F103/FX3U/modbusrtu.c create mode 100644 F1:F103/FX3U/modbusrtu.h 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 d00a77f618f978d48df3b16f5c87261a178d8cae..67a27748fd45dbd8ea58a1ce31216d16352d235b 100755 GIT binary patch literal 14576 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 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