From d014d8055fe37001b8a6a9d1cc410e5239d08c22 Mon Sep 17 00:00:00 2001 From: Rob Riggs Date: Mon, 24 Feb 2020 22:36:09 -0600 Subject: [PATCH] Convert serial RX code to DMA to improve reliability, throughput and flow control. --- Src/stm32l4xx_it.c | 8 ++ TNC/HdlcFrame.hpp | 2 + TNC/SerialPort.cpp | 270 ++++++++++++++++++++++++++++++++++----------- 3 files changed, 214 insertions(+), 66 deletions(-) diff --git a/Src/stm32l4xx_it.c b/Src/stm32l4xx_it.c index b8bf14e..6f28f29 100644 --- a/Src/stm32l4xx_it.c +++ b/Src/stm32l4xx_it.c @@ -41,6 +41,8 @@ #include "main.h" extern osMessageQId ioEventQueueHandle; +void idleInterruptCallback(UART_HandleTypeDef* huart); + /* USER CODE END 0 */ /* External variables --------------------------------------------------------*/ @@ -386,6 +388,12 @@ void USART3_IRQHandler(void) { /* USER CODE BEGIN USART3_IRQn 0 */ + if (__HAL_UART_GET_FLAG(&huart3, UART_FLAG_IDLE)) { + idleInterruptCallback(&huart3); + __HAL_UART_CLEAR_FLAG(&huart3, UART_FLAG_IDLE); + return; + } + /* USER CODE END USART3_IRQn 0 */ HAL_UART_IRQHandler(&huart3); /* USER CODE BEGIN USART3_IRQn 1 */ diff --git a/TNC/HdlcFrame.hpp b/TNC/HdlcFrame.hpp index d67be06..7b161fc 100644 --- a/TNC/HdlcFrame.hpp +++ b/TNC/HdlcFrame.hpp @@ -162,6 +162,8 @@ public: uint16_t size() const {return free_list_.size();} + static constexpr uint16_t capacity() { return SIZE; } + frame_type* acquire() { frame_type* result = nullptr; auto x = taskENTER_CRITICAL_FROM_ISR(); diff --git a/TNC/SerialPort.cpp b/TNC/SerialPort.cpp index 36685bd..b3a37d6 100644 --- a/TNC/SerialPort.cpp +++ b/TNC/SerialPort.cpp @@ -13,20 +13,28 @@ #include "cmsis_os.h" #include +#include #include #include extern UART_HandleTypeDef huart3; extern osMessageQId ioEventQueueHandle; -uint8_t rxBuffer; -std::atomic uart_error{HAL_UART_ERROR_NONE}; +std::atomic uart_error{HAL_UART_ERROR_NONE}; std::atomic txDoneFlag{true}; uint8_t tmpBuffer[mobilinkd::tnc::TX_BUFFER_SIZE]; uint8_t tmpBuffer2[mobilinkd::tnc::TX_BUFFER_SIZE]; +constexpr const int RX_BUFFER_SIZE = 127; +unsigned char rxBuffer[RX_BUFFER_SIZE * 2]; + +// 3 chunks of 128 bytes. The first byte in each chunk is the length. +typedef mobilinkd::tnc::memory::Pool< + 3, RX_BUFFER_SIZE + 1> serial_pool_type; +serial_pool_type serialPool; + void log_frame(mobilinkd::tnc::hdlc::IoFrame* frame) { int pos = 0; @@ -41,7 +49,56 @@ void log_frame(mobilinkd::tnc::hdlc::IoFrame* frame) DEBUG((char*)tmpBuffer2); } -extern "C" void startSerialTask(void const* arg) +// HAL does not have +HAL_StatusTypeDef UART_DMAPauseReceive(UART_HandleTypeDef *huart) +{ + /* Process Locked */ + __HAL_LOCK(huart); + + if ((huart->RxState == HAL_UART_STATE_BUSY_RX) && + (HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))) + { + /* Disable PE and ERR (Frame error, noise error, overrun error) interrupts */ + CLEAR_BIT(huart->Instance->CR1, USART_CR1_PEIE); + CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE); + + /* Disable the UART DMA Rx request */ + CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR); + } + + /* Process Unlocked */ + __HAL_UNLOCK(huart); + + return HAL_OK; +} + +HAL_StatusTypeDef UART_DMAResumeReceive(UART_HandleTypeDef *huart) +{ + /* Process Locked */ + __HAL_LOCK(huart); + + if (huart->RxState == HAL_UART_STATE_BUSY_RX) + { + /* Clear the Overrun flag before resuming the Rx transfer */ + __HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_OREF); + + /* Reenable PE and ERR (Frame error, noise error, overrun error) interrupts */ + SET_BIT(huart->Instance->CR1, USART_CR1_PEIE); + SET_BIT(huart->Instance->CR3, USART_CR3_EIE); + + /* Enable the UART DMA Rx request */ + SET_BIT(huart->Instance->CR3, USART_CR3_DMAR); + } + + /* Process Unlocked */ + __HAL_UNLOCK(huart); + + return HAL_OK; +} + +extern "C" void startSerialTask(void const* arg) __attribute__((optimize("-O1"))); + +void startSerialTask(void const* arg) { using namespace mobilinkd::tnc; @@ -58,90 +115,116 @@ extern "C" void startSerialTask(void const* arg) hdlc::IoFrame* frame = hdlc::acquire_wait(); - HAL_UART_Receive_IT(&huart3, &rxBuffer, 1); + HAL_UART_Receive_DMA(&huart3, rxBuffer, RX_BUFFER_SIZE * 2); + __HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE); uint32_t last_sent_time = osKernelSysTick(); uint32_t current_sent_time = 0; + bool paused = false; while (true) { osEvent evt = osMessageGet(serialPort->queue(), osWaitForever); if (evt.status != osEventMessage) { - HAL_UART_Receive_IT(&huart3, &rxBuffer, 1); continue; } - if (evt.value.v & 0x100) { + if (evt.value.v < FLASH_BASE) // Assumes FLASH_BASE < SRAM_BASE. + { + // Error received. hdlc::release(frame); - ERROR("UART Error: %08lx", evt.value.v); + ERROR("UART Error: %08lx", uart_error.load()); uart_error.store(HAL_UART_ERROR_NONE); frame = hdlc::acquire_wait(); - HAL_UART_Receive_IT(&huart3, &rxBuffer, 1); + HAL_UART_Receive_DMA(&huart3, rxBuffer, RX_BUFFER_SIZE * 2); + __HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE); continue; } - uint8_t c = evt.value.v; - switch (state) { - case WAIT_FBEGIN: - if (c == FEND) state = WAIT_FRAME_TYPE; - break; - case WAIT_FRAME_TYPE: - if (c == FEND) break; // Still waiting for FRAME_TYPE. - frame->type(c); - state = WAIT_FEND; - break; - case WAIT_FEND: - switch (c) { - case FESC: - state = WAIT_ESCAPED; + auto block = (serial_pool_type::chunk_type*) evt.value.p; + auto data = static_cast(block->buffer); + + uint8_t end = data[0] + 1; + for (uint8_t i = 1; i != end; ++i) { + uint8_t c = data[i]; + switch (state) { + case WAIT_FBEGIN: + if (c == FEND) state = WAIT_FRAME_TYPE; break; - case FEND: - frame->source(hdlc::IoFrame::SERIAL_DATA); - if (osMessagePut(ioEventQueueHandle, reinterpret_cast(frame), osWaitForever) != osOK) - { - hdlc::release(frame); + case WAIT_FRAME_TYPE: + if (c == FEND) break; // Still waiting for FRAME_TYPE. + if (c < 8 or c == 0xFF) { + frame->type(c); + state = WAIT_FEND; + } else { + WARN("Bad frame type"); + state = WAIT_FBEGIN; } - current_sent_time = osKernelSysTick(); - if (last_sent_time + 50 > current_sent_time) { - uint32_t delay = (last_sent_time + 50) - current_sent_time; - osDelay(delay); - } - last_sent_time = current_sent_time; - frame = hdlc::acquire_wait(); - state = WAIT_FBEGIN; break; - default: - if (not frame->push_back(c)) { + case WAIT_FEND: + switch (c) { + case FESC: + state = WAIT_ESCAPED; + break; + case FEND: + frame->source(hdlc::IoFrame::SERIAL_DATA); + if (osMessagePut( + ioEventQueueHandle, + reinterpret_cast(frame), + osWaitForever) != osOK) + { + WARN("Failed to send serial frame"); + hdlc::release(frame); + } + + if (hdlc::ioFramePool().size() < (hdlc::ioFramePool().capacity() / 4)) + { + UART_DMAPauseReceive(&huart3); + WARN("Pausing UART RX"); + while (hdlc::ioFramePool().size() < (hdlc::ioFramePool().capacity() / 2)) + { + osThreadYield(); + } + UART_DMAResumeReceive(&huart3); + } + + frame = hdlc::acquire_wait(); + state = WAIT_FBEGIN; + break; + default: + if (not frame->push_back(c)) { + hdlc::release(frame); + state = WAIT_FBEGIN; // Drop frame; + frame = hdlc::acquire_wait(); + } + } + break; + case WAIT_ESCAPED: + state = WAIT_FEND; + switch (c) { + case TFESC: + if (not frame->push_back(FESC)) { + hdlc::release(frame); + state = WAIT_FBEGIN; // Drop frame; + frame = hdlc::acquire_wait(); + } + break; + case TFEND: + if (not frame->push_back(FEND)) { + hdlc::release(frame); + state = WAIT_FBEGIN; // Drop frame; + frame = hdlc::acquire_wait(); + } + break; + default: hdlc::release(frame); state = WAIT_FBEGIN; // Drop frame; frame = hdlc::acquire_wait(); } + break; } - break; - case WAIT_ESCAPED: - state = WAIT_FEND; - switch (c) { - case TFESC: - if (not frame->push_back(FESC)) { - hdlc::release(frame); - state = WAIT_FBEGIN; // Drop frame; - frame = hdlc::acquire_wait(); - } - break; - case TFEND: - if (not frame->push_back(FEND)) { - hdlc::release(frame); - state = WAIT_FBEGIN; // Drop frame; - frame = hdlc::acquire_wait(); - } - break; - default: - hdlc::release(frame); - state = WAIT_FBEGIN; // Drop frame; - frame = hdlc::acquire_wait(); - } - break; } + serialPool.deallocate(block); } } @@ -151,18 +234,73 @@ extern "C" void HAL_UART_TxCpltCallback(UART_HandleTypeDef*) txDoneFlag = true; } -extern "C" void HAL_UART_RxCpltCallback(UART_HandleTypeDef *) +extern "C" void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart) { - osMessagePut(mobilinkd::tnc::getSerialPort()->queue(), (uint32_t) rxBuffer, 0); + uint32_t len = (RX_BUFFER_SIZE * 2) - __HAL_DMA_GET_COUNTER(huart->hdmarx); + if (len == 0) return; + if (len > RX_BUFFER_SIZE) { + len = RX_BUFFER_SIZE; // wrapped. + } - HAL_UART_Receive_IT(&huart3, &rxBuffer, 1); + auto block = serialPool.allocate(); + if (!block) return; + memmove(block->buffer + 1, rxBuffer, len); + block->buffer[0] = len; + auto status = osMessagePut(mobilinkd::tnc::getSerialPort()->queue(), (uint32_t) block, 0); + if (status != osOK) serialPool.deallocate(block); } +extern "C" void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) +{ + uint32_t hdmarx = __HAL_DMA_GET_COUNTER(huart->hdmarx); + uint32_t len = 0; + if (hdmarx > RX_BUFFER_SIZE) { + len = RX_BUFFER_SIZE; // wrapped. + } else { + len = RX_BUFFER_SIZE - hdmarx; + } + if (len == 0) return; + + auto block = serialPool.allocate(); + if (!block) return; + memmove(block->buffer + 1, rxBuffer + RX_BUFFER_SIZE, len); + block->buffer[0] = len; + auto status = osMessagePut(mobilinkd::tnc::getSerialPort()->queue(), (uint32_t) block, 0); + if (status != osOK) serialPool.deallocate(block); +} + +extern "C" void idleInterruptCallback(UART_HandleTypeDef* huart) +{ + uint32_t len = (RX_BUFFER_SIZE * 2) - __HAL_DMA_GET_COUNTER(huart->hdmarx); + + if (len == 0) return; + + auto block = serialPool.allocate(); + if (!block) return; + + HAL_UART_AbortReceive(huart); + + if (len > RX_BUFFER_SIZE) { + // Second half + len = len - RX_BUFFER_SIZE; + memmove(block->buffer + 1, rxBuffer + RX_BUFFER_SIZE, len); + } else { + // First half + memmove(block->buffer + 1, rxBuffer, len); + } + + block->buffer[0] = len; + + HAL_UART_Receive_DMA(huart, rxBuffer, RX_BUFFER_SIZE * 2); + + auto status = osMessagePut(mobilinkd::tnc::getSerialPort()->queue(), (uint32_t) block, 0); + if (status != osOK) serialPool.deallocate(block); + +} extern "C" void HAL_UART_ErrorCallback(UART_HandleTypeDef* huart) { - osMessagePut(mobilinkd::tnc::getSerialPort()->queue(), - (uint32_t) (huart->gState<<16) | huart->ErrorCode | 0x100, 0); + osMessagePut(mobilinkd::tnc::getSerialPort()->queue(), huart->ErrorCode, 0); uart_error.store((huart->gState<<16) | huart->ErrorCode); huart->ErrorCode = HAL_UART_ERROR_NONE; huart->gState = HAL_UART_STATE_READY;