From 57a44f1c66bcf209f566802e7c17503ca16a013e Mon Sep 17 00:00:00 2001 From: Edward Emelianov Date: Sat, 14 Mar 2026 22:46:28 +0300 Subject: [PATCH] OK, USART works, maybe add HEX input? --- F0:F030,F042,F072/usbcan_gpio/flash.c | 8 ++- F0:F030,F042,F072/usbcan_gpio/gpio.h | 7 +-- F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp | 17 ++++--- F0:F030,F042,F072/usbcan_gpio/gpioproto.h | 2 + F0:F030,F042,F072/usbcan_gpio/hardware.c | 8 +-- F0:F030,F042,F072/usbcan_gpio/strfunc.h | 2 +- F0:F030,F042,F072/usbcan_gpio/usart.c | 51 ++++++++++--------- F0:F030,F042,F072/usbcan_gpio/usart.h | 9 ++-- F0:F030,F042,F072/usbcan_gpio/usbcangpio.bin | Bin 23876 -> 23828 bytes F0:F030,F042,F072/usbcan_gpio/version.inc | 2 +- 10 files changed, 61 insertions(+), 45 deletions(-) diff --git a/F0:F030,F042,F072/usbcan_gpio/flash.c b/F0:F030,F042,F072/usbcan_gpio/flash.c index 793f154..f1e5b73 100644 --- a/F0:F030,F042,F072/usbcan_gpio/flash.c +++ b/F0:F030,F042,F072/usbcan_gpio/flash.c @@ -39,12 +39,15 @@ static int write2flash(const void*, const void*, uint32_t); // 'memcpy' forming offset 8 is out of the bounds [0, 4] of object '__varsstart' with type 'uint32_t' const user_conf *Flash_Data = (const user_conf *)(&__varsstart); -// default pin config: all are low speed floating inputs +// default pin config +// simple FL IN #define PINEN {.enable = 1} +// USART1 @9600 with monitoring +#define U1 {.enable = 1, .mode = MODE_AF, .speed = SPEED_HIGH, .afno = 1, .af = FUNC_USART, .monitor = 1} // GPIOA, enabled: PA0-PA3, PA5-PA7, PA9, PA10 #define PACONF \ [0] = PINEN, [1] = PINEN, [2] = PINEN, [3] = PINEN, [5] = PINEN, \ -[6] = PINEN, [7] = PINEN, [9] = PINEN, [10] = PINEN +[6] = PINEN, [7] = PINEN, [9] = U1, [10] = U1 // GPIOB, enabled: PB0-PB7, PB10, PB11 #define PBCONF \ @@ -60,6 +63,7 @@ user_conf the_conf = { }, .iIlengths = {14, 16}, .pinconfig = {[0] = {PACONF}, [1] = {PBCONF}}, + .usartconfig = {.speed = 9600, .idx = 0, .RXen = 1, .TXen = 1, .textproto = 1, .monitor = 1}, }; int currentconfidx = -1; // index of current configuration diff --git a/F0:F030,F042,F072/usbcan_gpio/gpio.h b/F0:F030,F042,F072/usbcan_gpio/gpio.h index b66eef5..4d5992d 100644 --- a/F0:F030,F042,F072/usbcan_gpio/gpio.h +++ b/F0:F030,F042,F072/usbcan_gpio/gpio.h @@ -60,13 +60,14 @@ typedef enum{ } pinspeed_t; // !!! FuncNames means position of bit in funcvalues_t.flags! -enum FuncNames{ // shift 1 by this to get "canUSART" etc; not more than 7! +typedef enum FuncNames{ // shift 1 by this to get "canUSART" etc; not more than 7! FUNC_AIN = 0, FUNC_USART = 1, FUNC_SPI = 2, FUNC_I2C = 3, + FUNC_PWM = 4, FUNC_AMOUNT // just for arrays' sizes -}; +} funcnames_t; /* typedef union{ struct{ @@ -84,7 +85,7 @@ typedef struct{ pinout_t otype : 1; pinspeed_t speed : 2; uint8_t afno : 3; // alternate function number (only if mode == MODE_AF) - uint8_t af : 3; // alternate function name (`FuncNames`) + funcnames_t af : 3; // alternate function name (`FuncNames`) uint8_t monitor : 1; // monitor changes uint16_t threshold; // threshold for ADC measurement } pinconfig_t; diff --git a/F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp b/F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp index 41611bb..054e550 100644 --- a/F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp +++ b/F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp @@ -170,6 +170,7 @@ static const char* errtxt[ERR_AMOUNT] = { [ERR_WRONGLEN] = "WRONGLEN", [ERR_CANTRUN] = "CANTRUN", [ERR_BUSY] = "BUSY", + [ERR_OVERFLOW] = "OVERFLOW", }; static const char *pinhelp = @@ -357,7 +358,7 @@ static errcodes_t pin_setter(uint8_t port, uint8_t pin, char *setter){ curconf.pull = static_cast (pull_set); curconf.otype = static_cast (otype_set); curconf.speed = SPEED_MEDIUM; - curconf.af = func_set; + curconf.af = static_cast (func_set); curconf.monitor = monitor; if(!set_pinfunc(port, pin, &curconf)) return ERR_BADVAL; return ERR_OK; @@ -637,7 +638,7 @@ static void sendusartdata(const uint8_t *buf, int len){ SEND(str_keywords[STR_USART]); SEND(EQ); if(usart_text){ USB_send(IGPIO, curbuf, len); - if(curbuf[len-1] != '\n') NL(); + NL(); // always add newline at the end to mark real newline ("\n\n") and piece of line ("\n") }else{ NL(); hexdump(sendfun, (uint8_t*)curbuf, len); @@ -653,10 +654,7 @@ static errcodes_t cmd_USART(const char _U_ *cmd, char *args){ if(usart_text){ // add '\n' as we removed it @ parser if(setter[l-1] != '\n') setter[l++] = '\n'; } - l = usart_send((uint8_t*)setter, l); - if(l < 0) return ERR_BUSY; - else if(l == 0) return ERR_CANTRUN; - return ERR_OK; + return usart_send((uint8_t*)setter, l); } // getter: try to read int l = usart_receive(curbuf, MAXSTRLEN); if(l < 0) return ERR_CANTRUN; @@ -711,3 +709,10 @@ void GPIO_process(){ if(ans) SENDn(ans); } } + +// starting init by flash settings +void GPIO_init(){ + gpio_reinit(); + usartconf_t usc; + if(get_curusartconf(&usc)) usart_text = usc.textproto; +} diff --git a/F0:F030,F042,F072/usbcan_gpio/gpioproto.h b/F0:F030,F042,F072/usbcan_gpio/gpioproto.h index 2facea9..b0adede 100644 --- a/F0:F030,F042,F072/usbcan_gpio/gpioproto.h +++ b/F0:F030,F042,F072/usbcan_gpio/gpioproto.h @@ -27,6 +27,7 @@ typedef enum{ ERR_WRONGLEN, // wrong message length ERR_CANTRUN, // can't run given command due to bad parameters or other ERR_BUSY, // target interface busy, try later + ERR_OVERFLOW, // string was too long -> overflow ERR_AMOUNT // amount of error codes or "send nothing" } errcodes_t; @@ -39,3 +40,4 @@ typedef enum{ #define ADC_THRES_DEFAULT 100 void GPIO_process(); +void GPIO_init(); diff --git a/F0:F030,F042,F072/usbcan_gpio/hardware.c b/F0:F030,F042,F072/usbcan_gpio/hardware.c index 45f312d..d225182 100644 --- a/F0:F030,F042,F072/usbcan_gpio/hardware.c +++ b/F0:F030,F042,F072/usbcan_gpio/hardware.c @@ -17,12 +17,12 @@ */ #include "adc.h" -#include "gpio.h" +#include "gpioproto.h" #include "hardware.h" uint8_t ledsON = 0; -TRUE_INLINE void gpio_setup(){ // setup some common GPIO +TRUE_INLINE void pins_setup(){ // setup some common GPIO // Set LEDS (PB15/PA8) as output pin_set(LED0_port, LED0_pin); // clear LEDs pin_set(LED1_port, LED1_pin); @@ -37,9 +37,9 @@ void hardware_setup(){ RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN | RCC_AHBENR_DMA1EN; RCC->APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_SYSCFGEN; RCC->APB1ENR |= RCC_APB1ENR_USART2EN; - gpio_setup(); - //gpio_reinit(); + pins_setup(); adc_setup(); + GPIO_init(); } void iwdg_setup(){ diff --git a/F0:F030,F042,F072/usbcan_gpio/strfunc.h b/F0:F030,F042,F072/usbcan_gpio/strfunc.h index 194d0db..c21ac44 100644 --- a/F0:F030,F042,F072/usbcan_gpio/strfunc.h +++ b/F0:F030,F042,F072/usbcan_gpio/strfunc.h @@ -37,7 +37,7 @@ #define REPOURL "https://github.com/eddyem/stm32samples/tree/master/F0:F030,F042,F072/usbcan_gpio " RLSDBG " build #" BUILD_NUMBER "@" BUILD_DATE "\n" // max string len to '\n' (including '\0') -#define MAXSTRLEN 128 +#define MAXSTRLEN 256 void hexdump(int (*sendfun)(const char *s), uint8_t *arr, uint16_t len); const char *u2str(uint32_t val); diff --git a/F0:F030,F042,F072/usbcan_gpio/usart.c b/F0:F030,F042,F072/usbcan_gpio/usart.c index 85be3a6..23b3967 100644 --- a/F0:F030,F042,F072/usbcan_gpio/usart.c +++ b/F0:F030,F042,F072/usbcan_gpio/usart.c @@ -209,10 +209,14 @@ int usart_process(uint8_t *buf, int len){ int remained = DMA1_Channel5->CNDTR; int write_idx = DMARXBUFSZ - remained; // next symbol to be written int available = (write_idx - dma_read_idx); // length of data available + if(available < 0) available += DMARXBUFSZ; // write to the left of read + if(available == 0){ + RXrdy = 0; // clear old ready flag if got no data + return 0; + } int monitored_len = available; uint8_t locmonitor = monitor; // if `buf` not pointed, set this flag to zero - if(available < 0) available += DMARXBUFSZ; // write to the left of read - if(available){ + if(available > 0){ if(locmonitor){ if(buf && len > 0){ if(len < monitored_len) monitored_len = len; @@ -222,24 +226,32 @@ int usart_process(uint8_t *buf, int len){ if(available >= (DMARXBUFSZ/2) || RXrdy){ // enough data or lonely couple of bytes but need to show // copy data in one or two chunks (wrap handling) int wrOK = FALSE; + // check if we can write to RB `available` bytes + int canRB = TRUE; + if(!locmonitor){ + int rballow = RBin.length - 1 - RB_datalen(&RBin); + if(rballow < available) canRB = FALSE; + } if(dma_read_idx + available <= DMARXBUFSZ){ // head before tail if(locmonitor){ memcpy(buf, &inbuffer[dma_read_idx], monitored_len); ret = monitored_len; wrOK = TRUE; }else{ - if(available == RB_write(&RBin, &inbuffer[dma_read_idx], available)) wrOK = TRUE; + if(canRB && available == RB_write(&RBin, &inbuffer[dma_read_idx], available)) wrOK = TRUE; + else if(buf && len > 0) ret = RB_read(&RBin, buf, len); // ringbuffer overfull -> emerge clearing } }else{ // head after tail - two chunks int first = DMARXBUFSZ - dma_read_idx; if(locmonitor){ memcpy(buf, &inbuffer[dma_read_idx], first); - memcpy(buf, inbuffer, monitored_len - first); + memcpy(buf + first, inbuffer, monitored_len - first); ret = monitored_len; wrOK = TRUE; }else{ - if((first == RB_write(&RBin, &inbuffer[dma_read_idx], first)) && + if(canRB && (first == RB_write(&RBin, &inbuffer[dma_read_idx], first)) && (available - first) == RB_write(&RBin, inbuffer, available - first)) wrOK = TRUE; + else if(buf && len > 0) ret = RB_read(&RBin, buf, len); } } if(wrOK){ @@ -247,29 +259,20 @@ int usart_process(uint8_t *buf, int len){ dma_read_idx = write_idx; // update read pointer } } + }else if(available < 0){ // das ist fantastisch! + if(buf && len > 0) ret = RB_read(&RBin, buf, len); + DBG("WTF? USART's `available` < 0!!!\n"); } -#if 0 - // Output data - if(TXrdy){ // ready to send new data - here we can process RBout, if have - int got = RB_read... - if(got > 0){ // send next data portion - volatile DMA_Channel_TypeDef *T = cfg->dma_tx_channel; - T->CCR &= ~DMA_CCR_EN; - T->CMAR = (uint32_t) outbuffers[i]; - T->CNDTR = got; - TXrdy = 0; - T->CCR |= DMA_CCR_EN; // start new transmission - } - } -#endif + // we can work with RBout to send more than `usart_send` can + // here we can send next data portion return ret; } // send data buffer -int usart_send(const uint8_t *data, int len){ - if(curUSARTidx == -1 || !data || len < 1) return 0; - if(TXrdy == 0) return -1; - if(len > DMATXBUFSZ) len = DMATXBUFSZ; +errcodes_t usart_send(const uint8_t *data, int len){ + if(curUSARTidx == -1 || !data || len < 1) return ERR_CANTRUN; + if(TXrdy == 0) return ERR_BUSY; + if(len > DMATXBUFSZ) return ERR_OVERFLOW; memcpy(outbuffer, data, len); volatile DMA_Channel_TypeDef *T = DMA1_Channel4; T->CCR &= ~DMA_CCR_EN; @@ -277,7 +280,7 @@ int usart_send(const uint8_t *data, int len){ T->CNDTR = len; TXrdy = 0; T->CCR |= DMA_CCR_EN; // start new transmission - return len; + return ERR_OK; } /** diff --git a/F0:F030,F042,F072/usbcan_gpio/usart.h b/F0:F030,F042,F072/usbcan_gpio/usart.h index f5e1e77..265cad7 100644 --- a/F0:F030,F042,F072/usbcan_gpio/usart.h +++ b/F0:F030,F042,F072/usbcan_gpio/usart.h @@ -19,12 +19,13 @@ #pragma once #include +#include "gpioproto.h" // DMA linear buffers for Rx/Tx -#define DMARXBUFSZ 128 -#define DMATXBUFSZ 128 +#define DMARXBUFSZ 192 +#define DMATXBUFSZ 192 // incoming ring buffer - only if there's a lot of data in DMA RX buffer -#define RXRBSZ 256 +#define RXRBSZ 384 #define USART_MIN_SPEED 1024 #define USART_MAX_SPEED 1000000 @@ -51,4 +52,4 @@ void get_defusartconf(usartconf_t *c); int usart_process(uint8_t *buf, int len); int usart_receive(uint8_t *buf, int len); -int usart_send(const uint8_t *data, int len); +errcodes_t usart_send(const uint8_t *data, int len); diff --git a/F0:F030,F042,F072/usbcan_gpio/usbcangpio.bin b/F0:F030,F042,F072/usbcan_gpio/usbcangpio.bin index ed09300fa3a5033de5b4ca23e9897138cc034b06..9f8adbd1a75ffe61f176e23797239aa8a645c903 100755 GIT binary patch delta 10623 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 nloBZKuUX~!LV2`FTc$Gn1lV|NU;h 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