From a19a0954d75b7184758f64a92338336c98ec6458 Mon Sep 17 00:00:00 2001 From: Antonino Di Guardo <64427768+digant73@users.noreply.github.com> Date: Tue, 31 Oct 2023 05:20:12 +0100 Subject: [PATCH] Added TX Interrupt/DMA based serial writing (#2840) --- TFT/src/User/API/Notification.c | 2 +- TFT/src/User/API/Printing.c | 7 +- TFT/src/User/API/RRFSendCmd.c | 64 +---- TFT/src/User/API/SerialConnection.c | 126 ++++++--- TFT/src/User/API/SerialConnection.h | 24 +- TFT/src/User/API/Settings.h | 4 +- TFT/src/User/API/interfaceCmd.c | 2 + TFT/src/User/API/menu.c | 21 +- TFT/src/User/API/parseACK.c | 23 +- TFT/src/User/Configuration.h | 18 +- TFT/src/User/Hal/gd32f20x/Serial.c | 355 +++++++++++++++++------ TFT/src/User/Hal/gd32f20x/Serial.h | 58 +++- TFT/src/User/Hal/gd32f20x/uart.c | 15 +- TFT/src/User/Hal/gd32f20x/uart.h | 3 +- TFT/src/User/Hal/stm32f10x/Serial.c | 299 ++++++++++++++++---- TFT/src/User/Hal/stm32f10x/Serial.h | 58 +++- TFT/src/User/Hal/stm32f10x/uart.c | 17 +- TFT/src/User/Hal/stm32f10x/uart.h | 5 +- TFT/src/User/Hal/stm32f2_f4xx/Serial.c | 372 +++++++++++++++++++------ TFT/src/User/Hal/stm32f2_f4xx/Serial.h | 58 +++- TFT/src/User/Hal/stm32f2_f4xx/uart.c | 11 +- TFT/src/User/Hal/stm32f2_f4xx/uart.h | 3 +- TFT/src/User/Menu/Monitoring.c | 12 +- TFT/src/User/Menu/Monitoring.h | 35 ++- TFT/src/User/Menu/Popup.c | 3 +- TFT/src/User/config.ini | 10 +- TFT/src/User/config_rrf.ini | 10 +- TFT/src/User/main.c | 8 +- TFT/src/User/main.h | 6 +- TFT/src/User/os_timer.c | 6 +- 30 files changed, 1208 insertions(+), 427 deletions(-) diff --git a/TFT/src/User/API/Notification.c b/TFT/src/User/API/Notification.c index dededf3055..cdd5577ff7 100644 --- a/TFT/src/User/API/Notification.c +++ b/TFT/src/User/API/Notification.c @@ -145,7 +145,7 @@ void addNotification(DIALOG_TYPE style, char *title, char *text, bool ShowDialog } // store message - msglist[nextMsgIndex].style = style; + msglist[nextMsgIndex].style = style; strncpy_no_pad(msglist[nextMsgIndex].text, text, MAX_MSG_LENGTH); strncpy_no_pad(msglist[nextMsgIndex].title, title, MAX_MSG_TITLE_LENGTH); diff --git a/TFT/src/User/API/Printing.c b/TFT/src/User/API/Printing.c index e7506ec2f7..78b6906ef5 100644 --- a/TFT/src/User/API/Printing.c +++ b/TFT/src/User/API/Printing.c @@ -110,7 +110,7 @@ void loopBreakToCondition(CONDITION_CALLBACK condCallback) uint16_t rIndex; TASK_LOOP_WHILE(condCallback(), - if ((rIndex = Serial_GetReadingIndex(SERIAL_PORT)) != rIndex_old) + if ((rIndex = Serial_GetReadingIndexRX(SERIAL_PORT)) != rIndex_old) { sendEmergencyCmd("M108\n"); rIndex_old = rIndex; @@ -310,7 +310,7 @@ void initPrintSummary(void) infoPrintSummary = (PRINT_SUMMARY){.name[0] = '\0', 0, 0, 0, 0, false}; // save print filename (short or long filename) - sprintf(infoPrintSummary.name, "%." STRINGIFY(SUMMARY_NAME_LEN) "s", getPrintFilename()); + strncpy_no_pad(infoPrintSummary.name, getPrintFilename(), SUMMARY_NAME_LEN); } void preparePrintSummary(void) @@ -903,6 +903,9 @@ void loopPrintFromTFT(void) } } } + + if (comment_parsing) // parse comment from gcode file + parseComment(); } if (gcode_count == 0) diff --git a/TFT/src/User/API/RRFSendCmd.c b/TFT/src/User/API/RRFSendCmd.c index e753cdcae7..b0ed58a8cd 100644 --- a/TFT/src/User/API/RRFSendCmd.c +++ b/TFT/src/User/API/RRFSendCmd.c @@ -1,58 +1,24 @@ -#include "RRFSendCmd.h" -#include "Serial.h" -#include +#include "includes.h" -static uint8_t n_sent = 0; static uint32_t line_number = 0; -static uint8_t checksum = 0; -void sendCharAndChecksum(const char c) +void rrfSendCmd(const char * cmd_ptr) { - checksum ^= c; - Serial_PutChar(SERIAL_PORT, c); - n_sent++; -} + char rrfCmd[CMD_MAX_SIZE]; + char * rrfCmd_ptr = rrfCmd; + uint8_t checksum = 0; -void sendChar(const char c) -{ - if (c == '\n') - { - if (n_sent != 0) - { - Serial_PutChar(SERIAL_PORT, '*'); - char digit0 = checksum % 10 + '0'; - checksum /= 10; - char digit1 = checksum % 10 + '0'; - checksum /= 10; - if (checksum != 0) - { - Serial_PutChar(SERIAL_PORT, checksum + '0'); - } - Serial_PutChar(SERIAL_PORT, digit1); - Serial_PutChar(SERIAL_PORT, digit0); - } - Serial_PutChar(SERIAL_PORT, c); - n_sent = 0; - } - else - { - if (n_sent == 0) - { - char number[11]; - checksum = 0; - sendCharAndChecksum('N'); - snprintf(number, 11, "%lu", line_number++); - rrfSendCmd(number); - sendCharAndChecksum(' '); - } - sendCharAndChecksum(c); - } -} + sprintf(rrfCmd, "N%lu %s", line_number++, cmd_ptr); -void rrfSendCmd(const char* cmd_ptr) -{ - while (*cmd_ptr != 0) + // calculate checksum + while (*rrfCmd_ptr != '\n') { - sendChar(*cmd_ptr++); + checksum ^= *rrfCmd_ptr++; } + + // add checksum and finalize formatting the RRF command + sprintf(rrfCmd_ptr, "*%u\n", checksum); + + // send the command to the serial port + Serial_Put(SERIAL_PORT, rrfCmd); } diff --git a/TFT/src/User/API/SerialConnection.c b/TFT/src/User/API/SerialConnection.c index 558548f9bc..532254420e 100644 --- a/TFT/src/User/API/SerialConnection.c +++ b/TFT/src/User/API/SerialConnection.c @@ -1,32 +1,39 @@ #include "SerialConnection.h" #include "includes.h" -#define SERIAL_PORT_QUEUE_SIZE NOBEYOND(512, RAM_SIZE * 64, 4096) -#define SERIAL_PORT_2_QUEUE_SIZE 512 -#define SERIAL_PORT_3_QUEUE_SIZE 512 -#define SERIAL_PORT_4_QUEUE_SIZE 512 +#define SERIAL_PORT_RX_QUEUE_SIZE NOBEYOND(256, RAM_SIZE * 64, 4096) // ACK messages reading queue from mainboard +#define SERIAL_PORT_2_RX_QUEUE_SIZE 512 +#define SERIAL_PORT_3_RX_QUEUE_SIZE 512 +#define SERIAL_PORT_4_RX_QUEUE_SIZE 512 + +#define SERIAL_PORT_TX_QUEUE_SIZE 256 // gcodes writing queue to mainboard +#define SERIAL_PORT_2_TX_QUEUE_SIZE 256 +#define SERIAL_PORT_3_TX_QUEUE_SIZE 256 +#define SERIAL_PORT_4_TX_QUEUE_SIZE 256 const SERIAL_PORT_INFO serialPort[SERIAL_PORT_COUNT] = { - {SERIAL_PORT, SERIAL_PORT_QUEUE_SIZE, "", "1 - Printer"}, + {SERIAL_PORT , SERIAL_PORT_RX_QUEUE_SIZE , SERIAL_PORT_TX_QUEUE_SIZE , "" , "1 - Printer"}, #ifdef SERIAL_PORT_2 - {SERIAL_PORT_2, SERIAL_PORT_2_QUEUE_SIZE, "2", "2 - WIFI"}, + {SERIAL_PORT_2, SERIAL_PORT_2_RX_QUEUE_SIZE, SERIAL_PORT_2_TX_QUEUE_SIZE, "2", "2 - WiFi"}, #endif #ifdef SERIAL_PORT_3 - {SERIAL_PORT_3, SERIAL_PORT_3_QUEUE_SIZE, "3", "3 - UART3"}, + {SERIAL_PORT_3, SERIAL_PORT_3_RX_QUEUE_SIZE, SERIAL_PORT_3_TX_QUEUE_SIZE, "3", "3 - UART3"}, #endif #ifdef SERIAL_PORT_4 - {SERIAL_PORT_4, SERIAL_PORT_4_QUEUE_SIZE, "4", "4 - UART4"} + {SERIAL_PORT_4, SERIAL_PORT_4_RX_QUEUE_SIZE, SERIAL_PORT_4_TX_QUEUE_SIZE, "4", "4 - UART4"} #endif }; -const uint32_t baudrateValues[BAUDRATE_COUNT] = { 0, 2400, 9600, 19200, 38400, 57600, 115200, 250000, 500000, 1000000}; -const char * const baudrateNames[BAUDRATE_COUNT] = {"OFF", "2400", "9600", "19200", "38400", "57600", "115200", "250000", "500000", "1000000"}; +const uint32_t baudrateValues[BAUDRATE_COUNT] = { + 0, 2400, 9600, 19200, 38400, 57600, 115200, 230400, 250000, 500000, 921600, 1000000}; +const char * const baudrateNames[BAUDRATE_COUNT] = { + "OFF", "2400", "9600", "19200", "38400", "57600", "115200", "230400", "250000", "500000", "921600", "1000000"}; static inline void Serial_InitPrimary(void) { InfoHost_Init(false); // initialize infoHost when disconnected - Serial_Config(serialPort[PORT_1].port, serialPort[PORT_1].cacheSize, baudrateValues[infoSettings.serial_port[PORT_1]]); + Serial_Config(serialPort[PORT_1].port, serialPort[PORT_1].cacheSizeRX, serialPort[PORT_1].cacheSizeTX, baudrateValues[infoSettings.serial_port[PORT_1]]); } static inline void Serial_DeInitPrimary(void) @@ -53,7 +60,7 @@ void Serial_Init(SERIAL_PORT_INDEX portIndex) // Disable the serial port when it is not in use and/or not connected to a device (floating) to // avoid to receive and process wrong data due to possible electromagnetic interference (EMI). if (infoSettings.serial_port[portIndex] > 0) // if serial port is enabled - Serial_Config(serialPort[portIndex].port, serialPort[portIndex].cacheSize, + Serial_Config(serialPort[portIndex].port, serialPort[portIndex].cacheSizeRX, serialPort[portIndex].cacheSizeTX, baudrateValues[infoSettings.serial_port[portIndex]]); } } @@ -64,7 +71,7 @@ void Serial_Init(SERIAL_PORT_INDEX portIndex) { if (infoSettings.serial_port[portIndex] > 0) // if serial port is enabled { - Serial_Config(serialPort[portIndex].port, serialPort[portIndex].cacheSize, + Serial_Config(serialPort[portIndex].port, serialPort[portIndex].cacheSizeRX, serialPort[portIndex].cacheSizeTX, baudrateValues[infoSettings.serial_port[portIndex]]); } } @@ -133,54 +140,89 @@ void Serial_Forward(SERIAL_PORT_INDEX portIndex, const char * msg) } } -uint16_t Serial_GetReadingIndex(SERIAL_PORT_INDEX portIndex) +bool Serial_NewDataAvailable(uint8_t port) { - if (!WITHIN(portIndex, PORT_1, SERIAL_PORT_COUNT - 1)) + // NOTE: used 32 bit variables for performance reasons + + // wIndex: update L1 cache's writing index (dynamically changed (by L1 cache's interrupt handler) variables/attributes) + // and make a static access (32 bit) to it to speedup performance on this function + // + uint32_t wIndex = dmaL1DataRX[port].wIndex = Serial_GetWritingIndexRX(port); // get the latest wIndex + uint32_t flag = dmaL1DataRX[port].flag; // get the current flag position + + if (flag == wIndex) // if no data to read from L1 cache, nothing to do return 0; - return dmaL1Data[portIndex].rIndex; + uint32_t cacheSize = dmaL1DataRX[port].cacheSize; + + while (dmaL1DataRX[port].cache[flag] != '\n' && flag != wIndex) // check presence of "\n" in available data + { + flag = (flag + 1) % cacheSize; + } + + dmaL1DataRX[port].flag = flag; // update queue's custom flag with flag + + // return "true" if "\n" was found (message complete), "False" otherwise + return (flag != wIndex); } -uint16_t Serial_Get(SERIAL_PORT_INDEX portIndex, char * buf, uint16_t bufSize) +uint16_t Serial_Get(uint8_t port, char * buf, uint16_t bufSize) { - // if port index is out of range or no data to read from L1 cache - if (!WITHIN(portIndex, PORT_1, SERIAL_PORT_COUNT - 1) || dmaL1Data[portIndex].flag == dmaL1Data[portIndex].wIndex) - return 0; + // NOTE: used 32 bit variables for performance reasons (in particular for data copy) + + // rIndex: L1 cache's reading index (not dynamically changed (by L1 cache's interrupt handler) variables/attributes) + // + DMA_CIRCULAR_BUFFER * dmaL1Data_ptr = &dmaL1DataRX[port]; + char * cache = dmaL1Data_ptr->cache; + uint32_t cacheSize = dmaL1Data_ptr->cacheSize; + uint32_t rIndex = dmaL1Data_ptr->rIndex; + uint32_t flag = dmaL1Data_ptr->flag; - DMA_CIRCULAR_BUFFER * dmaL1Data_ptr = &dmaL1Data[portIndex]; + while (cache[rIndex] == ' ' && rIndex != flag) // remove leading empty space, if any + { + rIndex = (rIndex + 1) % cacheSize; + } - // make a static access to dynamically changed (by L1 cache's interrupt handler) variables/attributes - uint16_t wIndex = dmaL1Data_ptr->wIndex; + // msgSize: message size (after updating rIndex removing leading empty spaces). Terminating null character '\0' not included + uint32_t msgSize = (cacheSize + flag - rIndex) % cacheSize + 1; - // L1 cache's reading index (not dynamically changed (by L1 cache's interrupt handler) variables/attributes) - uint16_t rIndex = dmaL1Data_ptr->rIndex; + // if buf size is not enough to store the data plus the terminating null character "\0", skip the data copy + // + // NOTE: the following check should never be matched if buf has a proper size and there is no reading error. + // If so, the check could be commented out just to improve performance. Just keep it to make the code more robust + // + if (bufSize < (msgSize + 1)) // +1 is for the terminating null character '\0' + return 0; - while (dmaL1Data_ptr->cache[rIndex] == ' ' && rIndex != wIndex) // remove leading empty space, if any + // if data is one chunk only, retrieve data from upper part of circular cache + if (rIndex <= flag) { - rIndex = (rIndex + 1) % dmaL1Data_ptr->cacheSize; + while (rIndex <= flag) + { + *(buf++) = cache[rIndex++]; + } } - - for (uint16_t i = 0; i < (bufSize - 1) && rIndex != wIndex; ) // retrieve data until buf is full or L1 cache is empty + else // data at end and beginning of cache { - buf[i] = dmaL1Data_ptr->cache[rIndex]; - rIndex = (rIndex + 1) % dmaL1Data_ptr->cacheSize; - - if (buf[i++] == '\n') // if data end marker is found + while (rIndex < cacheSize) { - buf[i] = '\0'; // end character - dmaL1Data_ptr->flag = dmaL1Data_ptr->rIndex = rIndex; // update queue's custom flag and reading index with rIndex + *(buf++) = cache[rIndex++]; + } - return i; // return the number of bytes stored in buf + rIndex = 0; + + while (rIndex <= flag) + { + *(buf++) = cache[rIndex++]; } } - // if here, a partial message is present on the L1 cache (message not terminated by "\n"). - // We temporary skip the message until it is fully received updating also dmaL1Data_ptr->flag to - // prevent to read again (multiple times) the same partial message on next function invokation + *buf = '\0'; // end character - dmaL1Data_ptr->flag = wIndex; // update queue's custom flag with wIndex + // update queue's custom flag and reading index with next index + dmaL1Data_ptr->flag = dmaL1Data_ptr->rIndex = (flag + 1) % cacheSize; - return 0; // return the number of bytes stored in buf + return msgSize; // return the number of bytes stored in buf } #ifdef SERIAL_PORT_2 @@ -199,7 +241,7 @@ void Serial_GetFromUART(void) #endif ) { - while (Serial_Get(serialPort[portIndex].port, cmd, CMD_MAX_SIZE) != 0) // if some data have been retrieved + while (Serial_NewDataAvailable(serialPort[portIndex].port) && Serial_Get(serialPort[portIndex].port, cmd, CMD_MAX_SIZE) != 0) { handleCmd(cmd, portIndex); } diff --git a/TFT/src/User/API/SerialConnection.h b/TFT/src/User/API/SerialConnection.h index 820a0a198e..5c71575613 100644 --- a/TFT/src/User/API/SerialConnection.h +++ b/TFT/src/User/API/SerialConnection.h @@ -10,7 +10,7 @@ extern "C" { #include "variants.h" // for SERIAL_PORT_2 etc. #include "uart.h" // for _UART_CNT etc. -#define BAUDRATE_COUNT 10 +#define BAUDRATE_COUNT 12 typedef enum { @@ -27,12 +27,13 @@ typedef enum PORT_4, #endif SERIAL_PORT_COUNT -} SERIAL_PORT_INDEX; // serial port index for all enabled serial ports (This is not actual physical port number) +} SERIAL_PORT_INDEX; // serial port index for all enabled serial ports (this is not the actual physical serial port number) typedef struct { uint8_t port; // physical port (e.g. _USART1) related to serial port (e.g. 0 for SERIAL_PORT, 1 for SERIAL_PORT_2 etc...) - uint16_t cacheSize; // queue size for sending/receiving data to/from the port + uint16_t cacheSizeRX; // buffer size for receiving data from the serial port + uint16_t cacheSizeTX; // buffer size for sending data to the serial port const char * const id; // serial port ID (e.g. "" for SERIAL_PORT, "2" for SERIAL_PORT_2 etc...) const char * const desc; // serial port description (e.g. "1 - Printer" for SERIAL_PORT, "2 - WIFI" for SERIAL_PORT_2 etc...) } SERIAL_PORT_INFO; // serial port info @@ -55,26 +56,27 @@ void Serial_Init(SERIAL_PORT_INDEX portIndex); // - specific port index: specific serial port void Serial_DeInit(SERIAL_PORT_INDEX portIndex); -// forward a message to the provided serial port/s, if enabled: +// forward a zero terminated message to the provided serial port/s, if enabled: // - portIndex: // - ALL_PORTS: all serial ports (primary and supplementary) // - SUP_PORTS: all supplementary serial ports // - specific port index: specific serial port +// - msg: message to send void Serial_Forward(SERIAL_PORT_INDEX portIndex, const char * msg); -// retrieve the next reading index in the message queue of the provided serial port: -// - portIndex: index of serial port +// test if a new message is available in the message queue of the provided physical serial port: +// - port: physical serial port where data availability is tested // -// - return value: next reading index -uint16_t Serial_GetReadingIndex(SERIAL_PORT_INDEX portIndex); +// - return value: "true" if a new message is available. "false" otherwise +bool Serial_NewDataAvailable(uint8_t port); -// retrieve a message from the provided serial port, if any: -// - portIndex: index of serial port where data are read from +// retrieve a message from the provided physical serial port: +// - port: physical serial port where data are read from // - buf: buffer where data are stored // - bufSize: size of buffer (max number of bytes that can be stored in buf) // // - return value: number of bytes stored in buf -uint16_t Serial_Get(SERIAL_PORT_INDEX portIndex, char * buf, uint16_t bufSize); +uint16_t Serial_Get(uint8_t port, char * buf, uint16_t bufSize); #ifdef SERIAL_PORT_2 // retrieve messages from all the enabled supplementary ports storing them diff --git a/TFT/src/User/API/Settings.h b/TFT/src/User/API/Settings.h index 499116f564..2dfa637a96 100644 --- a/TFT/src/User/API/Settings.h +++ b/TFT/src/User/API/Settings.h @@ -14,10 +14,10 @@ extern "C" { // Config version support (format YYYYMMDD) // change if new elements/keywords are added/removed/changed in the Configuration.h // this number should match CONFIG_VERSION in Configuration.h -#define CONFIG_SUPPPORT 20230821 +#define CONFIG_SUPPPORT 20230929 #define FONT_FLASH_SIGN 20230821 // (YYYYMMDD) change if fonts require updating -#define CONFIG_FLASH_SIGN 20230821 // (YYYYMMDD) change if any keyword(s) in config.ini is added or removed +#define CONFIG_FLASH_SIGN 20230929 // (YYYYMMDD) change if any keyword(s) in config.ini is added or removed #define LANGUAGE_FLASH_SIGN 20230821 // (YYYYMMDD) change if any keyword(s) in language pack is added or removed #define ICON_FLASH_SIGN 20230821 // (YYYYMMDD) change if any icon(s) is added or removed diff --git a/TFT/src/User/API/interfaceCmd.c b/TFT/src/User/API/interfaceCmd.c index 8173857671..d3e1cfb338 100644 --- a/TFT/src/User/API/interfaceCmd.c +++ b/TFT/src/User/API/interfaceCmd.c @@ -278,6 +278,8 @@ bool sendCmd(bool purge, bool avoidTerminal) if (!purge) // if command is not purged, send it to printer { + UPD_TX_KPIS(cmd_len); // debug monitoring KPI + if (infoMachineSettings.firmwareType != FW_REPRAPFW) Serial_Put(SERIAL_PORT, cmd_ptr); else diff --git a/TFT/src/User/API/menu.c b/TFT/src/User/API/menu.c index acfdadf89b..2727ddbca6 100644 --- a/TFT/src/User/API/menu.c +++ b/TFT/src/User/API/menu.c @@ -1218,15 +1218,19 @@ void loopBackEnd(void) // Parse the received slave response information parseACK(); - // Parse comment from gcode file - if (GET_BIT(infoSettings.general_settings, INDEX_FILE_COMMENT_PARSING) == 1) // if file comment parsing is enabled - parseComment(); - // Retrieve and store (in command queue) the gcodes received from other UART, such as ESP3D etc... #ifdef SERIAL_PORT_2 Serial_GetFromUART(); #endif + // Handle USB communication + #ifdef USB_FLASH_DRIVE_SUPPORT + USB_LoopProcess(); + #endif + + if ((bePriorityCounter++ % BE_PRIORITY_DIVIDER) != 0) // a divider value of 16 -> run 6% of the time only + return; + // Temperature monitor loopCheckHeater(); @@ -1245,11 +1249,6 @@ void loopBackEnd(void) if (infoMachineSettings.onboardSD == ENABLED) loopPrintFromOnboard(); - // Handle USB communication - #ifdef USB_FLASH_DRIVE_SUPPORT - USB_LoopProcess(); - #endif - // Check filament runout status #ifdef FIL_RUNOUT_PIN FIL_BE_CheckRunout(); @@ -1323,6 +1322,10 @@ void loopFrontEnd(void) void loopProcess(void) { loopBackEnd(); + + if ((fePriorityCounter++ % FE_PRIORITY_DIVIDER) != 0) // a divider value of 16 -> run 6% of the time only + return; + loopFrontEnd(); } diff --git a/TFT/src/User/API/parseACK.c b/TFT/src/User/API/parseACK.c index 40e3329f32..607d539cf1 100644 --- a/TFT/src/User/API/parseACK.c +++ b/TFT/src/User/API/parseACK.c @@ -43,10 +43,11 @@ const char magic_echo[] = "echo:"; const char magic_warning[] = "Warning:"; // RRF warning const char magic_message[] = "message"; // RRF message in Json format -#define ACK_CACHE_SIZE 512 // including ending character '\0' +// size of buffer where read ACK messages are stored (including terminating null character '\0') +#define ACK_CACHE_SIZE 512 char ack_cache[ACK_CACHE_SIZE]; // buffer where read ACK messages are stored -uint16_t ack_len; // length of data currently present in ack_cache +uint16_t ack_len; // length of data present in ack_cache without the terminating null character '\0' uint16_t ack_index; SERIAL_PORT_INDEX ack_port_index = PORT_1; // index of target serial port for the ACK message (related to originating gcode) bool hostDialog = false; @@ -358,8 +359,10 @@ void hostActionCommands(void) void parseACK(void) { - while ((ack_len = Serial_Get(SERIAL_PORT, ack_cache, ACK_CACHE_SIZE)) != 0) // if some data have been retrieved + while (Serial_NewDataAvailable(SERIAL_PORT) && (ack_len = Serial_Get(SERIAL_PORT, ack_cache, ACK_CACHE_SIZE)) != 0) // if some data have been retrieved { + UPD_RX_KPIS(ack_len); // debug monitoring KPI + #if defined(SERIAL_DEBUG_PORT) && defined(DEBUG_SERIAL_COMM) // dump raw serial data received to debug port Serial_Put(SERIAL_DEBUG_PORT, "<<"); @@ -487,7 +490,7 @@ void parseACK(void) requestCommandInfo.inResponse = false; } } - else if (strlen(requestCommandInfo.cmd_rev_buf) + ack_len < CMD_MAX_REV) + else if (strlen(requestCommandInfo.cmd_rev_buf) + (ack_len + 1) < CMD_MAX_REV) // +1 is for the terminating null character '\0' { strcat(requestCommandInfo.cmd_rev_buf, ack_cache); @@ -643,7 +646,7 @@ void parseACK(void) speedQuerySetWait(false); } // parse and store flow rate percentage - else if (ack_seen("Flow: ")) + else if (ack_seen("Flow:")) { speedSetCurPercent(1, ack_value()); speedQuerySetWait(false); @@ -916,15 +919,15 @@ void parseACK(void) mblUpdateStatus(true); } // parse G30, feedback to get the 4 corners Z value returned by Marlin for LevelCorner menu - else if (ack_seen("Bed X: ")) + else if (ack_seen("Bed X:")) { float x = ack_value(); float y = 0; - if (ack_continue_seen("Y: ")) + if (ack_continue_seen("Y:")) y = ack_value(); - if (ack_continue_seen("Z: ")) + if (ack_continue_seen("Z:")) levelingSetProbedPoint(x, y, ack_value()); // save probed Z value } #if DELTA_PROBE_TYPE != 0 @@ -934,10 +937,14 @@ void parseACK(void) BUZZER_PLAY(SOUND_SUCCESS); if (infoMachineSettings.EEPROM == 1) + { popupDialog(DIALOG_TYPE_SUCCESS, LABEL_DELTA_CONFIGURATION, LABEL_EEPROM_SAVE_INFO, LABEL_CONFIRM, LABEL_CANCEL, saveEepromSettings, NULL, NULL); + } else + { popupReminder(DIALOG_TYPE_SUCCESS, LABEL_DELTA_CONFIGURATION, LABEL_PROCESS_COMPLETED); + } } #endif diff --git a/TFT/src/User/Configuration.h b/TFT/src/User/Configuration.h index 679fd4772e..efc9266f7a 100644 --- a/TFT/src/User/Configuration.h +++ b/TFT/src/User/Configuration.h @@ -1,7 +1,7 @@ #ifndef _CONFIGURATION_H_ #define _CONFIGURATION_H_ -#define CONFIG_VERSION 20230821 +#define CONFIG_VERSION 20230929 //==================================================================================================== //=============================== Settings Configurable On config.ini ================================ @@ -30,11 +30,11 @@ * P2: WIFI (e.g. ESP3D) * P3: UART 3 (e.g. OctoPrint) * P4: UART 4 - * Value range: P1: [min: 1, max: 9] - * P2: [min: 0, max: 9] - * P3: [min: 0, max: 9] - * P4: [min: 0, max: 9] - * Options: [OFF (port disabled): 0, 2400: 1, 9600: 2, 19200: 3, 38400: 4, 57600: 5, 115200: 6, 250000: 7, 500000: 8, 1000000: 9] + * Value range: P1: [min: 1, max: 11] + * P2: [min: 0, max: 11] + * P3: [min: 0, max: 11] + * P4: [min: 0, max: 11] + * Options: [OFF (port disabled): 0, 2400: 1, 9600: 2, 19200: 3, 38400: 4, 57600: 5, 115200: 6, 230400: 7, 250000: 8, 500000: 9, 921600: 10, 1000000: 11] */ #define SP_1 6 // Default: 6 #define SP_2 0 // Default: 0 @@ -1117,7 +1117,7 @@ * Monitoring Debug * Uncomment/Enable to monitor/show system resources usage in Monitoring menu. */ -//#define DEBUG_MONITORING // Default: commented (disabled) +#define DEBUG_MONITORING // Default: commented (disabled) /** * Generic Debug @@ -1350,7 +1350,7 @@ * Enable alternative Move Menu Buttons Layout matching the direction * of actual printer axis Update the icons from alternate icon folder. */ -#define ALTERNATIVE_MOVE_MENU // Default: uncommented (enabled) +//#define ALTERNATIVE_MOVE_MENU // Default: uncommented (enabled) /** * Friendly Z Offset Language @@ -1431,7 +1431,7 @@ * Uncomment to enable a progress bar with 10% markers. * Comment to enable a standard progress bar. */ -//#define MARKED_PROGRESS_BAR // Default: commented (disabled) +#define MARKED_PROGRESS_BAR // Default: commented (disabled) /** * Live Text Common Color Layout (Status Screen menu) diff --git a/TFT/src/User/Hal/gd32f20x/Serial.c b/TFT/src/User/Hal/gd32f20x/Serial.c index 2b9badedcb..0a67aebc01 100644 --- a/TFT/src/User/Hal/gd32f20x/Serial.c +++ b/TFT/src/User/Hal/gd32f20x/Serial.c @@ -1,92 +1,195 @@ #include "Serial.h" -#include "includes.h" // for infoHost - -// dma rx buffer -DMA_CIRCULAR_BUFFER dmaL1Data[_UART_CNT] = {0}; - -// Config for USART Channel -//USART1 RX DMA2 Channel4 Steam2/5 -//USART2 RX DMA1 Channel4 Steam5 -//USART3 RX DMA1 Channel4 Steam1 -//UART4 RX DMA1 Channel4 Steam2 -//UART5 RX DMA1 Channel4 Steam0 -//USART6 RX DMA2 Channel5 Steam1/2 - -// Config for USART Channel -typedef struct -{ - uint32_t uart; - rcu_periph_enum dma_rcc; - uint8_t dma_channel; - uint32_t dma_stream; -} SERIAL_CFG; - -static const SERIAL_CFG Serial[_UART_CNT] = { - {USART0, RCU_DMA0, 4, DMA0}, - {USART1, RCU_DMA0, 5, DMA0}, - {USART2, RCU_DMA0, 2, DMA0}, - {UART3, RCU_DMA1, 2, DMA1}, - {UART4, RCU_DMA1, 1, DMA1}, - {USART5, RCU_DMA1, 5, DMA1}, +#include "includes.h" + +// set this line to "true" to enable IDLE Line interrupt. It is no more needed, so always set this macro to "false" +#define IDLE_LINE_IT false + +DMA_CIRCULAR_BUFFER dmaL1DataRX[_UART_CNT] = {0}; // DMA / interrupt RX buffer +DMA_CIRCULAR_BUFFER dmaL1DataTX[_UART_CNT] = {0}; // DMA / interrupt TX buffer + +// config for USART DMA channels +const SERIAL_CFG Serial[_UART_CNT] = { +#ifdef TX_DMA_WRITE +// USART RCU DMAx DMAx RX Channel TX Channel + {USART0, RCU_DMA0, DMA0, 4, 3}, + {USART1, RCU_DMA0, DMA0, 5, 6}, + {USART2, RCU_DMA0, DMA0, 2, 1}, + {UART3, RCU_DMA1, DMA1, 2, 4}, + {UART4, RCU_DMA1, DMA1, 1, 3}, + {USART5, RCU_DMA1, DMA1, 5, 6}, +#else +// USART RCU DMAx DMAx RX Channel + {USART0, RCU_DMA0, DMA0, 4}, + {USART1, RCU_DMA0, DMA0, 5}, + {USART2, RCU_DMA0, DMA0, 2}, + {UART3, RCU_DMA1, DMA1, 2}, + {UART4, RCU_DMA1, DMA1, 1}, + {USART5, RCU_DMA1, DMA1, 5}, +#endif }; -void Serial_DMAClearFlag(uint8_t port) +// disable RX DMA and clear all interrupt flags for a serial port +void Serial_DMA_DisableAndClearFlagsRX(uint8_t port) { - switch(port) + DMA_CHCTL(Serial[port].dma_stream, Serial[port].dma_channelRX) &= ~(1<<0); // disable RX DMA + + // table 12.5.2, 4 bits per channel, shift = channel * 4 + + switch (port) { - case _USART1: DMA_INTC(DMA0) = (0x0F << 12); break; - case _USART2: DMA_INTC(DMA0) = (0x0F << 20); break; - case _USART3: DMA_INTC(DMA0) = (0x0F << 8); break; - case _UART4: DMA_INTC(DMA1) = (0x0F << 8); break; - case _UART5: DMA_INTC(DMA1) = (0x0F << 4); break; - case _USART6: DMA_INTC(DMA1) = (0x0F << 20); break; + case _USART1: + DMA_INTC(DMA0) = (0x0F << 16); // DMA0 channel 4 + break; + + case _USART2: + DMA_INTC(DMA0) = (0x0F << 20); // DMA0 channel 5 + break; + + case _USART3: + DMA_INTC(DMA0) = (0x0F << 8); // DMA0 channel 2 + break; + + case _UART4: + DMA_INTC(DMA1) = (0x0F << 8); // DMA1 channel 2 + break; + + case _UART5: + DMA_INTC(DMA1) = (0x0F << 4); // DMA1 channel 1 + break; + + case _USART6: + DMA_INTC(DMA1) = (0x0F << 20); // DMA1 channel 5 + break; + } +} + +#ifdef TX_DMA_WRITE + +// disable TX DMA and clear all interrupt flags for a serial port +void Serial_DMA_DisableAndClearFlagsTX(uint8_t port) +{ + DMA_CHCTL(Serial[port].dma_stream, Serial[port].dma_channelTX) &= ~(1<<0); // disable TX DMA + + // table 12.5.2, 4 bits per channel, shift = channel * 4 + + switch (port) + { + case _USART1: + DMA_INTC(DMA0) = (0x0F << 12); // DMA0 channel 3 + break; + + case _USART2: + DMA_INTC(DMA0) = (0x0F << 24); // DMA0 channel 6 + break; + + case _USART3: + DMA_INTC(DMA0) = (0x0F << 4); // DMA0 channel 1 + break; + + case _UART4: + DMA_INTC(DMA1) = (0x0F << 16); // DMA1 channel 4 + break; + + case _UART5: + DMA_INTC(DMA1) = (0x0F << 12); // DMA1 channel 3 + break; + + case _USART6: + DMA_INTC(DMA1) = (0x0F << 24); // DMA1 channel 6 + break; } } +#endif + void Serial_DMA_Config(uint8_t port) { const SERIAL_CFG * cfg = &Serial[port]; - rcu_periph_clock_enable(cfg->dma_rcc); - DMA_CHCTL(cfg->dma_stream, cfg->dma_channel) &= ~(1<<0); // Disable DMA - Serial_DMAClearFlag(port); - USART_CTL2(cfg->uart) |= 1<<6; // DMA enable receiver + rcu_periph_clock_enable(cfg->dma_rcc); // enable DMA clock + + Serial_DMA_DisableAndClearFlagsRX(port); // RX disable DMA and clear all interrupt flags + + DMA_CHPADDR(cfg->dma_stream, cfg->dma_channelRX) = (uint32_t)(&USART_DATA(cfg->uart)); // RX peripheral address (usart) + DMA_CHMADDR(cfg->dma_stream, cfg->dma_channelRX) = (uint32_t)(dmaL1DataRX[port].cache); // RX destination address (memory) + DMA_CHCNT(cfg->dma_stream, cfg->dma_channelRX) = (uint32_t)(dmaL1DataRX[port].cacheSize); // RX buffer size + + DMA_CHCTL(cfg->dma_stream, cfg->dma_channelRX) = 0; // RX clear control register + + // primary serial port priority at highest level (TX higher than RX) + if (port == SERIAL_PORT) + DMA_CHCTL(cfg->dma_stream, cfg->dma_channelRX) |= (2<<12); // RX priority level: High + else + DMA_CHCTL(cfg->dma_stream, cfg->dma_channelRX) |= (0<<12); // RX priority level: Low + +//DMA_CHCTL(cfg->dma_stream, cfg->dma_channelRX) |= (0<<10); // RX memory data size: 8 bit +//DMA_CHCTL(cfg->dma_stream, cfg->dma_channelRX) |= (0<<8); // RX peripheral data size: 8 bit + DMA_CHCTL(cfg->dma_stream, cfg->dma_channelRX) |= (1<<7); // RX memory increment mode +//DMA_CHCTL(cfg->dma_stream, cfg->dma_channelRX) |= (0<<6); // RX peripheral no increment mode + DMA_CHCTL(cfg->dma_stream, cfg->dma_channelRX) |= (1<<5); // RX circular mode enabled +//DMA_CHCTL(cfg->dma_stream, cfg->dma_channelRX) |= (0<<4); // RX data transfer direction: Peripheral-to-memory + +#ifdef TX_DMA_WRITE // TX DMA based serial writing + Serial_DMA_DisableAndClearFlagsTX(port); // TX disable DMA and clear all interrupt flags + + DMA_CHPADDR(cfg->dma_stream, cfg->dma_channelTX) = (uint32_t)(&USART_DATA(cfg->uart)); // TX peripheral address (usart) + + DMA_CHCTL(cfg->dma_stream, cfg->dma_channelTX) = 0; // TX clear control register - DMA_CHPADDR(cfg->dma_stream, cfg->dma_channel) = (uint32_t)(&USART_DATA(cfg->uart)); - DMA_CHMADDR(cfg->dma_stream, cfg->dma_channel) = (uint32_t)(dmaL1Data[port].cache); - DMA_CHCNT(cfg->dma_stream, cfg->dma_channel) = dmaL1Data[port].cacheSize; + // primary serial port priority at highest level (TX higher than RX) + if (port == SERIAL_PORT) + DMA_CHCTL(cfg->dma_stream, cfg->dma_channelTX) |= (3<<12); // TX priority level: Very high + else + DMA_CHCTL(cfg->dma_stream, cfg->dma_channelTX) |= (1<<12); // TX priority level: Medium - // DMA_CHCTL(cfg->dma_stream, cfg->dma_channel) = cfg->dma_channel << 25; - DMA_CHCTL(cfg->dma_stream, cfg->dma_channel) |= 3<<12; // Priority level: Very high - DMA_CHCTL(cfg->dma_stream, cfg->dma_channel) |= 0<<10; // Memory data size: 8 - DMA_CHCTL(cfg->dma_stream, cfg->dma_channel) |= 0<<8; // Peripheral data size: 8 - DMA_CHCTL(cfg->dma_stream, cfg->dma_channel) |= 1<<7; // Memory increment mode - DMA_CHCTL(cfg->dma_stream, cfg->dma_channel) |= 0<<6; // Peripheral not increment mode - DMA_CHCTL(cfg->dma_stream, cfg->dma_channel) |= 1<<5; // Circular mode enabled - DMA_CHCTL(cfg->dma_stream, cfg->dma_channel) |= 0<<4; // Data transfer direction: Peripheral-to-memory - DMA_CHCTL(cfg->dma_stream, cfg->dma_channel) |= 1<<0; // Enable DMA +//DMA_CHCTL(cfg->dma_stream, cfg->dma_channelTX) |= (0<<10); // TX memory data size: 8 bit +//DMA_CHCTL(cfg->dma_stream, cfg->dma_channelTX) |= (0<<8); // TX peripheral data size: 8 bit + DMA_CHCTL(cfg->dma_stream, cfg->dma_channelTX) |= (1<<7); // TX memory increment mode +//DMA_CHCTL(cfg->dma_stream, cfg->dma_channelTX) |= (0<<6); // TX peripheral no increment mode +//DMA_CHCTL(cfg->dma_stream, cfg->dma_channelTX) |= (0<<5); // TX circular mode disabled + DMA_CHCTL(cfg->dma_stream, cfg->dma_channelTX) |= (1<<4); // TX data transfer direction: Memory-to-Peripheral + + USART_CTL2(cfg->uart) |= (1<<7); // enable DMA transmitter (DMAT) +//DMA_CHCTL(cfg->dma_stream, cfg->dma_channelTX) |= (1<<0); // TX enable DMA, done later when needed +#endif + + USART_CTL2(cfg->uart) |= (1<<6); // enable DMA receiver (DMAR) + DMA_CHCTL(cfg->dma_stream, cfg->dma_channelRX) |= (1<<0); // RX enable DMA } void Serial_ClearData(uint8_t port) { - dmaL1Data[port].wIndex = dmaL1Data[port].rIndex = dmaL1Data[port].flag = dmaL1Data[port].cacheSize = 0; + dmaL1DataRX[port].wIndex = dmaL1DataRX[port].rIndex = dmaL1DataRX[port].flag = dmaL1DataRX[port].cacheSize = 0; + + if (dmaL1DataRX[port].cache != NULL) + { + free(dmaL1DataRX[port].cache); + dmaL1DataRX[port].cache = NULL; + } - if (dmaL1Data[port].cache != NULL) + dmaL1DataTX[port].wIndex = dmaL1DataTX[port].rIndex = dmaL1DataTX[port].flag = dmaL1DataTX[port].cacheSize = 0; + + if (dmaL1DataTX[port].cache != NULL) { - free(dmaL1Data[port].cache); - dmaL1Data[port].cache = NULL; + free(dmaL1DataTX[port].cache); + dmaL1DataTX[port].cache = NULL; } } -void Serial_Config(uint8_t port, uint16_t cacheSize, uint32_t baudrate) +void Serial_Config(uint8_t port, uint32_t cacheSizeRX, uint32_t cacheSizeTX, uint32_t baudrate) { Serial_ClearData(port); - dmaL1Data[port].cacheSize = cacheSize; - dmaL1Data[port].cache = malloc(cacheSize); - while (!dmaL1Data[port].cache); // malloc failed + dmaL1DataRX[port].cacheSize = cacheSizeRX; + dmaL1DataRX[port].cache = malloc(cacheSizeRX); + while (!dmaL1DataRX[port].cache); // RX malloc failed, blocking! + + dmaL1DataTX[port].cacheSize = cacheSizeTX; + dmaL1DataTX[port].cache = malloc(cacheSizeTX); + while (!dmaL1DataTX[port].cache); // TX malloc failed, blocking! + + UART_Config(port, baudrate, USART_INT_IDLE, IDLE_LINE_IT); // configure serial line with or without IDLE Line interrupt - UART_Config(port, baudrate, USART_INT_IDLE); // IDLE interrupt Serial_DMA_Config(port); } @@ -94,20 +197,123 @@ void Serial_DeConfig(uint8_t port) { Serial_ClearData(port); - DMA_CHCTL(Serial[port].dma_stream, Serial[port].dma_channel) &= ~(1<<0); // Disable DMA - Serial_DMAClearFlag(port); + Serial_DMA_DisableAndClearFlagsRX(port); // disable RX DMA and clear all interrupt flags + +#ifdef TX_DMA_WRITE + Serial_DMA_DisableAndClearFlagsTX(port); // disable TX DMA and clear all interrupt flags +#endif + UART_DeConfig(port); } +#ifdef TX_DMA_WRITE // TX DMA based serial writing + +// DMA serial write support function +void Serial_Send_TX(uint8_t port) +{ + // setup DMA transfer, and wait for serial Transfer Complete (TX) interrupt in ISR + if (dmaL1DataTX[port].wIndex >= dmaL1DataTX[port].rIndex) + dmaL1DataTX[port].flag = dmaL1DataTX[port].wIndex - dmaL1DataTX[port].rIndex; + else // split transfer into 2 parts + dmaL1DataTX[port].flag = dmaL1DataTX[port].cacheSize - dmaL1DataTX[port].rIndex; + + Serial_DMA_DisableAndClearFlagsTX(port); // disable TX DMA and clear all interrupt flags + + DMA_CHMADDR(Serial[port].dma_stream, Serial[port].dma_channelTX) = (uint32_t)(&dmaL1DataTX[port].cache[dmaL1DataTX[port].rIndex]); // TX data start address + DMA_CHCNT(Serial[port].dma_stream, Serial[port].dma_channelTX) = dmaL1DataTX[port].flag; // number of bytes to transfer + + USART_CTL0(Serial[port].uart) |= USART_CTL0_TCIE; // enable Transfer Complete (TC) serial interrupt + DMA_CHCTL(Serial[port].dma_stream, Serial[port].dma_channelTX) |= (1<<0); // enable TX DMA +} + +void Serial_Put(uint8_t port, const char * msg) +{ + while (*msg) + { + // setup TX DMA, if no '\n' yet, but buffer is full AND DMA is not in progress already (waiting for Transfer Complete interrupt) + if ((((dmaL1DataTX[port].wIndex + 1) % dmaL1DataTX[port].cacheSize) == dmaL1DataTX[port].rIndex) && + ((USART_CTL0(Serial[port].uart) & USART_CTL0_TCIE) == 0)) + Serial_Send_TX(port); + + // blocking! wait for buffer to become available + while (((dmaL1DataTX[port].wIndex + 1) % dmaL1DataTX[port].cacheSize) == dmaL1DataTX[port].rIndex) { } + + dmaL1DataTX[port].cache[dmaL1DataTX[port].wIndex] = *msg; // copy character to cache + dmaL1DataTX[port].wIndex = (dmaL1DataTX[port].wIndex + 1) % dmaL1DataTX[port].cacheSize; // update wIndex + + if ((*msg == '\n') && ((USART_CTL0(Serial[port].uart) & USART_CTL0_TCIE) == 0)) + Serial_Send_TX(port); // start DMA process if command is complete and DMA is not in progress already + + msg++; // let the compiler optimize this, no need to do it manually! + } +} + +#else // TX interrupt based serial writing + +void Serial_Put(uint8_t port, const char * msg) +{ + while (*msg) + { + // blocking! wait for buffer to become available + while (((dmaL1DataTX[port].wIndex + 1) % dmaL1DataTX[port].cacheSize) == dmaL1DataTX[port].rIndex) { }; + + dmaL1DataTX[port].cache[dmaL1DataTX[port].wIndex] = *msg++; + dmaL1DataTX[port].wIndex = (dmaL1DataTX[port].wIndex + 1) % dmaL1DataTX[port].cacheSize; + + USART_CTL0(Serial[port].uart) |= USART_CTL0_TBEIE; // set TBE interrupt bit to start the serial transfer + } +} + +#endif + +// ISR, serial interrupt handler void USART_IRQHandler(uint8_t port) { - if ((USART_STAT0(Serial[port].uart) & (1<<4)) != 0) +#if IDLE_LINE_IT == true // IDLE Line interrupt + if ((USART_STAT0(Serial[port].uart) & USART_STAT0_IDLEF) != RESET) // check for IDLE Line interrupt { - USART_STAT0(Serial[port].uart); // Clear interrupt flag + USART_STAT0(Serial[port].uart); // clear IDLE Line bit USART_DATA(Serial[port].uart); - dmaL1Data[port].wIndex = dmaL1Data[port].cacheSize - DMA_CHCNT(Serial[port].dma_stream, Serial[port].dma_channel); + dmaL1DataRX[port].wIndex = dmaL1DataRX[port].cacheSize - DMA_CHCNT(Serial[port].dma_stream, Serial[port].dma_channelRX); + } +#endif + +#ifdef TX_DMA_WRITE // TX DMA based serial writing + if ((USART_STAT0(Serial[port].uart) & USART_STAT0_TC) != RESET) // check for Transfer Complete (TC) interrupt + { + USART_STAT0(Serial[port].uart) &= ~USART_STAT0_TC; // clear Transfer Complete (TC) bit + + // NOTE 1: use the serial TC, not the DMA TC because this only indicates DMA is done, peripheral might be still busy + // NOTE 2: the TC interrupt is sometimes called while DMA is still active, so check NDTR status! + // + if (DMA_CHCNT(Serial[port].dma_stream, Serial[port].dma_channelTX) == 0) // sending is complete + { + dmaL1DataTX[port].rIndex = (dmaL1DataTX[port].rIndex + dmaL1DataTX[port].flag) % dmaL1DataTX[port].cacheSize; + dmaL1DataTX[port].flag = 0; + + if (dmaL1DataTX[port].wIndex != dmaL1DataTX[port].rIndex) // is more data available? + Serial_Send_TX(port); // continue sending data + else + USART_CTL0(Serial[port].uart) &= ~USART_CTL0_TCIE; // disable Transfer Complete (TC) interrupt, nothing more to do + } + // else: more data is coming, wait for next Transfer Complete (TC) interrupt + } +#else // TX interrupt based serial writing + if ((USART_STAT0(Serial[port].uart) & USART_STAT0_TBE) != RESET) // check for TBE interrupt + { + if (dmaL1DataTX[port].rIndex == dmaL1DataTX[port].wIndex) // no more data? + { + USART_CTL0(Serial[port].uart) &= ~USART_CTL0_TBEIE; // disable TBE interrupt + } + else + { + USART_DATA(Serial[port].uart) = (uint8_t)dmaL1DataTX[port].cache[dmaL1DataTX[port].rIndex]; // write next available character + + dmaL1DataTX[port].rIndex = (dmaL1DataTX[port].rIndex + 1) % dmaL1DataTX[port].cacheSize; // increase reading index + } } +#endif } void USART0_IRQHandler(void) @@ -139,18 +345,3 @@ void USART5_IRQHandler(void) { USART_IRQHandler(_USART6); } - -void Serial_Put(uint8_t port, const char *s) -{ - while (*s) - { - while ((USART_STAT0(Serial[port].uart) & (1 << USART_FLAG_TC)) == (uint16_t)RESET); - USART_DATA(Serial[port].uart) = ((uint16_t)*s++ & (uint16_t)0x01FF); - } -} - -void Serial_PutChar(uint8_t port, const char ch) -{ - while ((USART_STAT0(Serial[port].uart) & (1 << USART_FLAG_TC)) == (uint16_t)RESET); - USART_DATA(Serial[port].uart) = (uint8_t) ch; -} diff --git a/TFT/src/User/Hal/gd32f20x/Serial.h b/TFT/src/User/Hal/gd32f20x/Serial.h index ca48a22082..d66960ba5a 100644 --- a/TFT/src/User/Hal/gd32f20x/Serial.h +++ b/TFT/src/User/Hal/gd32f20x/Serial.h @@ -5,20 +5,58 @@ #include "variants.h" // for uint32_t etc... #include "uart.h" -typedef volatile struct // precautionally declared as volatile due to access from interrupt handler and main thread +// comment out this line to use TX interrupt based serial writing instead of TX DMA based serial writing +#define TX_DMA_WRITE + +typedef struct { - char *cache; - uint16_t wIndex; // writing index - uint16_t rIndex; // reading index - uint16_t flag; // custom flag (for custom usage by the application) - uint16_t cacheSize; + char * cache; + uint32_t cacheSize; + volatile uint32_t wIndex; // writing index + volatile uint32_t rIndex; // reading index + volatile uint32_t flag; // custom flag (for custom usage by the application) } DMA_CIRCULAR_BUFFER; -extern DMA_CIRCULAR_BUFFER dmaL1Data[_UART_CNT]; +// config for USART DMA channels +typedef struct +{ + uint32_t uart; + rcu_periph_enum dma_rcc; // uint32_t + uint32_t dma_stream; + uint32_t dma_channelRX; +#ifdef TX_DMA_WRITE + uint32_t dma_channelTX; +#endif +} SERIAL_CFG; -void Serial_Config(uint8_t port, uint16_t cacheSize, uint32_t baudrate); +extern DMA_CIRCULAR_BUFFER dmaL1DataRX[_UART_CNT]; +extern const SERIAL_CFG Serial[_UART_CNT]; + +void Serial_Config(uint8_t port, uint32_t cacheSizeRX, uint32_t cacheSizeTX, uint32_t baudrate); void Serial_DeConfig(uint8_t port); -void Serial_Put(uint8_t port, const char *s); -void Serial_PutChar(uint8_t port, const char ch); + +// retrieve the next reading index in the RX message queue of the provided physical serial port: +// - port: physical serial port +// +// - return value: next reading index +static inline uint32_t Serial_GetReadingIndexRX(uint8_t port) +{ + return dmaL1DataRX[port].rIndex; +} + +// retrieve the next writing index in the RX message queue of the provided physical serial port +// based on Interrupt/DMA status while writing serial data in the background: +// - port: physical serial port +// +// - return value: next writing index +static inline uint32_t Serial_GetWritingIndexRX(uint8_t port) +{ + return dmaL1DataRX[port].cacheSize - DMA_CHCNT(Serial[port].dma_stream, Serial[port].dma_channelRX); +} + +// send a zero terminated message to UART port +// - port: index of serial port +// - msg: message to send +void Serial_Put(uint8_t port, const char * msg); #endif diff --git a/TFT/src/User/Hal/gd32f20x/uart.c b/TFT/src/User/Hal/gd32f20x/uart.c index bba1545d47..92b95a67eb 100644 --- a/TFT/src/User/Hal/gd32f20x/uart.c +++ b/TFT/src/User/Hal/gd32f20x/uart.c @@ -87,7 +87,7 @@ void UART_GPIO_DeInit(uint8_t port) GPIO_InitSet(uart_rx[port], MGPIO_MODE_IPN, 0); } -void UART_Protocol_Init(uint8_t port,uint32_t baud) +void UART_Protocol_Init(uint8_t port, uint32_t baud) { rcu_periph_clock_enable(rcu_uart_en[port]); // Enable clock @@ -103,19 +103,22 @@ void UART_Protocol_Init(uint8_t port,uint32_t baud) usart_enable(uart[port]); } -void UART_IRQ_Init(uint8_t port, uint16_t usart_it) +void UART_IRQ_Init(uint8_t port, uint16_t usart_it, bool idle_interrupt) { uint32_t IRQ_Channel[_UART_CNT] = {USART0_IRQn, USART1_IRQn, USART2_IRQn, UART3_IRQn, UART4_IRQn, USART5_IRQn}; nvic_irq_enable(IRQ_Channel[port], 0U, 0U); - usart_interrupt_enable(uart[port], usart_it); - usart_interrupt_flag_clear(uart[port], usart_it); + if (idle_interrupt) // enable or disable serial line IDLE interrupt + { + usart_interrupt_enable(uart[port], usart_it); + usart_interrupt_flag_clear(uart[port], usart_it); + } } -void UART_Config(uint8_t port, uint32_t baud, uint16_t usart_it) +void UART_Config(uint8_t port, uint32_t baud, uint16_t usart_it, bool idle_interrupt) { UART_Protocol_Init(port, baud); - UART_IRQ_Init(port, usart_it); + UART_IRQ_Init(port, usart_it, idle_interrupt); UART_GPIO_Init(port); // After all initialization is completed, enable IO, otherwise a 0xFF will be sent automatically after power-on } diff --git a/TFT/src/User/Hal/gd32f20x/uart.h b/TFT/src/User/Hal/gd32f20x/uart.h index 9f50557035..4230750ff6 100644 --- a/TFT/src/User/Hal/gd32f20x/uart.h +++ b/TFT/src/User/Hal/gd32f20x/uart.h @@ -1,6 +1,7 @@ #ifndef _UART_H_ #define _UART_H_ +#include #include #define _USART1 0 @@ -11,7 +12,7 @@ #define _USART6 5 #define _UART_CNT 6 -void UART_Config(uint8_t port, uint32_t baud, uint16_t usart_it); +void UART_Config(uint8_t port, uint32_t baud, uint16_t usart_it, bool idle_interrupt); void UART_DeConfig(uint8_t port); void UART_Puts(uint8_t port, uint8_t *str); void UART_Write(uint8_t port, uint8_t d); diff --git a/TFT/src/User/Hal/stm32f10x/Serial.c b/TFT/src/User/Hal/stm32f10x/Serial.c index bbe60e14c8..34cc882f8d 100644 --- a/TFT/src/User/Hal/stm32f10x/Serial.c +++ b/TFT/src/User/Hal/stm32f10x/Serial.c @@ -1,64 +1,162 @@ #include "Serial.h" -#include "includes.h" // for infoHost +#include "includes.h" -// dma rx buffer -DMA_CIRCULAR_BUFFER dmaL1Data[_UART_CNT] = {0}; +// set this line to "true" to enable IDLE Line interrupt. It is no more needed, so always set this macro to "false" +#define IDLE_LINE_IT false -// Config for USART Channel -typedef struct -{ - USART_TypeDef *uart; - uint32_t dma_rcc; - DMA_Channel_TypeDef *dma_chanel; -} SERIAL_CFG; - -static const SERIAL_CFG Serial[_UART_CNT] = { - {USART1, RCC_AHBPeriph_DMA1, DMA1_Channel5}, - {USART2, RCC_AHBPeriph_DMA1, DMA1_Channel6}, - {USART3, RCC_AHBPeriph_DMA1, DMA1_Channel3}, - {UART4, RCC_AHBPeriph_DMA2, DMA2_Channel3}, - //{UART5, -1, -1}, // UART5 don't support DMA +// DMA registers: +// +// CCR DMA stream x configuration register +// CNDTR DMA stream x number of data register +// CPAR DMA stream x peripheral address register +// CMAR DMA stream x memory 0 address register +// +// ISR DMA low interrupt status register +// ISR DMA high interrupt status register +// IFCR DMA low interrupt flag clear register +// IFCR DMA high interrupt flag clear register + +DMA_CIRCULAR_BUFFER dmaL1DataRX[_UART_CNT] = {0}; // DMA / interrupt RX buffer +DMA_CIRCULAR_BUFFER dmaL1DataTX[_UART_CNT] = {0}; // DMA / interrupt TX buffer + +// config for USART DMA channels +const SERIAL_CFG Serial[_UART_CNT] = { // RM0008 Table 78-79 +#ifdef TX_DMA_WRITE +// USART TCC DMAx DMAx RX Channel TX Channel + {USART1, RCC_AHBPeriph_DMA1, DMA1, DMA1_Channel5, DMA1_Channel4}, + {USART2, RCC_AHBPeriph_DMA1, DMA1, DMA1_Channel6, DMA1_Channel7}, + {USART3, RCC_AHBPeriph_DMA1, DMA1, DMA1_Channel3, DMA1_Channel2}, + {UART4, RCC_AHBPeriph_DMA2, DMA2, DMA2_Channel3, DMA2_Channel5}, + {UART5, -1, -1, -1, -1 }, // UART5 don't support DMA +#else +// USART TCC DMAx DMAx RX Channel + {USART1, RCC_AHBPeriph_DMA1, DMA1, DMA1_Channel5}, + {USART2, RCC_AHBPeriph_DMA1, DMA1, DMA1_Channel6}, + {USART3, RCC_AHBPeriph_DMA1, DMA1, DMA1_Channel3}, + {UART4, RCC_AHBPeriph_DMA2, DMA2, DMA2_Channel3}, + {UART5, -1, -1 -1, }, // UART5 don't support DMA +#endif }; +#ifdef TX_DMA_WRITE + +// disable TX DMA and clear all interrupt flags for a serial port +void Serial_DMA_DisableAndClearFlagsTX(uint8_t port) +{ + Serial[port].dma_channelTX->CCR &= ~(1<<0); // disable TX DMA + + // clear 4 bits, shift = channel * 4 + + switch (port) + { + case _USART1: + Serial[port].dma_controller->IFCR = (0x0F << 16); + break; + + case _USART2: + Serial[port].dma_controller->IFCR = (0x0F << 28); + break; + + case _USART3: + Serial[port].dma_controller->IFCR = (0x0F << 8); + break; + + case _UART4: + Serial[port].dma_controller->IFCR = (0x0F << 20); + break; + } +} + +#endif + void Serial_DMA_Config(uint8_t port) { const SERIAL_CFG * cfg = &Serial[port]; - RCC_AHBPeriphClockCmd(cfg->dma_rcc, ENABLE); // DMA RCC EN + RCC_AHBPeriphClockCmd(cfg->dma_rcc, ENABLE); // enable DMA clock + + cfg->dma_channelRX->CCR &= ~(1<<0); // RX disable DMA + + cfg->dma_channelRX->CPAR = (uint32_t)(&cfg->uart->DR); // RX peripheral address (usart) + cfg->dma_channelRX->CMAR = (uint32_t)(dmaL1DataRX[port].cache); // RX destination address (memory) + cfg->dma_channelRX->CNDTR = dmaL1DataRX[port].cacheSize; // RX buffer size + + cfg->dma_channelRX->CCR = 0; // RX clear control register + + // primary serial port priority at highest level (TX higher than RX) + if (port == SERIAL_PORT) + cfg->dma_channelRX->CCR |= (2<<12); // RX priority level: High + else + cfg->dma_channelRX->CCR |= (0<<12); // RX priority level: Low - cfg->dma_chanel->CCR &= ~(1<<0); // DMA disable - cfg->uart->CR3 |= 1<<6; // DMA enable receiver +//cfg->dma_channelRX->CCR |= (0<<10); // RX memory data size: 8 bit +//cfg->dma_channelRX->CCR |= (0<<8); // RX peripheral data size: 8 bit + cfg->dma_channelRX->CCR |= (1<<7); // RX memory increment mode +//cfg->dma_channelRX->CCR |= (0<<6); // RX peripheral no increment mode + cfg->dma_channelRX->CCR |= (1<<5); // RX circular mode enabled +//cfg->dma_channelRX->CCR &= ~(1<<4); // RX data transfer direction: Peripheral-to-memory - cfg->dma_chanel->CPAR = (uint32_t)(&cfg->uart->DR); - cfg->dma_chanel->CMAR = (uint32_t)(dmaL1Data[port].cache); - cfg->dma_chanel->CNDTR = dmaL1Data[port].cacheSize; - cfg->dma_chanel->CCR = 0X00000000; - cfg->dma_chanel->CCR |= 3<<12; // Channel priority level - cfg->dma_chanel->CCR |= 1<<7; // Memory increment mode - cfg->dma_chanel->CCR |= 1<<5; // Circular mode enabled - cfg->dma_chanel->CCR |= 1<<0; // DMA EN +#ifdef TX_DMA_WRITE // TX DMA based serial writing + Serial_DMA_DisableAndClearFlagsTX(port); // TX disable DMA and clear all interrupt flags + + cfg->dma_channelTX->CPAR = (uint32_t)(&cfg->uart->DR); // TX peripheral address (usart) + + cfg->dma_channelTX->CCR = 0; // TX clear control register + + // primary serial port priority at highest level (TX higher than RX) + if (port == SERIAL_PORT) + cfg->dma_channelTX->CCR |= (3<<12); // TX priority level: Very high + else + cfg->dma_channelTX->CCR |= (1<<12); // TX priority level: Medium + +//cfg->dma_channelTX->CCR |= (0<<10); // TX memory data size: 8 bit +//cfg->dma_channelTX->CCR |= (0<<8); // TX peripheral data size: 8 bit + cfg->dma_channelTX->CCR |= (1<<7); // TX memory increment mode +//cfg->dma_channelTX->CCR |= (0<<6); // TX peripheral no increment mode +//cfg->dma_channelTX->CCR |= (0<<5); // TX circular mode disabled + cfg->dma_channelTX->CCR |= (1<<4); // TX data transfer direction: Memory-to-Peripheral + + cfg->uart->CR3 |= (1<<7); // enable DMA transmitter (DMAT) +//cfg->dma_channelTX->CCR |= (1<<0); // TX enable DMA, done later when needed +#endif + + cfg->uart->CR3 |= (1<<6); // enable DMA receiver (DMAR) + cfg->dma_channelRX->CCR |= (1<<0); // RX enable DMA } void Serial_ClearData(uint8_t port) { - dmaL1Data[port].wIndex = dmaL1Data[port].rIndex = dmaL1Data[port].flag = dmaL1Data[port].cacheSize = 0; + dmaL1DataRX[port].wIndex = dmaL1DataRX[port].rIndex = dmaL1DataRX[port].flag = dmaL1DataRX[port].cacheSize = 0; + + if (dmaL1DataRX[port].cache != NULL) + { + free(dmaL1DataRX[port].cache); + dmaL1DataRX[port].cache = NULL; + } - if (dmaL1Data[port].cache != NULL) + dmaL1DataTX[port].wIndex = dmaL1DataTX[port].rIndex = dmaL1DataTX[port].flag = dmaL1DataTX[port].cacheSize = 0; + + if (dmaL1DataTX[port].cache != NULL) { - free(dmaL1Data[port].cache); - dmaL1Data[port].cache = NULL; + free(dmaL1DataTX[port].cache); + dmaL1DataTX[port].cache = NULL; } } -void Serial_Config(uint8_t port, uint16_t cacheSize, uint32_t baudrate) +void Serial_Config(uint8_t port, uint32_t cacheSizeRX, uint32_t cacheSizeTX, uint32_t baudrate) { Serial_ClearData(port); - dmaL1Data[port].cacheSize = cacheSize; - dmaL1Data[port].cache = malloc(cacheSize); - while (!dmaL1Data[port].cache); // malloc failed + dmaL1DataRX[port].cacheSize = cacheSizeRX; + dmaL1DataRX[port].cache = malloc(cacheSizeRX); + while (!dmaL1DataRX[port].cache); // RX malloc failed, blocking! + + dmaL1DataTX[port].cacheSize = cacheSizeTX; + dmaL1DataTX[port].cache = malloc(cacheSizeTX); + while (!dmaL1DataTX[port].cache); // TX malloc failed, blocking! + + UART_Config(port, baudrate, USART_IT_IDLE, IDLE_LINE_IT); // configure serial line with or without IDLE Line interrupt - UART_Config(port, baudrate, USART_IT_IDLE); // IDLE interrupt Serial_DMA_Config(port); } @@ -66,19 +164,123 @@ void Serial_DeConfig(uint8_t port) { Serial_ClearData(port); - Serial[port].dma_chanel->CCR &= ~(1<<0); // Disable DMA + Serial[port].dma_channelRX->CCR &= ~(1<<0); // disable RX DMA + +#ifdef TX_DMA_WRITE + Serial_DMA_DisableAndClearFlagsTX(port); // disable TX DMA and clear all interrupt flags +#endif + UART_DeConfig(port); } +#ifdef TX_DMA_WRITE // TX DMA based serial writing + +// DMA serial write support function +void Serial_Send_TX(uint8_t port) +{ + // setup DMA transfer, and wait for serial Transfer Complete (TX) interrupt in ISR + if (dmaL1DataTX[port].wIndex >= dmaL1DataTX[port].rIndex) + dmaL1DataTX[port].flag = dmaL1DataTX[port].wIndex - dmaL1DataTX[port].rIndex; + else // split transfer into 2 parts + dmaL1DataTX[port].flag = dmaL1DataTX[port].cacheSize - dmaL1DataTX[port].rIndex; + + Serial_DMA_DisableAndClearFlagsTX(port); // disable TX DMA and clear all interrupt flags + + Serial[port].dma_channelTX->CMAR = (uint32_t)(&dmaL1DataTX[port].cache[dmaL1DataTX[port].rIndex]); // TX data start address + Serial[port].dma_channelTX->CNDTR = dmaL1DataTX[port].flag; // number of bytes to transfer + + Serial[port].uart->CR1 |= USART_CR1_TCIE; // enable Transfer Complete (TC) serial interrupt + Serial[port].dma_channelTX->CCR |= (1<<0); // enable TX DMA +} + +void Serial_Put(uint8_t port, const char * msg) +{ + while (*msg) + { + // setup TX DMA, if no '\n' yet, but buffer is full AND DMA is not in progress already (waiting for Transfer Complete interrupt) + if ((((dmaL1DataTX[port].wIndex + 1) % dmaL1DataTX[port].cacheSize) == dmaL1DataTX[port].rIndex) && + ((Serial[port].uart->CR1 & USART_CR1_TCIE) == 0)) + Serial_Send_TX(port); + + // blocking! wait for buffer to become available + while (((dmaL1DataTX[port].wIndex + 1) % dmaL1DataTX[port].cacheSize) == dmaL1DataTX[port].rIndex) { } + + dmaL1DataTX[port].cache[dmaL1DataTX[port].wIndex] = *msg; // copy character to cache + dmaL1DataTX[port].wIndex = (dmaL1DataTX[port].wIndex + 1) % dmaL1DataTX[port].cacheSize; // update wIndex + + if ((*msg == '\n') && ((Serial[port].uart->CR1 & USART_CR1_TCIE) == 0)) + Serial_Send_TX(port); // start DMA process if command is complete and DMA is not in progress already + + msg++; // let the compiler optimize this, no need to do it manually! + } +} + +#else // TX interrupt based serial writing + +void Serial_Put(uint8_t port, const char * msg) +{ + while (*msg) + { + // blocking! wait for buffer to become available + while (((dmaL1DataTX[port].wIndex + 1) % dmaL1DataTX[port].cacheSize) == dmaL1DataTX[port].rIndex) { }; + + dmaL1DataTX[port].cache[dmaL1DataTX[port].wIndex] = *msg++; + dmaL1DataTX[port].wIndex = (dmaL1DataTX[port].wIndex + 1) % dmaL1DataTX[port].cacheSize; + + Serial[port].uart->CR1 |= USART_CR1_TXEIE; // set TXE interrupt bit to start the serial transfer + } +} + +#endif + +// ISR, serial interrupt handler void USART_IRQHandler(uint8_t port) { - if ((Serial[port].uart->SR & (1<<4)) != 0) +#if IDLE_LINE_IT == true // IDLE Line interrupt + if ((Serial[port].uart->SR & USART_SR_IDLE) != RESET) // check for IDLE Line interrupt { - Serial[port].uart->SR; + Serial[port].uart->SR; // clear IDLE Line bit Serial[port].uart->DR; - dmaL1Data[port].wIndex = dmaL1Data[port].cacheSize - Serial[port].dma_chanel->CNDTR; + dmaL1DataRX[port].wIndex = dmaL1DataRX[port].cacheSize - Serial[port].dma_channelRX->CNDTR; + } +#endif + +#ifdef TX_DMA_WRITE // TX DMA based serial writing + if ((Serial[port].uart->SR & USART_SR_TC) != RESET) // check for Transfer Complete (TC) interrupt + { + Serial[port].uart->SR &= ~USART_SR_TC; // clear Transfer Complete (TC) bit + + // NOTE 1: use the serial TC, not the DMA TC because this only indicates DMA is done, peripheral might be still busy + // NOTE 2: the TC interrupt is sometimes called while DMA is still active, so check NDTR status! + // + if (Serial[port].dma_channelTX->CNDTR == 0) // sending is complete + { + dmaL1DataTX[port].rIndex = (dmaL1DataTX[port].rIndex + dmaL1DataTX[port].flag) % dmaL1DataTX[port].cacheSize; + dmaL1DataTX[port].flag = 0; + + if (dmaL1DataTX[port].wIndex != dmaL1DataTX[port].rIndex) // is more data available? + Serial_Send_TX(port); // continue sending data + else + Serial[port].uart->CR1 &= ~USART_CR1_TCIE; // disable Transfer Complete (TC) interrupt, nothing more to do + } + // else: more data is coming, wait for next Transfer Complete (TC) interrupt + } +#else // TX interrupt based serial writing + if ((Serial[port].uart->SR & USART_SR_TXE) != RESET) // check for TXE interrupt + { + if (dmaL1DataTX[port].rIndex == dmaL1DataTX[port].wIndex) // no more data? + { + Serial[port].uart->CR1 &= ~USART_CR1_TXEIE; // disable TXE interrupt + } + else + { + Serial[port].uart->DR = (uint8_t)dmaL1DataTX[port].cache[dmaL1DataTX[port].rIndex]; // write next available character + + dmaL1DataTX[port].rIndex = (dmaL1DataTX[port].rIndex + 1) % dmaL1DataTX[port].cacheSize; // increase reading index + } } +#endif } void USART1_IRQHandler(void) @@ -105,18 +307,3 @@ void UART5_IRQHandler(void) { USART_IRQHandler(_UART5); } - -void Serial_Put(uint8_t port, const char *s) -{ - while (*s) - { - while ((Serial[port].uart->SR & USART_FLAG_TC) == (uint16_t)RESET); - Serial[port].uart->DR = ((uint16_t)*s++ & (uint16_t)0x01FF); - } -} - -void Serial_PutChar(uint8_t port, const char ch) -{ - while ((Serial[port].uart->SR & USART_FLAG_TC) == (uint16_t)RESET); - Serial[port].uart->DR = (uint8_t) ch; -} diff --git a/TFT/src/User/Hal/stm32f10x/Serial.h b/TFT/src/User/Hal/stm32f10x/Serial.h index ca48a22082..bd51bf52a0 100644 --- a/TFT/src/User/Hal/stm32f10x/Serial.h +++ b/TFT/src/User/Hal/stm32f10x/Serial.h @@ -5,20 +5,58 @@ #include "variants.h" // for uint32_t etc... #include "uart.h" -typedef volatile struct // precautionally declared as volatile due to access from interrupt handler and main thread +// comment out this line to use TX interrupt based serial writing instead of TX DMA based serial writing +#define TX_DMA_WRITE + +typedef struct { - char *cache; - uint16_t wIndex; // writing index - uint16_t rIndex; // reading index - uint16_t flag; // custom flag (for custom usage by the application) - uint16_t cacheSize; + char * cache; + uint32_t cacheSize; + volatile uint32_t wIndex; // writing index + volatile uint32_t rIndex; // reading index + volatile uint32_t flag; // custom flag (for custom usage by the application) } DMA_CIRCULAR_BUFFER; -extern DMA_CIRCULAR_BUFFER dmaL1Data[_UART_CNT]; +// config for USART DMA channels +typedef struct +{ + USART_TypeDef * uart; // uint32_t + uint32_t dma_rcc; + DMA_TypeDef * dma_controller; + DMA_Channel_TypeDef * dma_channelRX; +#ifdef TX_DMA_WRITE + DMA_Channel_TypeDef * dma_channelTX; +#endif +} SERIAL_CFG; -void Serial_Config(uint8_t port, uint16_t cacheSize, uint32_t baudrate); +extern DMA_CIRCULAR_BUFFER dmaL1DataRX[_UART_CNT]; +extern const SERIAL_CFG Serial[_UART_CNT]; + +void Serial_Config(uint8_t port, uint32_t cacheSizeRX, uint32_t cacheSizeTX, uint32_t baudrate); void Serial_DeConfig(uint8_t port); -void Serial_Put(uint8_t port, const char *s); -void Serial_PutChar(uint8_t port, const char ch); + +// retrieve the next reading index in the RX message queue of the provided physical serial port: +// - port: physical serial port +// +// - return value: next reading index +static inline uint32_t Serial_GetReadingIndexRX(uint8_t port) +{ + return dmaL1DataRX[port].rIndex; +} + +// retrieve the next writing index in the RX message queue of the provided physical serial port +// based on Interrupt/DMA status while writing serial data in the background: +// - port: physical serial port +// +// - return value: next writing index +static inline uint32_t Serial_GetWritingIndexRX(uint8_t port) +{ + return dmaL1DataRX[port].cacheSize - Serial[port].dma_channelRX->CNDTR; +} + +// send a zero terminated message to UART port +// - port: index of serial port +// - msg: message to send +void Serial_Put(uint8_t port, const char * msg); #endif diff --git a/TFT/src/User/Hal/stm32f10x/uart.c b/TFT/src/User/Hal/stm32f10x/uart.c index 6d93476090..b17881d198 100644 --- a/TFT/src/User/Hal/stm32f10x/uart.c +++ b/TFT/src/User/Hal/stm32f10x/uart.c @@ -85,11 +85,11 @@ void UART_GPIO_DeInit(uint8_t port) GPIO_InitSet(uart_rx[port], MGPIO_MODE_IPN, 0); } -void UART_Protocol_Init(uint8_t port,uint32_t baud) +void UART_Protocol_Init(uint8_t port, uint32_t baud) { USART_InitTypeDef USART_InitStructure; - *rcc_uart_en[port] |= rcc_uart_bit[port]; + *rcc_uart_en[port] |= rcc_uart_bit[port]; // Enable clock USART_InitStructure.USART_BaudRate = baud; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; @@ -102,7 +102,7 @@ void UART_Protocol_Init(uint8_t port,uint32_t baud) USART_Cmd(uart[port],ENABLE); } -void UART_IRQ_Init(uint8_t port, uint16_t usart_it) +void UART_IRQ_Init(uint8_t port, uint16_t usart_it, FunctionalState idle_interrupt) { uint32_t IRQ_Channel[_UART_CNT] = {USART1_IRQn, USART2_IRQn, USART3_IRQn, UART4_IRQn, UART5_IRQn}; @@ -113,15 +113,15 @@ void UART_IRQ_Init(uint8_t port, uint16_t usart_it) NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_Init(&NVIC_InitStructure); - USART_ITConfig(uart[port], usart_it, ENABLE); + USART_ITConfig(uart[port], usart_it, idle_interrupt); // enable or disable serial line IDLE interrupt USART_ClearITPendingBit(uart[port], usart_it); } -void UART_Config(uint8_t port, uint32_t baud, uint16_t usart_it) +void UART_Config(uint8_t port, uint32_t baud, uint16_t usart_it, bool idle_interrupt) { UART_Protocol_Init(port, baud); - UART_IRQ_Init(port, usart_it); - UART_GPIO_Init(port); // Finally, initialize IO, else will send 0xFF. + UART_IRQ_Init(port, usart_it, idle_interrupt ? ENABLE : DISABLE); + UART_GPIO_Init(port); // After all initialization is completed, enable IO, otherwise a 0xFF will be sent automatically after power-on } void UART_DeConfig(uint8_t port) @@ -129,7 +129,7 @@ void UART_DeConfig(uint8_t port) UART_GPIO_DeInit(port); *rcc_uart_rst[port] |= rcc_uart_bit[port]; - *rcc_uart_rst[port] &= ~rcc_uart_bit[port]; + *rcc_uart_rst[port] &= ~rcc_uart_bit[port]; // Reset clock } void UART_Write(uint8_t port, uint8_t d) @@ -137,6 +137,7 @@ void UART_Write(uint8_t port, uint8_t d) while ((uart[port]->SR & USART_FLAG_TC) == (uint16_t)RESET); uart[port]->DR = ((uint16_t)d & (uint16_t)0x01FF); } + void UART_Puts(uint8_t port, uint8_t *str) { while (*str) diff --git a/TFT/src/User/Hal/stm32f10x/uart.h b/TFT/src/User/Hal/stm32f10x/uart.h index b51881a306..3e481bdeb4 100644 --- a/TFT/src/User/Hal/stm32f10x/uart.h +++ b/TFT/src/User/Hal/stm32f10x/uart.h @@ -1,6 +1,7 @@ #ifndef _UART_H_ #define _UART_H_ +#include #include #define _USART1 0 @@ -8,9 +9,9 @@ #define _USART3 2 #define _UART4 3 #define _UART5 4 // UART5 don't support DMA -#define _UART_CNT 6 +#define _UART_CNT 5 -void UART_Config(uint8_t port, uint32_t baud, uint16_t usart_it); +void UART_Config(uint8_t port, uint32_t baud, uint16_t usart_it, bool idle_interrupt); void UART_DeConfig(uint8_t port); void UART_Puts(uint8_t port, uint8_t *str); void UART_Write(uint8_t port, uint8_t d); diff --git a/TFT/src/User/Hal/stm32f2_f4xx/Serial.c b/TFT/src/User/Hal/stm32f2_f4xx/Serial.c index fe931f69c2..88baa21a07 100644 --- a/TFT/src/User/Hal/stm32f2_f4xx/Serial.c +++ b/TFT/src/User/Hal/stm32f2_f4xx/Serial.c @@ -1,93 +1,213 @@ #include "Serial.h" -#include "includes.h" // for infoHost - -// dma rx buffer -DMA_CIRCULAR_BUFFER dmaL1Data[_UART_CNT] = {0}; - -// Config for USART Channel -//USART1 RX DMA2 Channel4 Steam2/5 -//USART2 RX DMA1 Channel4 Steam5 -//USART3 RX DMA1 Channel4 Steam1 -//UART4 RX DMA1 Channel4 Steam2 -//UART5 RX DMA1 Channel4 Steam0 -//USART6 RX DMA2 Channel5 Steam1/2 - -// Config for USART Channel -typedef struct -{ - USART_TypeDef *uart; - uint32_t dma_rcc; - uint8_t dma_channel; - DMA_Stream_TypeDef *dma_stream; -} SERIAL_CFG; - -static const SERIAL_CFG Serial[_UART_CNT] = { - {USART1, RCC_AHB1Periph_DMA2, 4, DMA2_Stream2}, - {USART2, RCC_AHB1Periph_DMA1, 4, DMA1_Stream5}, - {USART3, RCC_AHB1Periph_DMA1, 4, DMA1_Stream1}, - {UART4, RCC_AHB1Periph_DMA1, 4, DMA1_Stream2}, - {UART5, RCC_AHB1Periph_DMA1, 4, DMA1_Stream0}, - {USART6, RCC_AHB1Periph_DMA2, 5, DMA2_Stream1}, +#include "includes.h" + +// set this line to "true" to enable IDLE Line interrupt. It is no more needed, so always set this macro to "false" +#define IDLE_LINE_IT false + +// DMA registers: +// +// CR DMA stream x configuration register +// NDTR DMA stream x number of data register +// PAR DMA stream x peripheral address register +// M0AR DMA stream x memory 0 address register +// +// LISR DMA low interrupt status register +// HISR DMA high interrupt status register +// LIFCR DMA low interrupt flag clear register +// HIFCR DMA high interrupt flag clear register + +DMA_CIRCULAR_BUFFER dmaL1DataRX[_UART_CNT] = {0}; // DMA / interrupt RX buffer +DMA_CIRCULAR_BUFFER dmaL1DataTX[_UART_CNT] = {0}; // DMA / interrupt TX buffer + +// config for USART DMA channels +const SERIAL_CFG Serial[_UART_CNT] = { +#ifdef TX_DMA_WRITE +// USART TCC DMAx Channel RX Stream TX Stream + {USART1, RCC_AHB1Periph_DMA2, 4, DMA2_Stream2, DMA2_Stream7}, + {USART2, RCC_AHB1Periph_DMA1, 4, DMA1_Stream5, DMA1_Stream6}, + {USART3, RCC_AHB1Periph_DMA1, 4, DMA1_Stream1, DMA1_Stream3}, + {UART4, RCC_AHB1Periph_DMA1, 4, DMA1_Stream2, DMA1_Stream4}, + {UART5, RCC_AHB1Periph_DMA1, 4, DMA1_Stream0, DMA1_Stream7}, + {USART6, RCC_AHB1Periph_DMA2, 5, DMA2_Stream1, DMA2_Stream6}, +#else +// USART TCC DMAx Channel RX Stream + {USART1, RCC_AHB1Periph_DMA2, 4, DMA2_Stream2}, + {USART2, RCC_AHB1Periph_DMA1, 4, DMA1_Stream5}, + {USART3, RCC_AHB1Periph_DMA1, 4, DMA1_Stream1}, + {UART4, RCC_AHB1Periph_DMA1, 4, DMA1_Stream2}, + {UART5, RCC_AHB1Periph_DMA1, 4, DMA1_Stream0}, + {USART6, RCC_AHB1Periph_DMA2, 5, DMA2_Stream1}, +#endif }; -void Serial_DMAClearFlag(uint8_t port) +// disable RX DMA and clear all interrupt flags for a serial port +void Serial_DMA_DisableAndClearFlagsRX(uint8_t port) { - switch(port) + Serial[port].dma_streamRX->CR &= ~(1<<0); // disable RX DMA + + // Stream # to bits: Low 0: 0-5, 1: 6-11, 2: 16-21, 3: 22-27 + // Stream # to bits: High 4: 0-5, 5: 6-11, 6: 16-21, 7: 22-27 + // + // DMA low/high interrupt Control Register (DMA_LIFCR/DMA_HIFCR) + + switch (port) { - case _USART1: DMA2->LIFCR = (0x3F << 16); break; // DMA2_Stream2 low bits:16-21 - case _USART2: DMA1->HIFCR = (0xFC << 4); break; // DMA1_Stream5 high bits: 6-11 - case _USART3: DMA1->LIFCR = (0xFC << 4); break; // DMA1_Stream1 low bits: 6-11 - case _UART4: DMA1->LIFCR = (0x3F << 16); break; // DMA1_Stream2 low bits:16-21 - case _UART5: DMA1->LIFCR = (0x3F << 0); break; // DMA1_Stream0 low bits: 0-5 - case _USART6: DMA2->LIFCR = (0xFC << 4); break; // DMA2_Stream1 low bits: 6-11 + case _USART1: + DMA2->LIFCR = (0x3F << 16); // DMA2_Stream2 low bits:16-21 Channel 4 + break; + + case _USART2: + DMA1->HIFCR = (0x3F << 6); // DMA1_Stream5 high bits: 6-11 Channel 4 + break; + + case _USART3: + DMA1->LIFCR = (0x3F << 6); // DMA1_Stream1 low bits: 6-11 Channel 4 + break; + + case _UART4: + DMA1->LIFCR = (0x3F << 16); // DMA1_Stream2 low bits:16-21 Channel 4 + break; + + case _UART5: + DMA1->LIFCR = (0x3F << 0); // DMA1_Stream0 low bits: 0-5 Channel 4 + break; + + case _USART6: + DMA2->LIFCR = (0x3F << 6); // DMA2_Stream1 low bits: 6-11 Channel 5 + break; + } +} + +#ifdef TX_DMA_WRITE + +// disable TX DMA and clear all interrupt flags for a serial port +void Serial_DMA_DisableAndClearFlagsTX(uint8_t port) +{ + Serial[port].dma_streamTX->CR &= ~(1<<0); // disable TX DMA + + // Stream # to bits: Low 0: 0-5, 1: 6-11, 2: 16-21, 3: 22-27 + // Stream # to bits: High 4: 0-5, 5: 6-11, 6: 16-21, 7: 22-27 + // + // DMA low/high interrupt Control Register (DMA_LIFCR/DMA_HIFCR) + + switch (port) + { + case _USART1: + DMA2->HIFCR = (0x3F << 22); // DMA2_Stream7 high bits:22-27 Channel 4 + break; + + case _USART2: + DMA1->HIFCR = (0x3F << 16); // DMA1_Stream6 high bits:16-21 Channel 4 + break; + + case _USART3: + DMA1->LIFCR = (0x3F << 22); // DMA1_Stream3 low bits:22-27 Channel 4 + break; + + case _UART4: + DMA1->HIFCR = (0x3F << 0); // DMA1_Stream4 high bits: 0- 5 Channel 4 + break; + + case _UART5: + DMA1->HIFCR = (0x3F << 22); // DMA1_Stream7 high bits:22-27 Channel 4 + break; + + case _USART6: + DMA2->HIFCR = (0x3F << 16); // DMA2_Stream6 high bits:16-21 Channel 5 + break; } } +#endif + void Serial_DMA_Config(uint8_t port) { const SERIAL_CFG * cfg = &Serial[port]; - RCC_AHB1PeriphClockCmd(cfg->dma_rcc, ENABLE); // DMA RCC EN + RCC_AHB1PeriphClockCmd(cfg->dma_rcc, ENABLE); // enable DMA clock + + Serial_DMA_DisableAndClearFlagsRX(port); // RX disable DMA and clear all interrupt flags + + cfg->dma_streamRX->PAR = (uint32_t)(&cfg->uart->DR); // RX peripheral address (usart) + cfg->dma_streamRX->M0AR = (uint32_t)(dmaL1DataRX[port].cache); // RX destination address (memory) + cfg->dma_streamRX->NDTR = dmaL1DataRX[port].cacheSize; // RX buffer size + + cfg->dma_streamRX->CR = (cfg->dma_channel << 25); // RX channel selection, set to 0 all the other CR bits + + // primary serial port priority at highest level (TX higher than RX) + if (port == SERIAL_PORT) + cfg->dma_streamRX->CR |= (2<<16); // RX priority level: High + else + cfg->dma_streamRX->CR |= (0<<16); // RX priority level: Low + +//cfg->dma_streamRX->CR &= ~(3<<13); // RX memory data size: 8 bit +//cfg->dma_streamRX->CR &= ~(3<<11); // RX peripheral data size: 8 bit + cfg->dma_streamRX->CR |= (1<<10); // RX memory increment mode +//cfg->dma_streamRX->CR &= ~(1<<9); // RX peripheral no increment mode + cfg->dma_streamRX->CR |= (1<<8); // RX circular mode enabled +//cfg->dma_streamRX->CR &= ~(1<<6); // RX data transfer direction: Peripheral-to-memory + +#ifdef TX_DMA_WRITE // TX DMA based serial writing + Serial_DMA_DisableAndClearFlagsTX(port); // TX disable DMA and clear all interrupt flags + + cfg->dma_streamTX->PAR = (uint32_t)(&cfg->uart->DR); // TX peripheral address (usart) - cfg->dma_stream->CR &= ~(1<<0); // Disable DMA - Serial_DMAClearFlag(port); - cfg->uart->CR3 |= 1<<6; // DMA enable receiver + cfg->dma_streamTX->CR = (cfg->dma_channel << 25); // TX channel selection, set to 0 all the other CR bits - cfg->dma_stream->PAR = (uint32_t)(&cfg->uart->DR); - cfg->dma_stream->M0AR = (uint32_t)(dmaL1Data[port].cache); - cfg->dma_stream->NDTR = dmaL1Data[port].cacheSize; + // primary serial port priority at highest level (TX higher than RX) + if (port == SERIAL_PORT) + cfg->dma_streamTX->CR |= (3<<16); // TX priority level: Very high + else + cfg->dma_streamTX->CR |= (1<<16); // TX priority level: Medium - cfg->dma_stream->CR = cfg->dma_channel << 25; - cfg->dma_stream->CR |= 3<<16; // Priority level: Very high - cfg->dma_stream->CR |= 0<<13; // Memory data size: 8 - cfg->dma_stream->CR |= 0<<11; // Peripheral data size: 8 - cfg->dma_stream->CR |= 1<<10; // Memory increment mode - cfg->dma_stream->CR |= 0<<9; // Peripheral not increment mode - cfg->dma_stream->CR |= 1<<8; // Circular mode enabled - cfg->dma_stream->CR |= 0<<6; // Data transfer direction: Peripheral-to-memory - cfg->dma_stream->CR |= 1<<0; // Enable DMA +//cfg->dma_streamTX->CR &= ~(3<<13); // TX memory data size: 8 bit +//cfg->dma_streamTX->CR &= ~(3<<11); // TX peripheral data size: 8 bit + cfg->dma_streamTX->CR |= (1<<10); // TX memory increment mode +//cfg->dma_streamTX->CR &= ~(1<<9); // TX peripheral no increment mode +//cfg->dma_streamTX->CR &= ~(1<<8); // TX circular mode disabled + cfg->dma_streamTX->CR |= (1<<6); // TX data transfer direction: Memory-to-Peripheral + + cfg->uart->CR3 |= (1<<7); // enable DMA transmitter (DMAT) +//cfg->dma_streamTX->CR |= (1<<0); // TX enable DMA, done later when needed +#endif + + cfg->uart->CR3 |= (1<<6); // enable DMA receiver (DMAR) + cfg->dma_streamRX->CR |= (1<<0); // RX enable DMA } void Serial_ClearData(uint8_t port) { - dmaL1Data[port].wIndex = dmaL1Data[port].rIndex = dmaL1Data[port].flag = dmaL1Data[port].cacheSize = 0; + dmaL1DataRX[port].wIndex = dmaL1DataRX[port].rIndex = dmaL1DataRX[port].flag = dmaL1DataRX[port].cacheSize = 0; + + if (dmaL1DataRX[port].cache != NULL) + { + free(dmaL1DataRX[port].cache); + dmaL1DataRX[port].cache = NULL; + } - if (dmaL1Data[port].cache != NULL) + dmaL1DataTX[port].wIndex = dmaL1DataTX[port].rIndex = dmaL1DataTX[port].flag = dmaL1DataTX[port].cacheSize = 0; + + if (dmaL1DataTX[port].cache != NULL) { - free(dmaL1Data[port].cache); - dmaL1Data[port].cache = NULL; + free(dmaL1DataTX[port].cache); + dmaL1DataTX[port].cache = NULL; } } -void Serial_Config(uint8_t port, uint16_t cacheSize, uint32_t baudrate) +void Serial_Config(uint8_t port, uint32_t cacheSizeRX, uint32_t cacheSizeTX, uint32_t baudrate) { Serial_ClearData(port); - dmaL1Data[port].cacheSize = cacheSize; - dmaL1Data[port].cache = malloc(cacheSize); - while (!dmaL1Data[port].cache); // malloc failed + dmaL1DataRX[port].cacheSize = cacheSizeRX; + dmaL1DataRX[port].cache = malloc(cacheSizeRX); + while (!dmaL1DataRX[port].cache); // RX malloc failed, blocking! + + dmaL1DataTX[port].cacheSize = cacheSizeTX; + dmaL1DataTX[port].cache = malloc(cacheSizeTX); + while (!dmaL1DataTX[port].cache); // TX malloc failed, blocking! + + UART_Config(port, baudrate, USART_IT_IDLE, IDLE_LINE_IT); // configure serial line with or without IDLE Line interrupt - UART_Config(port, baudrate, USART_IT_IDLE); // IDLE interrupt Serial_DMA_Config(port); } @@ -95,20 +215,123 @@ void Serial_DeConfig(uint8_t port) { Serial_ClearData(port); - Serial[port].dma_stream->CR &= ~(1<<0); // Disable DMA - Serial_DMAClearFlag(port); + Serial_DMA_DisableAndClearFlagsRX(port); // disable RX DMA and clear all interrupt flags + +#ifdef TX_DMA_WRITE + Serial_DMA_DisableAndClearFlagsTX(port); // disable TX DMA and clear all interrupt flags +#endif + UART_DeConfig(port); } +#ifdef TX_DMA_WRITE // TX DMA based serial writing + +// DMA serial write support function +void Serial_Send_TX(uint8_t port) +{ + // setup DMA transfer, and wait for serial Transfer Complete (TX) interrupt in ISR + if (dmaL1DataTX[port].wIndex >= dmaL1DataTX[port].rIndex) + dmaL1DataTX[port].flag = dmaL1DataTX[port].wIndex - dmaL1DataTX[port].rIndex; + else // split transfer into 2 parts + dmaL1DataTX[port].flag = dmaL1DataTX[port].cacheSize - dmaL1DataTX[port].rIndex; + + Serial_DMA_DisableAndClearFlagsTX(port); // disable TX DMA and clear all interrupt flags + + Serial[port].dma_streamTX->M0AR = (uint32_t)(&dmaL1DataTX[port].cache[dmaL1DataTX[port].rIndex]); // TX data start address + Serial[port].dma_streamTX->NDTR = dmaL1DataTX[port].flag; // number of bytes to transfer + + Serial[port].uart->CR1 |= USART_CR1_TCIE; // enable Transfer Complete (TC) serial interrupt + Serial[port].dma_streamTX->CR |= (1<<0); // enable TX DMA +} + +void Serial_Put(uint8_t port, const char * msg) +{ + while (*msg) + { + // setup TX DMA, if no '\n' yet, but buffer is full AND DMA is not in progress already (waiting for Transfer Complete interrupt) + if ((((dmaL1DataTX[port].wIndex + 1) % dmaL1DataTX[port].cacheSize) == dmaL1DataTX[port].rIndex) && + ((Serial[port].uart->CR1 & USART_CR1_TCIE) == 0)) + Serial_Send_TX(port); + + // blocking! wait for buffer to become available + while (((dmaL1DataTX[port].wIndex + 1) % dmaL1DataTX[port].cacheSize) == dmaL1DataTX[port].rIndex) { } + + dmaL1DataTX[port].cache[dmaL1DataTX[port].wIndex] = *msg; // copy character to cache + dmaL1DataTX[port].wIndex = (dmaL1DataTX[port].wIndex + 1) % dmaL1DataTX[port].cacheSize; // update wIndex + + if ((*msg == '\n') && ((Serial[port].uart->CR1 & USART_CR1_TCIE) == 0)) + Serial_Send_TX(port); // start DMA process if command is complete and DMA is not in progress already + + msg++; // let the compiler optimize this, no need to do it manually! + } +} + +#else // TX interrupt based serial writing + +void Serial_Put(uint8_t port, const char * msg) +{ + while (*msg) + { + // blocking! wait for buffer to become available + while (((dmaL1DataTX[port].wIndex + 1) % dmaL1DataTX[port].cacheSize) == dmaL1DataTX[port].rIndex) { }; + + dmaL1DataTX[port].cache[dmaL1DataTX[port].wIndex] = *msg++; + dmaL1DataTX[port].wIndex = (dmaL1DataTX[port].wIndex + 1) % dmaL1DataTX[port].cacheSize; + + Serial[port].uart->CR1 |= USART_CR1_TXEIE; // set TXE interrupt bit to start the serial transfer + } +} + +#endif + +// ISR, serial interrupt handler void USART_IRQHandler(uint8_t port) { - if ((Serial[port].uart->SR & (1<<4)) != 0) +#if IDLE_LINE_IT == true // IDLE Line interrupt + if ((Serial[port].uart->SR & USART_SR_IDLE) != RESET) // check for IDLE Line interrupt { - Serial[port].uart->SR; + Serial[port].uart->SR; // clear IDLE Line bit Serial[port].uart->DR; - dmaL1Data[port].wIndex = dmaL1Data[port].cacheSize - Serial[port].dma_stream->NDTR; + dmaL1DataRX[port].wIndex = dmaL1DataRX[port].cacheSize - Serial[port].dma_streamRX->NDTR; + } +#endif + +#ifdef TX_DMA_WRITE // TX DMA based serial writing + if ((Serial[port].uart->SR & USART_SR_TC) != RESET) // check for Transfer Complete (TC) interrupt + { + Serial[port].uart->SR &= ~USART_SR_TC; // clear Transfer Complete (TC) bit + + // NOTE 1: use the serial TC, not the DMA TC because this only indicates DMA is done, peripheral might be still busy + // NOTE 2: the TC interrupt is sometimes called while DMA is still active, so check NDTR status! + // + if (Serial[port].dma_streamTX->NDTR == 0) // sending is complete + { + dmaL1DataTX[port].rIndex = (dmaL1DataTX[port].rIndex + dmaL1DataTX[port].flag) % dmaL1DataTX[port].cacheSize; + dmaL1DataTX[port].flag = 0; + + if (dmaL1DataTX[port].wIndex != dmaL1DataTX[port].rIndex) // is more data available? + Serial_Send_TX(port); // continue sending data + else + Serial[port].uart->CR1 &= ~USART_CR1_TCIE; // disable Transfer Complete (TC) interrupt, nothing more to do + } + // else: more data is coming, wait for next Transfer Complete (TC) interrupt + } +#else // TX interrupt based serial writing + if ((Serial[port].uart->SR & USART_SR_TXE) != RESET) // check for TXE interrupt + { + if (dmaL1DataTX[port].rIndex == dmaL1DataTX[port].wIndex) // no more data? + { + Serial[port].uart->CR1 &= ~USART_CR1_TXEIE; // disable TXE interrupt + } + else + { + Serial[port].uart->DR = (uint8_t)dmaL1DataTX[port].cache[dmaL1DataTX[port].rIndex]; // write next available character + + dmaL1DataTX[port].rIndex = (dmaL1DataTX[port].rIndex + 1) % dmaL1DataTX[port].cacheSize; // increase reading index + } } +#endif } void USART1_IRQHandler(void) @@ -140,18 +363,3 @@ void USART6_IRQHandler(void) { USART_IRQHandler(_USART6); } - -void Serial_Put(uint8_t port, const char *s) -{ - while (*s) - { - while ((Serial[port].uart->SR & USART_FLAG_TC) == (uint16_t)RESET); - Serial[port].uart->DR = ((uint16_t)*s++ & (uint16_t)0x01FF); - } -} - -void Serial_PutChar(uint8_t port, const char ch) -{ - while ((Serial[port].uart->SR & USART_FLAG_TC) == (uint16_t)RESET); - Serial[port].uart->DR = (uint8_t) ch; -} diff --git a/TFT/src/User/Hal/stm32f2_f4xx/Serial.h b/TFT/src/User/Hal/stm32f2_f4xx/Serial.h index ca48a22082..a1bdbdc6c8 100644 --- a/TFT/src/User/Hal/stm32f2_f4xx/Serial.h +++ b/TFT/src/User/Hal/stm32f2_f4xx/Serial.h @@ -5,20 +5,58 @@ #include "variants.h" // for uint32_t etc... #include "uart.h" -typedef volatile struct // precautionally declared as volatile due to access from interrupt handler and main thread +// comment out this line to use TX interrupt based serial writing instead of TX DMA based serial writing +#define TX_DMA_WRITE + +typedef struct { - char *cache; - uint16_t wIndex; // writing index - uint16_t rIndex; // reading index - uint16_t flag; // custom flag (for custom usage by the application) - uint16_t cacheSize; + char * cache; + uint32_t cacheSize; + volatile uint32_t wIndex; // writing index + volatile uint32_t rIndex; // reading index + volatile uint32_t flag; // custom flag (for custom usage by the application) } DMA_CIRCULAR_BUFFER; -extern DMA_CIRCULAR_BUFFER dmaL1Data[_UART_CNT]; +// config for USART DMA channels +typedef struct +{ + USART_TypeDef * uart; // uint32_t + uint32_t dma_rcc; + uint32_t dma_channel; + DMA_Stream_TypeDef * dma_streamRX; // uint32_t +#ifdef TX_DMA_WRITE + DMA_Stream_TypeDef * dma_streamTX; // uint32_t +#endif +} SERIAL_CFG; -void Serial_Config(uint8_t port, uint16_t cacheSize, uint32_t baudrate); +extern DMA_CIRCULAR_BUFFER dmaL1DataRX[_UART_CNT]; +extern const SERIAL_CFG Serial[_UART_CNT]; + +void Serial_Config(uint8_t port, uint32_t cacheSizeRX, uint32_t cacheSizeTX, uint32_t baudrate); void Serial_DeConfig(uint8_t port); -void Serial_Put(uint8_t port, const char *s); -void Serial_PutChar(uint8_t port, const char ch); + +// retrieve the next reading index in the RX message queue of the provided physical serial port: +// - port: physical serial port +// +// - return value: next reading index +static inline uint32_t Serial_GetReadingIndexRX(uint8_t port) +{ + return dmaL1DataRX[port].rIndex; +} + +// retrieve the next writing index in the RX message queue of the provided physical serial port +// based on Interrupt/DMA status while writing serial data in the background: +// - port: physical serial port +// +// - return value: next writing index +static inline uint32_t Serial_GetWritingIndexRX(uint8_t port) +{ + return dmaL1DataRX[port].cacheSize - Serial[port].dma_streamRX->NDTR; +} + +// send a zero terminated message to UART port +// - port: index of serial port +// - msg: message to send +void Serial_Put(uint8_t port, const char * msg); #endif diff --git a/TFT/src/User/Hal/stm32f2_f4xx/uart.c b/TFT/src/User/Hal/stm32f2_f4xx/uart.c index 91cc6c22f0..b2878583ea 100644 --- a/TFT/src/User/Hal/stm32f2_f4xx/uart.c +++ b/TFT/src/User/Hal/stm32f2_f4xx/uart.c @@ -98,7 +98,7 @@ void UART_GPIO_DeInit(uint8_t port) GPIO_InitSet(uart_rx[port], MGPIO_MODE_IPN, 0); } -void UART_Protocol_Init(uint8_t port,uint32_t baud) +void UART_Protocol_Init(uint8_t port, uint32_t baud) { USART_InitTypeDef USART_InitStructure; @@ -115,7 +115,7 @@ void UART_Protocol_Init(uint8_t port,uint32_t baud) USART_Cmd(uart[port],ENABLE); } -void UART_IRQ_Init(uint8_t port, uint16_t usart_it) +void UART_IRQ_Init(uint8_t port, uint16_t usart_it, FunctionalState idle_interrupt) { uint32_t IRQ_Channel[_UART_CNT] = {USART1_IRQn, USART2_IRQn, USART3_IRQn, UART4_IRQn, UART5_IRQn, USART6_IRQn}; @@ -126,14 +126,14 @@ void UART_IRQ_Init(uint8_t port, uint16_t usart_it) NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_Init(&NVIC_InitStructure); - USART_ITConfig(uart[port], usart_it, ENABLE); + USART_ITConfig(uart[port], usart_it, idle_interrupt); // enable or disable serial line IDLE interrupt USART_ClearITPendingBit(uart[port], usart_it); } -void UART_Config(uint8_t port, uint32_t baud, uint16_t usart_it) +void UART_Config(uint8_t port, uint32_t baud, uint16_t usart_it, bool idle_interrupt) { UART_Protocol_Init(port, baud); - UART_IRQ_Init(port, usart_it); + UART_IRQ_Init(port, usart_it, idle_interrupt ? ENABLE : DISABLE); UART_GPIO_Init(port); // After all initialization is completed, enable IO, otherwise a 0xFF will be sent automatically after power-on } @@ -150,6 +150,7 @@ void UART_Write(uint8_t port, uint8_t d) while ((uart[port]->SR & USART_FLAG_TC) == (uint16_t)RESET); uart[port]->DR = ((uint16_t)d & (uint16_t)0x01FF); } + void UART_Puts(uint8_t port, uint8_t *str) { while (*str) diff --git a/TFT/src/User/Hal/stm32f2_f4xx/uart.h b/TFT/src/User/Hal/stm32f2_f4xx/uart.h index 9f50557035..4230750ff6 100644 --- a/TFT/src/User/Hal/stm32f2_f4xx/uart.h +++ b/TFT/src/User/Hal/stm32f2_f4xx/uart.h @@ -1,6 +1,7 @@ #ifndef _UART_H_ #define _UART_H_ +#include #include #define _USART1 0 @@ -11,7 +12,7 @@ #define _USART6 5 #define _UART_CNT 6 -void UART_Config(uint8_t port, uint32_t baud, uint16_t usart_it); +void UART_Config(uint8_t port, uint32_t baud, uint16_t usart_it, bool idle_interrupt); void UART_DeConfig(uint8_t port); void UART_Puts(uint8_t port, uint8_t *str); void UART_Write(uint8_t port, uint8_t d); diff --git a/TFT/src/User/Menu/Monitoring.c b/TFT/src/User/Menu/Monitoring.c index b8dc42f618..6804222a60 100644 --- a/TFT/src/User/Menu/Monitoring.c +++ b/TFT/src/User/Menu/Monitoring.c @@ -23,7 +23,9 @@ void menuMonitoring(void) GUI_DispString(0, ICON_START_Y, (uint8_t *)"Buffered gcodes : "); GUI_DispString(0, ICON_START_Y + 1 * (BYTE_HEIGHT + 4), (uint8_t *)"Pending gcodes : "); GUI_DispString(0, ICON_START_Y + 2 * (BYTE_HEIGHT + 4), (uint8_t *)"Free TX slots : "); - GUI_DispString(0, ICON_START_Y + 3 * (BYTE_HEIGHT + 4), (uint8_t *)"Scan rate : "); + GUI_DispString(0, ICON_START_Y + 3 * (BYTE_HEIGHT + 4), (uint8_t *)"TX gcodes/bytes : "); + GUI_DispString(0, ICON_START_Y + 4 * (BYTE_HEIGHT + 4), (uint8_t *)"RX acks/bytes : "); + GUI_DispString(0, ICON_START_Y + 5 * (BYTE_HEIGHT + 4), (uint8_t *)"Scan rate : "); // draw bottom line and text GUI_HLine(0, LCD_HEIGHT - (BYTE_HEIGHT*2), LCD_WIDTH); @@ -48,9 +50,15 @@ void menuMonitoring(void) sprintf(str, "%d ", infoHost.tx_slots); GUI_DispString(18 * BYTE_WIDTH, ICON_START_Y + 2 * (BYTE_HEIGHT + 4), (uint8_t *)str); - sprintf(str, "%d ", infoMonitoring.scan_rate_per_second); + sprintf(str, "%d/%d ", infoMonitoring.tx_cmd_rate, infoMonitoring.tx_bytes_rate); GUI_DispString(18 * BYTE_WIDTH, ICON_START_Y + 3 * (BYTE_HEIGHT + 4), (uint8_t *)str); + sprintf(str, "%d/%d ", infoMonitoring.rx_ack_rate, infoMonitoring.rx_bytes_rate); + GUI_DispString(18 * BYTE_WIDTH, ICON_START_Y + 4 * (BYTE_HEIGHT + 4), (uint8_t *)str); + + sprintf(str, "%d ", infoMonitoring.scan_rate_per_second); + GUI_DispString(18 * BYTE_WIDTH, ICON_START_Y + 5 * (BYTE_HEIGHT + 4), (uint8_t *)str); + GUI_RestoreColorDefault(); } diff --git a/TFT/src/User/Menu/Monitoring.h b/TFT/src/User/Menu/Monitoring.h index e561f5a358..a79341d25f 100644 --- a/TFT/src/User/Menu/Monitoring.h +++ b/TFT/src/User/Menu/Monitoring.h @@ -10,23 +10,54 @@ extern "C" { #ifdef DEBUG_MONITORING typedef struct { + uint32_t tx_cmd; + uint32_t tx_cmd_rate; + uint32_t tx_bytes; + uint32_t tx_bytes_rate; + uint32_t rx_ack; + uint32_t rx_ack_rate; + uint32_t rx_bytes; + uint32_t rx_bytes_rate; uint32_t scan_rate_counter; uint32_t scan_rate_per_second; } MONITORING; extern MONITORING infoMonitoring; + #define UPD_TX_KPIS(bytes) \ + { \ + infoMonitoring.tx_cmd++; \ + infoMonitoring.tx_bytes += bytes; \ + } + + #define UPD_RX_KPIS(bytes) \ + { \ + infoMonitoring.rx_ack++; \ + infoMonitoring.rx_bytes += bytes; \ + } + #define UPD_SCAN_RATE() infoMonitoring.scan_rate_counter++ - #define AVG_SCAN_RATE() \ + + #define AVG_KPIS() \ { \ + infoMonitoring.tx_cmd_rate = infoMonitoring.tx_cmd; \ + infoMonitoring.tx_cmd = 0; \ + infoMonitoring.tx_bytes_rate = infoMonitoring.tx_bytes; \ + infoMonitoring.tx_bytes = 0; \ + infoMonitoring.rx_ack_rate = infoMonitoring.rx_ack; \ + infoMonitoring.rx_ack = 0; \ + infoMonitoring.rx_bytes_rate = infoMonitoring.rx_bytes; \ + infoMonitoring.rx_bytes = 0; \ infoMonitoring.scan_rate_per_second = infoMonitoring.scan_rate_counter; \ infoMonitoring.scan_rate_counter = 0; \ } void menuMonitoring(void); #else + #define UPD_TX_KPIS(bytes) + #define UPD_RX_KPIS(bytes) #define UPD_SCAN_RATE() - #define AVG_SCAN_RATE() + #define AVG_KPIS() #endif #ifdef __cplusplus diff --git a/TFT/src/User/Menu/Popup.c b/TFT/src/User/Menu/Popup.c index ca07b9ad17..181c0380b2 100644 --- a/TFT/src/User/Menu/Popup.c +++ b/TFT/src/User/Menu/Popup.c @@ -62,6 +62,8 @@ void windowReDrawButton(uint8_t position, uint8_t pressed) void popupDrawPage(DIALOG_TYPE type, BUTTON * btn, const uint8_t * title, const uint8_t * context, const uint8_t * yes, const uint8_t * no) { + window.type = type; // window.type is used by GUI_DrawWindow() function so it must be set before the function invokation + if (btn != NULL) { buttonNum = 0; @@ -90,7 +92,6 @@ void popupDrawPage(DIALOG_TYPE type, BUTTON * btn, const uint8_t * title, const } TSC_ReDrawIcon = windowReDrawButton; - window.type = type; } void menuDialog(void) diff --git a/TFT/src/User/config.ini b/TFT/src/User/config.ini index 969a6421f2..9d189db678 100644 --- a/TFT/src/User/config.ini +++ b/TFT/src/User/config.ini @@ -103,11 +103,11 @@ # P2: WIFI (e.g. ESP3D) # P3: UART 3 (e.g. OctoPrint) # P4: UART 4 -# Value range: P1: [min: 1, max: 9] -# P2: [min: 0, max: 9] -# P3: [min: 0, max: 9] -# P4: [min: 0, max: 9] -# Options: [OFF (port disabled): 0, 2400: 1, 9600: 2, 19200: 3, 38400: 4, 57600: 5, 115200: 6, 250000: 7, 500000: 8, 1000000: 9] +# Value range: P1: [min: 1, max: 11] +# P2: [min: 0, max: 11] +# P3: [min: 0, max: 11] +# P4: [min: 0, max: 11] +# Options: [OFF (port disabled): 0, 2400: 1, 9600: 2, 19200: 3, 38400: 4, 57600: 5, 115200: 6, 230400: 7, 250000: 8, 500000: 9, 921600: 10, 1000000: 11] serial_port:P1:6 P2:0 P3:0 P4:0 #### TX Slots diff --git a/TFT/src/User/config_rrf.ini b/TFT/src/User/config_rrf.ini index 02d29b005c..818af634c2 100644 --- a/TFT/src/User/config_rrf.ini +++ b/TFT/src/User/config_rrf.ini @@ -66,11 +66,11 @@ # P2: WIFI (e.g. ESP3D) # P3: UART 3 (e.g. OctoPrint) # P4: UART 4 -# Value range: P1: [min: 1, max: 9] -# P2: [min: 0, max: 9] -# P3: [min: 0, max: 9] -# P4: [min: 0, max: 9] -# Options: [OFF (port disabled): 0, 2400: 1, 9600: 2, 19200: 3, 38400: 4, 57600: 5, 115200: 6, 250000: 7, 500000: 8, 1000000: 9] +# Value range: P1: [min: 1, max: 11] +# P2: [min: 0, max: 11] +# P3: [min: 0, max: 11] +# P4: [min: 0, max: 11] +# Options: [OFF (port disabled): 0, 2400: 1, 9600: 2, 19200: 3, 38400: 4, 57600: 5, 115200: 6, 230400: 7, 250000: 8, 500000: 9, 921600: 10, 1000000: 11] serial_port:P1:5 P2:0 P3:0 P4:0 #### TX Slots diff --git a/TFT/src/User/main.c b/TFT/src/User/main.c index 4e90bae894..258455cd1c 100644 --- a/TFT/src/User/main.c +++ b/TFT/src/User/main.c @@ -1,9 +1,11 @@ #include "main.h" #include "includes.h" -MENU infoMenu; // Menu structure -HOST infoHost; // Information interaction with Marlin -CLOCKS mcuClocks; // System clocks: SYSCLK, AHB, APB1, APB2, APB1_Timer, APB2_Timer2 +MENU infoMenu; // Menu structure +HOST infoHost; // Information interaction with Marlin +CLOCKS mcuClocks; // System clocks: SYSCLK, AHB, APB1, APB2, APB1_Timer, APB2_Timer2 +uint32_t bePriorityCounter; // Back end priority counter +uint32_t fePriorityCounter; // Front end priority counter void InfoHost_Init(bool isConnected) { diff --git a/TFT/src/User/main.h b/TFT/src/User/main.h index 4329f6524e..91ecbf248a 100644 --- a/TFT/src/User/main.h +++ b/TFT/src/User/main.h @@ -10,7 +10,9 @@ extern "C" { #include "variants.h" // for RCC_ClocksTypeDef #include "uart.h" // for _UART_CNT -#define MAX_MENU_DEPTH 10 // max sub menu depth +#define MAX_MENU_DEPTH 10 // max sub menu depth +#define BE_PRIORITY_DIVIDER 16 // a divider value of 16 -> run 6% of the time only. Use a power of 2 for performance reasons! +#define FE_PRIORITY_DIVIDER 16 // a divider value of 16 -> run 6% of the time only. Use a power of 2 for performance reasons! typedef void (* FP_MENU)(void); @@ -54,6 +56,8 @@ typedef struct extern MENU infoMenu; extern HOST infoHost; extern CLOCKS mcuClocks; +extern uint32_t bePriorityCounter; +extern uint32_t fePriorityCounter; void InfoHost_Init(bool isConnected); void InfoHost_UpdateListeningMode(void); diff --git a/TFT/src/User/os_timer.c b/TFT/src/User/os_timer.c index f72e407f22..c09816a9e9 100644 --- a/TFT/src/User/os_timer.c +++ b/TFT/src/User/os_timer.c @@ -43,8 +43,9 @@ void TIMER6_IRQHandler(void) if (os_counter % 1000 == 0) { + AVG_KPIS(); // debug monitoring KPI + updatePrintTime(); - AVG_SCAN_RATE(); // debug monitoring KPI } loopTouchScreen(); @@ -61,8 +62,9 @@ void TIM7_IRQHandler(void) if (os_counter % 1000 == 0) { + AVG_KPIS(); // debug monitoring KPI + updatePrintTime(); - AVG_SCAN_RATE(); // debug monitoring KPI } loopTouchScreen();