diff --git a/UARTDriver/DMA_UARTDriver.cpp b/UARTDriver/DMA_UARTDriver.cpp new file mode 100644 index 0000000..a134dd3 --- /dev/null +++ b/UARTDriver/DMA_UARTDriver.cpp @@ -0,0 +1,129 @@ +/** ******************************************************************************** +* @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) +{ + 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){ + 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 + } + + // copy the message to the buffer + for(int i = 0; i < len; i++){ + lin_tx_buffer[i] = data[i]; // sets the data to the message buffer, with an offset if its not to serial + } + + // 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) + { + /* 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 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) + // 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 + + 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_ */ 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 */ +} +``` + + + + +