{"id":4763,"date":"2026-09-27T13:40:06","date_gmt":"2026-09-27T13:40:06","guid":{"rendered":"https:\/\/blog.embeddedexpert.io\/?p=4763"},"modified":"2026-09-27T14:03:14","modified_gmt":"2026-09-27T14:03:14","slug":"getting-started-with-stm32-low-layer-ll-delay-using-timers","status":"publish","type":"post","link":"https:\/\/blog.embeddedexpert.io\/?p=4763","title":{"rendered":"Getting Started with STM32 Low Layer (LL): Delay using Timers"},"content":{"rendered":"\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"585\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/stm32_microcontroller_banner-1024x585.jpg\" alt=\"\" class=\"wp-image-4764\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/stm32_microcontroller_banner-1024x585.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/stm32_microcontroller_banner-300x171.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/stm32_microcontroller_banner-768x439.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/stm32_microcontroller_banner-1150x657.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/stm32_microcontroller_banner-750x429.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/stm32_microcontroller_banner-400x229.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/stm32_microcontroller_banner-250x143.jpg 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/stm32_microcontroller_banner.jpg 1344w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Accurate timing is a fundamental requirement in most embedded applications, whether for blinking an LED, polling a sensor, or driving communication protocols. This guide shows how to turn TIM2 into a simple, reliable millisecond delay function using the STM32 Low-Layer (LL) API\u2014a lightweight, cycle-accurate alternative to busy-wait software loops.<\/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>STM32CubeMX setup.<\/li>\n\n\n\n<li>Importing the project to STM32CubeMX.<\/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>Timing is the invisible thread that runs through every embedded application. Whether you are blinking an LED, waiting for a sensor to complete a conversion, pacing an SPI transaction, or debouncing a mechanical switch, your firmware constantly has to answer one deceptively simple question: <em>how do I wait for an exact amount of time?<\/em><\/p>\n\n\n\n<p>Most beginners reach for the first answer that comes to mind \u2014 the empty software 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;}\">for (volatile uint32_t i = 0; i &lt; 100000; i++);<\/pre><\/div>\n\n\n\n<p>It feels harmless, and it even appears to work \u2014 until it doesn&#8217;t. The real duration of such a loop depends on the compiler optimization level, the CPU clock frequency, flash wait states, and even the interrupt load at runtime. Recompile the same code from <code>-O0<\/code> to <code>-O2<\/code>, and your carefully tuned 10 ms delay silently shrinks to 3 ms. Meanwhile, the CPU spends 100% of its cycles doing absolutely nothing useful. A software loop doesn&#8217;t measure time; it merely <em>estimates<\/em> it, and the estimate is only as good as the assumptions behind it.<\/p>\n\n\n\n<p>The next step up is <code>HAL_Delay()<\/code>. It is at least anchored to a real time base (SysTick), but it comes with its own caveats: it silently freezes if it is called while interrupts are disabled, it can deadlock when called from within an interrupt of higher priority, and it drags the entire HAL clock infrastructure along with it. For a simple delay, that is a lot of machinery to depend on.<\/p>\n\n\n\n<p>There is a better tool, and it has been sitting on the chip the whole time: a <strong>hardware timer<\/strong>. Every STM32 microcontroller includes a set of independent, 16- or 32-bit timers that count with crystal-level precision, are completely unaffected by compiler settings, and require nothing more than a few register accesses to operate. No callbacks, no interrupt handlers, no framework \u2014 just a peripheral doing exactly what it was designed to do.<\/p>\n\n\n\n<p>In this guide, we will build a precise, blocking <strong>millisecond delay function on TIM2<\/strong> using STM32CubeMX and the <strong>Low-Layer (LL) API<\/strong>. The idea is simple and elegant: we configure the prescaler so the counter ticks at exactly 1 kHz \u2014 one tick per millisecond \u2014 then let the counter count <em>down<\/em> from our desired delay to zero. To wait, we simply poll the update flag and go on with our lives. Choosing the LL drivers is deliberate: they map almost one-to-one onto the timer&#8217;s registers, compile down to a handful of instructions, and \u2014 most importantly \u2014 teach you what is <em>actually<\/em>happening inside the peripheral instead of hiding it behind layers of abstraction.<\/p>\n\n\n\n<p>Along the way, you will learn:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>How the <strong>prescaler (PSC)<\/strong> and <strong>auto-reload register (ARR)<\/strong> work together to shape timing,<\/li>\n\n\n\n<li>Why <code>PSC = 16000 - 1<\/code> turns a 16 MHz clock into exactly <strong>1 ms per tick<\/strong>,<\/li>\n\n\n\n<li>How <strong>down-counting<\/strong> works and why the update event fires on underflow,<\/li>\n\n\n\n<li>Why an <strong>explicit update event is required<\/strong> on the first call (the shadow-register gotcha that trips up almost everyone),<\/li>\n\n\n\n<li>How to assemble all of this into a compact, reusable delay function you can drop into any project.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. STM32CubeMX Setup:<\/h2>\n\n\n\n<p>Open STM32CubeMX as start a new project as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"662\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-1024x662.jpg\" alt=\"\" class=\"wp-image-3990\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-1024x662.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-300x194.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-768x497.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-1536x993.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-2048x1324.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-1150x744.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-750x485.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-400x259.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-250x162.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Search for your STM32 MCU, select the MCU and click on Start New Project as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"724\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-1024x724.jpg\" alt=\"\" class=\"wp-image-3991\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-1024x724.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-300x212.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-768x543.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-1536x1087.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-2048x1449.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-1150x814.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-750x531.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-400x283.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-250x177.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Next, from Timer, select TIM2:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Set Clock source to Internal Clock.<\/li>\n\n\n\n<li>Set the PSC (prescaler to 16000-1)<\/li>\n\n\n\n<li>Set the Counter Mode to Down.<\/li>\n<\/ul>\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\/09\/2026-09-27_16-17-22-1024x662.jpg\" alt=\"\" class=\"wp-image-4765\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-17-22-1024x662.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-17-22-300x194.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-17-22-768x497.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-17-22-1536x993.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-17-22-2048x1324.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-17-22-1150x744.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-17-22-750x485.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-17-22-400x259.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-17-22-250x162.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Next, set PA5 as output.<\/p>\n\n\n\n<p>Next, from project manager, Advanced Settings, set both TIM and GPIO as LL 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\/09\/2026-09-27_16-22-39-1024x662.jpg\" alt=\"\" class=\"wp-image-4766\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-22-39-1024x662.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-22-39-300x194.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-22-39-768x497.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-22-39-1536x993.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-22-39-2048x1324.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-22-39-1150x744.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-22-39-750x485.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-22-39-400x259.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-22-39-250x162.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Next, from Code Generator, enable Create peripheral initialization as pair of .c\/.h files per peripheral as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"662\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-1024x662.jpg\" alt=\"\" class=\"wp-image-4759\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-1024x662.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-300x194.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-768x497.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-1536x993.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-2048x1324.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-1150x744.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-750x485.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-400x259.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-250x162.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Next, from Project, give the project a name and set toolchain\/IDE to STM32CubeIDE and click on 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\/09\/2026-09-27_16-23-46-1-1024x662.jpg\" alt=\"\" class=\"wp-image-4768\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-23-46-1-1024x662.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-23-46-1-300x194.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-23-46-1-768x497.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-23-46-1-1536x993.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-23-46-1-2048x1324.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-23-46-1-1150x744.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-23-46-1-750x485.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-23-46-1-400x259.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-27_16-23-46-1-250x162.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Thats all for the STM32CubeMX setup.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3. Importing the Project to STM32CubeIDE:<\/h2>\n\n\n\n<p>Open STM32CubeIDE, select your workspace and click on Launch.<\/p>\n\n\n\n<p>From the IDE, click File and select STM32 Project Create\/Import as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"873\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_16-57-21-1024x873.jpg\" alt=\"\" class=\"wp-image-3997\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_16-57-21-1024x873.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_16-57-21-300x256.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_16-57-21-768x655.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_16-57-21-1150x981.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_16-57-21-750x640.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_16-57-21-400x341.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_16-57-21-250x213.jpg 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_16-57-21.jpg 1524w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Next, from Import STM32 Project, select STM32CubeMX\/STM32CubeIDE Project and click on Next as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"769\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_17-00-37-1024x769.jpg\" alt=\"\" class=\"wp-image-3998\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_17-00-37-1024x769.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_17-00-37-300x225.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_17-00-37-768x577.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_17-00-37-1150x863.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_17-00-37-750x563.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_17-00-37-400x300.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_17-00-37-250x188.jpg 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_17-00-37.jpg 1172w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Next, select the folder that contains the .ioc file and click on Finish as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"652\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41-1024x652.jpg\" alt=\"\" class=\"wp-image-4360\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41-1024x652.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41-300x191.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41-768x489.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41-1536x977.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41-1150x732.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41-750x477.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41-400x255.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41-250x159.jpg 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41.jpg 1760w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Note: Project name is for reference only.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. Firmware Development:<\/h2>\n\n\n\n<p>Open tim.h.<\/p>\n\n\n\n<p>In USER CODE BEGIN Prototypes, 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 TIM_Delay_ms(TIM_TypeDef *TIMx, uint32_t ms);<\/pre><\/div>\n\n\n\n<p>This function shall block delay for certain amount of time in milli seconds.<\/p>\n\n\n\n<p>The function takes the following parameters:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Typedef for which timer, TIM2 in this guide.<\/li>\n\n\n\n<li>The amount to be delayed in milliseconds.<\/li>\n<\/ul>\n\n\n\n<p>Next, open tim.c<\/p>\n\n\n\n<p>In USER CODE BEGIN 1, 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 TIM_Delay_ms(TIM_TypeDef *TIMx, uint32_t ms)<\/pre><\/div>\n\n\n\n<p>Within the function, disable the timer:<\/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_TIM_DisableCounter(TIMx);<\/pre><\/div>\n\n\n\n<p>Set the ARR (Auto Reload Register) to the desired delay amount.<\/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_TIM_SetAutoReload(TIMx, ms);<\/pre><\/div>\n\n\n\n<p>Generate the event update flag and clear the 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;}\">\/\/\t\/\/ Force an update event so ARR is loaded into the active register\n\/\/\t\/\/ and CNT is reloaded with ARR (down-count mode)\nLL_TIM_GenerateEvent_UPDATE(TIMx);\n\/\/\nLL_TIM_ClearFlag_UPDATE(TIMx);<\/pre><\/div>\n\n\n\n<p>Next, enable the timer:<\/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_TIM_EnableCounter(TIMx);<\/pre><\/div>\n\n\n\n<p>Wait for the update flag to set:<\/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;}\">while (!LL_TIM_IsActiveFlag_UPDATE(TIMx))\n{\n  \/\/ Blocking wait\n}<\/pre><\/div>\n\n\n\n<p>Finally, clear the update flag the stop the timer:<\/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;}\">\/\/ Stop the timer and clear the flag\nLL_TIM_DisableCounter(TIMx);\nLL_TIM_ClearFlag_UPDATE(TIMx);<\/pre><\/div>\n\n\n\n<p>Hence, the entire 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 TIM_Delay_ms(TIM_TypeDef *TIMx, uint32_t ms)\n{\n\n\tLL_TIM_DisableCounter(TIMx);\n\n\tLL_TIM_SetAutoReload(TIMx, ms);\n\n\/\/\t\/\/ Force an update event so ARR is loaded into the active register\n\/\/\t\/\/ and CNT is reloaded with ARR (down-count mode)\n\tLL_TIM_GenerateEvent_UPDATE(TIMx);\n\/\/\n\tLL_TIM_ClearFlag_UPDATE(TIMx);\n\n\tLL_TIM_EnableCounter(TIMx);\n\n\twhile (!LL_TIM_IsActiveFlag_UPDATE(TIMx))\n\t{\n\t\t\/\/ Blocking wait\n\t}\n\n\t\/\/ Stop the timer and clear the flag\n\tLL_TIM_DisableCounter(TIMx);\n\tLL_TIM_ClearFlag_UPDATE(TIMx);\n}<\/pre><\/div>\n\n\n\n<p><\/p>\n\n\n\n<p>In main.c:<\/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;}\">LL_GPIO_TogglePin(GPIOA, LL_GPIO_PIN_5);\nTIM_Delay_ms(TIM2,1000);<\/pre><\/div>\n\n\n\n<p>Toggle pin PA5 and wait for 1 seconds (1000ms)<\/p>\n\n\n\n<p>Thats all for the firmware.<\/p>\n\n\n\n<p>Save, build and run the project on your board.<\/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\/04\/image-1-1024x34.png\" alt=\"\" class=\"wp-image-4349\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-1024x34.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-300x10.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-768x26.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-1536x51.png 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-1150x38.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-750x25.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-400x13.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-250x8.png 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1.png 1986w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>You may download the project from <a href=\"https:\/\/github.com\/hussamaldean\/Embedded-Expert-Post-Projects\/tree\/main\/Projects\/TIM_LL\" data-type=\"link\" data-id=\"https:\/\/github.com\/hussamaldean\/Embedded-Expert-Post-Projects\/tree\/main\/Projects\/TIM_LL\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">5. Results:<\/h2>\n\n\n\n<p>You should get the following<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"LED toggle using DMA and timer\" width=\"1170\" height=\"658\" src=\"https:\/\/www.youtube.com\/embed\/xFh9q-DyUX4?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<p>Later, we shall build on this to develop more timer applications.<\/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>Accurate timing is a fundamental requirement in most embedded applications, whether for blinking an LED, polling a sensor, or driving communication protocols. This guide shows how to turn TIM2 into a simple, reliable millisecond delay function using the STM32 Low-Layer (LL) API\u2014a lightweight, cycle-accurate alternative to busy-wait software loops. In this guide, we shall cover [&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-4763","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\/4763"}],"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=4763"}],"version-history":[{"count":2,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4763\/revisions"}],"predecessor-version":[{"id":4771,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4763\/revisions\/4771"}],"wp:attachment":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4763"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4763"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4763"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}