Browse by type
This application note contains explanations with examples for two distinct topics:
We start with the examples instructions. The most attractive part of the repository.
All examples are developed with CMake build system generation, Ninja build system and GCC compiler.
Each example comes with .vscode folder and provides basic set of files for recommended extensions, simple tasks, launch/debug config and C/C++ extension intellisense configuration for CMake data provider.
git clone https://github.com/MaJerle/stm32-usart-uart-dma-rx-tx or download the zip package.arm-none-eabi-gcc) and make sure they are available on your PATH. Either install each of them separately or download and install STM32CubeCLT, CLI tools for STM32 development.To build all examples at once with python helper script:
python3 scripts/build.py [--clean] [--path projects/project-folder]
To manually build selected project with cmake
cd projects/projects-folder
cmake --list-presets
cmake --preset <preset_name>
cmake --build --preset <preset_name>
GitHub automatically generates a table of contents, available in the top-left corner of this document.
STM32 includes peripherals like USART, UART, and LPUART. For the purposes of this example, the specific differences between them aren't important, since the same concept applies to all. In a few words, USART supports synchronous operation on top of asynchronous (UART) and LPUART supports Low-Power operation in STOP mode. When synchronous mode or low-power mode is not used, USART, UART and LPUART can be considered identical. For a complete set of details, check the product's reference manual and datasheet.
For the sake of this application note, we will only use the term UART.
UART in STM32 allows configuration using different transmit (TX) and receive (RX) modes:
9600 or lower115200, up to ~921600 baud> 1Mbps) and for low-power applicationsThis guide focuses exclusively on DMA-based RX operation and explains how to handle cases where the data length is unknown.
Every STM32 has at least one UART peripheral and at least one DMA controller built in. This is all we need for successful data transmission. The application uses default features to implement a very efficient DMA-based transmit system.
The implementation for TX operation is fairly straightforward (set the pointer to the data, define its length, and go), but this may not be the case for receive. When implementing DMA receive, the application needs to know the number of received bytes to be processed by DMA before it is considered done. However, the UART protocol does not offer such information. (A higher-level protocol could provide it, but that is a separate topic not covered here — we assume we need to implement a very reliable low-level communication protocol.)
STM32 UART peripherals can detect when the RX line remains inactive for a certain period of time. This is done using two methods: - IDLE line event: Triggered when the RX line has been in idle state (normally high) for one frame time after the last received byte. Frame time is based on the baudrate — a higher baudrate means a shorter frame time for a single byte. - RTO (Receiver Timeout) event: Triggered when the line has been idle for a programmable time. It is fully configurable by firmware.
Both events can trigger an interrupt, which is an essential feature for effective receive operation.
Not all STM32 have IDLE line or RTO features available. When not available, examples concerning these features may not be used.
An example: transmitting 1 byte at 115200 baud takes approximately ~100us; 3 bytes would take ~300us in total.
The IDLE line event triggers an interrupt when the line has been idle for 1 frame time (in this case ~100us) after the third byte has been received.

This is a real experiment using STM32F4 and the IDLE line event. After the IDLE event is triggered, data is echoed back (loopback mode):
3 bytes, taking approx ~300us at 115200 baud1 frame time (approx ~100us)1 frame timeDMA in STM32 can be configured in normal or circular mode.
For each mode, DMA requires the number of elements to transfer before its events (half-transfer complete, transfer complete) are triggered.
0.While a transfer is active, 2 (among others) interrupts may be triggered:
HT): Triggers when DMA has transferred half the elementsTC): Triggers when DMA has transferred all elementsWhen DMA operates in circular mode, these interrupts are triggered periodically.
The number of elements to transfer must be written to the relevant DMA register before the start of the transfer.
Now it is time to understand which features to use to receive data with UART and DMA to offload the CPU.
For the sake of this example, we use a memory buffer array of 20 bytes. DMA will transfer data received from UART to this buffer.
Listed are the steps to begin. The initial assumption is that UART has been initialized prior to reaching this step, and the same for basic DMA setup:
20 to the relevant DMA register for data lengthHT event (or interrupt) after the first 10 bytes have been transferred from UART to memoryTC event (or interrupt) after 20 bytes have been transferred from UART to memoryThis configuration is important, as we do not know the length in advance. The application must assume it may receive an endless number of bytes, so DMA must operate endlessly.
We used a
20-byte-long array for demonstration purposes. In a real application, this size may need to be increased. It depends on the UART baudrate (a higher speed means more data may be received in a fixed window) and on how fast the application can process the received data (using interrupt notification, RTOS, or polling mode)
Everything gets simpler when the application transmits data: the length of the data is known in advance, and the memory to transmit is ready.
For the sake of this example, we use memory for the HelloWorld message. In C language it would be:
const char
hello_world_arr[] = "HelloWorld";
strlen(hello_world_arr) or 10TC event (or interrupt) after all bytes have been transmitted from memory to UART via DMAPlease note that the
TCevent is triggered before the last UART byte has been fully transmitted over UART. That is because theTCevent is part of DMA and not part of UART. It is triggered when DMA transfers all the bytes from point A to point B. That is, point A for DMA is memory, and point B is the UART data register. It is then up to UART to clock the byte out to the GPIO pin.
This section describes 4 possible cases, plus one additional case that explains why HT and TC events are both necessary in the application.
Abbreviations used for the image:
- R: Read pointer, used by the application to read data from memory. Later also used as old_ptr
- W: Write pointer, used by the DMA to write next byte to. Increased every time DMA writes new byte. Later also used as new_ptr
- HT: Half-Transfer Complete event triggered by DMA
- TC: Transfer-Complete event - triggered by DMA
- I: IDLE line event - triggered by USART
DMA configuration:
- Circular mode
- 20 bytes data length
- Consequently HT event gets triggered at 10 bytes being transmitted
- Consequently TC event gets triggered at 20 bytes being transmitted
Possible cases during real-life execution:
- Case A: DMA transfers 10 bytes. The application receives a notification via the HT event and may process the received data
- Case B: DMA transfers the next 10 bytes. The application receives a notification via the TC event. Processing now starts from the last known position to the end of memory
- DMA is in circular mode, so it continues right from the beginning of the buffer, at the top of the picture
- Case C: DMA transfers 10 bytes, but not aligned with HT or TC events
- The application gets notified with the HT event when the first 6 bytes are transferred. Processing may start from the last known read location
- The application receives the IDLE line event after the next 4 bytes are successfully transferred to memory
- Case D: DMA transfers 10 bytes in overflow mode and not aligned with HT or TC events
- The application receives a notification via the TC event when the first 4 bytes are transferred. Processing may start from the last known read location
- The application receives a notification via the IDLE event after the next 6 bytes are transferred. Processing may start
$ claude mcp add stm32-usart-uart-dma-rx-tx \
-- python -m otcore.mcp_server <graph>