From 9166996bff243c4b4e2f008c29d4f1f7be3810a4 Mon Sep 17 00:00:00 2001 From: Edward Emelianov Date: Mon, 26 Aug 2024 20:47:04 +0300 Subject: [PATCH] fixed USB problem --- F3:F303/Multistepper/Readme.md | 2 + F3:F303/Multistepper/can.h | 4 +- F3:F303/Multistepper/flash.c | 51 +++++--- F3:F303/Multistepper/hashgen/hdr.c | 5 + F3:F303/Multistepper/hashgen/hdr.h | 1 + F3:F303/Multistepper/hashgen/helpcmds.in | 1 + F3:F303/Multistepper/hashgen/test | Bin 22536 -> 0 bytes F3:F303/Multistepper/hashgen/testdic | 1 + F3:F303/Multistepper/main.c | 12 +- F3:F303/Multistepper/multistepper.bin | Bin 44308 -> 44920 bytes F3:F303/Multistepper/proto.c | 2 +- F3:F303/Multistepper/ringbuffer.c | 116 +++++++++++------ F3:F303/Multistepper/ringbuffer.h | 3 +- F3:F303/Multistepper/usb.c | 151 +++++++++-------------- F3:F303/Multistepper/usb.h | 10 +- F3:F303/Multistepper/usb_lib.c | 134 +++++++++++++------- F3:F303/Multistepper/usb_lib.h | 56 +++++---- F3:F303/Multistepper/usbhw.c | 11 +- F3:F303/Multistepper/usbhw.h | 2 +- F3:F303/Multistepper/version.inc | 4 +- 20 files changed, 326 insertions(+), 240 deletions(-) delete mode 100755 F3:F303/Multistepper/hashgen/test diff --git a/F3:F303/Multistepper/Readme.md b/F3:F303/Multistepper/Readme.md index 82939a6..f7789f9 100644 --- a/F3:F303/Multistepper/Readme.md +++ b/F3:F303/Multistepper/Readme.md @@ -390,6 +390,8 @@ Dump command codes. Returns all list of CAN bus command codes. ### dumpconf Dump current configuration. Returns a lot of information both common (like CAN bus speed, ID and so on) and for each motor driver. You can call independeng getters for each of this parameter. +### dumpmotN +Dump configuration of Nth motor only. ### dumpmotflags Dump motor flags' bits (for `motflagsN`) and reaction to limit switches (`eswreact`) values: diff --git a/F3:F303/Multistepper/can.h b/F3:F303/Multistepper/can.h index fa09df4..feb3556 100644 --- a/F3:F303/Multistepper/can.h +++ b/F3:F303/Multistepper/can.h @@ -20,8 +20,8 @@ #include -// amount of filter banks in STM32F0 -#define STM32F0FBANKNO 28 +// amount of filter banks in STM32F +#define STM32FBANKNO 28 // flood period in milliseconds #define FLOOD_PERIOD_MS 5 diff --git a/F3:F303/Multistepper/flash.c b/F3:F303/Multistepper/flash.c index 0553832..f6afdf3 100644 --- a/F3:F303/Multistepper/flash.c +++ b/F3:F303/Multistepper/flash.c @@ -200,6 +200,33 @@ int erase_storage(int npage){ return ret; } +int fn_dumpmot(uint32_t _U_ hash, char _U_ *args){ // "dumpmot" (1224122507) + if(!args || !*args) return RET_WRONGCMD; + uint32_t i; + const char *eq = getnum(args, &i); + if(eq == args || i > STP_STATE_AMOUNT) return RET_WRONGCMD; + char cur = '0' + i; +#define PROPNAME(nm) do{newline(); USB_sendstr(nm); USB_putbyte(cur); USB_putbyte('=');}while(0) + USB_sendstr("microsteps"); USB_putbyte(cur); USB_putbyte('='); + printu(the_conf.microsteps[i]); + PROPNAME("accel"); + printu(the_conf.accel[i]); + PROPNAME("maxspeed"); + printu(the_conf.maxspd[i]); + PROPNAME("minspeed"); + printu(the_conf.minspd[i]); + PROPNAME("maxsteps"); + printu(the_conf.maxsteps[i]); + PROPNAME("motcurrent"); + printu(the_conf.motcurrent[i]); + PROPNAME("motflags"); + printuhex(*((uint8_t*)&the_conf.motflags[i])); + PROPNAME("eswreact"); + printu(the_conf.ESW_reaction[i]); +#undef PROPNAME + newline(); + return RET_GOOD; +} int fn_dumpconf(uint32_t _U_ hash, char _U_ *args){ // "dumpconf" (3271513185) #ifdef EBUG @@ -212,28 +239,12 @@ int fn_dumpconf(uint32_t _U_ hash, char _U_ *args){ // "dumpconf" (3271513185) USB_sendstr("\nuserconf_sz="); printu(the_conf.userconf_sz); USB_sendstr("\ncanspeed="); printu(the_conf.CANspeed); USB_sendstr("\ncanid="); printu(the_conf.CANID); + newline(); + char x[2] = {0}; // motors' data for(int i = 0; i < MOTORSNO; ++i){ - char cur = '0' + i; -#define PROPNAME(nm) do{newline(); USB_sendstr(nm); USB_putbyte(cur); USB_putbyte('=');}while(0) - PROPNAME("microsteps"); - printu(the_conf.microsteps[i]); - PROPNAME("accel"); - printu(the_conf.accel[i]); - PROPNAME("maxspeed"); - printu(the_conf.maxspd[i]); - PROPNAME("minspeed"); - printu(the_conf.minspd[i]); - PROPNAME("maxsteps"); - printu(the_conf.maxsteps[i]); - PROPNAME("motcurrent"); - printu(the_conf.motcurrent[i]); - PROPNAME("motflags"); - printuhex(*((uint8_t*)&the_conf.motflags[i])); - PROPNAME("eswreact"); - printu(the_conf.ESW_reaction[i]); -#undef PROPNAME + x[0] = '0' + i; + fn_dumpmot(0, x); } - newline(); return RET_GOOD; } diff --git a/F3:F303/Multistepper/hashgen/hdr.c b/F3:F303/Multistepper/hashgen/hdr.c index b3a39ba..e4f5cf5 100644 --- a/F3:F303/Multistepper/hashgen/hdr.c +++ b/F3:F303/Multistepper/hashgen/hdr.c @@ -56,6 +56,8 @@ int fn_dumpconf(uint32_t _U_ hash, char _U_ *args) WAL; // "dumpconf" (327151318 int fn_dumperr(uint32_t _U_ hash, char _U_ *args) WAL; // "dumperr" (1223989764) +int fn_dumpmot(uint32_t _U_ hash, char _U_ *args) WAL; // "dumpmot" (1224122507) + int fn_dumpmotflags(uint32_t _U_ hash, char _U_ *args) WAL; // "dumpmotflags" (36159640) int fn_dumpstates(uint32_t _U_ hash, char _U_ *args) WAL; // "dumpstates" (4235564367) @@ -216,6 +218,9 @@ int parsecmd(const char *str){ case CMD_DUMPERR: return fn_dumperr(h, args); break; + case CMD_DUMPMOT: + return fn_dumpmot(h, args); + break; case CMD_DUMPMOTFLAGS: return fn_dumpmotflags(h, args); break; diff --git a/F3:F303/Multistepper/hashgen/hdr.h b/F3:F303/Multistepper/hashgen/hdr.h index 1bd8555..99831e3 100644 --- a/F3:F303/Multistepper/hashgen/hdr.h +++ b/F3:F303/Multistepper/hashgen/hdr.h @@ -38,6 +38,7 @@ extern char lastcmd[]; #define CMD_DUMPCMD (1223955823) #define CMD_DUMPCONF (3271513185) #define CMD_DUMPERR (1223989764) +#define CMD_DUMPMOT (1224122507) #define CMD_DUMPMOTFLAGS (36159640) #define CMD_DUMPSTATES (4235564367) #define CMD_EMSTOP (2965919005) diff --git a/F3:F303/Multistepper/hashgen/helpcmds.in b/F3:F303/Multistepper/hashgen/helpcmds.in index 23e2d78..4a90b0e 100644 --- a/F3:F303/Multistepper/hashgen/helpcmds.in +++ b/F3:F303/Multistepper/hashgen/helpcmds.in @@ -20,6 +20,7 @@ "dumperr - dump error codes\n" "dumpcmd - dump command codes\n" "dumpconf - dump current configuration\n" + "dumpmotN - dump Nth motor configuration\n" "dumpmotflags - dump motor flags' bits\n" "dumpstates - dump motors' state codes\n" "emstop[N] - emergency stop motor N or all\n" diff --git a/F3:F303/Multistepper/hashgen/test b/F3:F303/Multistepper/hashgen/test deleted file mode 100755 index 8fae8fc4fde72574fb426ee093032d1d8e118d4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22536 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 diff --git a/F3:F303/Multistepper/hashgen/testdic b/F3:F303/Multistepper/hashgen/testdic index 0fe640b..530ceb7 100644 --- a/F3:F303/Multistepper/hashgen/testdic +++ b/F3:F303/Multistepper/hashgen/testdic @@ -20,6 +20,7 @@ drvtype dumperr dumpcmd dumpconf +dumpmot dumpmotflags dumpstates emstop diff --git a/F3:F303/Multistepper/main.c b/F3:F303/Multistepper/main.c index e76ddcb..04d2427 100644 --- a/F3:F303/Multistepper/main.c +++ b/F3:F303/Multistepper/main.c @@ -45,11 +45,11 @@ int main(void){ hw_setup(); // GPIO, ADC, timers, watchdog etc. USBPU_OFF(); // make a reconnection flashstorage_init(); - init_steppers(); USB_setup(); CAN_setup(the_conf.CANspeed); adc_setup(); pdnuart_setup(); + init_steppers(); USBPU_ON(); uint32_t ctr = 0; CAN_message *can_mesg; @@ -60,10 +60,9 @@ int main(void){ LED_blink(); } CAN_proc(); - USB_proc(); // TODO: remove deprecated trash code! process_steppers(); if(CAN_get_status() == CAN_FIFO_OVERRUN){ - USB_sendstr("CAN bus fifo overrun occured!\n"); + USB_sendstr("CAN_FIFO_OVERRUN\n"); } while((can_mesg = CAN_messagebuf_pop())){ if(can_mesg && isgood(can_mesg->ID)){ @@ -82,8 +81,13 @@ int main(void){ } } int l = USB_receivestr(inbuff, MAXSTRLEN); - if(l < 0) USB_sendstr("ERROR: USB buffer overflow or string was too long\n"); + if(l < 0) USB_sendstr("USB_BUF_OVERFLOW\n"); else if(l){ +#ifdef EBUG + USB_sendstr("USB GOT:\n"); + USB_sendstr(inbuff); + USB_sendstr("\n--------\n"); +#endif const char *ans = cmd_parser(inbuff); if(ans) USB_sendstr(ans); } diff --git a/F3:F303/Multistepper/multistepper.bin b/F3:F303/Multistepper/multistepper.bin index 77b79235c79e0521a3681211144bba078ab2efcf..3c309ec64e23a596eef5376da2f2628e9b079885 100755 GIT binary patch delta 14304 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 delta 13560 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 TCX7q$1n`(=xOeE|&~X0)2K5(J diff --git a/F3:F303/Multistepper/proto.c b/F3:F303/Multistepper/proto.c index 38f38c9..8c04193 100644 --- a/F3:F303/Multistepper/proto.c +++ b/F3:F303/Multistepper/proto.c @@ -205,7 +205,7 @@ static void add_filter(const char *str){ USB_sendstr("No bank# given"); return; } - if(N > STM32F0FBANKNO-1){ + if(N > STM32FBANKNO-1){ USB_sendstr("bank# > 27"); return; } diff --git a/F3:F303/Multistepper/ringbuffer.c b/F3:F303/Multistepper/ringbuffer.c index 462d4c6..7f78f8c 100644 --- a/F3:F303/Multistepper/ringbuffer.c +++ b/F3:F303/Multistepper/ringbuffer.c @@ -18,19 +18,21 @@ #include "ringbuffer.h" -// stored data length -int RB_datalen(ringbuffer *b){ +static int datalen(ringbuffer *b){ if(b->tail >= b->head) return (b->tail - b->head); else return (b->length - b->head + b->tail); } -/** - * @brief RB_hasbyte - check if buffer has given byte stored - * @param b - buffer - * @param byte - byte to find - * @return index if found, -1 if none - */ -int RB_hasbyte(ringbuffer *b, uint8_t byte){ +// stored data length +int RB_datalen(ringbuffer *b){ + if(b->busy) return -1; + b->busy = 1; + int l = datalen(b); + b->busy = 0; + return l; +} + +static int hasbyte(ringbuffer *b, uint8_t byte){ if(b->head == b->tail) return -1; // no data in buffer int startidx = b->head; if(b->head > b->tail){ // @@ -43,6 +45,20 @@ int RB_hasbyte(ringbuffer *b, uint8_t byte){ return -1; } +/** + * @brief RB_hasbyte - check if buffer has given byte stored + * @param b - buffer + * @param byte - byte to find + * @return index if found, -1 if none or busy + */ +int RB_hasbyte(ringbuffer *b, uint8_t byte){ + if(b->busy) return -1; + b->busy = 1; + int ret = hasbyte(b, byte); + b->busy = 0; + return ret; +} + // poor memcpy static void mcpy(uint8_t *targ, const uint8_t *src, int l){ while(l--) *targ++ = *src++; @@ -54,15 +70,8 @@ TRUE_INLINE void incr(ringbuffer *b, volatile int *what, int n){ if(*what >= b->length) *what -= b->length; } -/** - * @brief RB_read - read data from ringbuffer - * @param b - buffer - * @param s - array to write data - * @param len - max len of `s` - * @return bytes read - */ -int RB_read(ringbuffer *b, uint8_t *s, int len){ - int l = RB_datalen(b); +static int read(ringbuffer *b, uint8_t *s, int len){ + int l = datalen(b); if(!l) return 0; if(l > len) l = len; int _1st = b->length - b->head; @@ -78,33 +87,49 @@ int RB_read(ringbuffer *b, uint8_t *s, int len){ return _1st; } +/** + * @brief RB_read - read data from ringbuffer + * @param b - buffer + * @param s - array to write data + * @param len - max len of `s` + * @return bytes read or -1 if busy + */ +int RB_read(ringbuffer *b, uint8_t *s, int len){ + if(b->busy) return -1; + b->busy = 1; + int r = read(b, s, len); + b->busy = 0; + return r; +} + +static int readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){ + int idx = hasbyte(b, byte); + if(idx < 0) return 0; + int partlen = idx + 1 - b->head; + // now calculate length of new data portion + if(idx < b->head) partlen += b->length; + if(partlen > len) return -read(b, s, len); + return read(b, s, partlen); +} + /** * @brief RB_readto fill array `s` with data until byte `byte` (with it) * @param b - ringbuffer * @param byte - check byte * @param s - buffer to write data * @param len - length of `s` - * @return amount of bytes written (negative, if lenhead; - // now calculate length of new data portion - if(idx < b->head) partlen += b->length; - if(partlen > len) return -RB_read(b, s, len); - return RB_read(b, s, partlen); + if(b->busy) return -1; + b->busy = 1; + int n = readto(b, byte, s, len); + b->busy = 0; + return n; } -/** - * @brief RB_write - write some data to ringbuffer - * @param b - buffer - * @param str - data - * @param l - length - * @return amount of bytes written - */ -int RB_write(ringbuffer *b, const uint8_t *str, int l){ - int r = b->length - 1 - RB_datalen(b); // rest length +static int write(ringbuffer *b, const uint8_t *str, int l){ + int r = b->length - 1 - datalen(b); // rest length if(l > r) l = r; if(!l) return 0; int _1st = b->length - b->tail; @@ -117,8 +142,27 @@ int RB_write(ringbuffer *b, const uint8_t *str, int l){ return l; } +/** + * @brief RB_write - write some data to ringbuffer + * @param b - buffer + * @param str - data + * @param l - length + * @return amount of bytes written or -1 if busy + */ +int RB_write(ringbuffer *b, const uint8_t *str, int l){ + if(b->busy) return -1; + b->busy = 1; + int w = write(b, str, l); + b->busy = 0; + return w; +} + // just delete all information in buffer `b` -void RB_clearbuf(ringbuffer *b){ +int RB_clearbuf(ringbuffer *b){ + if(b->busy) return -1; + b->busy = 1; b->head = 0; b->tail = 0; + b->busy = 0; + return 1; } diff --git a/F3:F303/Multistepper/ringbuffer.h b/F3:F303/Multistepper/ringbuffer.h index 6d6b5d3..549b5c0 100644 --- a/F3:F303/Multistepper/ringbuffer.h +++ b/F3:F303/Multistepper/ringbuffer.h @@ -25,6 +25,7 @@ typedef struct{ const int length; // its length int head; // head index int tail; // tail index + volatile int busy; // == TRUE if buffer is busy now } ringbuffer; int RB_read(ringbuffer *b, uint8_t *s, int len); @@ -32,4 +33,4 @@ int RB_readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len); int RB_hasbyte(ringbuffer *b, uint8_t byte); int RB_write(ringbuffer *b, const uint8_t *str, int l); int RB_datalen(ringbuffer *b); -void RB_clearbuf(ringbuffer *b); +int RB_clearbuf(ringbuffer *b); diff --git a/F3:F303/Multistepper/usb.c b/F3:F303/Multistepper/usb.c index 590a33e..313e723 100644 --- a/F3:F303/Multistepper/usb.c +++ b/F3:F303/Multistepper/usb.c @@ -1,6 +1,6 @@ /* * This file is part of the multistepper 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 @@ -19,27 +19,31 @@ #include #include "hardware.h" -#include "ringbuffer.h" #include "usb.h" #include "usb_lib.h" +#ifdef EBUG +#include "strfunc.h" +#endif static volatile uint8_t usbbuff[USB_TXBUFSZ]; // temporary buffer for sending data // ring buffers for incoming and outgoing data static uint8_t obuf[RBOUTSZ], ibuf[RBINSZ]; -static volatile ringbuffer out = {.data = obuf, .length = RBOUTSZ, .head = 0, .tail = 0}; -static volatile ringbuffer in = {.data = ibuf, .length = RBINSZ, .head = 0, .tail = 0}; -// transmission is succesfull -static volatile uint8_t bufisempty = 1; -static volatile uint8_t bufovrfl = 0; +volatile ringbuffer rbout = {.data = obuf, .length = RBOUTSZ, .head = 0, .tail = 0}; +volatile ringbuffer rbin = {.data = ibuf, .length = RBINSZ, .head = 0, .tail = 0}; +// inbuf overflow when receiving +volatile uint8_t bufovrfl = 0; +// last send data size +static volatile int lastdsz = 0; -static void send_next(){ - if(bufisempty) return; - static int lastdsz = 0; - int buflen = RB_read((ringbuffer*)&out, (uint8_t*)usbbuff, USB_TXBUFSZ); - if(!buflen){ +// called from transmit EP +void send_next(){ + int buflen = RB_read((ringbuffer*)&rbout, (uint8_t*)usbbuff, USB_TXBUFSZ); + if(buflen == 0){ if(lastdsz == 64) EP_Write(3, NULL, 0); // send ZLP after 64 bits packet when nothing more to send lastdsz = 0; - bufisempty = 1; + return; + }else if(buflen < 0){ + EP_Write(3, NULL, 0); // send ZLP if buffer is in writting state now return; } EP_Write(3, (uint8_t*)usbbuff, buflen); @@ -48,44 +52,42 @@ static void send_next(){ // blocking send full content of ring buffer int USB_sendall(){ - while(!bufisempty){ - if(!usbON) return 0; + while(lastdsz > 0){ + if(!usbON) return FALSE; } - return 1; + return TRUE; } // put `buf` into queue to send int USB_send(const uint8_t *buf, int len){ - if(!buf || !usbON || !len) return 0; + if(!buf || !usbON || !len) return FALSE; while(len){ - int a = RB_write((ringbuffer*)&out, buf, len); - len -= a; - buf += a; - if(bufisempty){ - bufisempty = 0; - send_next(); - } + int a = RB_write((ringbuffer*)&rbout, buf, len); + if(a > 0){ + len -= a; + buf += a; + } else if (a < 0) continue; // do nothing if buffer is in reading state + if(lastdsz == 0) send_next(); // need to run manually - all data sent, so no IRQ on IN } - return 1; + return TRUE; } int USB_putbyte(uint8_t byte){ - if(!usbON) return 0; - while(0 == RB_write((ringbuffer*)&out, &byte, 1)){ - if(bufisempty){ - bufisempty = 0; - send_next(); - } + if(!usbON) return FALSE; + int l = 0; + while((l = RB_write((ringbuffer*)&rbout, &byte, 1)) != 1){ + if(l < 0) continue; } - return 1; + if(lastdsz == 0) send_next(); // need to run manually - all data sent, so no IRQ on IN + return TRUE; } int USB_sendstr(const char *string){ - if(!string || !usbON) return 0; + if(!string || !usbON) return FALSE; int len = 0; const char *b = string; while(*b++) ++len; - if(!len) return 0; + if(!len) return FALSE; return USB_send((const uint8_t*)string, len); } @@ -96,13 +98,14 @@ int USB_sendstr(const char *string){ * @return amount of received bytes (negative, if overfull happened) */ int USB_receive(uint8_t *buf, int len){ - int sz = RB_read((ringbuffer*)&in, buf, len); + chkin(); if(bufovrfl){ - RB_clearbuf((ringbuffer*)&in); - if(!sz) sz = -1; - else sz = -sz; + while(1 != RB_clearbuf((ringbuffer*)&rbin)); bufovrfl = 0; + return -1; } + int sz = RB_read((ringbuffer*)&rbin, buf, len); + if(sz < 0) return 0; // buffer in writting state return sz; } @@ -113,65 +116,25 @@ int USB_receive(uint8_t *buf, int len){ * @return strlen or negative value indicating overflow (if so, string won't be ends with 0 and buffer should be cleared) */ int USB_receivestr(char *buf, int len){ - int l = RB_readto((ringbuffer*)&in, '\n', (uint8_t*)buf, len); - if(l == 0) return 0; - if(--l < 0 || bufovrfl) RB_clearbuf((ringbuffer*)&in); - else buf[l] = 0; // replace '\n' with strend + chkin(); if(bufovrfl){ - if(l > 0) l = -l; - else l = -1; + while(1 != RB_clearbuf((ringbuffer*)&rbin)); bufovrfl = 0; + return -1; } + int l = RB_readto((ringbuffer*)&rbin, '\n', (uint8_t*)buf, len); + if(l < 1){ + if(rbin.length == RB_datalen((ringbuffer*)&rbin)){ // buffer is full but no '\n' found + while(1 != RB_clearbuf((ringbuffer*)&rbin)); + return -1; + } + return 0; + } +#ifdef EBUG + USB_sendstr("readto, l="); USB_sendstr(u2str(l)); newline(); +#endif + if(l == 0) return 0; + buf[l-1] = 0; // replace '\n' with strend return l; } -// interrupt IN handler (never used?) -static void EP1_Handler(){ - uint16_t epstatus = KEEP_DTOG(USB->EPnR[1]); - if(RX_FLAG(epstatus)) epstatus = (epstatus & ~USB_EPnR_STAT_TX) ^ USB_EPnR_STAT_RX; // set valid RX - else epstatus = epstatus & ~(USB_EPnR_STAT_TX|USB_EPnR_STAT_RX); - // clear CTR - epstatus = (epstatus & ~(USB_EPnR_CTR_RX|USB_EPnR_CTR_TX)); - USB->EPnR[1] = epstatus; -} - -// data IN/OUT handlers -static void transmit_Handler(){ // EP3IN - uint16_t epstatus = KEEP_DTOG_STAT(USB->EPnR[3]); - // clear CTR keep DTOGs & STATs - USB->EPnR[3] = (epstatus & ~(USB_EPnR_CTR_TX)); // clear TX ctr - send_next(); -} - -static void receive_Handler(){ // EP2OUT - uint8_t buf[USB_RXBUFSZ]; - uint16_t epstatus = KEEP_DTOG(USB->EPnR[2]); - uint8_t sz = EP_Read(2, (uint8_t*)buf); - if(sz){ - if(RB_write((ringbuffer*)&in, buf, sz) != sz) bufovrfl = 1; - } - // keep stat_tx & set ACK rx, clear RX ctr - USB->EPnR[2] = (epstatus & ~USB_EPnR_CTR_RX) ^ USB_EPnR_STAT_RX; -} - -void USB_proc(){ - switch(USB_Dev.USB_Status){ - case USB_STATE_CONFIGURED: - // make new BULK endpoint - // Buffer have 1024 bytes, but last 256 we use for CAN bus (30.2 of RM: USB main features) - EP_Init(1, EP_TYPE_INTERRUPT, USB_EP1BUFSZ, 0, EP1_Handler); // IN1 - transmit - EP_Init(2, EP_TYPE_BULK, 0, USB_RXBUFSZ, receive_Handler); // OUT2 - receive data - EP_Init(3, EP_TYPE_BULK, USB_TXBUFSZ, 0, transmit_Handler); // IN3 - transmit data - USB_Dev.USB_Status = USB_STATE_CONNECTED; - break; - case USB_STATE_DEFAULT: - case USB_STATE_ADDRESSED: - if(usbON){ - usbON = 0; - } - break; - default: // USB_STATE_CONNECTED - send next data portion - // if(!usbON) return; // WTF? - break; - } -} diff --git a/F3:F303/Multistepper/usb.h b/F3:F303/Multistepper/usb.h index 5b62cc5..cd2808e 100644 --- a/F3:F303/Multistepper/usb.h +++ b/F3:F303/Multistepper/usb.h @@ -1,6 +1,6 @@ /* * This file is part of the multistepper 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,11 +18,12 @@ #pragma once +#include "ringbuffer.h" #include "usbhw.h" // sizes of ringbuffers for outgoing and incoming data #define RBOUTSZ (512) -#define RBINSZ (512) +#define RBINSZ (256) #define newline() USB_putbyte('\n') #define USND(s) do{USB_sendstr(s); USB_putbyte('\n');}while(0) @@ -36,7 +37,10 @@ #define DBG(str) #endif -void USB_proc(); +extern volatile ringbuffer rbout, rbin; +extern volatile uint8_t bufisempty, bufovrfl; + +void send_next(); int USB_sendall(); int USB_send(const uint8_t *buf, int len); int USB_putbyte(uint8_t byte); diff --git a/F3:F303/Multistepper/usb_lib.c b/F3:F303/Multistepper/usb_lib.c index 4db6281..3ced8ec 100644 --- a/F3:F303/Multistepper/usb_lib.c +++ b/F3:F303/Multistepper/usb_lib.c @@ -1,6 +1,6 @@ /* * This file is part of the multistepper 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 @@ -17,14 +17,16 @@ */ #include +#include "usb.h" #include "usb_lib.h" +#include "usbhw.h" ep_t endpoints[STM32ENDPOINTS]; -usb_dev_t USB_Dev; +static uint16_t USB_Addr = 0; static usb_LineCoding lineCoding = {115200, 0, 0, 8}; -config_pack_t setup_packet; -uint8_t ep0databuf[EP0DATABUF_SIZE]; +uint8_t ep0databuf[EP0DATABUF_SIZE], setupdatabuf[EP0DATABUF_SIZE]; +config_pack_t *setup_packet = (config_pack_t*) setupdatabuf; usb_LineCoding getLineCoding(){return lineCoding;} @@ -53,9 +55,9 @@ static const uint8_t USB_DeviceDescriptor[] = { 0x23, // idProduct_H 0x00, // bcdDevice_Ver_L 0x03, // bcdDevice_Ver_H - 0x01, // iManufacturer - 0x02, // iProduct - 0x00, // iSerialNumber + iMANUFACTURER_DESCR, // iManufacturer + iPRODUCT_DESCR, // iProduct + iSERIAL_DESCR, // iSerialNumber bNumConfigurations // bNumConfigurations }; @@ -95,7 +97,7 @@ static const uint8_t USB_ConfigDescriptor[] = { 0xff, /* bInterfaceClass */ 0x00, /* bInterfaceSubClass */ 0x00, /* bInterfaceProtocol */ - 0x00, /* iInterface: */ + iINTERFACE_DESCR, /* iInterface: */ /////////////////////////////////////////////////// /*Endpoint 1 Descriptor*/ 0x07, /* bLength: Endpoint Descriptor size */ @@ -125,11 +127,19 @@ static const uint8_t USB_ConfigDescriptor[] = { 0x00, /* bInterval: ignore for Bulk transfer */ }; -_USB_LANG_ID_(USB_StringLangDescriptor, LANG_US); -// these descriptors are not used in PL2303 emulator! -_USB_STRING_(USB_StringSerialDescriptor, u"0"); -_USB_STRING_(USB_StringManufacturingDescriptor, u"Prolific Technology Inc."); -_USB_STRING_(USB_StringProdDescriptor, u"USB-Serial Controller"); +_USB_LANG_ID_(LD, LANG_US); +_USB_STRING_(SD, u"0.0.1"); +_USB_STRING_(MD, u"Prolific Technology Inc."); +_USB_STRING_(PD, u"USB-Serial Controller"); +_USB_STRING_(ID, u"multistepper"); +static void const *StringDescriptor[iDESCR_AMOUNT] = { + [iLANGUAGE_DESCR] = &LD, + [iMANUFACTURER_DESCR] = &MD, + [iPRODUCT_DESCR] = &PD, + [iSERIAL_DESCR] = &SD, + [iINTERFACE_DESCR] = &ID +}; + /* * default handlers @@ -171,7 +181,7 @@ void WEAK vendor_handler(config_pack_t *packet){ } static void wr0(const uint8_t *buf, uint16_t size){ - if(setup_packet.wLength < size) size = setup_packet.wLength; // shortened request + if(setup_packet->wLength < size) size = setup_packet->wLength; // shortened request if(size < endpoints[0].txbufsz){ EP_WriteIRQ(0, buf, size); return; @@ -199,24 +209,18 @@ static void wr0(const uint8_t *buf, uint16_t size){ } static inline void get_descriptor(){ - switch(setup_packet.wValue){ + uint8_t descrtype = setup_packet->wValue >> 8, + descridx = setup_packet->wValue & 0xff; + switch(descrtype){ case DEVICE_DESCRIPTOR: wr0(USB_DeviceDescriptor, sizeof(USB_DeviceDescriptor)); break; case CONFIGURATION_DESCRIPTOR: wr0(USB_ConfigDescriptor, sizeof(USB_ConfigDescriptor)); break; - case STRING_LANG_DESCRIPTOR: - wr0((const uint8_t *)&USB_StringLangDescriptor, STRING_LANG_DESCRIPTOR_SIZE_BYTE); - break; - case STRING_MAN_DESCRIPTOR: - wr0((const uint8_t *)&USB_StringManufacturingDescriptor, USB_StringManufacturingDescriptor.bLength); - break; - case STRING_PROD_DESCRIPTOR: - wr0((const uint8_t *)&USB_StringProdDescriptor, USB_StringProdDescriptor.bLength); - break; - case STRING_SN_DESCRIPTOR: - wr0((const uint8_t *)&USB_StringSerialDescriptor, USB_StringSerialDescriptor.bLength); + case STRING_DESCRIPTOR: + if(descridx < iDESCR_AMOUNT) wr0((const uint8_t *)StringDescriptor[descridx], *((uint8_t*)StringDescriptor[descridx])); + else EP_WriteIRQ(0, (uint8_t*)0, 0); break; case DEVICE_QUALIFIER_DESCRIPTOR: wr0(USB_DeviceQualifierDescriptor, USB_DeviceQualifierDescriptor[0]); @@ -229,7 +233,7 @@ static inline void get_descriptor(){ static uint16_t configuration = 0; // reply for GET_CONFIGURATION (==1 if configured) static inline void std_d2h_req(){ uint16_t status = 0; // bus powered - switch(setup_packet.bRequest){ + switch(setup_packet->bRequest){ case GET_DESCRIPTOR: get_descriptor(); break; @@ -244,16 +248,61 @@ static inline void std_d2h_req(){ } } +// interrupt IN handler (never used?) +static void EP1_Handler(){ + uint16_t epstatus = KEEP_DTOG(USB->EPnR[1]); + if(RX_FLAG(epstatus)) epstatus = (epstatus & ~USB_EPnR_STAT_TX) ^ USB_EPnR_STAT_RX; // set valid RX + else epstatus = epstatus & ~(USB_EPnR_STAT_TX|USB_EPnR_STAT_RX); + // clear CTR + epstatus = (epstatus & ~(USB_EPnR_CTR_RX|USB_EPnR_CTR_TX)); + USB->EPnR[1] = epstatus; +} + +// data IN/OUT handlers +static void transmit_Handler(){ // EP3IN + uint16_t epstatus = KEEP_DTOG_STAT(USB->EPnR[3]); + // clear CTR keep DTOGs & STATs + USB->EPnR[3] = (epstatus & ~(USB_EPnR_CTR_TX)); // clear TX ctr + send_next(); +} + +static uint8_t rcvbuf[USB_RXBUFSZ]; +static uint8_t volatile rcvbuflen = 0; + +void chkin(){ + if(bufovrfl) return; + if(!rcvbuflen) return; + int w = RB_write((ringbuffer*)&rbin, rcvbuf, rcvbuflen); + if(w < 0) return; + if(w != rcvbuflen) bufovrfl = 1; + rcvbuflen = 0; + uint16_t status = KEEP_DTOG(USB->EPnR[2]); // don't change DTOG + USB->EPnR[2] = status ^ USB_EPnR_STAT_RX; +} + +// receiver reads data from local buffer and only then ACK'ed +static void receive_Handler(){ // EP2OUT + uint16_t status = KEEP_DTOG_STAT(USB->EPnR[2]); // don't change DTOG and NACK + if(rcvbuflen){ + bufovrfl = 1; // lost last data + rcvbuflen = 0; + } + rcvbuflen = EP_Read(2, (uint8_t*)rcvbuf); + USB->EPnR[2] = status & ~USB_EPnR_CTR_RX; +} + static inline void std_h2d_req(){ - switch(setup_packet.bRequest){ + switch(setup_packet->bRequest){ case SET_ADDRESS: // new address will be assigned later - after acknowlegement or request to host - USB_Dev.USB_Addr = setup_packet.wValue; + USB_Addr = setup_packet->wValue; break; case SET_CONFIGURATION: // Now device configured - USB_Dev.USB_Status = USB_STATE_CONFIGURED; - configuration = setup_packet.wValue; + configuration = setup_packet->wValue; + EP_Init(1, EP_TYPE_INTERRUPT, USB_EP1BUFSZ, 0, EP1_Handler); // IN1 - transmit + EP_Init(2, EP_TYPE_BULK, 0, USB_RXBUFSZ, receive_Handler); // OUT2 - receive data + EP_Init(3, EP_TYPE_BULK, USB_TXBUFSZ, 0, transmit_Handler); // IN3 - transmit data break; default: break; @@ -271,8 +320,8 @@ bmRequestType: 76543210 */ void EP0_Handler(){ uint16_t epstatus = USB->EPnR[0]; // EP0R on input -> return this value after modifications - uint8_t reqtype = setup_packet.bmRequestType & 0x7f; - uint8_t dev2host = (setup_packet.bmRequestType & 0x80) ? 1 : 0; + uint8_t reqtype = setup_packet->bmRequestType & 0x7f; + uint8_t dev2host = (setup_packet->bmRequestType & 0x80) ? 1 : 0; int rxflag = RX_FLAG(epstatus); if(rxflag && SETUP_FLAG(epstatus)){ switch(reqtype){ @@ -285,15 +334,15 @@ void EP0_Handler(){ } break; case STANDARD_ENDPOINT_REQUEST_TYPE: // standard endpoint request - if(setup_packet.bRequest == CLEAR_FEATURE){ + if(setup_packet->bRequest == CLEAR_FEATURE){ EP_WriteIRQ(0, (uint8_t *)0, 0); } break; case VENDOR_REQUEST_TYPE: - vendor_handler(&setup_packet); + vendor_handler(setup_packet); break; case CONTROL_REQUEST_TYPE: - switch(setup_packet.bRequest){ + switch(setup_packet->bRequest){ case GET_LINE_CODING: EP_WriteIRQ(0, (uint8_t*)&lineCoding, sizeof(lineCoding)); break; @@ -301,7 +350,7 @@ void EP0_Handler(){ break; case SET_CONTROL_LINE_STATE: usbON = 1; - clstate_handler(setup_packet.wValue); + clstate_handler(setup_packet->wValue); break; case SEND_BREAK: usbON = 0; @@ -310,23 +359,22 @@ void EP0_Handler(){ default: break; } - if(setup_packet.bRequest != GET_LINE_CODING) EP_WriteIRQ(0, (uint8_t *)0, 0); // write acknowledgement + if(setup_packet->bRequest != GET_LINE_CODING) EP_WriteIRQ(0, (uint8_t *)0, 0); // write acknowledgement break; default: EP_WriteIRQ(0, (uint8_t *)0, 0); } }else if(rxflag){ // got data over EP0 or host acknowlegement if(endpoints[0].rx_cnt){ - if(setup_packet.bRequest == SET_LINE_CODING){ + if(setup_packet->bRequest == SET_LINE_CODING){ linecoding_handler((usb_LineCoding*)ep0databuf); } } } else if(TX_FLAG(epstatus)){ // package transmitted // now we can change address after enumeration - if ((USB->DADDR & USB_DADDR_ADD) != USB_Dev.USB_Addr){ - USB->DADDR = USB_DADDR_EF | USB_Dev.USB_Addr; - // change state to ADRESSED - USB_Dev.USB_Status = USB_STATE_ADDRESSED; + if ((USB->DADDR & USB_DADDR_ADD) != USB_Addr){ + USB->DADDR = USB_DADDR_EF | USB_Addr; + usbON = 0; } } epstatus = KEEP_DTOG(USB->EPnR[0]); diff --git a/F3:F303/Multistepper/usb_lib.h b/F3:F303/Multistepper/usb_lib.h index ddfc8b2..f35917d 100644 --- a/F3:F303/Multistepper/usb_lib.h +++ b/F3:F303/Multistepper/usb_lib.h @@ -1,6 +1,6 @@ /* * This file is part of the multistepper 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 @@ -60,14 +60,21 @@ #define CONTROL_DTR 0x01 #define CONTROL_RTS 0x02 -// wValue -#define DEVICE_DESCRIPTOR 0x100 -#define CONFIGURATION_DESCRIPTOR 0x200 -#define STRING_LANG_DESCRIPTOR 0x300 -#define STRING_MAN_DESCRIPTOR 0x301 -#define STRING_PROD_DESCRIPTOR 0x302 -#define STRING_SN_DESCRIPTOR 0x303 -#define DEVICE_QUALIFIER_DESCRIPTOR 0x600 +// string descriptors +enum{ + iLANGUAGE_DESCR, + iMANUFACTURER_DESCR, + iPRODUCT_DESCR, + iSERIAL_DESCR, + iINTERFACE_DESCR, + iDESCR_AMOUNT +}; + +// Types of descriptors +#define DEVICE_DESCRIPTOR 0x01 +#define CONFIGURATION_DESCRIPTOR 0x02 +#define STRING_DESCRIPTOR 0x03 +#define DEVICE_QUALIFIER_DESCRIPTOR 0x06 #define RX_FLAG(epstat) (epstat & USB_EPnR_CTR_RX) #define TX_FLAG(epstat) (epstat & USB_EPnR_CTR_TX) @@ -77,14 +84,6 @@ #define KEEP_DTOG_STAT(EPnR) (EPnR & ~(USB_EPnR_STAT_RX|USB_EPnR_STAT_TX|USB_EPnR_DTOG_RX|USB_EPnR_DTOG_TX)) #define KEEP_DTOG(EPnR) (EPnR & ~(USB_EPnR_DTOG_RX|USB_EPnR_DTOG_TX)) -// USB state: uninitialized, addressed, ready for use -typedef enum{ - USB_STATE_DEFAULT, - USB_STATE_ADDRESSED, - USB_STATE_CONFIGURED, - USB_STATE_CONNECTED -} USB_state; - // EP types #define EP_TYPE_BULK 0x00 #define EP_TYPE_CONTROL 0x01 @@ -93,6 +92,16 @@ typedef enum{ #define LANG_US (uint16_t)0x0409 +#if 0 +typedef struct{ + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t *bString; +} string_descriptor_t; + +#define _USB_STRING_(name, str) string_descriptor_t name = {(sizeof(str) + 2), STRING_DESCRIPTOR, str} +#endif + #define _USB_STRING_(name, str) \ static const struct name \ { \ @@ -113,7 +122,6 @@ static const struct name \ \ } \ name = {0x04, 0x03, lng_id} -#define STRING_LANG_DESCRIPTOR_SIZE_BYTE (4) // EP0 configuration packet typedef struct { @@ -133,12 +141,6 @@ typedef struct{ unsigned rx_cnt : 10; // received data counter } ep_t; -// USB status & its address -typedef struct { - uint8_t USB_Status; - uint16_t USB_Addr; -}usb_dev_t; - typedef struct { uint32_t dwDTERate; uint8_t bCharFormat; @@ -163,10 +165,9 @@ typedef struct { } __attribute__ ((packed)) usb_cdc_notification; extern ep_t endpoints[]; -extern usb_dev_t USB_Dev; extern volatile uint8_t usbON; -extern config_pack_t setup_packet; -extern uint8_t ep0databuf[]; +extern config_pack_t *setup_packet; +extern uint8_t ep0databuf[], setupdatabuf[]; void EP0_Handler(); @@ -179,3 +180,4 @@ void linecoding_handler(usb_LineCoding *lc); void clstate_handler(uint16_t val); void break_handler(); void vendor_handler(config_pack_t *packet); +void chkin(); diff --git a/F3:F303/Multistepper/usbhw.c b/F3:F303/Multistepper/usbhw.c index 33a3432..9f6dc67 100644 --- a/F3:F303/Multistepper/usbhw.c +++ b/F3:F303/Multistepper/usbhw.c @@ -1,6 +1,6 @@ /* * This file is part of the multistepper 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 @@ -50,10 +50,10 @@ static uint16_t lastaddr = LASTADDR_DEFAULT; int EP_Init(uint8_t number, uint8_t type, uint16_t txsz, uint16_t rxsz, void (*func)(ep_t ep)){ if(number >= STM32ENDPOINTS) return 4; // out of configured amount if(txsz > USB_BTABLE_SIZE || rxsz > USB_BTABLE_SIZE) return 1; // buffer too large - if(lastaddr + txsz + rxsz >= USB_BTABLE_SIZE) return 2; // out of btable + if(lastaddr + txsz + rxsz >= USB_BTABLE_SIZE/ACCESSZ) return 2; // out of btable USB->EPnR[number] = (type << 9) | (number & USB_EPnR_EA); USB->EPnR[number] ^= USB_EPnR_STAT_RX | USB_EPnR_STAT_TX_1; - if(rxsz & 1 || rxsz > 512) return 3; // wrong rx buffer size + if(rxsz & 1 || rxsz > USB_BTABLE_SIZE) return 3; // wrong rx buffer size uint16_t countrx = 0; if(rxsz < 64) countrx = rxsz / 2; else{ @@ -84,7 +84,6 @@ void usb_lp_isr(){ lastaddr = LASTADDR_DEFAULT; // clear address, leave only enable bit USB->DADDR = USB_DADDR_EF; - USB_Dev.USB_Status = USB_STATE_DEFAULT; if(EP_Init(0, EP_TYPE_CONTROL, USB_EP0_BUFSZ, USB_EP0_BUFSZ, EP0_Handler)){ return; } @@ -101,10 +100,10 @@ void usb_lp_isr(){ if(USB->ISTR & USB_ISTR_DIR){ // OUT interrupt - receive data, CTR_RX==1 (if CTR_TX == 1 - two pending transactions: receive following by transmit) if(n == 0){ // control endpoint if(epstatus & USB_EPnR_SETUP){ // setup packet -> copy data to conf_pack - EP_Read(0, (uint8_t*)&setup_packet); + EP_Read(0, setupdatabuf); // interrupt handler will be called later }else if(epstatus & USB_EPnR_CTR_RX){ // data packet -> push received data to ep0databuf - EP_Read(0, (uint8_t*)&ep0databuf); + EP_Read(0, ep0databuf); } } } diff --git a/F3:F303/Multistepper/usbhw.h b/F3:F303/Multistepper/usbhw.h index ca6fd25..5915191 100644 --- a/F3:F303/Multistepper/usbhw.h +++ b/F3:F303/Multistepper/usbhw.h @@ -1,6 +1,6 @@ /* * This file is part of the multistepper 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 diff --git a/F3:F303/Multistepper/version.inc b/F3:F303/Multistepper/version.inc index d05e2b0..16d71ff 100644 --- a/F3:F303/Multistepper/version.inc +++ b/F3:F303/Multistepper/version.inc @@ -1,2 +1,2 @@ -#define BUILD_NUMBER "119" -#define BUILD_DATE "2024-08-16" +#define BUILD_NUMBER "160" +#define BUILD_DATE "2024-08-26"