{"id":4539,"date":"2026-06-18T09:15:22","date_gmt":"2026-06-18T09:15:22","guid":{"rendered":"https:\/\/blog.embeddedexpert.io\/?p=4539"},"modified":"2026-06-18T09:32:32","modified_gmt":"2026-06-18T09:32:32","slug":"getting-started-with-stm32-low-layer-ll-uart-reception-using-interrupt","status":"publish","type":"post","link":"https:\/\/blog.embeddedexpert.io\/?p=4539","title":{"rendered":"Getting Started with STM32 Low Layer (LL): UART Reception using Interrupt"},"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_4doaco4doaco4doa-1024x559.png\" alt=\"\" class=\"wp-image-4540\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_4doaco4doaco4doa-1024x559.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_4doaco4doaco4doa-300x164.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_4doaco4doaco4doa-768x419.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_4doaco4doaco4doa-1150x627.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_4doaco4doaco4doa-750x409.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_4doaco4doaco4doa-400x218.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_4doaco4doaco4doa-250x136.png 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_4doaco4doaco4doa.png 1408w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Efficiently parse variable-length incoming data frames by combining UART receive interrupts with the hardware&#8217;s built-in\u00a0<strong>IDLE line<\/strong>\u00a0detection feature. This architecture uses the Read Data Register Not Empty (RXNE) interrupt to collect individual bytes in the background, while the IDLE line interrupt fires automatically as soon as the transmission stops, immediately alerting your firmware that a complete packet has arrived without requiring fixed-length buffers or tedious timeouts.<\/p>\n\n\n\n<p><\/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>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>Receiving data over UART can be significantly more challenging than transmitting it. While transmission is deterministic\u2014your firmware knows exactly how many bytes it intends to send\u2014incoming data from external modules, sensors, or host PCs is often unpredictable. In many real-world applications, messages arrive in variable lengths, meaning your microcontroller cannot rely on a fixed byte-count threshold to determine when a complete packet has safely arrived.<\/p>\n\n\n\n<p>Historically, developers solved this problem using inefficient polling loops or by running a dedicated hardware timer alongside the UART line. This timer would reset on every incoming byte; if it expired, the system assumed the transmission had paused, signaling a frame timeout. While functional, this method consumes valuable hardware timers, introduces unnecessary processing overhead, and adds software complexity.<\/p>\n\n\n\n<p>The STM32F4 provides an elegant, hardware-level solution to this architectural bottleneck: the&nbsp;<strong>IDLE Line Detection<\/strong>feature.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Mechanics of IDLE Line Detection<\/h3>\n\n\n\n<p>The UART protocol dictates that a communication wire rests in a continuous&nbsp;<strong>Logic HIGH<\/strong>&nbsp;state when no data is being moved. The STM32 hardware exploits this rule by actively monitoring the RX line during reception.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Data Phase (RXNE):<\/strong>\u00a0As a packet arrives, the hardware triggers a\u00a0<strong>Read Data Register Not Empty (RXNE)<\/strong>interrupt for every single byte. Your firmware instantly captures each character and drops it into a ring buffer or linear array in the background.<\/li>\n\n\n\n<li><strong>Idle Phase (IDLE):<\/strong>\u00a0The moment the transmitting device finishes sending its payload, the RX line returns to its high state. If the hardware detects that the line remains idle for exactly\u00a0<strong>one full frame period<\/strong>\u00a0(the time it takes to transmit one start bit, the data payload, and stop bits at your configured baud rate), the microcontroller automatically sets the internal\u00a0<code>IDLE<\/code>\u00a0status flag.<\/li>\n<\/ul>\n\n\n\n<p>If enabled, this event instantly triggers a specialized interrupt. Instead of guessing when a packet ends or relying on heavy software timeouts, your application is handed a single, clean interrupt the exact moment the transmission ceases. This allows your firmware to immediately freeze the buffer, calculate the exact number of bytes received, and hand the complete packet off to your parsing engines with zero CPU downtime.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. Firmware Development:<\/h2>\n\n\n\n<p>We shall continue from the previous guide from <a href=\"https:\/\/blog.embeddedexpert.io\/?p=4518\" data-type=\"link\" data-id=\"https:\/\/blog.embeddedexpert.io\/?p=4518\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Open main.c of the project, within user code begin PV (Private Variable), declare 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;}\">#define RXBuffSize 100\n\nchar uart_buff_rx[RXBuffSize];\nuint16_t rx_index;\n\nuint16_t ReceivedLen;\n\nuint8_t rxDone=0;<\/pre><\/div>\n\n\n\n<p>First, we declared a buffer size of 100 characters and rx_index to track the received characters.<\/p>\n\n\n\n<p>Furthermore, track how many characters has been received once the IDLE line is detected along side that rxDone to indicate the end of reception once the IDLE line is detected.<\/p>\n\n\n\n<p>Next, in <strong>USART2_IRQHandler<\/strong> function, add the following:<\/p>\n\n\n\n<p>First, handle the incoming character and store it into buffer as follows:<\/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;}\">\/\/ RXNE interrupt\nif (LL_USART_IsActiveFlag_RXNE(USART2))\n{\n  uart_buff_rx[rx_index++] = LL_USART_ReceiveData8(USART2);\n  if(rx_index==RXBuffSize)\n  {\n    rx_index=0;\n  }\n}<\/pre><\/div>\n\n\n\n<p>For the IDLE line:<\/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;}\">\/\/ IDLE line interrupt\n\tif(LL_USART_IsActiveFlag_IDLE(USART2))\n\t{\n\t\tLL_USART_ClearFlag_IDLE(USART2);\n\t\tReceivedLen=rx_index;\n\t\trx_index=0;\n\t\trxDone=1;\n\t}<\/pre><\/div>\n\n\n\n<p>Please note that reseting the rx_index is determined by your application.<\/p>\n\n\n\n<p>Also, you need to clear the pending flag of IDLE line using 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;}\">LL_USART_ClearFlag_IDLE(USART2);<\/pre><\/div>\n\n\n\n<p>Next, in main function, enable RXNE interrupt along side IDLE line detection as follows:<\/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;}\">LL_USART_EnableIT_RXNE(USART2);\nLL_USART_EnableIT_IDLE(USART2);<\/pre><\/div>\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==1)\n{\n  for (int i=0;i&lt;ReceivedLen;i++)\n  {\n    UART_Send_Char(uart_buff_rx[i]);\n  }\n  rxDone=0;\n}<\/pre><\/div>\n\n\n\n<p>Hence, the follow of the code as follows:<\/p>\n\n\n\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_pb3qppb3qppb3qpp-1024x559.png\" alt=\"\" class=\"wp-image-4541\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_pb3qppb3qppb3qpp-1024x559.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_pb3qppb3qppb3qpp-300x164.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_pb3qppb3qppb3qpp-768x419.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_pb3qppb3qppb3qpp-1150x627.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_pb3qppb3qppb3qpp-750x409.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_pb3qppb3qppb3qpp-400x218.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_pb3qppb3qppb3qpp-250x136.png 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_pb3qppb3qppb3qpp.png 1408w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\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 <a href=\"https:\/\/github.com\/hussamaldean\/Embedded-Expert-Post-Projects\/tree\/main\/Projects\/LL_UART\" data-type=\"link\" data-id=\"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<h2 class=\"wp-block-heading\">3. Results:<\/h2>\n\n\n\n<p>Open your favourite terminal and set the baudrate to 115200 and send some data, you should get the data back as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"708\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Screen-Recording-2026-06-18-at-11.46.18-1024x708.gif\" alt=\"\" class=\"wp-image-4542\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Screen-Recording-2026-06-18-at-11.46.18-1024x708.gif 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Screen-Recording-2026-06-18-at-11.46.18-300x208.gif 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Screen-Recording-2026-06-18-at-11.46.18-768x531.gif 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Screen-Recording-2026-06-18-at-11.46.18-1536x1063.gif 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Screen-Recording-2026-06-18-at-11.46.18-1150x795.gif 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Screen-Recording-2026-06-18-at-11.46.18-750x519.gif 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Screen-Recording-2026-06-18-at-11.46.18-400x277.gif 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Screen-Recording-2026-06-18-at-11.46.18-250x173.gif 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>In next part, we shall mix DMA and IDLE line to improve the performance by miles.<\/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>Efficiently parse variable-length incoming data frames by combining UART receive interrupts with the hardware&#8217;s built-in\u00a0IDLE line\u00a0detection feature. This architecture uses the Read Data Register Not Empty (RXNE) interrupt to collect individual bytes in the background, while the IDLE line interrupt fires automatically as soon as the transmission stops, immediately alerting your firmware that a complete [&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-4539","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\/4539"}],"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=4539"}],"version-history":[{"count":2,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4539\/revisions"}],"predecessor-version":[{"id":4545,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4539\/revisions\/4545"}],"wp:attachment":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4539"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4539"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4539"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}