add modbus (not tested yet)

This commit is contained in:
Edward Emelianov 2024-09-19 17:32:05 +03:00
parent bdbd7d68d9
commit 22a205001a
22 changed files with 1030 additions and 118 deletions

View File

@ -7,6 +7,9 @@ You can see pinout table in file `hardware.c`.
## Serial protocol (each string ends with '\n'). ## Serial protocol (each string ends with '\n').
(TODO: add new)
``` ```
commands format: parameter[number][=setter] commands format: parameter[number][=setter]
parameter [CAN idx] - help parameter [CAN idx] - help
@ -49,11 +52,11 @@ Value in square brackets is CAN bus command code.
All data in little-endian format! All data in little-endian format!
BIT - MEANING BYTE - MEANING
0, 1 - (uint16_t) - command code (value in square brackets upper); 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); 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. 5 - `ERR_CANTRUN` - can't run given command due to bad parameters or other reason.
## MODBUS-RTU protocol
(TODO)

View File

@ -236,6 +236,7 @@ void CAN_proc(){
CAN_status CAN_send(CAN_message *message){ CAN_status CAN_send(CAN_message *message){
if(!message) return CAN_ERR; if(!message) return CAN_ERR;
IWDG->KR = IWDG_REFRESH;
uint8_t *msg = message->data; uint8_t *msg = message->data;
uint8_t len = message->length; uint8_t len = message->length;
uint16_t target_id = message->ID; uint16_t target_id = message->ID;

View File

@ -20,7 +20,7 @@
#include "canproto.h" #include "canproto.h"
#include "flash.h" #include "flash.h"
#include "hardware.h" #include "hardware.h"
#include "proto.h" #include "modbusrtu.h"
#include "strfunc.h" #include "strfunc.h"
#include "usart.h" #include "usart.h"
@ -41,49 +41,53 @@ static errcodes reset(CAN_message _U_ *msg){
// get/set Tms // get/set Tms
static errcodes time_getset(CAN_message *msg){ static errcodes time_getset(CAN_message *msg){
if(ISSETTER(msg->data)){ if(ISSETTER(msg->data)){
Tms = *(uint32_t*)&msg->data[4]; Tms = MSGP_GET_U32(msg);
}else FIXDL(msg); }
*(uint32_t*)&msg->data[4] = Tms; FIXDL(msg);
MSGP_SET_U32(msg, Tms);
return ERR_OK; return ERR_OK;
} }
// get MCU T // get MCU T
static errcodes mcut(CAN_message *msg){ static errcodes mcut(CAN_message *msg){
FIXDL(msg); FIXDL(msg);
*(int32_t*)&msg->data[4] = getMCUtemp(); MSGP_SET_U32(msg, getMCUtemp());
return ERR_OK; return ERR_OK;
} }
// get ADC raw values // get ADC raw values
static errcodes adcraw(CAN_message *msg){ static errcodes adcraw(CAN_message *msg){
FIXDL(msg); FIXDL(msg);
uint8_t no = msg->data[2] & ~SETTER_FLAG; uint8_t no = PARVAL(msg->data);
if(no >= ADC_CHANNELS) return ERR_BADPAR; if(no >= ADC_CHANNELS) return ERR_BADPAR;
*(uint32_t*)&msg->data[4] = getADCval(no); MSGP_SET_U32(msg, getADCval(no));
return ERR_OK; return ERR_OK;
} }
// set common CAN ID / get CAN IN in // set common CAN ID / get CAN IN in
static errcodes canid(CAN_message *msg){ static errcodes canid(CAN_message *msg){
if(ISSETTER(msg->data)){ 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 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; return ERR_OK;
} }
// get/set input CAN ID // get/set input CAN ID
static errcodes canidin(CAN_message *msg){ static errcodes canidin(CAN_message *msg){
if(ISSETTER(msg->data)){ 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 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; return ERR_OK;
} }
// get/set output CAN ID // get/set output CAN ID
static errcodes canidout(CAN_message *msg){ static errcodes canidout(CAN_message *msg){
if(ISSETTER(msg->data)){ if(ISSETTER(msg->data)){
the_conf.CANIDout = *(uint32_t*)&msg->data[4]; the_conf.CANIDout = (uint16_t)MSGP_GET_U32(msg);
}else FIXDL(msg); }
*(uint32_t*)&msg->data[4] = the_conf.CANIDout; FIXDL(msg);
MSGP_SET_U32(msg, the_conf.CANIDout);
return ERR_OK; return ERR_OK;
} }
@ -101,32 +105,42 @@ static errcodes erasestor(CAN_message _U_ *msg){
// relay management // relay management
static errcodes relay(CAN_message *msg){ static errcodes relay(CAN_message *msg){
uint8_t no = OUTMAX+1; 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(ISSETTER(msg->data)){
if(set_relay(no, *(uint32_t*)&msg->data[4]) < 0) return ERR_BADPAR; if(set_relay(no, MSGP_GET_U32(msg)) < 0) return ERR_BADPAR;
}else FIXDL(msg); }
FIXDL(msg);
int rval = get_relay(no); int rval = get_relay(no);
if(rval < 0) return ERR_BADPAR; if(rval < 0) return ERR_BADPAR;
*(uint32_t*)&msg->data[4] = (uint32_t)rval; MSGP_SET_U32(msg, (uint32_t)rval);
return ERR_OK; return ERR_OK;
} }
// get current ESW status // get current ESW status
static errcodes esw(CAN_message *msg){ static errcodes esw(CAN_message *msg){
uint8_t no = INMAX+1; 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); int val = get_esw(no);
if(val < 0) return ERR_BADPAR; if(val < 0) return ERR_BADPAR;
*(uint32_t*)&msg->data[4] = (uint32_t) val; MSGP_SET_U32(msg, (uint32_t)val);
FIXDL(msg); FIXDL(msg);
return ERR_OK; return ERR_OK;
} }
// bounce-free ESW get status // bounce-free ESW get status
static errcodes eswg(CAN_message *msg){ static errcodes eswg(CAN_message *msg){
uint8_t no = INMAX+1; 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(); uint32_t curval = get_ab_esw();
if(no > INMAX) *(uint32_t*)&msg->data[4] = curval; if(no > INMAX) MSGP_SET_U32(msg, curval);
else *(uint32_t*)&msg->data[4] = (curval & (1<<no)) ? 0 : 1; else MSGP_SET_U32(msg, (curval & (1<<no)) ? 0 : 1);
FIXDL(msg); FIXDL(msg);
return ERR_OK; return ERR_OK;
} }
@ -140,41 +154,49 @@ static errcodes led(CAN_message *m){
// common uint32_t setter/getter // common uint32_t setter/getter
static errcodes u32setget(CAN_message *msg){ static errcodes u32setget(CAN_message *msg){
uint16_t idx = *(uint16_t*)msg->data; uint16_t cmd = *(uint16_t*)msg->data;
uint32_t *ptr = NULL; uint32_t *ptr = NULL, val;
switch(idx){ switch(cmd){
case CMD_CANSPEED: ptr = &the_conf.CANspeed; CAN_reinit(*(uint32_t*)&msg->data[4]); break; case CMD_CANSPEED: ptr = &the_conf.CANspeed; CAN_reinit(MSGP_GET_U32(msg)); break;
case CMD_BOUNCE: ptr = &the_conf.bouncetime; break; case CMD_BOUNCE: ptr = &the_conf.bouncetime; break;
case CMD_USARTSPEED: ptr = &the_conf.usartspeed; 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; default: break;
} }
if(!ptr) return ERR_CANTRUN; // unknown error if(!ptr) return ERR_CANTRUN; // unknown error
if(ISSETTER(msg->data)){ if(ISSETTER(msg->data)){
*ptr = *(uint32_t*)&msg->data[4]; if(cmd == CMD_INCHNLS || cmd == CMD_OUTCHNLS) return ERR_CANTRUN; // can't set getter-only
}else FIXDL(msg); *ptr = MSGP_GET_U32(msg);
*(uint32_t*)&msg->data[4] = *ptr; }
FIXDL(msg);
MSGP_SET_U32(msg, *ptr);
return ERR_OK; return ERR_OK;
} }
/*
// common bitflag setter/getter // common bitflag setter/getter
// without parno - all flags, with - flag with number N
static errcodes flagsetget(CAN_message *msg){ static errcodes flagsetget(CAN_message *msg){
uint16_t idx = *(uint16_t*)msg->data; uint8_t idx = NO_PARNO;
uint8_t bit = 32; if(msg->length > 2){
switch(idx){ idx = PARVAL(msg->data);
case CMD_ENCISSSI: bit = FLAGBIT(ENC_IS_SSI); break; if(idx != NO_PARNO && idx > MAX_FLAG_BITNO) return ERR_BADPAR;
case CMD_EMULPEP: bit = FLAGBIT(EMULATE_PEP); break;
default: break;
} }
if(bit > 31) return ERR_CANTRUN; // unknown error
if(ISSETTER(msg->data)){ if(ISSETTER(msg->data)){
if(msg->data[4]) the_conf.flags |= 1<<bit; uint32_t val = MSGP_GET_U32(msg);
else the_conf.flags &= ~(1<<bit); if(idx == NO_PARNO) the_conf.flags.u32 = val; // all bits
}else FIXDL(msg); else{ // only selected
*(uint32_t*)&msg->data[4] = (the_conf.flags & (1<<bit)) ? 1 : 0; if(val) the_conf.flags.u32 |= 1 << idx;
else the_conf.flags.u32 &= ~(1 << idx);
}
}
FIXDL(msg);
if(idx == NO_PARNO) MSGP_SET_U32(msg, the_conf.flags.u32);
else MSGP_SET_U32(msg, (the_conf.flags.u32 & (1<<idx)) ? 1:0);
return ERR_OK; return ERR_OK;
} }
************ END of all common functions list (for `funclist`) ************/ /************ END of all common functions list (for `funclist`) ************/
typedef struct{ typedef struct{
errcodes (*fn)(CAN_message *msg); // function to run with can packet `msg` errcodes (*fn)(CAN_message *msg); // function to run with can packet `msg`
@ -203,6 +225,11 @@ static const commonfunction funclist[CMD_AMOUNT] = {
[CMD_BOUNCE] = {u32setget, 0, 1000, 0}, [CMD_BOUNCE] = {u32setget, 0, 1000, 0},
[CMD_USARTSPEED] = {u32setget, 1200, 3000000, 0}, [CMD_USARTSPEED] = {u32setget, 1200, 3000000, 0},
[CMD_LED] = {led, 0, 0, 0}, [CMD_LED] = {led, 0, 0, 0},
[CMD_FLAGS] = {flagsetget, 0, 0, 0},
[CMD_INCHNLS] = {u32setget, 0, 0, 0},
[CMD_OUTCHNLS] = {u32setget, 0, 0, 0},
[CMD_MODBUSID] = {u32setget, 0, MODBUS_MAX_ID, 0}, // 0 - master!
[CMD_MODBUSSPEED] = {u32setget, 1200, 115200, 0},
}; };
@ -225,7 +252,6 @@ void run_can_cmd(CAN_message *msg){
for(int i = 0; i < msg->length; ++i){ for(int i = 0; i < msg->length; ++i){
usart_send(uhex2str(msg->data[i])); usart_putchar(' '); usart_send(uhex2str(msg->data[i])); usart_putchar(' ');
} }
//for(int i = msg->length-1; i < 8; ++i) msg->data[i] = 0;
newline(); newline();
#endif #endif
if(datalen < 2){ if(datalen < 2){

View File

@ -25,9 +25,11 @@
#define SETTER_FLAG (0x80) #define SETTER_FLAG (0x80)
#define ISSETTER(data) ((data[2] & SETTER_FLAG)) #define ISSETTER(data) ((data[2] & SETTER_FLAG))
// parameter number 127 means there no parameter number at all (don't need paremeter or get all) // 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) // base value of parameter (even if it is a setter)
#define PARBASE(x) (x & 0x7f) #define PARBASE(x) (x & 0x7f)
// get parameter value of msg->data
#define PARVAL(data) (data[2] & 0x7f)
// make error for CAN answer // make error for CAN answer
#define FORMERR(m, err) do{m->data[3] = err; if(m->length < 4) m->length = 4;}while(0) #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 ERR_AMOUNT // amount of error codes
} errcodes; } 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 // CAN commands indexes
enum{ enum{
CMD_PING, // just ping CMD_PING, // just ping
@ -62,6 +77,11 @@ enum{
CMD_BOUNCE, // get/set bounce constant (ms) CMD_BOUNCE, // get/set bounce constant (ms)
CMD_USARTSPEED, // get/set USART1 speed (if encoder on RS-422) CMD_USARTSPEED, // get/set USART1 speed (if encoder on RS-422)
CMD_LED, // onboard LED 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: // should be the last:
CMD_AMOUNT // amount of CAN commands CMD_AMOUNT // amount of CAN commands
}; };

View File

@ -18,6 +18,7 @@
#include "stm32f1.h" #include "stm32f1.h"
#include "modbusrtu.h"
#include "flash.h" #include "flash.h"
#include "strfunc.h" #include "strfunc.h"
#include "usart.h" #include "usart.h"
@ -34,6 +35,8 @@ static uint32_t maxCnum = 1024 / sizeof(user_conf); // can't use blocksize here
,.CANIDout = 2 \ ,.CANIDout = 2 \
,.usartspeed = 115200 \ ,.usartspeed = 115200 \
,.bouncetime = 50 \ ,.bouncetime = 50 \
,.modbusID = MODBUS_MASTER_ID \
,.modbusspeed = 9600 \
} }
static int write2flash(const void*, const void*, uint32_t); static int write2flash(const void*, const void*, uint32_t);

View File

@ -23,6 +23,15 @@
#define FLASH_SIZE_REG ((uint32_t)0x1FFFF7E0) #define FLASH_SIZE_REG ((uint32_t)0x1FFFF7E0)
#define FLASH_SIZE *((uint16_t*)FLASH_SIZE_REG) #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 * struct to save user configurations
*/ */
@ -30,10 +39,13 @@ typedef struct __attribute__((aligned(4))){
uint16_t userconf_sz; // "magick number" uint16_t userconf_sz; // "magick number"
uint16_t CANIDin; // CAN bus device ID for input commands uint16_t CANIDin; // CAN bus device ID for input commands
uint16_t CANIDout; // -//- for output signals 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 bouncetime; // anti-bounce timeout (ms)
uint32_t usartspeed; // RS-232 speed uint32_t usartspeed; // RS-232 speed
uint32_t CANspeed; // CAN bus 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; } user_conf;
extern user_conf the_conf; extern user_conf the_conf;

Binary file not shown.

View File

@ -10,6 +10,10 @@ flash.h
hardware.c hardware.c
hardware.h hardware.h
main.c main.c
modbusproto.c
modbusproto.h
modbusrtu.c
modbusrtu.h
proto.c proto.c
proto.h proto.h
strfunc.c strfunc.c

View File

@ -20,10 +20,12 @@
#include "canproto.h" #include "canproto.h"
#include "flash.h" #include "flash.h"
#include "hardware.h" #include "hardware.h"
/*
#ifdef EBUG #ifdef EBUG
#include "strfunc.h" #include "strfunc.h"
#include "uchar.h" #include "usart.h"
#endif #endif
*/
/* pinout: /* pinout:
@ -257,11 +259,18 @@ void proc_esw(){
if(oldesw != ESW_ab_values){ if(oldesw != ESW_ab_values){
//usart_send("esw="); usart_send(u2str(ESW_ab_values)); newline(); //usart_send("esw="); usart_send(u2str(ESW_ab_values)); newline();
CAN_message msg = {.ID = the_conf.CANIDout, .length = 8}; CAN_message msg = {.ID = the_conf.CANIDout, .length = 8};
msg.data[0] = CMD_GETESW; MSG_SET_CMD(msg, CMD_GETESW);
*((uint32_t*)(&msg.data[4])) = ESW_ab_values; MSG_SET_U32(msg, ESW_ab_values);
uint32_t Tstart = Tms; uint32_t Tstart = Tms;
while(Tms - Tstart < SEND_TIMEOUT_MS){ 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;
}
} }
} }
} }

View File

@ -33,6 +33,9 @@
// max number of in/out pins // max number of in/out pins
#define INMAX (15) #define INMAX (15)
#define OUTMAX (11) #define OUTMAX (11)
// and 8-multiple
#define INMAXBYTES (2)
#define OUTMAXBYTES (2)
// onboard LED - PD10 // onboard LED - PD10
#define LEDPORT GPIOD #define LEDPORT GPIOD

View File

@ -20,6 +20,8 @@
#include "can.h" #include "can.h"
#include "flash.h" #include "flash.h"
#include "hardware.h" #include "hardware.h"
#include "modbusproto.h"
#include "modbusrtu.h"
#include "proto.h" #include "proto.h"
#include "strfunc.h" #include "strfunc.h"
@ -30,17 +32,31 @@ void sys_tick_handler(void){
++Tms; ++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){ int main(void){
uint32_t lastT = 0; uint32_t lastT = 0;
CAN_message *can_mesg; CAN_message *can_mesg;
StartHSE(); StartHSE();
RCC->CSR |= RCC_CSR_RMVF; // remove reset flags
SysTick_Config(72000); SysTick_Config(72000);
flashstorage_init(); flashstorage_init();
gpio_setup(); // should be run before other peripherial setup gpio_setup(); // should be run before other peripherial setup
adc_setup(); adc_setup();
usart_setup(the_conf.usartspeed); usart_setup(the_conf.usartspeed);
CAN_setup(the_conf.CANspeed); CAN_setup(the_conf.CANspeed);
RCC->CSR |= RCC_CSR_RMVF; // remove reset flags modbus_setup(the_conf.modbusspeed);
#ifndef EBUG #ifndef EBUG
iwdg_setup(); iwdg_setup();
#endif #endif
@ -80,6 +96,7 @@ int main(void){
int g = usart_getline(&str); int g = usart_getline(&str);
if(g < 0) usart_send("USART IN buffer overflow!\n"); if(g < 0) usart_send("USART IN buffer overflow!\n");
else if(g > 0) cmd_parser(str); else if(g > 0) cmd_parser(str);
parsemodbus();
} }
return 0; return 0;
} }

261
F1:F103/FX3U/modbusproto.c Normal file
View File

@ -0,0 +1,261 @@
/*
* This file is part of the fx3u project.
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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*)&regval, .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();
}

View File

@ -0,0 +1,48 @@
/*
* This file is part of the fx3u project.
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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);

238
F1:F103/FX3U/modbusrtu.c Normal file
View File

@ -0,0 +1,238 @@
/*
* This file is part of the fx3u project.
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "modbusrtu.h"
#include "flash.h"
#include "strfunc.h"
#ifdef EBUG
#include "usart.h"
#endif
#include <stm32f1.h>
#include <string.h> // 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;
}
}

80
F1:F103/FX3U/modbusrtu.h Normal file
View File

@ -0,0 +1,80 @@
/*
* This file is part of the fx3u project.
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
// 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);

View File

@ -22,6 +22,7 @@
#include "flash.h" #include "flash.h"
#include "hardware.h" #include "hardware.h"
#include "proto.h" #include "proto.h"
#include "modbusrtu.h"
#include "strfunc.h" #include "strfunc.h"
#include "usart.h" #include "usart.h"
#include "version.inc" #include "version.inc"
@ -38,6 +39,8 @@ typedef enum{
TCMD_CANSEND, // send CAN message TCMD_CANSEND, // send CAN message
TCMD_CANSNIFFER, // sniff all CAN messages TCMD_CANSNIFFER, // sniff all CAN messages
TCMD_CANBUSERRPRNT, // pring all errors of bus 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 TCMD_AMOUNT
} text_cmd; } text_cmd;
@ -56,6 +59,7 @@ static const funcdescr funclist[] = {
{NULL, 0, "CAN bus commands"}, {NULL, 0, "CAN bus commands"},
{"canbuserr", -TCMD_CANBUSERRPRNT, "print all CAN bus errors (a lot of if not connected)"}, {"canbuserr", -TCMD_CANBUSERRPRNT, "print all CAN bus errors (a lot of if not connected)"},
{"cansniff", -TCMD_CANSNIFFER, "switch CAN sniffer mode"}, {"cansniff", -TCMD_CANSNIFFER, "switch CAN sniffer mode"},
{"s", -TCMD_CANSEND, "send CAN message: ID 0..8 data bytes"},
{NULL, 0, "Configuration"}, {NULL, 0, "Configuration"},
{"bounce", CMD_BOUNCE, "set/get anti-bounce timeout (ms, max: 1000)"}, {"bounce", CMD_BOUNCE, "set/get anti-bounce timeout (ms, max: 1000)"},
{"canid", CMD_CANID, "set both (in/out) CAN ID / get in CAN ID"}, {"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)"}, {"canspeed", CMD_CANSPEED, "get/set CAN speed (bps)"},
{"dumpconf", -TCMD_DUMPCONF, "dump current configuration"}, {"dumpconf", -TCMD_DUMPCONF, "dump current configuration"},
{"eraseflash", CMD_ERASESTOR, "erase all flash storage"}, {"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"}, {"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"}, {"usartspeed", CMD_USARTSPEED, "get/set USART1 speed"},
{NULL, 0, "IN/OUT"}, {NULL, 0, "IN/OUT"},
{"adc", CMD_ADCRAW, "get raw ADC values for given channel"}, {"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"}, {"led", CMD_LED, "work with onboard LED"},
{"relay", CMD_RELAY, "get/set relay state (0 - off, 1 - on)"}, {"relay", CMD_RELAY, "get/set relay state (0 - off, 1 - on)"},
{NULL, 0, "Other commands"}, {NULL, 0, "Other commands"},
{"inchannels", CMD_INCHNLS, "get u32 with bits set on supported IN channels"},
{"mcutemp", CMD_MCUTEMP, "get MCU temperature (*10degrC)"}, {"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"}, {"reset", CMD_RESET, "reset MCU"},
{"s", -TCMD_CANSEND, "send CAN message: ID 0..8 data bytes"},
{"time", CMD_TIME, "get/set time (1ms, 32bit)"}, {"time", CMD_TIME, "get/set time (1ms, 32bit)"},
{"wdtest", -TCMD_WDTEST, "test watchdog"}, {"wdtest", -TCMD_WDTEST, "test watchdog"},
{NULL, 0, NULL} // last record {NULL, 0, NULL} // last record
@ -83,7 +93,7 @@ static const funcdescr funclist[] = {
static void printhelp(){ static void printhelp(){
const funcdescr *c = funclist; 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("commands format: parameter[number][=setter]\n");
usart_send("parameter [CAN idx] - help\n"); usart_send("parameter [CAN idx] - help\n");
usart_send("--------------------------\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; uint32_t U;
if(str){ if(str){
if(*str == '=') str = omit_spaces(str + 1); if(*str == '=') str = omit_spaces(str + 1);
@ -122,7 +132,7 @@ static errcodes cansnif(const char *str){
return ERR_OK; return ERR_OK;
} }
static errcodes canbuserr(const char *str){ static errcodes canbuserr(const char *str, text_cmd _U_ cmd){
uint32_t U; uint32_t U;
if(str){ if(str){
if(*str == '=') str = omit_spaces(str + 1); if(*str == '=') str = omit_spaces(str + 1);
@ -135,21 +145,20 @@ static errcodes canbuserr(const char *str){
return ERR_OK; 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_send("Wait for reboot\n");
usart_transmit(); usart_transmit();
while(1){nop();} while(1){nop();}
return ERR_OK; return ERR_OK;
} }
/*
// names of bit flags (ordered from LSE of[0]) // names of bit flags (ordered from LSE of[0])
static const char * const bitfields[] = { static const char * const bitfields[] = {
"encisSSI", "sw_send_relay_cmd",
"emulatePEP",
NULL NULL
};*/ };
static errcodes dumpconf(const char _U_ *str){ static errcodes dumpconf(const char _U_ *str, text_cmd _U_ cmd){
#ifdef EBUG #ifdef EBUG
uint32_t sz = FLASH_SIZE*1024; uint32_t sz = FLASH_SIZE*1024;
usart_send("flashsize="); printu(sz); usart_putchar('/'); 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(float2str(the_conf.adcmul[i], 3));
}*/ }*/
usart_send("\nusartspeed="); printu(the_conf.usartspeed); 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); usart_send("\nbouncetime="); printu(the_conf.bouncetime);
/*
const char * const *p = bitfields; const char * const *p = bitfields;
int bit = 0; int bit = 0;
usart_send("\nflags="); usart_putchar('='); printuhex(the_conf.flags.u32);
while(*p){ while(*p){
newline(); newline(); usart_putchar(' ');
usart_send(*p); usart_putchar('='); usart_putchar((the_conf.flags & (1<<bit)) ? '1' : '0'); usart_send(*p); usart_putchar('='); usart_putchar((the_conf.flags.u32 & (1<<bit)) ? '1' : '0');
if(++bit > 31) break; if(++bit > MAX_FLAG_BITNO) break;
++p; ++p;
}*/ }
newline(); newline();
return ERR_OK; return ERR_OK;
} }
static errcodes cansend(const char *txt){ static errcodes cansend(const char *txt, text_cmd _U_ cmd){
CAN_message canmsg; CAN_message canmsg;
bzero(&canmsg, sizeof(canmsg)); bzero(&canmsg, sizeof(canmsg));
int ctr = -1; int ctr = -1;
@ -221,10 +232,70 @@ static errcodes cansend(const char *txt){
return ERR_CANTRUN; 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 // 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] = { static textfn textfunctions[TCMD_AMOUNT] = {
[TCMD_PROHIBITED] = NULL, [TCMD_PROHIBITED] = NULL,
[TCMD_WDTEST] = wdtest, [TCMD_WDTEST] = wdtest,
@ -232,6 +303,8 @@ static textfn textfunctions[TCMD_AMOUNT] = {
[TCMD_CANSEND] = cansend, [TCMD_CANSEND] = cansend,
[TCMD_CANSNIFFER] = cansnif, [TCMD_CANSNIFFER] = cansnif,
[TCMD_CANBUSERRPRNT] = canbuserr, [TCMD_CANBUSERRPRNT] = canbuserr,
[TCMD_SW_SEND_RELAY] = confflags,
[TCMD_MODBUS_SEND] = modbussend,
}; };
static const char* const errors_txt[ERR_AMOUNT] = { static const char* const errors_txt[ERR_AMOUNT] = {
@ -254,9 +327,9 @@ static void errtext(errcodes e){
* @param txt - buffer with commands & data * @param txt - buffer with commands & data
*/ */
void cmd_parser(const char *str){ void cmd_parser(const char *str){
errcodes ecode = ERR_BADCMD;
if(!str || !*str) goto ret; if(!str || !*str) goto ret;
char cmd[MAXCMDLEN + 1]; char cmd[MAXCMDLEN + 1];
errcodes ecode = ERR_BADCMD;
int idx = CMD_AMOUNT; int idx = CMD_AMOUNT;
const funcdescr *c = funclist; const funcdescr *c = funclist;
int l = 0; int l = 0;
@ -279,7 +352,7 @@ void cmd_parser(const char *str){
} }
str = omit_spaces(ptr); str = omit_spaces(ptr);
if(idx < 0){ // text-only function if(idx < 0){ // text-only function
ecode = textfunctions[-idx](str); ecode = textfunctions[-idx](str, -idx);
goto ret; goto ret;
} }
// common CAN/text function: we need to form 8-byte data buffer // common CAN/text function: we need to form 8-byte data buffer

View File

@ -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 idatalen[2] = {0,0}; // received data line length (including '\n')
static volatile int odatalen[2] = {0,0}; 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 static volatile int usart_linerdy = 0 // received data ready
,dlen = 0 // length of data (including '\n') in current buffer ,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 rbuf[2][UARTBUFSZI], tbuf[2][UARTBUFSZO]; // receive & transmit buffers
static char *recvdata = NULL; static char *recvdata = NULL;

View File

@ -18,6 +18,8 @@
#pragma once #pragma once
#include <stdint.h>
// input and output buffers size // input and output buffers size
#define UARTBUFSZI (64) #define UARTBUFSZI (64)
#define UARTBUFSZO (128) #define UARTBUFSZO (128)
@ -25,8 +27,6 @@
#define usartrx() (usart_linerdy) #define usartrx() (usart_linerdy)
#define usartovr() (usart_bufovr) #define usartovr() (usart_bufovr)
extern volatile int usart_txrdy;
int usart_transmit(); int usart_transmit();
void usart_setup(uint32_t speed); void usart_setup(uint32_t speed);
int usart_getline(char **line); int usart_getline(char **line);

View File

@ -1,2 +1,2 @@
#define BUILD_NUMBER "56" #define BUILD_NUMBER "63"
#define BUILD_DATE "2024-06-04" #define BUILD_DATE "2024-09-19"

View File

@ -1,6 +1,5 @@
/* /*
* This file is part of the i2cscan project. * Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
* Copyright 2023 Edward V. Emelianov <edward.emelianoff@gmail.com>.
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -16,15 +15,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <stm32g0.h> #include <math.h>
#include <stm32f0.h>
#include <string.h>
#include "usb.h"
/** /**
* @brief hexdump - dump hex array by 16 bytes in string * @brief hexdump - dump hex array by 16 bytes in string
* @param sendfun - function to send data
* @param arr - array to dump * @param arr - array to dump
* @param len - length of `arr` * @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; char buf[52], *bptr = buf;
for(uint16_t l = 0; l < len; ++l, ++arr){ for(uint16_t l = 0; l < len; ++l, ++arr){
for(int16_t j = 1; j > -1; --j){ 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){ if(l % 16 == 15){
*bptr++ = '\n'; *bptr++ = '\n';
*bptr = 0; *bptr = 0;
sendfun(buf); USB_sendstr(buf);
bptr = buf; bptr = buf;
}else *bptr++ = ' '; }else *bptr++ = ' ';
} }
if(bptr != buf){ if(bptr != buf){
*bptr++ = '\n'; *bptr++ = '\n';
*bptr = 0; *bptr = 0;
sendfun(buf); USB_sendstr(buf);
} }
} }
@ -60,8 +60,11 @@ static char *_2str(uint32_t val, uint8_t minus){
*(--bufptr) = '0'; *(--bufptr) = '0';
}else{ }else{
while(val){ while(val){
*(--bufptr) = val % 10 + '0'; uint32_t x = val / 10;
val /= 10; *(--bufptr) = (val - 10*x) + '0';
val = x;
//*(--bufptr) = val % 10 + '0';
//val /= 10;
} }
} }
if(minus) *(--bufptr) = '-'; if(minus) *(--bufptr) = '-';
@ -113,12 +116,12 @@ char *uhex2str(uint32_t val){
* @param buf - string * @param buf - string
* @return - pointer to first character in `buf` > ' ' * @return - pointer to first character in `buf` > ' '
*/ */
char *omit_spaces(const char *buf){ const char *omit_spaces(const char *buf){
while(*buf){ while(*buf){
if(*buf > ' ') break; if(*buf > ' ') break;
++buf; ++buf;
} }
return (char*)buf; return buf;
} }
/** /**
@ -127,7 +130,7 @@ char *omit_spaces(const char *buf){
* @param N - number read * @param N - number read
* @return Next non-number symbol. In case of overflow return `buf` and N==0xffffffff * @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; char *start = (char*)buf;
uint32_t num = 0; uint32_t num = 0;
while(*buf){ while(*buf){
@ -144,11 +147,11 @@ static char *getdec(const char *buf, uint32_t *N){
++buf; ++buf;
} }
*N = num; *N = num;
return (char*)buf; return buf;
} }
// read hexadecimal number (without 0x prefix!) // read hexadecimal number (without 0x prefix!)
static char *gethex(const char *buf, uint32_t *N){ static const char *gethex(const char *buf, uint32_t *N){
char *start = (char*)buf; const char *start = buf;
uint32_t num = 0; uint32_t num = 0;
while(*buf){ while(*buf){
char c = *buf; char c = *buf;
@ -173,11 +176,11 @@ static char *gethex(const char *buf, uint32_t *N){
++buf; ++buf;
} }
*N = num; *N = num;
return (char*)buf; return buf;
} }
// read octal number (without 0 prefix!) // read octal number (without 0 prefix!)
static char *getoct(const char *buf, uint32_t *N){ static const char *getoct(const char *buf, uint32_t *N){
char *start = (char*)buf; const char *start = (char*)buf;
uint32_t num = 0; uint32_t num = 0;
while(*buf){ while(*buf){
char c = *buf; char c = *buf;
@ -193,11 +196,11 @@ static char *getoct(const char *buf, uint32_t *N){
++buf; ++buf;
} }
*N = num; *N = num;
return (char*)buf; return buf;
} }
// read binary number (without b prefix!) // read binary number (without b prefix!)
static char *getbin(const char *buf, uint32_t *N){ static const char *getbin(const char *buf, uint32_t *N){
char *start = (char*)buf; const char *start = (char*)buf;
uint32_t num = 0; uint32_t num = 0;
while(*buf){ while(*buf){
char c = *buf; char c = *buf;
@ -213,7 +216,7 @@ static char *getbin(const char *buf, uint32_t *N){
++buf; ++buf;
} }
*N = num; *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 * @return pointer to first non-number symbol in buf
* (if it is == buf, there's no number or if *N==0xffffffff there was overflow) * (if it is == buf, there's no number or if *N==0xffffffff there was overflow)
*/ */
char *getnum(const char *txt, uint32_t *N){ const char *getnum(const char *txt, uint32_t *N){
char *nxt = NULL; const char *nxt = NULL;
char *s = omit_spaces(txt); const char *s = omit_spaces(txt);
if(*s == '0'){ // hex, oct or 0 if(*s == '0'){ // hex, oct or 0
if(s[1] == 'x' || s[1] == 'X'){ // hex if(s[1] == 'x' || s[1] == 'X'){ // hex
nxt = gethex(s+2, N); nxt = gethex(s+2, N);
@ -246,3 +249,101 @@ char *getnum(const char *txt, uint32_t *N){
} }
return nxt; 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;
}

View File

@ -1,6 +1,5 @@
/* /*
* This file is part of the i2cscan project. * Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
* Copyright 2023 Edward V. Emelianov <edward.emelianoff@gmail.com>.
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -18,9 +17,15 @@
#pragma once #pragma once
void hexdump(int (*sendfun)(const char *s), uint8_t *arr, uint16_t len); #include <stdint.h>
char *u2str(uint32_t val);
char *i2str(int32_t i); void hexdump(int ifno, uint8_t *arr, uint16_t len);
char *uhex2str(uint32_t val); const char *u2str(uint32_t val);
char *getnum(const char *txt, uint32_t *N); const char *i2str(int32_t i);
char *omit_spaces(const char *buf); 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);

View File

@ -124,6 +124,10 @@ typedef struct {
__IO uint32_t FNR; __IO uint32_t FNR;
__IO uint32_t DADDR; __IO uint32_t DADDR;
__IO uint32_t BTABLE; __IO uint32_t BTABLE;
#ifdef STM32F0
__IO uint32_t LPMCSR;
__IO uint32_t BCDR;
#endif
} USB_TypeDef; } USB_TypeDef;
// F303 D/E have 2x16 access scheme // F303 D/E have 2x16 access scheme