This application note contains explanations with examples for 2 distinct topics:
GitHub supports ToC by default. It is 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 term UART.
UART in STM32 allows configuration using different transmit (TX) and receive (RX) modes:
9600 or lower115200, up to ~921600 bauds> 1Mbps and low-power applicationsThis guide focuses exclusively on DMA‑based RX operation and explains how to handle cases where data length is unknown
Every STM32 has at least one (1) UART IP and at least one (1) DMA controller available in its DNA.
This is all we need for successful data transmission.
The application uses default features to implement very efficient transmit system using DMA.
While the implementation is fairly straightforward for TX (set the pointer to data, define its length, and go) operation, 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, UART protocol does not offer such information (it could work with higher-level protocol, but that is another story entirely that we will not cover here. We assume we have 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 2 methods:
- IDLE LINE event: Triggered when RX line has been in idle state (normally high state) for 1 frame time, after the last received byte. Frame time is based on baudrate. Higher baudrate means lower frame time for single byte.
- RTO (Receiver Timeout) event: Triggered when line has been in idle state for 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: To transmit 1 byte at 115200 bauds, it takes approximately (for easier estimation purposes) ~100us; for 3 bytes it would be ~300us in total.
IDLE line event triggers an interrupt when line has been in idle state for 1 frame time (in this case 100us), after third byte has been received.

This is a real experiment demo using STM32F4 and IDLE LINE event. After IDLE event is triggered, data are echoed back (loopback mode):
3 bytes, takes approx ~300us at 115200 bauds1 frame time (approx 100us)1 frame timeDMA in STM32 can be configured in normal or circular mode.
For each mode, DMA requires number of elements to transfer before its events (half-transfer complete, transfer complete) are triggered.
0.While transfer is active, 2 (among others) interrupts may be triggered:
HT: Triggers when DMA transfers half count of elementsTC: Triggers when DMA transfers all elementsWhen DMA operates in circular mode, these interrupts are triggered periodically
Number of elements to transfer by DMA hardware must be written to relevant DMA register before start of transfer
Now it is time to understand which features to use to receive data with UART and DMA to offload CPU.
As for the sake of this example, we use memory buffer array of 20 bytes. DMA will transfer data received from UART to this buffer.
Listed are steps to begin. Initial assumption is that UART has been initialized prior to reaching this step, same for basic DMA setup, the rest:
20 to 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 are transferred from UART to memoryThis configuration is important as we do not know length in advance. Application needs to assume it may be endless number of bytes received, therefore DMA must be operational endlessly.
We have used
20bytes long array for demonstration purposes. In real app this size may need to be increased. It all depends on UART baudrate (higher speed, more data may be received in fixed window) and how fast application can process the received data (either using interrupt notification, RTOS, or polling mode)
Everything gets simpler when the application transmits data, the length of data is known in advance and the memory to transmit is ready.
For the sake of this example, we use memory for 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, point B is UART data register. Now it is up to UART to clock out byte to GPIO pin
This section describes 4 possible cases and one additional which 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 received data
- Case B: DMA transfers next 10 bytes. The application receives a notification thanks to the TC event. Processing now starts from the last known position to the end of memory
- DMA is in circular mode, thus it will continue right from beginning of the buffer, on top of the picture
- Case C: DMA transfers 10 bytes, but not aligned with HT or TC events
- 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 by the TC event when the first 4 bytes are transferred. Processing may start from the last known read location
- The application receives a notification by the IDLE event after the next 6 bytes are transferred. Processing may start from beginning of buffer
- Case E: Example of what may happen when the application relies only on the IDLE event
- If application receives 30 bytes in burst, 10 bytes get overwritten by DMA as application did not process it quickly enough
- The application gets the IDLE line event once the RX line is steady for 1 byte timeframe
- Red part of data represents first 10 received bytes from burst which were overwritten by last 10 bytes in burst
- An option to avoid such a scenario is to poll for DMA changes quicker than a burst of 20 bytes takes; or by using TC and HT events
Example code to read data from memory and process it, for cases A-D
```c / * \brief Check for new data received with DMA * * User must select context to call this function from: * - Only interrupts (DMA HT, DMA TC, UART IDLE) with same preemption priority level * - Only thread context (outside interrupts) * * If called from both contexts, exclusive access protection must be implemented * This mode is not advised as it usually means architecture design problems * * When IDLE interrupt is not present, application must rely only on thread context, * by manually calling function as quickly as possible, to make sure * data are read from raw buffer and processed. * * Not doing reads fast enough may cause DMA to overflow unread received bytes, * hence the application will lose useful data. * * Solutions to this are: * - Improve architecture design to achieve faster reads * - Increase raw buffer size and allow DMA to write more data before this function is called / void usart_rx_check(void) { / * Set
$ claude mcp add stm32-usart-uart-dma-rx-tx \
-- python -m otcore.mcp_server <graph>