{"id":4563,"date":"2026-06-27T09:08:30","date_gmt":"2026-06-27T09:08:30","guid":{"rendered":"https:\/\/blog.embeddedexpert.io\/?p=4563"},"modified":"2026-06-27T09:08:32","modified_gmt":"2026-06-27T09:08:32","slug":"getting-started-with-stm32-low-layer-ll-uart-reception-using-dma","status":"publish","type":"post","link":"https:\/\/blog.embeddedexpert.io\/?p=4563","title":{"rendered":"Getting Started with STM32 Low Layer (LL): UART Reception using DMA"},"content":{"rendered":"\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"559\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_vf6lgvf6lgvf6lgv-1024x559.png\" alt=\"\" class=\"wp-image-4564\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_vf6lgvf6lgvf6lgv-1024x559.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_vf6lgvf6lgvf6lgv-300x164.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_vf6lgvf6lgvf6lgv-768x419.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_vf6lgvf6lgvf6lgv-1150x627.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_vf6lgvf6lgvf6lgv-750x409.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_vf6lgvf6lgvf6lgv-400x218.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_vf6lgvf6lgvf6lgv-250x136.png 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_vf6lgvf6lgvf6lgv.png 1408w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Eliminate the CPU overhead of processing individual incoming characters by offloading variable-length UART reception entirely to the DMA controller. By pairing a circular or linear DMA stream with the UART hardware&#8217;s IDLE line interrupt, you can seamlessly buffer unpredictable incoming data frames directly into RAM, freeing the processor to focus on high-level application logic until a complete packet is ready for parsing.<\/p>\n\n\n\n<p>In this guide, we shall cover the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Introduction.<\/li>\n\n\n\n<li>STM32CubeMX configuration.<\/li>\n\n\n\n<li>Firmware development.<\/li>\n\n\n\n<li>Results.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">1. Introduction:<\/h2>\n\n\n\n<p>While utilizing the&nbsp;<code>RXNE<\/code>&nbsp;(Read Data Register Not Empty) interrupt prevents the CPU from getting stuck in blocking polling loops, high-speed data streams can still overwhelm your application. At high baud rates, a dense influx of data forces the processor to constantly execute context switches\u2014tripping an interrupt, saving registers to the stack, reading a single byte, and returning\u2014dozens of thousands of times per second. This processing tax drastically diminishes the CPU cycles available for execution of your core application logic.<\/p>\n\n\n\n<p>The ultimate architecture for high-performance serial communication combines&nbsp;<strong>Direct Memory Access (DMA)<\/strong>&nbsp;with&nbsp;<strong>IDLE Line Detection<\/strong>.<\/p>\n\n\n\n<p>By assigning a dedicated DMA stream (such as&nbsp;<strong>DMA1, Stream 5, Channel 4<\/strong>&nbsp;for USART2 RX on the STM32F4) to operate in circular or continuous mode, incoming bytes bypass the CPU entirely. The UART hardware automatically signals the DMA controller the microsecond a byte enters its shift register. The DMA controller immediately captures that byte and streams it directly into a designated RAM buffer without a single line of code being executed by the processor.<\/p>\n\n\n\n<p>The true power of this setup comes from pairing it with the&nbsp;<code>IDLE<\/code>&nbsp;line interrupt. Instead of waking up the CPU for every character, the processor remains at&nbsp;<strong>0% utilization<\/strong>&nbsp;during the active transfer. The CPU is only interrupted once: when the transmitting device stops sending data and the RX line rests at logic high for one frame period. This triggers a single&nbsp;<code>IDLE<\/code>interrupt, alerting your application that a complete, variable-length packet is sitting in memory and is ready for immediate processing.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. STM32CubeMX Configuration:<\/h2>\n\n\n\n<p>We shall continue from the previous guide from <a href=\"https:\/\/blog.embeddedexpert.io\/?p=4539\" data-type=\"link\" data-id=\"https:\/\/blog.embeddedexpert.io\/?p=4539\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>.<\/p>\n\n\n\n<p>Open project&#8217;s .ioc file in STM32CubeMX.<\/p>\n\n\n\n<p>From USART2 tab, select DMA setting and add new DMA for USART2_RX as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"662\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-05-1024x662.png\" alt=\"\" class=\"wp-image-4565\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-05-1024x662.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-05-300x194.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-05-768x497.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-05-1536x993.png 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-05-2048x1324.png 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-05-1150x744.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-05-750x485.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-05-400x259.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-05-250x162.png 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Next, from System Core, NVIC, code generation, disable code generation of DMA1_Stream5_IRQ_Handler and generate the code as follows<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"662\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-29-1024x662.png\" alt=\"\" class=\"wp-image-4566\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-29-1024x662.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-29-300x194.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-29-768x497.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-29-1536x993.png 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-29-2048x1324.png 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-29-1150x744.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-29-750x485.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-29-400x259.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_10-51-29-250x162.png 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Thats all for the STM32CubeMX configuration.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3. Firmware Development:<\/h2>\n\n\n\n<p>Open your project in STM32CubeIDE.<\/p>\n\n\n\n<p>In user code begin PV, declare this extra flag:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">uint8_t iDMARX=0;<\/pre><\/div>\n\n\n\n<p>This will allow us to determine when IDLE line is triggered if the reception in interrupt mode or DMA <\/p>\n\n\n\n<p>Next, for DMA1_Stream5 interrupt handler:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">void DMA1_Stream5_IRQHandler(void)\n{\n\tif (LL_DMA_IsActiveFlag_TC5(DMA1))\n\t{\n\t\tLL_DMA_ClearFlag_TC5(DMA1);\n\t\tLL_DMA_DisableStream(DMA1, LL_DMA_STREAM_5);\n\t\tReceivedLen=RXBuffSize;\n\n\n\t}\n}<\/pre><\/div>\n\n\n\n<p>Once the DMA received all the characters defined by RXBuffSize, TC will be triggered.<\/p>\n\n\n\n<p>Once it is triggered, clear TC flag, disable the stream and set the received length to the buffer size.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Next, modify the IDLE interrupt within USART2_IRQ_Handler to have the following:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">if(LL_USART_IsActiveFlag_IDLE(USART2))\n{\n  LL_USART_ClearFlag_IDLE(USART2);\n  if(iDMARX==0)\n  {\n    ReceivedLen=rx_index;\n    rx_index=0;\n    rxDone=1;\n  }\n\n  if(iDMARX==1)\n  {\n    \/* Calculate bytes received *\/\n    ReceivedLen=RXBuffSize-LL_DMA_GetDataLength(DMA1, LL_DMA_STREAM_5);\n\n    \/*Clear IDLE flag*\/\n    LL_USART_ClearFlag_IDLE(USART2);\n\n    \/* Stop DMA to preserve received data *\/\n    LL_DMA_DisableStream(DMA1, LL_DMA_STREAM_5);\n\n    \/* Clear any pending DMA flags *\/\n    LL_DMA_ClearFlag_TC5(DMA1);\n\n    rxDone = 1;\n  }\n}<\/pre><\/div>\n\n\n\n<p>This will allow the firmware to determine the received length in both cases, DMA or interrupt mode.<\/p>\n\n\n\n<p>Next, declare the following function:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">void UART_Receive_DMA_IDLE(uint8_t *ch, uint16_t len) <\/pre><\/div>\n\n\n\n<p>This function shall allow the user to receive data either for the length (100 characters for example) or until IDLE line is detected.<\/p>\n\n\n\n<p>Within the function:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">rxDone = 0;\nReceivedLen = 0;\niDMARX=1;\n\nLL_DMA_DisableStream(DMA1, LL_DMA_STREAM_5);\n\nLL_DMA_ClearFlag_TC5(DMA1);\nLL_DMA_ClearFlag_HT5(DMA1);\nLL_DMA_ClearFlag_TE5(DMA1);\n\nLL_DMA_EnableIT_TC(DMA1, LL_DMA_STREAM_5);   \/\/ Enable Transfer Complete Interrupt\n\nLL_USART_ClearFlag_IDLE(USART2);\n\nLL_USART_EnableIT_IDLE(USART2);\n\nLL_USART_EnableDMAReq_RX(USART2);  \/\/ Enable USART RX via DMA\n\nLL_DMA_SetMemoryAddress(DMA1, LL_DMA_STREAM_5, (uint32_t)ch);\nLL_DMA_SetPeriphAddress(DMA1, LL_DMA_STREAM_5, LL_USART_DMA_GetRegAddr(USART2));\nLL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_5, len);\n\nLL_DMA_EnableStream(DMA1, LL_DMA_STREAM_5);<\/pre><\/div>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. Reset Status Flags<\/h3>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">rxDone = 0;\nReceivedLen = 0;\niDMARX = 1;<\/pre><\/div>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>rxDone = 0<\/strong>: Clear the reception complete flag so the main loop knows no new data has arrived yet<\/li>\n\n\n\n<li><strong>ReceivedLen = 0<\/strong>: Reset the received byte counter to zero<\/li>\n\n\n\n<li><strong>iDMARX = 1<\/strong>: Set a flag indicating DMA mode is active (used in the ISR to know that DMA, not interrupt-based RX, is handling reception)<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2. Disable DMA Stream Before Configuration<\/h3>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">LL_DMA_DisableStream(DMA1, LL_DMA_STREAM_5);<\/pre><\/div>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Purpose<\/strong>: DMA stream must be disabled before you can modify its configuration registers<\/li>\n\n\n\n<li><strong>DMA1<\/strong>: The DMA controller being used<\/li>\n\n\n\n<li><strong>LL_DMA_STREAM_5<\/strong>: Stream 5 which is connected to USART2 RX<\/li>\n\n\n\n<li><strong>Effect<\/strong>: Stops any ongoing DMA transfer if one was in progress<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3. Clear All Pending DMA Flags<\/h3>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">LL_DMA_ClearFlag_TC5(DMA1);\nLL_DMA_ClearFlag_HT5(DMA1);\nLL_DMA_ClearFlag_TE5(DMA1);<\/pre><\/div>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>TC5 (Transfer Complete)<\/strong>: Clears flag that indicates DMA finished transferring all requested data (buffer full)<\/li>\n\n\n\n<li><strong>HT5 (Half Transfer)<\/strong>: Clears flag that indicates DMA has transferred half the buffer<\/li>\n\n\n\n<li><strong>TE5 (Transfer Error)<\/strong>: Clears any error flags from previous transfers<\/li>\n\n\n\n<li><strong>Why<\/strong>: Prevents false triggers from previous operations<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">4. Enable DMA Transfer Complete Interrupt<\/h3>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">LL_DMA_EnableIT_TC(DMA1, LL_DMA_STREAM_5);<\/pre><\/div>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Purpose<\/strong>: Enable interrupt that fires when the DMA buffer becomes completely full<\/li>\n\n\n\n<li><strong>Effect<\/strong>: If no IDLE line is detected before buffer fills up, this interrupt handles it<\/li>\n\n\n\n<li><strong>Result<\/strong>: <code>DMA1_Stream5_IRQHandler<\/code> will execute when buffer is full<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">5. Clear USART IDLE Flag<\/h3>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">LL_USART_ClearFlag_IDLE(USART2);<\/pre><\/div>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Purpose<\/strong>: Clear any previously pending IDLE line detection flag<\/li>\n\n\n\n<li><strong>Why<\/strong>: The flag might be set from previous operations or during initialization<\/li>\n\n\n\n<li><strong>How<\/strong>: Reads the USART status register followed by the data register to clear the IDLE flag<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">6. Enable USART IDLE Line Interrupt<\/h3>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">LL_USART_EnableIT_IDLE(USART2);<\/pre><\/div>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Purpose<\/strong>: Enable the interrupt that triggers when the RX line has been idle for one character period<\/li>\n\n\n\n<li><strong>Meaning<\/strong>: When sender stops transmitting (end of message), this interrupt fires<\/li>\n\n\n\n<li><strong>Effect<\/strong>: The IDLE check in <code>USART2_IRQHandler<\/code> will now work<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">7. Enable USART DMA Request for RX<\/h3>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">LL_USART_EnableDMAReq_RX(USART2);<\/pre><\/div>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Purpose<\/strong>: Tell the USART peripheral to use DMA for receiving data instead of generating RXNE interrupts<\/li>\n\n\n\n<li><strong>Effect<\/strong>: Every time a byte arrives, USART automatically triggers DMA to move it from the USART data register to memory<\/li>\n\n\n\n<li><strong>Result<\/strong>: No CPU involvement needed for individual byte transfers<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">8. Configure DMA Source and Destination<\/h3>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">LL_DMA_SetMemoryAddress(DMA1, LL_DMA_STREAM_5, (uint32_t)ch);\nLL_DMA_SetPeriphAddress(DMA1, LL_DMA_STREAM_5, LL_USART_DMA_GetRegAddr(USART2));\nLL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_5, len);<\/pre><\/div>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Memory Address<\/strong>: Where received data will be stored (your buffer <code>ch<\/code>)<\/li>\n\n\n\n<li><strong>Peripheral Address<\/strong>: Where data comes from (USART2 data register)<\/li>\n\n\n\n<li><strong>Data Length<\/strong>: Maximum number of bytes to transfer before stopping (your buffer size)<\/li>\n\n\n\n<li><strong>Direction<\/strong>: Automatically moves data from USART to memory when each byte arrives<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">9. Enable DMA Stream<\/h3>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_5);<\/pre><\/div>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Purpose<\/strong>: Start the DMA engine<\/li>\n\n\n\n<li><strong>Effect<\/strong>: DMA is now ready and waiting for USART to request transfers<\/li>\n\n\n\n<li><strong>What happens next<\/strong>: When a byte arrives at USART, USART requests DMA, DMA moves the byte to memory, increments memory address, decrements remaining count<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Flow After This Function:<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Byte arrives at USART<\/strong> \u2192 USART requests DMA<\/li>\n\n\n\n<li><strong>DMA transfers byte<\/strong> to your buffer, increments address, decrements counter<\/li>\n\n\n\n<li><strong>Repeat<\/strong> for each incoming byte<\/li>\n\n\n\n<li><strong>If buffer fills<\/strong> \u2192 <code>DMA1_Stream5_IRQHandler<\/code> fires, sets <code>ReceivedLen = buffer size<\/code><\/li>\n\n\n\n<li><strong>If line goes idle<\/strong> \u2192 <code>USART2_IRQHandler<\/code> fires, stops DMA, calculates <code>ReceivedLen = buffer size - remaining DMA count<\/code><\/li>\n\n\n\n<li><strong>Either way<\/strong> \u2192 <code>rxDone<\/code> flag is set to 1<\/li>\n\n\n\n<li><strong>Main loop<\/strong> detects <code>rxDone == 1<\/code>, processes the data, then calls this function again to restart<\/li>\n<\/ol>\n\n\n\n<p>The key advantage: CPU does nothing during reception, only processes data when a complete message arrives (detected by IDLE line) or buffer is full.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Next, in while 1 loop:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">if(rxDone==0)\n{\n  UART_Receive_DMA_IDLE((uint8_t*)uart_buff_rx,RXBuffSize);\n\n  while(rxDone==0);\n  rxDone=0;\n  UART_Send_String(uart_buff_rx,ReceivedLen);\n\n}<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Code Explanation for UART DMA IDLE Reception Guide<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Main Loop Code:<\/h3>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">if(rxDone == 0)\n{\n    UART_Receive_DMA_IDLE((uint8_t*)uart_buff_rx, RXBuffSize);\n\n    while(rxDone == 0);\n    rxDone = 0;\n    UART_Send_String(uart_buff_rx, ReceivedLen);\n}<\/pre><\/div>\n\n\n\n<p><strong><code>if(rxDone == 0)<\/code><\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Checks if no reception has been completed yet<\/li>\n\n\n\n<li><code>rxDone<\/code> is a global flag shared between main loop and interrupt handlers<\/li>\n\n\n\n<li>Initially <code>rxDone = 0<\/code> means &#8220;no data received&#8221;<\/li>\n\n\n\n<li>This condition will be true at startup, entering the block to start reception<\/li>\n<\/ul>\n\n\n\n<p><strong><code>UART_Receive_DMA_IDLE((uint8_t*)uart_buff_rx, RXBuffSize);<\/code><\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Calls the function that configures and starts DMA reception<\/li>\n\n\n\n<li>Parameters:<\/li>\n\n\n\n<li><code>(uint8_t*)uart_buff_rx<\/code>: Casts the character buffer to uint8_t pointer, telling DMA where to store incoming bytes<\/li>\n\n\n\n<li><code>RXBuffSize<\/code>: Maximum number of bytes to receive (100 in this case)<\/li>\n\n\n\n<li>This function:<\/li>\n\n\n\n<li>Disables any previous DMA transfer on Stream 5<\/li>\n\n\n\n<li>Clears old DMA flags<\/li>\n\n\n\n<li>Configures DMA to move data from USART2 data register to <code>uart_buff_rx<\/code><\/li>\n\n\n\n<li>Enables IDLE line detection interrupt<\/li>\n\n\n\n<li>Starts the DMA stream<\/li>\n\n\n\n<li>After this call, DMA is actively waiting for incoming UART data<\/li>\n<\/ul>\n\n\n\n<p><strong><code>while(rxDone == 0);<\/code><\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Blocking wait<\/strong>: The CPU stays here doing nothing until <code>rxDone<\/code> becomes 1<\/li>\n\n\n\n<li>This is a polling loop &#8211; it continuously checks the flag<\/li>\n\n\n\n<li>The flag will be set to 1 in either:<\/li>\n\n\n\n<li><code>USART2_IRQHandler<\/code> when IDLE line is detected (end of message)<\/li>\n\n\n\n<li><code>DMA1_Stream5_IRQHandler<\/code> when buffer is completely full<\/li>\n\n\n\n<li>During this wait, interrupts still fire and handle the actual data reception<\/li>\n\n\n\n<li>The semicolon means &#8220;do nothing while waiting&#8221;<\/li>\n<\/ul>\n\n\n\n<p><strong><code>rxDone = 0;<\/code><\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Once the while loop exits (rxDone became 1), immediately clear the flag<\/li>\n\n\n\n<li>Prepares for next reception cycle<\/li>\n\n\n\n<li>Must be cleared before processing to avoid missing the next completion<\/li>\n<\/ul>\n\n\n\n<p><strong><code>UART_Send_String(uart_buff_rx, ReceivedLen);<\/code><\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Processes the received data by echoing it back<\/li>\n\n\n\n<li><code>uart_buff_rx<\/code>: Contains the bytes that were received via DMA<\/li>\n\n\n\n<li><code>ReceivedLen<\/code>: Set by the ISR, tells exactly how many bytes were received<\/li>\n\n\n\n<li><code>UART_Send_String<\/code> is a blocking function that transmits each byte and waits for completion<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h3 class=\"wp-block-heading\">Complete Flow:<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>First iteration<\/strong>: <code>rxDone<\/code> is 0, so code enters the if block<\/li>\n\n\n\n<li>DMA is configured and started to receive into <code>uart_buff_rx<\/code><\/li>\n\n\n\n<li>CPU blocks at <code>while(rxDone == 0)<\/code> waiting for data<\/li>\n\n\n\n<li>When data arrives, DMA automatically moves it from USART to buffer<\/li>\n\n\n\n<li>When line goes idle or buffer fills, ISR sets <code>rxDone = 1<\/code> and <code>ReceivedLen<\/code><\/li>\n\n\n\n<li>While loop exits, <code>rxDone<\/code> is cleared to 0<\/li>\n\n\n\n<li>Received data is echoed back via <code>UART_Send_String<\/code><\/li>\n\n\n\n<li>Loop repeats: <code>rxDone<\/code> is 0 again, so DMA is reconfigured and started again<\/li>\n<\/ol>\n\n\n\n<p>Thats all for the firmware.<\/p>\n\n\n\n<p>Save, build and run the project as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"34\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/image-1024x34.png\" alt=\"\" class=\"wp-image-4523\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/image-1024x34.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/image-300x10.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/image-768x26.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/image-1536x51.png 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/image-1150x38.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/image-750x25.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/image-400x13.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/image-250x8.png 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/image.png 1986w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>You may download the code from&nbsp;<a href=\"https:\/\/github.com\/hussamaldean\/Embedded-Expert-Post-Projects\/tree\/main\/Projects\/LL_UART\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. Results:<\/h2>\n\n\n\n<p>Open your favourite terminal application, set the baudrate to 115200 and send any characters you want and you should get the following:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"705\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_12-06-55-1-1024x705.png\" alt=\"\" class=\"wp-image-4569\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_12-06-55-1-1024x705.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_12-06-55-1-300x207.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_12-06-55-1-768x529.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_12-06-55-1-1536x1058.png 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_12-06-55-1-1150x792.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_12-06-55-1-750x517.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_12-06-55-1-400x276.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_12-06-55-1-250x172.png 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-27_12-06-55-1.png 1890w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Now, your driver is ready for your application.<\/p>\n\n\n\n<p><br>Next part, we shall user circular mode to handle the data professionally.<\/p>\n\n\n\n<p>Stay tuned.<\/p>\n\n\n\n<p>Happy coding \ud83d\ude09<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Eliminate the CPU overhead of processing individual incoming characters by offloading variable-length UART reception entirely to the DMA controller. By pairing a circular or linear DMA stream with the UART hardware&#8217;s IDLE line interrupt, you can seamlessly buffer unpredictable incoming data frames directly into RAM, freeing the processor to focus on high-level application logic until [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2,11,12],"tags":[],"class_list":["post-4563","post","type-post","status-publish","format-standard","hentry","category-embedded-systems","category-peripheral-drivers","category-stm32"],"_links":{"self":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4563"}],"collection":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4563"}],"version-history":[{"count":1,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4563\/revisions"}],"predecessor-version":[{"id":4570,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4563\/revisions\/4570"}],"wp:attachment":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4563"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4563"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4563"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}