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
- Include Header and Declare Object
#include "IntrUART.h" ... IntrUART Uart1; ...
- Initialization Function
void InitUarts(void); ... /* * */ void InitUarts(void) { Uart1 = IntrUART(USART1, &CTask); Uart1.InitInterrupt(); }
- Call Initialization in main.c
... InitUarts(); ...
- 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
- Include Header and Declare Object
#include "DmaUART.h" ... DmaUART Uart1; ...
- Initialization Function
void InitUarts(void); ... /* * */ void InitUarts(void) { Uart1 = DmaUART(USART1, DMA2, LL_DMA_STREAM_7, DMA2, LL_DMA_STREAM_2, &CTask); Uart1.DMARxEnable(); }
- Call Initialization in main.c
... InitUarts(); ... while(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
- No data received → Check if RXNEIE and TXEIE interrupts are enabled.
- Corrupted data → Verify baud rate, parity, and data length settings.
- ISR not triggered → Check NVIC configuration and vector table mapping.
- 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