Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions UARTDriver/DMA_UARTDriver.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
49 changes: 49 additions & 0 deletions UARTDriver/Inc/DMA_UARTDriver.hpp
Original file line number Diff line number Diff line change
@@ -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_ */
105 changes: 105 additions & 0 deletions UARTDriver/UARTDriver_README.md
Original file line number Diff line number Diff line change
@@ -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 */
}
```