Using the UART Peripheral in C++

UART (Universal Asynchronous Receiver/Transmitter) is one of the most widely used communication devices in embedded systems.

It is used for various purposes such as debugging, log output, and data exchange with external devices, providing a simple yet reliable communication method.

In this section, we’ll explore how to implement UART in a class-based structure.

Starting from the base class BaseUART, we’ll examine how it can be extended into IntrUART (interrupt-based) and DmaUART (DMA-based) versions depending on performance requirements.


BaseUART: The Base Class

The BaseUART class provides the fundamental features required for UART communication. Its main functions include:

  • Transmit/Receive Queues
    • Temporary storage for data before and after transmission.
  • Read/Write Functions
    • APIs for controlling UART data transmission and reception.
  • Configuration Properties
    • Management of communication parameters such as baud rate, parity, and stop bits.

This class serves as the common foundation for other UART implementations.


IntrUART: Interrupt-Based UART

IntrUART inherits from BaseUART and operates using interrupts.

  • Suitable for small data transfers.
  • Enables event-driven processing → saves CPU idle time.
  • Handles data queues inside the ISR (Interrupt Service Routine).

Whenever a receive event occurs, an interrupt is triggered, the incoming data is stored in the queue, and the application can easily retrieve it later.


DmaUART: DMA-Based UART

DmaUART also inherits from BaseUART but supports high-speed data transfer using DMA.

  • Maximizes performance by minimizing CPU involvement.
  • Optimized for large or continuous data streams.
  • Operates reliably in parallel with other tasks.

It’s particularly effective for applications that require heavy log output or high-bandwidth data exchange.


Code Examples

Example 1 – Using IntrUART

  1. Include Header and Declare Object
#include "IntrUART.h"
...
IntrUART Uart1;
...
  1. Initialization Function
void InitUarts(void);
...
/*
 *
 */
void InitUarts(void)
{
	Uart1 = IntrUART(USART1, &CTask);
        Uart1.InitInterrupt();
}
  1. Call Initialization in main.c
...
  InitUarts();
...
  1. Interrupt ISR Handling
#include <IntrUART.h>

...
extern IntrUART Uart1;

/**
  * @brief This function handles USART2 global interrupt.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
    Uart1.RxInterruptHandler();
    Uart1.TxInterruptHandler();
  /* USER CODE END USART1_IRQn 0 */
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

Example 2 – Using DmaUART

  1. Include Header and Declare Object
#include "DmaUART.h"
...
DmaUART Uart1;
...
  1. Initialization Function
void InitUarts(void);
...
/*
 *
 */
void InitUarts(void)
{
	Uart1 = DmaUART(USART1, DMA2, LL_DMA_STREAM_7, DMA2, LL_DMA_STREAM_2, &CTask);
        Uart1.DMARxEnable();
}
  1. Call Initialization in main.c
...
    InitUarts();
...
    while(1){
      ...
    }
  1. DMA ISR Handling
...
#include <ConsoleTask.h>

...
extern ConsoleTask CTask;

...
/**
  * @brief This function handles USART1 global interrupt.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	CTask.uart.IDLEInterruptHandler();
  /* USER CODE END USART1_IRQn 0 */
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}
...
/**
  * @brief This function handles DMA2 stream2 global interrupt.
  */
void DMA2_Stream2_IRQHandler(void)
{
  /* USER CODE BEGIN DMA2_Stream2_IRQn 0 */
	CTask.uart.RXDMAInterruptHandler();
  /* USER CODE END DMA2_Stream2_IRQn 0 */
  /* USER CODE BEGIN DMA2_Stream2_IRQn 1 */

  /* USER CODE END DMA2_Stream2_IRQn 1 */
}
...
/**
  * @brief This function handles DMA2 stream7 global interrupt.
  */
void DMA2_Stream7_IRQHandler(void)
{
  /* USER CODE BEGIN DMA2_Stream7_IRQn 0 */
	CTask.uart.TXDMAInterruptHandler();
  /* USER CODE END DMA2_Stream7_IRQn 0 */
  /* USER CODE BEGIN DMA2_Stream7_IRQn 1 */

  /* USER CODE END DMA2_Stream7_IRQn 1 */
}

Troubleshooting & Tips

  1. No data received → Check if RXNEIE and TXEIE interrupts are enabled.
  2. Corrupted data → Verify baud rate, parity, and data length settings.
  3. ISR not triggered → Check NVIC configuration and vector table mapping.
  4. Slow processing → Consider using DmaUART instead of IntrUART.

Conclusion

In this section, we designed a class hierarchy for UART communication consisting of:

  • BaseUART – Common base implementation
  • IntrUART – Interrupt-driven UART
  • DmaUART – DMA-driven high-speed UART

This layered design allows you to easily switch communication methods at the application level without modifying your main logic.


0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *

en_USEnglish