From 27eb723d80cf24832b4b3926445ec81beda2d037 Mon Sep 17 00:00:00 2001 From: Edward Emelianov Date: Sat, 14 Mar 2026 23:44:54 +0300 Subject: [PATCH] add "hex+text" input --- F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp | 98 ++++++++++++++++--- F0:F030,F042,F072/usbcan_gpio/strfunc.c | 3 +- F0:F030,F042,F072/usbcan_gpio/strfunc.h | 1 + F0:F030,F042,F072/usbcan_gpio/usbcangpio.bin | Bin 23828 -> 24296 bytes F0:F030,F042,F072/usbcan_gpio/version.inc | 2 +- 5 files changed, 87 insertions(+), 17 deletions(-) diff --git a/F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp b/F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp index 054e550..3b52601 100644 --- a/F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp +++ b/F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp @@ -38,6 +38,7 @@ extern volatile uint32_t Tms; static uint8_t curbuf[MAXSTRLEN]; // buffer for receiving data from USART etc static uint8_t usart_text = 0; // ==1 for text USART proto +static uint8_t hex_input_mode = 0; // ==0 for text input, 1 for HEX + text in quotes // TODO: add analog threshold! @@ -48,6 +49,7 @@ static uint8_t usart_text = 0; // ==1 for text USART proto COMMAND(dumpconf, "dump current configuration") \ COMMAND(eraseflash, "erase full flash storage") \ COMMAND(help, "show this help") \ + COMMAND(hexinput, "input is text (0) or hex + text in quotes (1)") \ COMMAND(mcutemp, "get MCU temperature (degC*10)") \ COMMAND(mcureset, "reset MCU") \ COMMAND(PA, "GPIOA setter/getter (type PA0=help for further info)") \ @@ -59,9 +61,8 @@ static uint8_t usart_text = 0; // ==1 for text USART proto COMMAND(setiface, "set/get name of interface x (0 - CAN, 1 - GPIO)") \ COMMAND(storeconf, "save config to flash") \ COMMAND(time, "show current time (ms)") \ - COMMAND(vdd, "get approx Vdd value (V*100)") \ - COMMAND(USART, "Read USART data or send (USART=hex)") -// COMMAND(usartconf, "set USART params (e.g. usartconf=115200 8N1)") + COMMAND(USART, "Read USART data or send (USART=hex)") \ + COMMAND(vdd, "get approx Vdd value (V*100)") // COMMAND(SPI, "Read SPI data or send (SPI=hex)") // COMMAND(spiconf, "set SPI params") @@ -102,7 +103,7 @@ enum MiscValues{ MISC_THRESHOLD, MISC_SPEED, MISC_TEXT, - MISC_BIN + MISC_HEX }; // TODO: add HEX input? @@ -124,7 +125,7 @@ KW(AIN) \ KW(THRESHOLD) \ KW(SPEED) \ KW(TEXT) \ - KW(BIN) \ + KW(HEX) \ enum{ // indexes of string keywords #define KW(k) STR_ ## k, @@ -157,7 +158,7 @@ static const Keyword keywords[] = { KEY(THRESHOLD, GROUP_MISC, MISC_THRESHOLD) KEY(SPEED, GROUP_MISC, MISC_SPEED) KEY(TEXT, GROUP_MISC, MISC_TEXT) - KEY(BIN, GROUP_MISC, MISC_BIN) + KEY(HEX, GROUP_MISC, MISC_HEX) #undef K }; #define NUM_KEYWORDS (sizeof(keywords)/sizeof(keywords[0])) @@ -182,8 +183,8 @@ static const char *pinhelp = " MISC: MONITOR - send data by USB as only state changed\n" " THRESHOLD (ADC only) - monitoring threshold, ADU\n" " SPEED - interface speed/frequency\n" - " TEXT - USART means data as text ('\n'-separated strings)\n" - " BIN - USART means data as binary (output: HEX)\n" + " TEXT - USART means data as text ('\\n'-separated strings)\n" + " HEX - USART means data as binary (output: HEX)\n" "\n" ; @@ -236,6 +237,47 @@ static bool argsvals(char *args, int32_t *parno, int32_t *parval){ return false; } +/** + * @brief parse_hex_data - data parsing in case of `hex + text` input format + * @param input - input string + * @param output - output data + * @param max_len - length of `output` + * @return amount of parsed bytes or -1 in case of overflow or error + */ +static int parse_hex_data(char *input, uint8_t *output, int max_len){ + if(!input || !*input || !output || max_len < 1) return 0; + char *p = input; + int out_idx = 0; + while(*p && out_idx < max_len){ + while(*p == ' ' || *p == ',') ++p; // omit spaces and commas as delimeters + if(*p == '\0') break; // EOL + if(*p == '"'){ // TEXT (start/end) + ++p; + while(*p && *p != '"'){ + if(out_idx >= max_len) return -1; + output[out_idx++] = *p++; + } + if(*p == '"'){ + ++p; // go to next symbol after closing quotation mark + }else return -1; // no closing + }else{ // HEX number + char *start = p; + while(*p && *p != ' ' && *p != ',' && *p != '"') ++p; + char saved = *p; + *p = '\0'; // temporarily for `gethex` + uint32_t val; + const char *end = gethex(start, &val); + if(end != p || val > 0xFF){ // not a hex number or have more than 2 symbols + *p = saved; + return -1; + } + *p = saved; + output[out_idx++] = (uint8_t)val; + } + } + return out_idx; +} + // `port` and `pin` are checked in `parse_pin_command` // `PAx = ` also printed there static void pin_getter(uint8_t port, uint8_t pin){ @@ -330,7 +372,7 @@ static errcodes_t pin_setter(uint8_t port, uint8_t pin, char *setter){ case MISC_TEXT: // what to do, if textproto is set, but user wants binary? UsartConf.textproto = 1; break; - case MISC_BIN: // clear text flag + case MISC_HEX: // clear text flag UsartConf.textproto = 0; break; } @@ -575,8 +617,17 @@ static errcodes_t cmd_sendcan(const char _U_ *cmd, char *args){ if(!args) return ERR_BADVAL; char *setter = splitargs(args, NULL); if(!setter) return ERR_BADVAL; - if(USB_sendstr(ICAN, setter)) return ERR_OK; - USB_putbyte(ICAN, '\n'); + if(hex_input_mode){ + int len = parse_hex_data(setter, curbuf, MAXSTRLEN); + if(len < 0) return ERR_BADVAL; + if(len == 0) return ERR_AMOUNT; + if(USB_send(ICAN, curbuf, len)) return ERR_OK; + }else{ + if(USB_sendstr(ICAN, setter)){ + USB_putbyte(ICAN, '\n'); + return ERR_OK; + } + } return ERR_CANTRUN; } @@ -628,6 +679,17 @@ static errcodes_t cmd_help(const char _U_ *cmd, char _U_ *args){ return ERR_AMOUNT; } +static errcodes_t cmd_hexinput(const char *cmd, char *args) { + int32_t val; + if(argsvals(args, NULL, &val)){ + if(val == 0 || val == 1) hex_input_mode = (uint8_t)val; + else return ERR_BADVAL; + } + CMDEQ(); + SENDn(hex_input_mode ? "1" : "0"); + return ERR_AMOUNT; +} + static int sendfun(const char *s){ if(!s) return 0; return USB_sendstr(IGPIO, s); @@ -650,11 +712,17 @@ static errcodes_t cmd_USART(const char _U_ *cmd, char *args){ char *setter = splitargs(args, NULL); if(setter){ DBG("Try to send over USART\n"); - int l = strlen(setter); - if(usart_text){ // add '\n' as we removed it @ parser - if(setter[l-1] != '\n') setter[l++] = '\n'; + if(hex_input_mode){ + int len = parse_hex_data(setter, curbuf, MAXSTRLEN); + if(len < 0) return ERR_BADVAL; + if(len > 0) return usart_send(curbuf, len); + }else{ // text mode: "AS IS" + int l = strlen(setter); + if(usart_text){ // add '\n' as we removed it @ parser + setter[l++] = '\n'; + } + return usart_send((uint8_t*)setter, l); } - return usart_send((uint8_t*)setter, l); } // getter: try to read int l = usart_receive(curbuf, MAXSTRLEN); if(l < 0) return ERR_CANTRUN; diff --git a/F0:F030,F042,F072/usbcan_gpio/strfunc.c b/F0:F030,F042,F072/usbcan_gpio/strfunc.c index 279eaf4..77e2ea5 100644 --- a/F0:F030,F042,F072/usbcan_gpio/strfunc.c +++ b/F0:F030,F042,F072/usbcan_gpio/strfunc.c @@ -147,7 +147,8 @@ static const char *getdec(const char *buf, uint32_t *N){ return buf; } // read hexadecimal number (without 0x prefix!) -static const char *gethex(const char *buf, uint32_t *N){ +const char *gethex(const char *buf, uint32_t *N){ + if(!buf || !N) return NULL; const char *start = buf; uint32_t num = 0; while(*buf){ diff --git a/F0:F030,F042,F072/usbcan_gpio/strfunc.h b/F0:F030,F042,F072/usbcan_gpio/strfunc.h index c21ac44..9fc05c2 100644 --- a/F0:F030,F042,F072/usbcan_gpio/strfunc.h +++ b/F0:F030,F042,F072/usbcan_gpio/strfunc.h @@ -43,6 +43,7 @@ void hexdump(int (*sendfun)(const char *s), 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 *gethex(const char *buf, uint32_t *N); char *getnum(const char *txt, uint32_t *N); char *omit_spaces(const char *buf); char *getint(const char *txt, int32_t *I); diff --git a/F0:F030,F042,F072/usbcan_gpio/usbcangpio.bin b/F0:F030,F042,F072/usbcan_gpio/usbcangpio.bin index 9f8adbd1a75ffe61f176e23797239aa8a645c903..f5500feebd1801f6a4372ac85faffd9cb6ff57c5 100755 GIT binary patch delta 11785 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 sEgpk2w3=K0+7wua|0HXOO-0|=hmz5oCK delta 11323 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