{"id":4518,"date":"2026-06-06T09:19:53","date_gmt":"2026-06-06T09:19:53","guid":{"rendered":"https:\/\/blog.embeddedexpert.io\/?p=4518"},"modified":"2026-06-06T09:19:54","modified_gmt":"2026-06-06T09:19:54","slug":"getting-started-with-stm32-low-layer-ll-uart-transmit-using-interrupt","status":"publish","type":"post","link":"https:\/\/blog.embeddedexpert.io\/?p=4518","title":{"rendered":"Getting Started with STM32 Low Layer (LL): UART Transmit 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_49fkh49fkh49fkh4-1024x559.png\" alt=\"\" class=\"wp-image-4519\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_49fkh49fkh49fkh4-1024x559.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_49fkh49fkh49fkh4-300x164.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_49fkh49fkh49fkh4-768x419.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_49fkh49fkh49fkh4-1150x627.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_49fkh49fkh49fkh4-750x409.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_49fkh49fkh49fkh4-400x218.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_49fkh49fkh49fkh4-250x136.png 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_49fkh49fkh49fkh4.png 1408w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Eliminate the CPU bottlenecks caused by blocking transmission loops by shifting to an interrupt-driven UART architecture using Low Layer (LL) drivers. This guide demonstrates how to leverage the Transmit Data Register Empty (TXE) interrupt to background-stream character buffers, allowing your main application logic to run completely uninterrupted while the hardware handles the bit-clocking.<\/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>Why interrupt over polling.<\/li>\n\n\n\n<li>STM32CubeMX setup.<\/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. Why Interrupt Over Polling:<\/h2>\n\n\n\n<p>The choice between&nbsp;<strong>Interrupt-driven execution<\/strong>&nbsp;and&nbsp;<strong>Polling<\/strong>&nbsp;is one of the most fundamental architectural decisions in firmware engineering. It represents the trade-off between strict CPU dedication and asynchronous multitasking.<\/p>\n\n\n\n<p>Here is a detailed breakdown of why interrupts are preferred over polling in professional embedded applications:<\/p>\n\n\n\n<h5 class=\"wp-block-heading\">1. CPU Efficiency and Utilization<\/h5>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Polling (The Bottleneck):<\/strong>\u00a0In a polling mode, the CPU is held hostage by a hardware flag. For example, when waiting for a UART byte to transmit at 9600 baud, a single character takes about\u00a0$1\\text{ ms}$. At a\u00a0$168\\text{ MHz}$\u00a0CPU clock speed, the processor executes roughly\u00a0<strong>168,000 completely useless instruction cycles<\/strong>\u00a0just sitting in a\u00a0<code>while(!TXE)<\/code>\u00a0loop.<\/li>\n\n\n\n<li><strong>Interrupts (The Fix):<\/strong>\u00a0With interrupts, the CPU continues executing your main application logic (calculating sensor data, updating user interfaces, running state machines). When the hardware buffer becomes empty, the hardware autonomously triggers an execution pause, hands the CPU a single byte to send, and immediately returns control to the main loop. The CPU only spends a fraction of a microsecond handling the hardware event.<\/li>\n<\/ul>\n\n\n\n<h5 class=\"wp-block-heading\">2. Power Optimization<\/h5>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Polling:<\/strong>\u00a0Because the processor must continuously check a register flag in a tight loop, it must remain running in full-power mode. This quickly drains batteries and generates unnecessary heat.<\/li>\n\n\n\n<li><strong>Interrupts:<\/strong>\u00a0Interrupts are the gateway to low-power design. You can put the microcontroller into a deep sleep mode (<code>Sleep<\/code>,\u00a0<code>Stop<\/code>, or\u00a0<code>Standby<\/code>), which completely shuts down the CPU core clock. The core consumes almost zero power until an external event or hardware peripheral trips an interrupt line, instantly waking the CPU to handle the task before it drops back to sleep.<\/li>\n<\/ul>\n\n\n\n<h5 class=\"wp-block-heading\">3. Real-Time Responsiveness and Latency<\/h5>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Polling:<\/strong>\u00a0If your main loop takes\u00a0$20\\text{ ms}$\u00a0to execute due to complex math operations, and a critical sensor changes state at millisecond 2, your application will not know about that change until the loop finishes its cycle\u00a0$18\\text{ ms}$\u00a0later. This jitter is unacceptable in safety-critical systems (like motor control or over-current protection).<\/li>\n\n\n\n<li><strong>Interrupts:<\/strong>\u00a0The Nested Vectored Interrupt Controller (NVIC) inside the ARM Cortex core watches the hardware lines directly. The exact nanosecond a line switches state, the core halts the main program, preserves the current register stack, and handles the event within a few clock cycles, ensuring\u00a0<strong>deterministic real-time response<\/strong>.<\/li>\n<\/ul>\n\n\n\n<h5 class=\"wp-block-heading\">4. Architectural Scalability<\/h5>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Polling:<\/strong>\u00a0Polling works well for a single, trivial task. However, as soon as you add a display, multiple communication lines (UART, SPI,\u00a0$I^2C$), and a couple of user buttons, a polling architecture collapses. One slow peripheral will stall the entire system, causing dropped communication bytes and missed button presses.<\/li>\n\n\n\n<li><strong>Interrupts:<\/strong>\u00a0By treating external inputs and peripheral buffers as asynchronous background tasks, your software architecture remains clean, scalable, and modular. It separates your time-critical hardware drivers from your high-level application logic.<\/li>\n<\/ul>\n\n\n\n<h5 class=\"wp-block-heading\">Direct Comparison<\/h5>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Feature<\/strong><\/td><td><strong>Polling Mode<\/strong><\/td><td><strong>Interrupt Mode<\/strong><\/td><\/tr><\/thead><tbody><tr><td><strong>CPU Overhead<\/strong><\/td><td><strong>High<\/strong>&nbsp;(Stuck in blocking loops)<\/td><td><strong>Low<\/strong>&nbsp;(Executes only on demand)<\/td><\/tr><tr><td><strong>Power Consumption<\/strong><\/td><td><strong>High<\/strong>&nbsp;(CPU must stay fully active)<\/td><td><strong>Low<\/strong>&nbsp;(Allows sleep\/wake cycles)<\/td><\/tr><tr><td><strong>Response Time<\/strong><\/td><td>Dependent on main loop execution time<\/td><td>Instantaneous (Hardware-triggered)<\/td><\/tr><tr><td><strong>Complexity<\/strong><\/td><td>Simple to write and debug<\/td><td>Requires ISR management &amp; race-condition care<\/td><\/tr><tr><td><strong>Dropped Data Risk<\/strong><\/td><td><strong>High<\/strong>&nbsp;if the CPU is busy elsewhere<\/td><td><strong>Very Low<\/strong>&nbsp;due to immediate hardware buffering<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. STM32CubeMX Setup:<\/h2>\n\n\n\n<p>We shall continue from the previous which can be found <a href=\"https:\/\/blog.embeddedexpert.io\/?p=4495\" data-type=\"link\" data-id=\"https:\/\/blog.embeddedexpert.io\/?p=4495\">here<\/a>.<\/p>\n\n\n\n<p>From connectivity, USART2, From NVIC settings, enable USART2 global interrupt 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-06_11-31-11-1024x662.png\" alt=\"\" class=\"wp-image-4520\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-06_11-31-11-1024x662.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-06_11-31-11-300x194.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-06_11-31-11-768x497.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-06_11-31-11-1536x993.png 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-06_11-31-11-2048x1324.png 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-06_11-31-11-1150x744.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-06_11-31-11-750x485.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-06_11-31-11-400x259.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-06_11-31-11-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, disable Generate IRQ handler and click Generate 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-06_12-06-10-1024x662.png\" alt=\"\" class=\"wp-image-4521\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-06_12-06-10-1024x662.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-06_12-06-10-300x194.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-06_12-06-10-768x497.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-06_12-06-10-1536x993.png 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-06_12-06-10-2048x1324.png 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-06_12-06-10-1150x744.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-06_12-06-10-750x485.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-06_12-06-10-400x259.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/2026-06-06_12-06-10-250x162.png 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Thats all for 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>In order to send data using interrupt, the code flow 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_bf1zlubf1zlubf1z-1024x559.png\" alt=\"\" class=\"wp-image-4522\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_bf1zlubf1zlubf1z-1024x559.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_bf1zlubf1zlubf1z-300x164.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_bf1zlubf1zlubf1z-768x419.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_bf1zlubf1zlubf1z-1150x627.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_bf1zlubf1zlubf1z-750x409.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_bf1zlubf1zlubf1z-400x218.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_bf1zlubf1zlubf1z-250x136.png 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/06\/Gemini_Generated_Image_bf1zlubf1zlubf1z.png 1408w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>In summery:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Phase 1: Transmit Data Register Empty (<code>TXE<\/code>) Handling<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Step 1: Verify the Source<\/strong>\u00a0The handler checks if the\u00a0<code>TXE<\/code>\u00a0hardware flag is active\u00a0<strong>and<\/strong>\u00a0if the\u00a0<code>TXE<\/code>\u00a0interrupt is actually enabled. This ensures the handler only processes data when the hardware buffer is truly ready to accept a new byte.<\/li>\n\n\n\n<li><strong>Step 2: Stream Data<\/strong>\u00a0If the current buffer position (<code>tx_index<\/code>) is less than the total message length (<code>buff_len<\/code>), the next character from\u00a0<code>uart_buff<\/code>\u00a0is written directly to the data register using\u00a0<code>LL_USART_TransmitData8<\/code>, and the index is post-incremented.<\/li>\n\n\n\n<li><strong>Step 3: Detect the Last Byte<\/strong>\u00a0Immediately after loading the byte, the handler checks if\u00a0<code>tx_index<\/code>\u00a0has reached\u00a0<code>buff_len<\/code>.<\/li>\n\n\n\n<li><strong>Step 4: Shift Gears to\u00a0<code>TC<\/code><\/strong>\u00a0If it was the final byte of the buffer, the handler disables the\u00a0<code>TXE<\/code>\u00a0interrupt (since there is no more data to load) and enables the\u00a0<code>TC<\/code>\u00a0(Transmission Complete) interrupt.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Phase 2: Transmission Complete (<code>TC<\/code>) Handling<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Step 5: Verify the Wire is Idle<\/strong>\u00a0The handler checks if the\u00a0<code>TC<\/code>\u00a0flag is active\u00a0<strong>and<\/strong>\u00a0if the\u00a0<code>TC<\/code>\u00a0interrupt is enabled. This flag only trips after the very last byte has completely cleared the internal Shift Register and physical TX pin.<\/li>\n\n\n\n<li><strong>Step 6: Clear and Cleanup<\/strong>\u00a0Once confirmed, the handler clears the hardware\u00a0<code>TC<\/code>\u00a0flag using\u00a0<code>LL_USART_ClearFlag_TC<\/code>\u00a0and disables the\u00a0<code>TC<\/code>\u00a0interrupt to prevent the handler from firing repeatedly.<\/li>\n\n\n\n<li><strong>Step 7: Flag Completion<\/strong>\u00a0The application-level status flag\u00a0<code>Tx_Done<\/code>\u00a0is set to\u00a0<code>1<\/code>, signaling your main program loop that the entire string has been completely and successfully transmitted over the wire.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<p>Next, the firmware.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>In user code begin PV, declare the following variables:<\/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;}\">uint16_t tx_index;\n\nuint8_t Tx_Done;<\/pre><\/div>\n\n\n\n<p><\/p>\n\n\n\n<p>Next, USART2 IRQ handler 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;}\">void USART2_IRQHandler(void)\n{\n\tif (LL_USART_IsActiveFlag_TXE(USART2) &amp;&amp; LL_USART_IsEnabledIT_TXE(USART2))\n\t{\n\t\tif (tx_index &lt; buff_len)\n\t\t{\n\t\t\tLL_USART_TransmitData8(USART2, uart_buff[tx_index++]);\n\n\t\t\tif (tx_index == buff_len)\n\t\t\t{\n\t\t\t\tLL_USART_DisableIT_TXE(USART2);\n\t\t\t\tLL_USART_EnableIT_TC(USART2);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (LL_USART_IsActiveFlag_TC(USART2) &amp;&amp; LL_USART_IsEnabledIT_TC(USART2))\n\t{\n\t        LL_USART_ClearFlag_TC(USART2);\n\t        LL_USART_DisableIT_TC(USART2);\n\t        Tx_Done = 1;\n\t}\n}<\/pre><\/div>\n\n\n\n<p>Next, declare a function that will send buffer over UART using interrupt:<\/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_Send_IT(void)\n{\n\ttx_index = 0;\n\tTx_Done = 0;\n\n\tLL_USART_ClearFlag_TC(USART2);\n\tLL_USART_EnableIT_TXE(USART2);\n\n}<\/pre><\/div>\n\n\n\n<p>The function resets the index and tx done flag then clears TC flag and enable TXE (Transfer Buffer Empty).<\/p>\n\n\n\n<p>In user code begin 3 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;}\">buff_len=sprintf(uart_buff,&quot;Counter Value =%d \\r\\n&quot;,counter++);\n\nUART_Send_IT();\nwhile(Tx_Done==0);\nTx_Done=0;<\/pre><\/div>\n\n\n\n<p>Create the required string.<\/p>\n\n\n\n<p>Send the data using interrupt and wait for the transmission to end.<\/p>\n\n\n\n<p><\/p>\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 size-large\"><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><\/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 you should get the following:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"707\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/05\/2026-05-31_09-50-11-1024x707.png\" alt=\"\" class=\"wp-image-4501\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/05\/2026-05-31_09-50-11-1024x707.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/05\/2026-05-31_09-50-11-300x207.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/05\/2026-05-31_09-50-11-768x530.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/05\/2026-05-31_09-50-11-1536x1060.png 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/05\/2026-05-31_09-50-11-1150x794.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/05\/2026-05-31_09-50-11-750x518.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/05\/2026-05-31_09-50-11-400x276.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/05\/2026-05-31_09-50-11-250x173.png 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/05\/2026-05-31_09-50-11.png 1866w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Congratulations, you send the string successfully using interrupt.<\/p>\n\n\n\n<p>You may download the project 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>Happy coding\u00a0\ud83d\ude09<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Eliminate the CPU bottlenecks caused by blocking transmission loops by shifting to an interrupt-driven UART architecture using Low Layer (LL) drivers. This guide demonstrates how to leverage the Transmit Data Register Empty (TXE) interrupt to background-stream character buffers, allowing your main application logic to run completely uninterrupted while the hardware handles the bit-clocking. In this [&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-4518","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\/4518"}],"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=4518"}],"version-history":[{"count":1,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4518\/revisions"}],"predecessor-version":[{"id":4524,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4518\/revisions\/4524"}],"wp:attachment":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4518"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4518"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4518"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}