From d015e37f15ed831b7d79d0048ff27c96564c20a5 Mon Sep 17 00:00:00 2001 From: Noah Vickerson Date: Wed, 9 Oct 2024 20:02:27 -0600 Subject: [PATCH 1/6] brought over Drivers folder from SoarOperatingSystem --- UARTDriver/Inc/UARTDriver.hpp | 90 ++++++++++++++++++++++ UARTDriver/UARTDriver.cpp | 132 ++++++++++++++++++++++++++++++++ UARTDriver/UARTDriver_README.md | 105 +++++++++++++++++++++++++ 3 files changed, 327 insertions(+) create mode 100644 UARTDriver/Inc/UARTDriver.hpp create mode 100644 UARTDriver/UARTDriver.cpp create mode 100644 UARTDriver/UARTDriver_README.md diff --git a/UARTDriver/Inc/UARTDriver.hpp b/UARTDriver/Inc/UARTDriver.hpp new file mode 100644 index 0000000..e278339 --- /dev/null +++ b/UARTDriver/Inc/UARTDriver.hpp @@ -0,0 +1,90 @@ +#ifndef CUBE_UART_DRIVER_HPP_ +#define CUBE_UART_DRIVER_HPP_ +/** + ****************************************************************************** + * File Name : UARTDriver.hpp + * Description : UART Driver + * Uses the STM32 LL library + * Author : cjchanx (Chris) + ****************************************************************************** +*/ + +/* Includes ------------------------------------------------------------------*/ +#include "SystemDefines.hpp" +#include "cmsis_os.h" + +// Example code on how to declare UART Driver Instances in the HPP +#if EXAMPLE_CODE +/* UART Driver Instances ------------------------------------------------------------------*/ +class UARTDriver; + +namespace Driver { + extern UARTDriver uart1; + extern UARTDriver uart2; + extern UARTDriver uart3; + extern UARTDriver uart5; +} + +/* UART Driver Aliases ------------------------------------------------------------------*/ +namespace UART { + constexpr UARTDriver* Umbilical_RCU = &Driver::uart1; + constexpr UARTDriver* Radio = &Driver::uart2; + constexpr UARTDriver* Conduit_PBB = &Driver::uart3; + // UART 4 (GPS) uses HAL + constexpr UARTDriver* Debug = &Driver::uart5; +} +#endif + +/* UART Receiver Base Class ------------------------------------------------------------------*/ +/** + * @brief Any classes that are expected to receive using a UART driver + * must derive from this base class and provide an implementation + * for InterruptRxData + */ +class UARTReceiverBase +{ +public: + virtual void InterruptRxData(uint8_t errors) = 0; +}; + + +/* UART Driver Class ------------------------------------------------------------------*/ +/** + * @brief This is a basic UART driver designed for Interrupt Rx and Polling Tx + * based on the STM32 LL Library + */ +class UARTDriver +{ +public: + UARTDriver(USART_TypeDef* uartInstance) : + kUart_(uartInstance), + rxCharBuf_(nullptr), + rxReceiver_(nullptr) {} + + // Polling Functions + bool Transmit(uint8_t* data, uint16_t len); + + // Interrupt Functions + bool ReceiveIT(uint8_t* charBuf, UARTReceiverBase* receiver); + + + // Interrupt Handlers + void HandleIRQ_UART(); // This MUST be called inside USARTx_IRQHandler + +protected: + // Helper Functions + bool HandleAndClearRxError(); + bool GetRxErrors(); + + + // Constants + USART_TypeDef* kUart_; // Stores the UART instance + + // Variables + uint8_t* rxCharBuf_; // Stores a pointer to the buffer to store the received data + UARTReceiverBase* rxReceiver_; // Stores a pointer to the receiver object +}; + + + +#endif // CUBE_UART_DRIVER_HPP_ diff --git a/UARTDriver/UARTDriver.cpp b/UARTDriver/UARTDriver.cpp new file mode 100644 index 0000000..348e4d0 --- /dev/null +++ b/UARTDriver/UARTDriver.cpp @@ -0,0 +1,132 @@ +/** + ****************************************************************************** + * File Name : UARTDriver.cpp + * Description : UART Driver + * Author : cjchanx (Chris) + ****************************************************************************** + * + * Notes: + * If further efficiency is required, DMA can be used to transmit and receive. + * A good reference for this is MaJerle's STM32 USART DMA RX/TX example + * https://github.com/MaJerle/stm32-usart-uart-dma-rx-tx/blob/main/projects/usart_rx_idle_line_irq_rtos_F4/Src/main.c + * + ****************************************************************************** +*/ +#include "UARTDriver.hpp" + +/** + * @brief Transmits data via polling + * @param data The data to transmit + * @param len The length of the data to transmit + * @return True if the transmission was successful, false otherwise + */ +bool UARTDriver::Transmit(uint8_t* data, uint16_t len) +{ + // Loop through and transmit each byte via. polling + for (uint16_t i = 0; i < len; i++) { + LL_USART_TransmitData8(kUart_, data[i]); + + // Wait until the TX Register Empty Flag is set + while (!LL_USART_IsActiveFlag_TXE(kUart_)) {} + } + + // Wait until the transfer complete flag is set + while (!LL_USART_IsActiveFlag_TC(kUart_)) {} + + return true; +} + +/** +* @brief Receives 1 byte of data via interrupt +* @param receiver +* @return TRUE if interrupt was successfully enabled, FALSE otherwise +*/ +bool UARTDriver::ReceiveIT(uint8_t* charBuf, UARTReceiverBase* receiver) +{ + // Check flags + HandleAndClearRxError(); + if (LL_USART_IsActiveFlag_RXNE(kUart_)) { + // Read the data and ignore it + LL_USART_ReceiveData8(kUart_); + } + + // Set the buffer and receiver + rxCharBuf_ = charBuf; + rxReceiver_ = receiver; + + // Enable the receive interrupt + LL_USART_EnableIT_RXNE(kUart_); + + return true; +} + +/** + * @brief Clears any error flags that may have been set + * @return true if flags had to be cleared, false otherwise + */ +bool UARTDriver::HandleAndClearRxError() +{ + bool shouldClearFlags = false; + if (LL_USART_IsActiveFlag_ORE(kUart_)) { + shouldClearFlags = true; + } + if (LL_USART_IsActiveFlag_NE(kUart_)) { + shouldClearFlags = true; + } + if(LL_USART_IsActiveFlag_FE(kUart_)) { + shouldClearFlags = true; + } + if(LL_USART_IsActiveFlag_PE(kUart_)) { + shouldClearFlags = true; + } + + // Clearing the ORE here also clears PE, NE, FE, IDLE + if(shouldClearFlags) + LL_USART_ClearFlag_ORE(kUart_); + + return !shouldClearFlags; +} + +/** + * @brief Checks UART Rx error flags, if any are set returns true + * @return true if any error flags are set, false otherwise + */ +bool UARTDriver::GetRxErrors() +{ + bool hasErrors = false; + + if (LL_USART_IsActiveFlag_ORE(kUart_)) { + hasErrors = true; + } + else if (LL_USART_IsActiveFlag_NE(kUart_)) { + hasErrors = true; + } + else if(LL_USART_IsActiveFlag_FE(kUart_)) { + hasErrors = true; + } + else if(LL_USART_IsActiveFlag_PE(kUart_)) { + hasErrors = true; + } + + return hasErrors; +} + +/** + * @brief Handles an interrupt for the UART + * @attention MUST be called inside USARTx_IRQHandler + */ +void UARTDriver::HandleIRQ_UART() +{ + // Call the callback if RXNE is set + if (LL_USART_IsActiveFlag_RXNE(kUart_)) { + // Read the data from the data register + if (rxCharBuf_ != nullptr) { + *rxCharBuf_ = LL_USART_ReceiveData8(kUart_); + } + + // Call the receiver interrupt + if(rxReceiver_ != nullptr) { + rxReceiver_->InterruptRxData(GetRxErrors()); + } + } +} diff --git a/UARTDriver/UARTDriver_README.md b/UARTDriver/UARTDriver_README.md new file mode 100644 index 0000000..121091f --- /dev/null +++ b/UARTDriver/UARTDriver_README.md @@ -0,0 +1,105 @@ +# UART Driver + +## Usage +### 1 Changing STM32 UART Line from HAL to UART +- First ensure the .ioc indicates the UART line is enabled +- (Note this was done in the Cube++ setup previously) Second go to the **Project Manager** tab at the top, under UART select the UART lines you want to use the driver and change them to use the LL library. +- Lastly ensure the UART Global Interrupt for any lines you want to use the driver for is enabled (TODO: Add screenshot) + +### 2 Defining and Initializing UART Driver Instances + +- Inside a .hpp file setup something like this, with names/number of drivers changed for however many uart lines you want +- I recommended setting up a section for aliases so you can reference a driver simply by doing UART::Debug-> for example +```C++ +/* UART Driver Instances ------------------------------------------------------------------*/ +class UARTDriver; + +namespace Driver { + extern UARTDriver lpuart1; + extern UARTDriver uart1; + extern UARTDriver uart2; + extern UARTDriver uart3; +} + +/* UART Driver Aliases ------------------------------------------------------------------*/ +namespace UART { + constexpr UARTDriver* RPI = &Driver::lpuart1; // UART Link to Raspberry Pi + constexpr UARTDriver* SOB = &Driver::uart1; + constexpr UARTDriver* Umbilical_DMB = &Driver::uart2; + constexpr UARTDriver* Radio = &Driver::uart3; + + constexpr UARTDriver* Debug = &Driver::uart2; +} +``` + +Inside a `.cpp` file setup something like this, with names/number of drivers changed for however many uart lines you want +```C++ +// Declare the global UART driver objects +namespace Driver { + UARTDriver lpuart1(LPUART1); + UARTDriver uart1(USART1); + UARTDriver uart2(USART2); + UARTDriver uart3(USART3); +} +``` + +Inside a RunInterface file or equivalent (a file that gives C functions access to C++ functions without name problems) define a function called +something like cpp_USART5_IRQHandler(), for example: +```C++ +/* + * RunInterface.cpp + */ + +#include "main_system.hpp" +#include "UARTDriver.hpp" + +extern "C" { + void run_interface() + { + run_main(); + } + + void cpp_USART5_IRQHandler() + { + Driver::uart5.HandleIRQ_UART(); + } +} + +``` +```C++ + +/* + * RunInterface.hpp + */ + +#ifndef C__IFACE_HPP_ +#define C__IFACE_HPP_ + +void run_interface(); + +void cpp_USART5_IRQHandler(); + +#endif /* C__IFACE_HPP_ */ + +``` + +In this case you must call cpp_USART5_IRQHandler() inside Core/Src/stm32##xx_it.c where ## is the processor family. For example: +```C++ +/** + * @brief This function handles USART2 global interrupt. + */ +void USART2_IRQHandler(void) +{ + /* USER CODE BEGIN USART2_IRQn 0 */ + + /* USER CODE END USART2_IRQn 0 */ + /* USER CODE BEGIN USART2_IRQn 1 */ + cpp_USART2_IRQHandler(); + /* USER CODE END USART2_IRQn 1 */ +} +``` + + + + + From dfb9fcad26f04be6b0b0234b76ca75ec212c29fd Mon Sep 17 00:00:00 2001 From: Noah Vickerson Date: Wed, 16 Oct 2024 19:00:49 -0600 Subject: [PATCH 2/6] first rough draft for DMA UART --- UARTDriver/Inc/UARTDriver.hpp | 110 ++++++++++------------- UARTDriver/UARTDriver.cpp | 158 +++++++++++++++++++++++++--------- 2 files changed, 161 insertions(+), 107 deletions(-) diff --git a/UARTDriver/Inc/UARTDriver.hpp b/UARTDriver/Inc/UARTDriver.hpp index e278339..6f4ed17 100644 --- a/UARTDriver/Inc/UARTDriver.hpp +++ b/UARTDriver/Inc/UARTDriver.hpp @@ -1,39 +1,13 @@ -#ifndef CUBE_UART_DRIVER_HPP_ -#define CUBE_UART_DRIVER_HPP_ -/** - ****************************************************************************** - * File Name : UARTDriver.hpp - * Description : UART Driver - * Uses the STM32 LL library - * Author : cjchanx (Chris) - ****************************************************************************** -*/ +#ifndef CUBE_UART_DRIVER_DMA_HPP_ +#define CUBE_UART_DRIVER_DMA_HPP_ -/* Includes ------------------------------------------------------------------*/ #include "SystemDefines.hpp" #include "cmsis_os.h" +#include -// Example code on how to declare UART Driver Instances in the HPP -#if EXAMPLE_CODE -/* UART Driver Instances ------------------------------------------------------------------*/ -class UARTDriver; - -namespace Driver { - extern UARTDriver uart1; - extern UARTDriver uart2; - extern UARTDriver uart3; - extern UARTDriver uart5; -} - -/* UART Driver Aliases ------------------------------------------------------------------*/ -namespace UART { - constexpr UARTDriver* Umbilical_RCU = &Driver::uart1; - constexpr UARTDriver* Radio = &Driver::uart2; - constexpr UARTDriver* Conduit_PBB = &Driver::uart3; - // UART 4 (GPS) uses HAL - constexpr UARTDriver* Debug = &Driver::uart5; -} -#endif +#define ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0])) +#define MAX_DMA_BUFFER_LEN 64 +#define STM_H7xx /* UART Receiver Base Class ------------------------------------------------------------------*/ /** @@ -47,44 +21,52 @@ class UARTReceiverBase virtual void InterruptRxData(uint8_t errors) = 0; }; +/* Functions to Read from and Write to circular buffers */ +uint8_t read_byte(uint8_t*& read_head, uint8_t* buffer, uint16_t bufferSize); +void write_byte(uint8_t byte, uint8_t*& write_head, uint8_t* buffer, uint16_t bufferSize); -/* UART Driver Class ------------------------------------------------------------------*/ -/** - * @brief This is a basic UART driver designed for Interrupt Rx and Polling Tx - * based on the STM32 LL Library - */ -class UARTDriver -{ +class UARTDriver{ public: - UARTDriver(USART_TypeDef* uartInstance) : + UARTDriver(USART_TypeDef* uartInstance, DMA_TypeDef* dmaInstance, uint16_t DMA_DATA_STREAM) : kUart_(uartInstance), - rxCharBuf_(nullptr), - rxReceiver_(nullptr) {} - - // Polling Functions - bool Transmit(uint8_t* data, uint16_t len); + kDma_(dmaInstance), + DMA_DATA_STREAM(DMA_DATA_STREAM) {} - // Interrupt Functions - bool ReceiveIT(uint8_t* charBuf, UARTReceiverBase* receiver); + // DMA (buffer reading/writing) Functions + bool Transmit(uint8_t* data, uint16_t len); + bool Recieve(uint8_t* charBuf); - - // Interrupt Handlers - void HandleIRQ_UART(); // This MUST be called inside USARTx_IRQHandler + // Interrupt Handlers + void HandleIRQ_UART(); // This MUST be called inside USARTx_IRQHandler protected: - // Helper Functions - bool HandleAndClearRxError(); - bool GetRxErrors(); - - - // Constants - USART_TypeDef* kUart_; // Stores the UART instance - - // Variables - uint8_t* rxCharBuf_; // Stores a pointer to the buffer to store the received data - UARTReceiverBase* rxReceiver_; // Stores a pointer to the receiver object + // Flag? for the DMA buffer filled over the bytes being read + bool DMA_DATA_OVERRUN_FLAG = false; + + // Helper Functions + bool HandleAndClearRxError(); + bool GetRxErrors(); + + // constants + USART_TypeDef* kUart_; // Stores the UART instance + DMA_TypeDef* kDma_; // Stores the DMA instance + + /* DMA buffers */ + /** + * @brief DMA buffers + * @details Messages will be in the form (bytewise): [message length1][message length0][message data][checkbit/stopbit] + */ + uint8_t rx_buffer[MAX_DMA_BUFFER_LEN]; // need a static allocated buffer to store recieved data while we aren't recieveing data (circular) + uint8_t lin_tx_buffer[MAX_DMA_BUFFER_LEN]; // static allocated buffer to store data to be sent (linear) + + // Circular buffer read and write heads + uint8_t* rx_read_head = usart_rx_dma_buffer; // pointer to read from rx_buffer + + uint16_t DMA_DATA_STREAM; + + // UNUSED VARIABLES + /* Message queue ID */ + osMessageQueueId_t usart_rx_dma_queue_id; }; - - -#endif // CUBE_UART_DRIVER_HPP_ +#endif \ No newline at end of file diff --git a/UARTDriver/UARTDriver.cpp b/UARTDriver/UARTDriver.cpp index 348e4d0..c4fddd1 100644 --- a/UARTDriver/UARTDriver.cpp +++ b/UARTDriver/UARTDriver.cpp @@ -1,62 +1,129 @@ -/** - ****************************************************************************** - * File Name : UARTDriver.cpp - * Description : UART Driver - * Author : cjchanx (Chris) - ****************************************************************************** - * - * Notes: - * If further efficiency is required, DMA can be used to transmit and receive. - * A good reference for this is MaJerle's STM32 USART DMA RX/TX example - * https://github.com/MaJerle/stm32-usart-uart-dma-rx-tx/blob/main/projects/usart_rx_idle_line_irq_rtos_F4/Src/main.c - * - ****************************************************************************** -*/ #include "UARTDriver.hpp" +// can get current data length with LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2) + + +void write_byte(uint8_t byte, uint8_t*& write_head, uint8_t* buffer, uint16_t bufferSize) +{ + // write the byte to the current write head + *write_head = byte; + + // increment the write head, loop it around the circular buffer + if(write_head - buffer < bufferSize + 1){ + write_head++; + }else{ + write_head = buffer; + } +} + +uint8_t read_byte(uint8_t*& read_head, uint8_t* buffer, uint16_t bufferSize) +{ + // read the byte from the current read head + uint8_t copy_to = *read_head; + + // after reading the byte, set the byte at the read head to some placeholder bit + *read_head = 0xFF; + + // increment the read head, loop it around the circular buffer + if(read_head - buffer < bufferSize + 1){ + read_head++; + }else{ + read_head = buffer; + } + + return copy_to; +} + +/** + * @brief Send data on DMA + * @note Contains API calls only for the H7xx chips + * @return `1` if transfer just started + */ +uint8_t +usart_start_tx_dma_transfer(uint16_t len, uint16_t* buffer_adr) +{ + /* Disable channel if enabled */ + LL_DMA_DisableStream(kDma_, DMA_DATA_STREAM); + + /* Clear all flags */ + LL_DMA_ClearFlag_TC1(kDma_); + LL_DMA_ClearFlag_HT1(kDma_); + LL_DMA_ClearFlag_TE1(kDma_); + LL_DMA_ClearFlag_DME1(kDma_); + LL_DMA_ClearFlag_FE1(kDma_); + + /* Prepare DMA data and length */ + LL_DMA_SetDataLength(kDma_, DMA_DATA_STREAM, len); + LL_DMA_SetMemoryAddress(kDma_, DMA_DATA_STREAM, buffer_adr); + + /* Start transfer */ + LL_DMA_EnableChannel(kDma_, DMA_DATA_STREAM); + + return true; +} + + /** - * @brief Transmits data via polling - * @param data The data to transmit - * @param len The length of the data to transmit - * @return True if the transmission was successful, false otherwise + * @brief Transmits data via the DMA + * @brief Should be called until returns non-zero + * @param data, len The data to transmit, the length of data to transmit + * @return 0 if any previous data is still transmitting, 1 if the transmit was successfully started, -1 if the data is too large to transmit + * @note need to add protection from multiple threads calling this function at once */ -bool UARTDriver::Transmit(uint8_t* data, uint16_t len) +char UARTDriver::Transmit(uint8_t* data, uint16_t len) { - // Loop through and transmit each byte via. polling - for (uint16_t i = 0; i < len; i++) { - LL_USART_TransmitData8(kUart_, data[i]); + // make sure the data wont overflow the buffer + if(len + 3 > MAX_DMA_BUFFER_LEN){ + return -1; + } - // Wait until the TX Register Empty Flag is set - while (!LL_USART_IsActiveFlag_TXE(kUart_)) {} + // make sure there is no current transmit on the DMA channel + if (!LL_DMA_GetDataLength(_kDma, DMA_DATA_STREAM)) { + // "Once the stream is enabled, the return value indicates the remaining bytes to be transmitted." + return 0; // if there are bytes remaining to be transmitted, dont start transmitting a new message } - // Wait until the transfer complete flag is set - while (!LL_USART_IsActiveFlag_TC(kUart_)) {} + // set length bytes for the message + lin_tx_buffer[0] = (uint8_t)(len & 0xFF00 >> 8); + lin_tx_buffer[1] = (uint8_t)(len & 0x00FF); + + // copy the message to the buffer + for(int i = 0; i < len; i++){ + lin_tx_buffer[i+2] = data[i]; + } + + // set the checkbyte in the buffer + lin_tx_buffer[len+2] = data[len-1] | (uint8_t)(len & 0x00FF); // takes the last byte of data and &'s it with length byte to get some random check + + // starts the process of transmitting the data via DMA + usart_start_tx_dma_transfer(len + 3, lin_tx_buffer); return true; } /** -* @brief Receives 1 byte of data via interrupt -* @param receiver +* @brief Reads the circular buffer and copies the data into charBuf +* @param charBuf, receiver * @return TRUE if interrupt was successfully enabled, FALSE otherwise +* @note can make reading set the read head to some placeholder bit so read head wont advance until its not a placeholder bit */ -bool UARTDriver::ReceiveIT(uint8_t* charBuf, UARTReceiverBase* receiver) +bool UARTDriver::Receive(uint8_t* charBuf, UARTReceiverBase* receiver) { - // Check flags - HandleAndClearRxError(); - if (LL_USART_IsActiveFlag_RXNE(kUart_)) { - // Read the data and ignore it - LL_USART_ReceiveData8(kUart_); - } + // read the length bytes + uint16_t len = read_byte(rx_read_head, rx_buffer, 64) << 8; + len |= read_byte(rx_read_head, rx_buffer, 64); - // Set the buffer and receiver - rxCharBuf_ = charBuf; - rxReceiver_ = receiver; - - // Enable the receive interrupt - LL_USART_EnableIT_RXNE(kUart_); + // copy the data bytes into charBuf + for(int i = 0; i < len; i++){ + charBuf[i] = read_byte(rx_read_head, rx_buffer, 64); + } + // read the check byte + if(read_byte(rx_read_head, rx_buffer, 64) != charBuf[len - 1] | (len & 0x00FF)){ + DMA_DATA_OVERRUN_FLAG = true; + return false; // returns false if the check byte is not what was calculated + } // indicates that the write head overwrote the read head + // returns true if the data was successfully read return true; } @@ -66,6 +133,7 @@ bool UARTDriver::ReceiveIT(uint8_t* charBuf, UARTReceiverBase* receiver) */ bool UARTDriver::HandleAndClearRxError() { + bool shouldClearOverflowFlag = false; bool shouldClearFlags = false; if (LL_USART_IsActiveFlag_ORE(kUart_)) { shouldClearFlags = true; @@ -79,12 +147,14 @@ bool UARTDriver::HandleAndClearRxError() if(LL_USART_IsActiveFlag_PE(kUart_)) { shouldClearFlags = true; } + if(DMA_DATA_OVERRUN_FLAG) + shouldClearOverflowFlag = true; // Clearing the ORE here also clears PE, NE, FE, IDLE if(shouldClearFlags) LL_USART_ClearFlag_ORE(kUart_); - return !shouldClearFlags; + return !shouldClearFlags || !shouldClearOverflowFlag; } /** @@ -106,6 +176,8 @@ bool UARTDriver::GetRxErrors() } else if(LL_USART_IsActiveFlag_PE(kUart_)) { hasErrors = true; + }else if(DMA_DATA_OVERRUN_FLAG){ + hasErrors = true; } return hasErrors; @@ -129,4 +201,4 @@ void UARTDriver::HandleIRQ_UART() rxReceiver_->InterruptRxData(GetRxErrors()); } } -} +} \ No newline at end of file From 5647c288c1ec2e52069a26714901558be675f7ca Mon Sep 17 00:00:00 2001 From: Noah Vickerson Date: Sat, 19 Oct 2024 10:39:59 -0600 Subject: [PATCH 3/6] Reconfigured transmit to better fit existing function calls, added (incomplete) IRQ handlers --- UARTDriver/Inc/UARTDriver.hpp | 29 +++---- UARTDriver/UARTDriver.cpp | 150 +++++++++++++++++++++++----------- 2 files changed, 117 insertions(+), 62 deletions(-) diff --git a/UARTDriver/Inc/UARTDriver.hpp b/UARTDriver/Inc/UARTDriver.hpp index 6f4ed17..dd839da 100644 --- a/UARTDriver/Inc/UARTDriver.hpp +++ b/UARTDriver/Inc/UARTDriver.hpp @@ -5,7 +5,6 @@ #include "cmsis_os.h" #include -#define ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0])) #define MAX_DMA_BUFFER_LEN 64 #define STM_H7xx @@ -23,18 +22,19 @@ class UARTReceiverBase /* Functions to Read from and Write to circular buffers */ uint8_t read_byte(uint8_t*& read_head, uint8_t* buffer, uint16_t bufferSize); -void write_byte(uint8_t byte, uint8_t*& write_head, uint8_t* buffer, uint16_t bufferSize); class UARTDriver{ public: - UARTDriver(USART_TypeDef* uartInstance, DMA_TypeDef* dmaInstance, uint16_t DMA_DATA_STREAM) : + UARTDriver(USART_TypeDef* uartInstance, DMA_TypeDef* dmaInstance, uint16_t DMA_DATA_STREAM_tx, uint16_t DMA_DATA_STREAM_rx) : kUart_(uartInstance), kDma_(dmaInstance), - DMA_DATA_STREAM(DMA_DATA_STREAM) {} + DMA_DATA_STREAM_tx(DMA_DATA_STREAM_tx), + DMA_DATA_STREAM_rx(DMA_DATA_STREAM_rx), + rxReceiver_(nullptr) {} // DMA (buffer reading/writing) Functions bool Transmit(uint8_t* data, uint16_t len); - bool Recieve(uint8_t* charBuf); + bool Receive(uint8_t* charBuf, UARTReceiverBase* receiver); // Interrupt Handlers void HandleIRQ_UART(); // This MUST be called inside USARTx_IRQHandler @@ -46,12 +46,14 @@ class UARTDriver{ // Helper Functions bool HandleAndClearRxError(); bool GetRxErrors(); + uint8_t start_tx_data_transfer(uint16_t len, uint8_t* buffer_adr); + char SendData(uint8_t* data, uint16_t len); // constants USART_TypeDef* kUart_; // Stores the UART instance DMA_TypeDef* kDma_; // Stores the DMA instance - - /* DMA buffers */ + uint16_t DMA_DATA_STREAM; // stores a variable to the DMA_DATA_STREAM so its not hard coded + /** * @brief DMA buffers * @details Messages will be in the form (bytewise): [message length1][message length0][message data][checkbit/stopbit] @@ -59,14 +61,9 @@ class UARTDriver{ uint8_t rx_buffer[MAX_DMA_BUFFER_LEN]; // need a static allocated buffer to store recieved data while we aren't recieveing data (circular) uint8_t lin_tx_buffer[MAX_DMA_BUFFER_LEN]; // static allocated buffer to store data to be sent (linear) - // Circular buffer read and write heads - uint8_t* rx_read_head = usart_rx_dma_buffer; // pointer to read from rx_buffer - - uint16_t DMA_DATA_STREAM; - - // UNUSED VARIABLES - /* Message queue ID */ - osMessageQueueId_t usart_rx_dma_queue_id; + // variables + uint8_t* rx_read_head = rx_buffer; // Circular buffer read head + UARTReceiverBase* rxReceiver_; // Stores a pointer to the receiver object }; -#endif \ No newline at end of file +#endif diff --git a/UARTDriver/UARTDriver.cpp b/UARTDriver/UARTDriver.cpp index c4fddd1..cf1ac3f 100644 --- a/UARTDriver/UARTDriver.cpp +++ b/UARTDriver/UARTDriver.cpp @@ -1,29 +1,11 @@ #include "UARTDriver.hpp" -// can get current data length with LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2) - - -void write_byte(uint8_t byte, uint8_t*& write_head, uint8_t* buffer, uint16_t bufferSize) -{ - // write the byte to the current write head - *write_head = byte; - - // increment the write head, loop it around the circular buffer - if(write_head - buffer < bufferSize + 1){ - write_head++; - }else{ - write_head = buffer; - } -} uint8_t read_byte(uint8_t*& read_head, uint8_t* buffer, uint16_t bufferSize) { // read the byte from the current read head uint8_t copy_to = *read_head; - // after reading the byte, set the byte at the read head to some placeholder bit - *read_head = 0xFF; - // increment the read head, loop it around the circular buffer if(read_head - buffer < bufferSize + 1){ read_head++; @@ -39,30 +21,28 @@ uint8_t read_byte(uint8_t*& read_head, uint8_t* buffer, uint16_t bufferSize) * @note Contains API calls only for the H7xx chips * @return `1` if transfer just started */ -uint8_t -usart_start_tx_dma_transfer(uint16_t len, uint16_t* buffer_adr) +uint8_t UARTDriver::start_tx_data_transfer(uint16_t len, uint8_t* buffer_adr) { /* Disable channel if enabled */ - LL_DMA_DisableStream(kDma_, DMA_DATA_STREAM); + LL_DMA_DisableStream(kDma_, DMA_DATA_STREAM_tx); /* Clear all flags */ - LL_DMA_ClearFlag_TC1(kDma_); - LL_DMA_ClearFlag_HT1(kDma_); - LL_DMA_ClearFlag_TE1(kDma_); - LL_DMA_ClearFlag_DME1(kDma_); - LL_DMA_ClearFlag_FE1(kDma_); - + LL_DMA_ClearFlag_TC0(kDma_); // assuming DMA_DATA_STREAM == stream 0 + LL_DMA_ClearFlag_HT0(kDma_); // USART8 + LL_DMA_ClearFlag_TE0(kDma_); + LL_DMA_ClearFlag_DME0(kDma_); + LL_DMA_ClearFlag_FE0(kDma_); + /* Prepare DMA data and length */ - LL_DMA_SetDataLength(kDma_, DMA_DATA_STREAM, len); - LL_DMA_SetMemoryAddress(kDma_, DMA_DATA_STREAM, buffer_adr); + LL_DMA_SetDataLength(kDma_, DMA_DATA_STREAM_tx, len); + LL_DMA_SetMemoryAddress(kDma_, DMA_DATA_STREAM_tx, (uint32_t)buffer_adr); /* Start transfer */ - LL_DMA_EnableChannel(kDma_, DMA_DATA_STREAM); + LL_DMA_EnableStream(kDma_, DMA_DATA_STREAM_tx); return true; } - /** * @brief Transmits data via the DMA * @brief Should be called until returns non-zero @@ -70,15 +50,35 @@ usart_start_tx_dma_transfer(uint16_t len, uint16_t* buffer_adr) * @return 0 if any previous data is still transmitting, 1 if the transmit was successfully started, -1 if the data is too large to transmit * @note need to add protection from multiple threads calling this function at once */ -char UARTDriver::Transmit(uint8_t* data, uint16_t len) +bool UARTDriver::Transmit(uint8_t* data, uint16_t len) { + + int n = SendData(data, len); // try to send the data + for(int i = 0; !n; i++){ + n = SendData(data, len); // continually try, waiting for any current transfers to finish + + // set a timeout + if(i > 1000) { + SOAR_ASSERT(false, "Transmit operation timed out"); + return false; + } + } + if(n == -1){ + SOAR_ASSERT(false, "Data too large to transmit\n"); // if this error is called, just set MAX_DMA_BUFFER_LEN to a number greater than 64 + return false; + } + + return true; +} + +char UARTDriver::SendData(uint8_t* data, uint16_t len){ // make sure the data wont overflow the buffer if(len + 3 > MAX_DMA_BUFFER_LEN){ return -1; } // make sure there is no current transmit on the DMA channel - if (!LL_DMA_GetDataLength(_kDma, DMA_DATA_STREAM)) { + if (!LL_DMA_GetDataLength(kDma_, DMA_DATA_STREAM_tx)) { // "Once the stream is enabled, the return value indicates the remaining bytes to be transmitted." return 0; // if there are bytes remaining to be transmitted, dont start transmitting a new message } @@ -93,18 +93,18 @@ char UARTDriver::Transmit(uint8_t* data, uint16_t len) } // set the checkbyte in the buffer - lin_tx_buffer[len+2] = data[len-1] | (uint8_t)(len & 0x00FF); // takes the last byte of data and &'s it with length byte to get some random check + lin_tx_buffer[len+2] = data[len-2] | (uint8_t)(len & 0x00FF); // takes the last byte of data and &'s it with length byte to get some random check // starts the process of transmitting the data via DMA - usart_start_tx_dma_transfer(len + 3, lin_tx_buffer); + start_tx_data_transfer(len + 3, lin_tx_buffer); - return true; + return 1; } /** * @brief Reads the circular buffer and copies the data into charBuf * @param charBuf, receiver -* @return TRUE if interrupt was successfully enabled, FALSE otherwise +* @return TRUE if data was successfully read, false otherwise * @note can make reading set the read head to some placeholder bit so read head wont advance until its not a placeholder bit */ bool UARTDriver::Receive(uint8_t* charBuf, UARTReceiverBase* receiver) @@ -115,15 +115,37 @@ bool UARTDriver::Receive(uint8_t* charBuf, UARTReceiverBase* receiver) // copy the data bytes into charBuf for(int i = 0; i < len; i++){ - charBuf[i] = read_byte(rx_read_head, rx_buffer, 64); + // if it is a placeholder byte, wait until it is not + uint8_t byte = read_byte(rx_read_head, rx_buffer, 64); + + for(int j; byte == '\0'; byte = read_byte(rx_read_head, rx_buffer, 64), j++){ + if(i >= len -1 ) {break;} // skip the string null terminator + if(j > 1000) return false; // configure a timeout + } + + // write the current byte + charBuf[i] = byte; + + // replace the just-read byte in the read buffer with 0 to indicate it doesnt contain data + if(rx_read_head == rx_buffer){ + rx_buffer[MAX_DMA_BUFFER_LEN - 1] = 0; + }else{ + *(rx_read_head - 1) = 0; + } } + // null terminate the message (replace the stop bit in the message '\r') + charBuf[len-1] = '\0'; + // read the check byte - if(read_byte(rx_read_head, rx_buffer, 64) != charBuf[len - 1] | (len & 0x00FF)){ + if(read_byte(rx_read_head, rx_buffer, 64) != (charBuf[len - 2] | (len & 0x00FF))){ DMA_DATA_OVERRUN_FLAG = true; return false; // returns false if the check byte is not what was calculated } // indicates that the write head overwrote the read head - // returns true if the data was successfully read + // set the pointer to the reciever object so we can call its interrupt + rxReceiver_ = receiver; + + // returns true if the data was successfully read return true; } @@ -189,16 +211,52 @@ bool UARTDriver::GetRxErrors() */ void UARTDriver::HandleIRQ_UART() { + /* Check for IDLE line interrupt */ + if (LL_USART_IsEnabledIT_IDLE(kUart_) && LL_USART_IsActiveFlag_IDLE(kUart_)) { + LL_USART_ClearFlag_IDLE(kUart_); /* Clear IDLE line flag */ + usart_rx_check(); /* Check for data to process */ + } + // Call the callback if RXNE is set if (LL_USART_IsActiveFlag_RXNE(kUart_)) { - // Read the data from the data register - if (rxCharBuf_ != nullptr) { - *rxCharBuf_ = LL_USART_ReceiveData8(kUart_); - } - // Call the receiver interrupt if(rxReceiver_ != nullptr) { rxReceiver_->InterruptRxData(GetRxErrors()); } } -} \ No newline at end of file +} + +/** + * @brief DMA interrupt handler for USART TX + */ +void UARTDriver::DMA_Stream_tx_IRQHandler(void) { + /* Check half-transfer complete interrupt */ + if (LL_DMA_IsEnabledIT_HT(kDma_, DMA_DATA_STREAM_tx) && LL_DMA_IsActiveFlag_HT0(kDma_)) { + LL_DMA_ClearFlag_HT0(kDma_); /* Clear half-transfer complete flag */ + usart_rx_check(); /* Check for data to process */ + } + + /* Check transfer-complete interrupt */ + if (LL_DMA_IsEnabledIT_TC(kDma_, DMA_DATA_STREAM_tx) && LL_DMA_IsActiveFlag_TC0(kDma_)) { + LL_DMA_ClearFlag_TC0(kDma_); /* Clear transfer complete flag */ + usart_rx_check(); /* Check for data to process */ + } + + /* Implement other events when needed */ +} + +/** + * @brief DMA interrupt handler for USART RX + */ +void UARTDriver::DMA1_Stream1_IRQHandler(void) { + /* Check transfer complete */ + if (LL_DMA_IsEnabledIT_TC(kDma_, DMA_DATA_STREAM_rx) && LL_DMA_IsActiveFlag_TC1(kDma_)) { + LL_DMA_ClearFlag_TC1(kDma_); /* Clear transfer complete flag */ + lwrb_skip(&usart_tx_rb, usart_tx_dma_current_len);/* Skip sent data, mark as read */ + usart_tx_dma_current_len = 0; /* Clear length variable */ + usart_start_tx_dma_transfer(); /* Start sending more data */ + } + + /* Implement other events when needed */ +} + From db8aaace60ff66c38a0767db89910d9bbefc8798 Mon Sep 17 00:00:00 2001 From: Noah Vickerson Date: Sat, 9 Nov 2024 11:59:52 -0700 Subject: [PATCH 4/6] added DMA UART driver --- UARTDriver/DMA_UARTDriver.cpp | 149 ++++++++++++++++++++++++++++++ UARTDriver/Inc/DMA_UARTDriver.hpp | 49 ++++++++++ 2 files changed, 198 insertions(+) create mode 100644 UARTDriver/DMA_UARTDriver.cpp create mode 100644 UARTDriver/Inc/DMA_UARTDriver.hpp diff --git a/UARTDriver/DMA_UARTDriver.cpp b/UARTDriver/DMA_UARTDriver.cpp new file mode 100644 index 0000000..b91f429 --- /dev/null +++ b/UARTDriver/DMA_UARTDriver.cpp @@ -0,0 +1,149 @@ +/** ******************************************************************************** +* @file DMA_UARTDriver.cpp +* @author Noah +* @date Nov 8, 2024 +* @brief ******************************************************************************** */ +/************************************ * INCLUDES ************************************/ +#include "DMA_UARTDriver.hpp" +/************************************ * PRIVATE MACROS AND DEFINES ************************************/ +/************************************ * VARIABLES ************************************/ +/************************************ * FUNCTION DECLARATIONS ************************************/ + +/** + * @brief Starts a DMA transmission + * @param data The data to transmit + * @param len The length of the data to transmit + * @return True if the transmission was successful, False otherwise + */ +bool DMA_UARTDriver::Transmit(uint8_t* data, uint16_t len); + +/** + * @brief Reads a byte from a circular buffer, increments buffer for valid bytes (waits for valid data otherwise) + * @return the byte value + */ +uint8_t DMA_UARTDriver::read_byte(uint8_t*& read_head, uint8_t* buffer, uint16_t bufferSize); + +/** +* @brief Read the uart data buffer and store it in a passed data array +* @param charBuf buffer to store the read data +* @attention calling function must handle cases where the read message overflows charBuf +* @attention calling function should also call this until it returns true +* allowing for the function to be called before a message is complete +* @param buffIdx the index in charBuf that is currently being wrote to +* @return TRUE if interrupt was successfully enabled, FALSE otherwise +*/ +bool DMA_UARTDriver::Receive(uint8_t* charBuf, uint8_t& buffIdx); + + +/************************************ * FUNCTION DEFINITIONS ************************************/ + +bool DMA_UARTDriver::Transmit(uint8_t* data, uint16_t len) +{ + // HAL DMA transmission + // metabytes to send messages in any form (dont have to '\r' terminate like for debug messages) + uint8_t metaBytes = 0, headerBytes = 0; + + // make sure the data wont overflow the buffer + if(len + metaBytes > MAX_DMA_BUFFER_LEN){ + return false; + } + + // check if current transfer is complete + for (int i = 0; HAL_UART_GetState(hUart_) != HAL_UART_STATE_READY; i++){ + if(i > 10000) break; // TEMP fix for hal uart not readying error + } + + // set length bytes for the message + if(!is_Debug){ + lin_tx_buffer[0] = (uint8_t)(len & 0xFF00 >> 8); + lin_tx_buffer[1] = (uint8_t)(len & 0x00FF); + headerBytes = 2; + metaBytes = 3; + } + + // copy the message to the buffer + for(int i = 0; i < len; i++){ + lin_tx_buffer[i + headerBytes] = data[i]; // sets the data to the message buffer, with an offset if its not to serial + } + + // set the checkbyte in the buffer + if(!is_Debug){ + lin_tx_buffer[len+2] = data[len-2] | (uint8_t)(len & 0x00FF); // takes the last byte of data and &'s it with length byte to get some random check + } + + // starts the process of transmitting the data via DMA + if(HAL_UART_Transmit_DMA(hUart_, (uint8_t*)lin_tx_buffer, len + metaBytes)!= HAL_OK) + { + /* Transfer error in transmission process */ + return false; + } + + return true; +} + +bool UARTDriver::Receive(uint8_t* charBuf, uint8_t& buffIdx) +{ + + if(HAL_UART_Receive_DMA(hUart_, (uint8_t *)rx_buffer, MAX_DMA_BUFFER_LEN) != HAL_OK){ + + } // make sure we're receiving + + // read the next byte in the curcular buffer + charBuf[buffIdx] = read_byte(rx_read_head, rx_buffer, MAX_DMA_BUFFER_LEN); + + if(charBuf[buffIdx] && is_Debug) Transmit(charBuf+buffIdx, 1); // send input response to debug terminal + + // makes debug messages cleaner by eliminating the metadata + // requires specific messages to work (ie '\r\n' terminated) + if(is_Debug){ + // loop until end of line character + while(charBuf[buffIdx] != '\n'){ + + if(charBuf[buffIdx] == '\0') return false; // wait until message is finished + + buffIdx++; + charBuf[buffIdx] = read_byte(rx_read_head, rx_buffer, MAX_DMA_BUFFER_LEN); // continue to copy data + } + + charBuf[buffIdx-1] = '\0'; // null terminate for a string + charBuf[buffIdx] = '\0'; + buffIdx = 0; // reset the buffer + + }else{ + uint8_t len = (read_byte(rx_read_head, rx_buffer, MAX_DMA_BUFFER_LEN) << 8) | (read_byte(rx_read_head, rx_buffer, MAX_DMA_BUFFER_LEN)); + + for(int i = 0; i < len; i++){ // read the number of message bytes and copy into charbuf + charBuf[buffIdx] = read_byte(rx_read_head, rx_buffer, MAX_DMA_BUFFER_LEN); + buffIdx++; + } + uint8_t checkByte = read_byte(rx_read_head, rx_buffer, MAX_DMA_BUFFER_LEN); // check to make sure the transfer works + + if(checkByte != (charBuf[buffIdx-2] | (charBuf[buffIdx-1] & 0x00FF))){ + buffIdx = 0; // reset the transfer for failed transfer + return false; // check if the checkbyte is correct + } + } + + return true; +} + +uint8_t UARTDriver::read_byte(uint8_t*& read_head, uint8_t* buffer, uint16_t bufferSize) +{ + // read the byte from the current read head + uint8_t copy_to = *read_head; + + // return if the read data hasnt been updated (no unread data) + if(copy_to == 0) return '\0'; + + // reset the address to a read state + *read_head = '\0'; + + // increment the read head, loop it around the circular buffer + if(read_head - buffer < bufferSize - 1){ + read_head++; + }else{ + read_head = buffer; + } + + return copy_to; +} \ No newline at end of file diff --git a/UARTDriver/Inc/DMA_UARTDriver.hpp b/UARTDriver/Inc/DMA_UARTDriver.hpp new file mode 100644 index 0000000..879a8c6 --- /dev/null +++ b/UARTDriver/Inc/DMA_UARTDriver.hpp @@ -0,0 +1,49 @@ +/** ******************************************************************************** + * @file DMA_UARTDriver.hpp + * @author Noah + * @date Nov 8, 2024 + * @brief ******************************************************************************** */ +#ifndef SOARCOMMUNICATIONS_UART_INC_DMA_UARTDRIVER_HPP_ +#define SOARCOMMUNICATIONS_UART_INC_DMA_UARTDRIVER_HPP_ +/************************************ * INCLUDES ************************************/ +#include "SystemDefines.hpp" +#include "cmsis_os.h" +/************************************ * MACROS AND DEFINES ************************************/ +#define MAX_DMA_BUFFER_LEN 64 +/************************************ * TYPEDEFS ************************************/ +/************************************ * CLASS DEFINITIONS ************************************/ + +/* UART Driver Class ------------------------------------------------------------------*/ +/** + * @brief This is a basic UART driver designed for DMA Rx and Tx + * based on the STM32 HAL Library + */ +class DMA_UARTDriver +{ +public: + DMA_UARTDriver(UART_HandleTypeDef& uartInstance, bool debug = false) : + hUart_(&uartInstance), + is_Debug(debug) { + // prime the reciever + HAL_UART_Receive_DMA(hUart_, (uint8_t *)rx_buffer, MAX_DMA_BUFFER_LEN); + } + + // DMA functions + bool Transmit(uint8_t* data, uint16_t len); + bool Receive(uint8_t* charBuf, uint8_t& buffIdx); // DMA reciever function + +protected: + // Helper Functions + uint8_t read_byte(uint8_t*& read_head, uint8_t* buffer, uint16_t bufferSize); + + // Constants + UART_HandleTypeDef* hUart_; // Stores the UART instance + + // Variables + uint8_t rx_buffer[MAX_DMA_BUFFER_LEN]; // Stores a pointer to the buffer to store the received data + uint8_t* rx_read_head = rx_buffer; // Circular buffer read head + uint8_t lin_tx_buffer[MAX_DMA_BUFFER_LEN]; // static allocated buffer to store data to be sent (linear) + bool is_Debug; // debug value for output formatting +}; +/************************************ * FUNCTION DECLARATIONS ************************************/ +#endif /* EXAMPLE_TASK_HPP_ */ From 8ca44cb15326e098af97abb85af67084791b202f Mon Sep 17 00:00:00 2001 From: Noah Vickerson Date: Sat, 9 Nov 2024 12:02:03 -0700 Subject: [PATCH 5/6] removed the polling rewrite of the driver --- UARTDriver/Inc/UARTDriver.hpp | 69 --------- UARTDriver/UARTDriver.cpp | 262 ---------------------------------- 2 files changed, 331 deletions(-) delete mode 100644 UARTDriver/Inc/UARTDriver.hpp delete mode 100644 UARTDriver/UARTDriver.cpp diff --git a/UARTDriver/Inc/UARTDriver.hpp b/UARTDriver/Inc/UARTDriver.hpp deleted file mode 100644 index dd839da..0000000 --- a/UARTDriver/Inc/UARTDriver.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef CUBE_UART_DRIVER_DMA_HPP_ -#define CUBE_UART_DRIVER_DMA_HPP_ - -#include "SystemDefines.hpp" -#include "cmsis_os.h" -#include - -#define MAX_DMA_BUFFER_LEN 64 -#define STM_H7xx - -/* UART Receiver Base Class ------------------------------------------------------------------*/ -/** - * @brief Any classes that are expected to receive using a UART driver - * must derive from this base class and provide an implementation - * for InterruptRxData - */ -class UARTReceiverBase -{ -public: - virtual void InterruptRxData(uint8_t errors) = 0; -}; - -/* Functions to Read from and Write to circular buffers */ -uint8_t read_byte(uint8_t*& read_head, uint8_t* buffer, uint16_t bufferSize); - -class UARTDriver{ -public: - UARTDriver(USART_TypeDef* uartInstance, DMA_TypeDef* dmaInstance, uint16_t DMA_DATA_STREAM_tx, uint16_t DMA_DATA_STREAM_rx) : - kUart_(uartInstance), - kDma_(dmaInstance), - DMA_DATA_STREAM_tx(DMA_DATA_STREAM_tx), - DMA_DATA_STREAM_rx(DMA_DATA_STREAM_rx), - rxReceiver_(nullptr) {} - - // DMA (buffer reading/writing) Functions - bool Transmit(uint8_t* data, uint16_t len); - bool Receive(uint8_t* charBuf, UARTReceiverBase* receiver); - - // Interrupt Handlers - void HandleIRQ_UART(); // This MUST be called inside USARTx_IRQHandler - -protected: - // Flag? for the DMA buffer filled over the bytes being read - bool DMA_DATA_OVERRUN_FLAG = false; - - // Helper Functions - bool HandleAndClearRxError(); - bool GetRxErrors(); - uint8_t start_tx_data_transfer(uint16_t len, uint8_t* buffer_adr); - char SendData(uint8_t* data, uint16_t len); - - // constants - USART_TypeDef* kUart_; // Stores the UART instance - DMA_TypeDef* kDma_; // Stores the DMA instance - uint16_t DMA_DATA_STREAM; // stores a variable to the DMA_DATA_STREAM so its not hard coded - - /** - * @brief DMA buffers - * @details Messages will be in the form (bytewise): [message length1][message length0][message data][checkbit/stopbit] - */ - uint8_t rx_buffer[MAX_DMA_BUFFER_LEN]; // need a static allocated buffer to store recieved data while we aren't recieveing data (circular) - uint8_t lin_tx_buffer[MAX_DMA_BUFFER_LEN]; // static allocated buffer to store data to be sent (linear) - - // variables - uint8_t* rx_read_head = rx_buffer; // Circular buffer read head - UARTReceiverBase* rxReceiver_; // Stores a pointer to the receiver object -}; - -#endif diff --git a/UARTDriver/UARTDriver.cpp b/UARTDriver/UARTDriver.cpp deleted file mode 100644 index cf1ac3f..0000000 --- a/UARTDriver/UARTDriver.cpp +++ /dev/null @@ -1,262 +0,0 @@ -#include "UARTDriver.hpp" - - -uint8_t read_byte(uint8_t*& read_head, uint8_t* buffer, uint16_t bufferSize) -{ - // read the byte from the current read head - uint8_t copy_to = *read_head; - - // increment the read head, loop it around the circular buffer - if(read_head - buffer < bufferSize + 1){ - read_head++; - }else{ - read_head = buffer; - } - - return copy_to; -} - -/** - * @brief Send data on DMA - * @note Contains API calls only for the H7xx chips - * @return `1` if transfer just started - */ -uint8_t UARTDriver::start_tx_data_transfer(uint16_t len, uint8_t* buffer_adr) -{ - /* Disable channel if enabled */ - LL_DMA_DisableStream(kDma_, DMA_DATA_STREAM_tx); - - /* Clear all flags */ - LL_DMA_ClearFlag_TC0(kDma_); // assuming DMA_DATA_STREAM == stream 0 - LL_DMA_ClearFlag_HT0(kDma_); // USART8 - LL_DMA_ClearFlag_TE0(kDma_); - LL_DMA_ClearFlag_DME0(kDma_); - LL_DMA_ClearFlag_FE0(kDma_); - - /* Prepare DMA data and length */ - LL_DMA_SetDataLength(kDma_, DMA_DATA_STREAM_tx, len); - LL_DMA_SetMemoryAddress(kDma_, DMA_DATA_STREAM_tx, (uint32_t)buffer_adr); - - /* Start transfer */ - LL_DMA_EnableStream(kDma_, DMA_DATA_STREAM_tx); - - return true; -} - -/** - * @brief Transmits data via the DMA - * @brief Should be called until returns non-zero - * @param data, len The data to transmit, the length of data to transmit - * @return 0 if any previous data is still transmitting, 1 if the transmit was successfully started, -1 if the data is too large to transmit - * @note need to add protection from multiple threads calling this function at once - */ -bool UARTDriver::Transmit(uint8_t* data, uint16_t len) -{ - - int n = SendData(data, len); // try to send the data - for(int i = 0; !n; i++){ - n = SendData(data, len); // continually try, waiting for any current transfers to finish - - // set a timeout - if(i > 1000) { - SOAR_ASSERT(false, "Transmit operation timed out"); - return false; - } - } - if(n == -1){ - SOAR_ASSERT(false, "Data too large to transmit\n"); // if this error is called, just set MAX_DMA_BUFFER_LEN to a number greater than 64 - return false; - } - - return true; -} - -char UARTDriver::SendData(uint8_t* data, uint16_t len){ - // make sure the data wont overflow the buffer - if(len + 3 > MAX_DMA_BUFFER_LEN){ - return -1; - } - - // make sure there is no current transmit on the DMA channel - if (!LL_DMA_GetDataLength(kDma_, DMA_DATA_STREAM_tx)) { - // "Once the stream is enabled, the return value indicates the remaining bytes to be transmitted." - return 0; // if there are bytes remaining to be transmitted, dont start transmitting a new message - } - - // set length bytes for the message - lin_tx_buffer[0] = (uint8_t)(len & 0xFF00 >> 8); - lin_tx_buffer[1] = (uint8_t)(len & 0x00FF); - - // copy the message to the buffer - for(int i = 0; i < len; i++){ - lin_tx_buffer[i+2] = data[i]; - } - - // set the checkbyte in the buffer - lin_tx_buffer[len+2] = data[len-2] | (uint8_t)(len & 0x00FF); // takes the last byte of data and &'s it with length byte to get some random check - - // starts the process of transmitting the data via DMA - start_tx_data_transfer(len + 3, lin_tx_buffer); - - return 1; -} - -/** -* @brief Reads the circular buffer and copies the data into charBuf -* @param charBuf, receiver -* @return TRUE if data was successfully read, false otherwise -* @note can make reading set the read head to some placeholder bit so read head wont advance until its not a placeholder bit -*/ -bool UARTDriver::Receive(uint8_t* charBuf, UARTReceiverBase* receiver) -{ - // read the length bytes - uint16_t len = read_byte(rx_read_head, rx_buffer, 64) << 8; - len |= read_byte(rx_read_head, rx_buffer, 64); - - // copy the data bytes into charBuf - for(int i = 0; i < len; i++){ - // if it is a placeholder byte, wait until it is not - uint8_t byte = read_byte(rx_read_head, rx_buffer, 64); - - for(int j; byte == '\0'; byte = read_byte(rx_read_head, rx_buffer, 64), j++){ - if(i >= len -1 ) {break;} // skip the string null terminator - if(j > 1000) return false; // configure a timeout - } - - // write the current byte - charBuf[i] = byte; - - // replace the just-read byte in the read buffer with 0 to indicate it doesnt contain data - if(rx_read_head == rx_buffer){ - rx_buffer[MAX_DMA_BUFFER_LEN - 1] = 0; - }else{ - *(rx_read_head - 1) = 0; - } - } - // null terminate the message (replace the stop bit in the message '\r') - charBuf[len-1] = '\0'; - - // read the check byte - if(read_byte(rx_read_head, rx_buffer, 64) != (charBuf[len - 2] | (len & 0x00FF))){ - DMA_DATA_OVERRUN_FLAG = true; - return false; // returns false if the check byte is not what was calculated - } // indicates that the write head overwrote the read head - - // set the pointer to the reciever object so we can call its interrupt - rxReceiver_ = receiver; - - // returns true if the data was successfully read - return true; -} - -/** - * @brief Clears any error flags that may have been set - * @return true if flags had to be cleared, false otherwise - */ -bool UARTDriver::HandleAndClearRxError() -{ - bool shouldClearOverflowFlag = false; - bool shouldClearFlags = false; - if (LL_USART_IsActiveFlag_ORE(kUart_)) { - shouldClearFlags = true; - } - if (LL_USART_IsActiveFlag_NE(kUart_)) { - shouldClearFlags = true; - } - if(LL_USART_IsActiveFlag_FE(kUart_)) { - shouldClearFlags = true; - } - if(LL_USART_IsActiveFlag_PE(kUart_)) { - shouldClearFlags = true; - } - if(DMA_DATA_OVERRUN_FLAG) - shouldClearOverflowFlag = true; - - // Clearing the ORE here also clears PE, NE, FE, IDLE - if(shouldClearFlags) - LL_USART_ClearFlag_ORE(kUart_); - - return !shouldClearFlags || !shouldClearOverflowFlag; -} - -/** - * @brief Checks UART Rx error flags, if any are set returns true - * @return true if any error flags are set, false otherwise - */ -bool UARTDriver::GetRxErrors() -{ - bool hasErrors = false; - - if (LL_USART_IsActiveFlag_ORE(kUart_)) { - hasErrors = true; - } - else if (LL_USART_IsActiveFlag_NE(kUart_)) { - hasErrors = true; - } - else if(LL_USART_IsActiveFlag_FE(kUart_)) { - hasErrors = true; - } - else if(LL_USART_IsActiveFlag_PE(kUart_)) { - hasErrors = true; - }else if(DMA_DATA_OVERRUN_FLAG){ - hasErrors = true; - } - - return hasErrors; -} - -/** - * @brief Handles an interrupt for the UART - * @attention MUST be called inside USARTx_IRQHandler - */ -void UARTDriver::HandleIRQ_UART() -{ - /* Check for IDLE line interrupt */ - if (LL_USART_IsEnabledIT_IDLE(kUart_) && LL_USART_IsActiveFlag_IDLE(kUart_)) { - LL_USART_ClearFlag_IDLE(kUart_); /* Clear IDLE line flag */ - usart_rx_check(); /* Check for data to process */ - } - - // Call the callback if RXNE is set - if (LL_USART_IsActiveFlag_RXNE(kUart_)) { - // Call the receiver interrupt - if(rxReceiver_ != nullptr) { - rxReceiver_->InterruptRxData(GetRxErrors()); - } - } -} - -/** - * @brief DMA interrupt handler for USART TX - */ -void UARTDriver::DMA_Stream_tx_IRQHandler(void) { - /* Check half-transfer complete interrupt */ - if (LL_DMA_IsEnabledIT_HT(kDma_, DMA_DATA_STREAM_tx) && LL_DMA_IsActiveFlag_HT0(kDma_)) { - LL_DMA_ClearFlag_HT0(kDma_); /* Clear half-transfer complete flag */ - usart_rx_check(); /* Check for data to process */ - } - - /* Check transfer-complete interrupt */ - if (LL_DMA_IsEnabledIT_TC(kDma_, DMA_DATA_STREAM_tx) && LL_DMA_IsActiveFlag_TC0(kDma_)) { - LL_DMA_ClearFlag_TC0(kDma_); /* Clear transfer complete flag */ - usart_rx_check(); /* Check for data to process */ - } - - /* Implement other events when needed */ -} - -/** - * @brief DMA interrupt handler for USART RX - */ -void UARTDriver::DMA1_Stream1_IRQHandler(void) { - /* Check transfer complete */ - if (LL_DMA_IsEnabledIT_TC(kDma_, DMA_DATA_STREAM_rx) && LL_DMA_IsActiveFlag_TC1(kDma_)) { - LL_DMA_ClearFlag_TC1(kDma_); /* Clear transfer complete flag */ - lwrb_skip(&usart_tx_rb, usart_tx_dma_current_len);/* Skip sent data, mark as read */ - usart_tx_dma_current_len = 0; /* Clear length variable */ - usart_start_tx_dma_transfer(); /* Start sending more data */ - } - - /* Implement other events when needed */ -} - From 328f42b04b99745554503855a6933eb9bd42f942 Mon Sep 17 00:00:00 2001 From: Noah Vickerson Date: Sat, 9 Nov 2024 13:31:13 -0700 Subject: [PATCH 6/6] unified message structure --- UARTDriver/DMA_UARTDriver.cpp | 66 ++++++++++++----------------------- 1 file changed, 23 insertions(+), 43 deletions(-) diff --git a/UARTDriver/DMA_UARTDriver.cpp b/UARTDriver/DMA_UARTDriver.cpp index b91f429..a134dd3 100644 --- a/UARTDriver/DMA_UARTDriver.cpp +++ b/UARTDriver/DMA_UARTDriver.cpp @@ -39,12 +39,14 @@ bool DMA_UARTDriver::Receive(uint8_t* charBuf, uint8_t& buffIdx); bool DMA_UARTDriver::Transmit(uint8_t* data, uint16_t len) { - // HAL DMA transmission - // metabytes to send messages in any form (dont have to '\r' terminate like for debug messages) - uint8_t metaBytes = 0, headerBytes = 0; + uint8_t metabytes = 0; + if(!is_Debug){ + metabytes = 2; // add stop bytes for non-debug messages + } + // HAL DMA transmission // make sure the data wont overflow the buffer - if(len + metaBytes > MAX_DMA_BUFFER_LEN){ + if(len + metabytes > MAX_DMA_BUFFER_LEN){ return false; } @@ -53,26 +55,20 @@ bool DMA_UARTDriver::Transmit(uint8_t* data, uint16_t len) if(i > 10000) break; // TEMP fix for hal uart not readying error } - // set length bytes for the message - if(!is_Debug){ - lin_tx_buffer[0] = (uint8_t)(len & 0xFF00 >> 8); - lin_tx_buffer[1] = (uint8_t)(len & 0x00FF); - headerBytes = 2; - metaBytes = 3; - } - // copy the message to the buffer for(int i = 0; i < len; i++){ - lin_tx_buffer[i + headerBytes] = data[i]; // sets the data to the message buffer, with an offset if its not to serial + lin_tx_buffer[i] = data[i]; // sets the data to the message buffer, with an offset if its not to serial } - // set the checkbyte in the buffer - if(!is_Debug){ - lin_tx_buffer[len+2] = data[len-2] | (uint8_t)(len & 0x00FF); // takes the last byte of data and &'s it with length byte to get some random check + // set the stop bytes in the buffer + // can ignore for terminal communications + if(!isdebug){ + lin_tx_buffer[len] = '\r'; + lin_tx_buffer[len + 1] = '\n'; } // starts the process of transmitting the data via DMA - if(HAL_UART_Transmit_DMA(hUart_, (uint8_t*)lin_tx_buffer, len + metaBytes)!= HAL_OK) + if(HAL_UART_Transmit_DMA(hUart_, (uint8_t*)lin_tx_buffer, len + metabytes)!= HAL_OK) { /* Transfer error in transmission process */ return false; @@ -88,42 +84,26 @@ bool UARTDriver::Receive(uint8_t* charBuf, uint8_t& buffIdx) } // make sure we're receiving - // read the next byte in the curcular buffer + // read the next byte in the circular buffer charBuf[buffIdx] = read_byte(rx_read_head, rx_buffer, MAX_DMA_BUFFER_LEN); if(charBuf[buffIdx] && is_Debug) Transmit(charBuf+buffIdx, 1); // send input response to debug terminal // makes debug messages cleaner by eliminating the metadata // requires specific messages to work (ie '\r\n' terminated) - if(is_Debug){ - // loop until end of line character - while(charBuf[buffIdx] != '\n'){ - - if(charBuf[buffIdx] == '\0') return false; // wait until message is finished - - buffIdx++; - charBuf[buffIdx] = read_byte(rx_read_head, rx_buffer, MAX_DMA_BUFFER_LEN); // continue to copy data - } + // loop until end of line character + while(charBuf[buffIdx] != '\n'){ - charBuf[buffIdx-1] = '\0'; // null terminate for a string - charBuf[buffIdx] = '\0'; - buffIdx = 0; // reset the buffer + if(charBuf[buffIdx] == '\0') return false; // wait until message is finished - }else{ - uint8_t len = (read_byte(rx_read_head, rx_buffer, MAX_DMA_BUFFER_LEN) << 8) | (read_byte(rx_read_head, rx_buffer, MAX_DMA_BUFFER_LEN)); - - for(int i = 0; i < len; i++){ // read the number of message bytes and copy into charbuf - charBuf[buffIdx] = read_byte(rx_read_head, rx_buffer, MAX_DMA_BUFFER_LEN); - buffIdx++; - } - uint8_t checkByte = read_byte(rx_read_head, rx_buffer, MAX_DMA_BUFFER_LEN); // check to make sure the transfer works - - if(checkByte != (charBuf[buffIdx-2] | (charBuf[buffIdx-1] & 0x00FF))){ - buffIdx = 0; // reset the transfer for failed transfer - return false; // check if the checkbyte is correct - } + buffIdx++; + charBuf[buffIdx] = read_byte(rx_read_head, rx_buffer, MAX_DMA_BUFFER_LEN); // continue to copy data } + charBuf[buffIdx-1] = '\0'; // null terminate for a string + charBuf[buffIdx] = '\0'; + buffIdx = 0; // reset the buffer + return true; }