Blinking an LED is usually the first program written by a beginner on any microcontroller. It is simple, easy to understand, and helps confirm that the board, clock, and GPIO configuration are working correctly.
But once you move beyond this basic step and want to blink multiple LEDs at different speeds on STM32, things are no longer that straightforward. If you are at this stage, you are definitely in the right place.
Now I believe, at this point, you might be thinking:
How can I blink multiple LEDs on STM32 at different frequencies?
This is a very common requirement once you move beyond basic STM32 examples. When I started learning STM32, I faced the same problem:
How do I blink multiple LEDs independently without blocking the CPU?
Understanding this concept is an important step in learning real embedded systems programming. In this tutorial, you will learn how to blink multiple LEDs independently at different frequencies on STM32, using a non-blocking timing technique based on HAL_GetTick().
What You Will Learn from This Post
By the end of this Blinking Multiple LEDs at Different Speeds Using STM32 tutorial, you will be able to learn:
- How HAL_GetTick() enables non-blocking timing.
- How to blink multiple LEDs independently.
- Why HAL_Delay() destroys system responsiveness.
- How to write scalable STM32 firmware.
- How to manage multiple software timers using structs for scalability.
Components Required:
To blink an LED using STM32, you need the following components:
| Component Name | Quantity | Purchase Link |
|---|---|---|
| STM32 NUCLEO Development Board | 1 | Amazon |
| LED | 3 | Amazon |
| Resistor (220Ω) | 3 | Amazon |
| Breadboard | 1 | Amazon |
| Jumper Wire Pack | 1 | Amazon |
| USB Mini-B Cable | 1 | Amazon |
For troubleshooting, some extremely useful test equipment:
For troubleshooting and debugging embedded systems, the right test equipment helps you quickly identify hardware, signal, and power-related issues, saving both time and effort.
Below is a list of essential tools that allow you to analyze signals, verify hardware behavior, and detect power, timing, and communication problems during development and testing.
| Equipment Name | Purchase Link |
|---|---|
| Best Oscilloscope for Professionals | Amazon |
| Best Oscilloscope for Beginners and Students | Amazon |
| Logic Analyzer | Amazon |
| Best Budget Multimeter | Amazon |
| Adjustable Bench Power Supply | Amazon |
Circuit Diagram Explanation:
To keep this tutorial simple and beginner friendly. I will follow a clear step-by-step wiring approach.
In this setup, each LED is connected to a different STM32 digital pin using a current-limiting resistor to prevent damage from excessive current.
LED to STM32H5 Nucleo-144 board Pin Mapping:
| LED | STM32H5 Pin | Purpose |
|---|---|---|
| LED 1 | PORTB-13 | Blink / Output Indicator |
| LED 2 | PORTB-14 | Blink / Output Indicator |
| LED 3 | PORTB-15 | Blink / Output Indicator |
Step-by-Step Wiring:
Follow these simple steps to connect an LED to your Arduino board safely.
Step 1: Insert the LEDs into the breadboard
Make sure each LED is placed in a separate row.
Step 2: Identify LED terminals
- Long leg → Anode (+)
- Short leg → Cathode (–)
Step 3: Connect current-limiting resistors
Attach a 220Ω resistor to the anode (long leg) of each LED.
Step 4: Connect resistors to Stm32 Nucleo Board pins
- LED 1 → PORTB 13
- LED 2 → PORTB 14
- LED 3 → PORTB 15
Step 5: Connect all cathodes to ground
- Join all short legs (cathodes) of the LEDs.
- Connect them to the GND pin of the Arduino.
Logic Behind Blinking Multiple LEDs at Different Speeds:
To blink multiple LEDs independently, think of each LED as a small independent task. Think of this like cooking three different dishes at the same time. You don’t sit and stare at the oven for 20 minutes; you check the clock, do something else, and come back when it is time.
For each LED, we maintain three simple things:
1. Each LED Own Timer:
- Each LED remembers the last time it was turned ON or OFF. This helps us calculate how much time has passed since the last change.
2. Each LED Own Blink Interval:
- Each LED has a fixed time interval:
- Fast LED → small interval
- Slow LED → large interval
When the elapsed time becomes equal to this interval, the LED is ready to toggle.
3. Each LED Own State (ON or OFF):
Each LED remembers whether it is currently:
- ON, or
- OFF
When the time is up, the state is flipped:
- ON → OFF
- OFF → ON
Blinking Multiple LEDs Independently Using HAL_GetTick()
In STM32-based firmware, you can use HAL_GetTick() for blinking multiple LEDs at different rates. It returns the elapsed time in milliseconds since startup, allowing multiple LEDs to blink at different rates independently. Unlike HAL_Delay(), this method keeps the system responsive and is ideal for real-time and professional STM32 applications.
Example: Different Blink Speeds:
| LED Name | STM32 Pin | Blink Interval | Blink Speed |
|---|---|---|---|
| LED 1 | PORTB 13 | 500 ms | Fast |
| LED 2 | PORTB 14 | 1000 ms | Medium |
| LED 3 | PORTB 15 | 2000 ms | Slow |
Each LED maintains its own timing reference, allowing it to toggle at a different interval without affecting others.
Why Avoid HAL_Delay() in Real Firmware?
HAL_Delay() behaves very similarly to Arduino’s delay(). While it may look simple, it introduces serious limitations in production firmware.
When HAL_Delay() is used:
- The CPU is blocked.
- No other application code executes.
- Button presses and sensor inputs are ignored.
- Communication stacks (UART, SPI, I2C) not work properly.
- System responsiveness degrades.
Why Use HAL_GetTick() Instead?
HAL_GetTick() enables non-blocking timing, allowing your firmware to remain responsive while tracking elapsed time.
Advantages of Using HAL_GetTick():
- Time is measured without stopping program execution.
- Multiple tasks run concurrently.
- Each LED or task can have its own independent interval
- Firmware becomes scalable and maintainable
- Ideal for state machines, schedulers, and RTOS-ready designs.
Scalable Way to Blink Multiple LEDs on STM32 (HAL API):
In this approach, we use a structure to group everything an LED needs such as its GPIO pin, blink interval, and runtime state into a single unit. This design keeps all LED-related information in one place, making the code cleaner, easier to understand, and simpler to maintain.
With this design, adding or removing an LED requires only minimal changes, while the main logic remains exactly the same. As a result, the code becomes:
- Easier to extend
- Safer to modify
- More suitable for real-world projects.
Let’s now look at a more scalable implementation where each LED is treated as an independent task, and the program automatically adapts to the number of LEDs defined.
#include "stm32h563xx.h"
#include "stm32h5xx_hal.h"
/*
* Created by AticleWorld.com
* Description: Blink multiple LEDs at different speeds using STM32 HAL
*/
//LED Task Structure
typedef struct
{
GPIO_TypeDef *port; // GPIO port (GPIOA, GPIOB, etc.)
uint16_t pin; // GPIO pin (GPIO_PIN_x)
uint32_t interval; // Blink interval in milliseconds
uint32_t lastTick; // Last toggle time
GPIO_PinState state; // Current LED state
} LEDTask_t;
//Led Configuration
LEDTask_t leds[] =
{
{ GPIOB, GPIO_PIN_13, 500, 0, GPIO_PIN_RESET }, // Fast blink
{ GPIOB, GPIO_PIN_14, 1000, 0, GPIO_PIN_RESET }, // Medium blink
{ GPIOB, GPIO_PIN_15, 2000, 0, GPIO_PIN_RESET } // Slow blink
};
//Calculate number of LEDs
#define LED_COUNT (sizeof(leds) / sizeof(leds[0]))
//function Declaration
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void ProcessLEDs(void);
void error(void);
int main(void)
{
/* Reset peripherals, init Flash and Systick */
HAL_Init();
/* Configure system clock */
SystemClock_Config();
/* Initialize GPIO */
MX_GPIO_Init();
/* Infinite loop */
while (1)
{
// Non-blocking LED processing
ProcessLEDs();
}
}
/**
* @brief Initializes GPIO pins used for LEDs.
*
* Configures PB13, PB14, and PB15 as push-pull outputs
* with no pull-up/down resistors.
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = { 0 };
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
/**
* @brief Periodically toggles LEDs based on configured time intervals.
*
* Uses the system tick counter to implement non-blocking LED blinking.
* Each LED is toggled independently when its interval expires.
*/
static void ProcessLEDs(void)
{
uint32_t currentTick = HAL_GetTick();
for (uint32_t i = 0; i < LED_COUNT; i++)
{
if ((currentTick - leds[i].lastTick) >= leds[i].interval)
{
leds[i].lastTick = currentTick;
/* Toggle LED state */
leds[i].state =
(leds[i].state == GPIO_PIN_SET) ? GPIO_PIN_RESET : GPIO_PIN_SET;
HAL_GPIO_WritePin(leds[i].port, leds[i].pin, leds[i].state);
}
}
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = { 0 };
RCC_ClkInitTypeDef RCC_ClkInitStruct = { 0 };
/** Configure the main internal regulator output voltage
*/
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);
while (!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY))
{
}
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_BYPASS_DIGITAL;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLL1_SOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 4;
RCC_OscInitStruct.PLL.PLLN = 250;
RCC_OscInitStruct.PLL.PLLP = 2;
RCC_OscInitStruct.PLL.PLLQ = 2;
RCC_OscInitStruct.PLL.PLLR = 2;
RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1_VCIRANGE_1;
RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1_VCORANGE_WIDE;
RCC_OscInitStruct.PLL.PLLFRACN = 0;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
error();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2 | RCC_CLOCKTYPE_PCLK3;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB3CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
error();
}
/** Configure the programming delay
*/
__HAL_FLASH_SET_PROGRAM_DELAY(FLASH_PROGRAMMING_DELAY_2);
}
void error(void)
{
while (1)
{
}
}
Troubleshooting & Common Mistakes (Multiple Blinking LEDs on STM32)
If your LEDs are not blinking as expected while working on a Multiple Blinking LED on Arduino project, check the following points:
- GPIO clock for the LED port not enabled.
- LED connected in reverse (polarity issue)
- Wrong GPIO port or pin selected (STM32 uses (port + pin), not just pin number).
- GPIO pin configured in the wrong mode.
- Missing or incorrect current-limiting resistor.
- Incorrect GPIO speed configuration.
- GPIO pin overloaded or exceeding maximum current.
- GND not connected properly.
- Loose or incorrect wiring.
- Check Pin may Pin conflict with SWD/JTAG or other peripheral remap.
- SysTick not running because HAL_Init() was not called.
- Flash latency or voltage scaling misconfigured at high system clocks (Remember this simple rule: Higher clock → higher voltage scale → more flash waits states)
Fixing these basic issues solves most beginner problems.
FAQ: Blink LEDs with Different Frequencies:
Q: Why cannot I use the standard HAL_Delay() function to blink LEDs at different frequencies?
The HAL_Delay() function is “blocking function,” meaning it pauses the entire processor. It stops the CPU from executing any other application code until the delay time has fully elapsed. For example, if you tell LED_1 to wait 1000ms, LED_2 cannot turn on or off during that second. So, to blink at different rates, you must use “non-blocking” code.
Q: Is it better to use software timers or hardware interrupts for blinking?
For simple visual effects such as LED blinking, software timers (for example, using HAL_GetTick() or RTOS timers) are sufficient on STM32. However, if you need precise frequencies (e.g., for data transmission or high-speed strobing), use Hardware Timer Interrupts. These run independently of your main loop and guarantee exact timing.
Q: How can I manage code for 10+ LEDs blinking at different rates without it getting messy?
Use Object-Oriented Programming (C++) or Structs (C). Create a “Class” or “Structure” that holds the state, pin number, and frequency for one LED. You can then create an array of these objects and update them all inside a single for loop, keeping your main code clean and scalable.
Q: Why do my LEDs seem to synchronize periodically even though I set different frequencies?
This is called a polyrhythm effect. When blink timings share a common factor (like 500 ms and 1000 ms), they naturally line up, causing the LEDs to turn ON together again and again.
Real-Life Analogy:
- Imagine two people clapping:
- Person A claps every 2 seconds
- Person B claps every 4 seconds
- Every 4 seconds, both claps together.
If you want the LEDs to blink in a more random way, choose timings that do not fit nicely together, like 503 ms or 701 ms.
Q: Is it possible to synchronize multiple LEDs with different frequencies?
Yes, multiple LEDs with different frequencies can be synchronized on STM32 by using a non-blocking time-based approach (for example, HAL_GetTick()), instead of blocking delays like HAL_Delay(). This allows each LED to operate independently while running concurrently in the main loop.
Q: Can I control the brightness of each LED along with its frequency?
Yes, on STM32 you can control both the brightness and frequency of each LED. Brightness is controlled using hardware PWM generated by timer peripherals, while blinking frequency is managed using non-blocking timing logic or a separate timer. This approach allows smooth brightness control with precise and scalable LED timing.
Q: How can I add more LEDs and control them individually?
Simply connect each new LED to an unused digital pin (and its own current-limiting resistor). In your code, create a separate object or variable set for each pin to manage its state independently.
Q: What is the maximum number of LEDs I can control simultaneously?
On STM32, the maximum number of LEDs you can control directly depends on the number of available GPIO pins, but in practice it is limited by the MCU’s total current handling capability. Each GPIO pin and port has strict current limits, so driving many LEDs directly from the microcontroller is unsafe.
To control dozens or hundreds of LEDs reliably, external solutions such as shift registers (74HC595), dedicated LED driver ICs, or addressable LEDs should be used. These approaches reduce GPIO usage and protect the microcontroller from excessive current.
Q: How do I calculate the delay time for a specific frequency?
A: For a standard blink (50% on, 50% off), the formula is: Delay (ms) = 1000/ 2 * Frequency (Hz). Example: For 2 Hz, the total period is 500ms. The “On” time is 250ms, and “Off” time is 250ms.
Q: How can I create fading or pulsing effects?
Fading requires PWM. So, to create fading or pulsing LED effects on STM32, PWM must be used. The LED brightness is controlled by varying the PWM duty cycle. By gradually increasing and decreasing the duty cycle over time, smooth fade or breathing effects can be achieved.
Q: How do I avoid flickering when using PWM for fading?
Flickering usually occurs when the PWM frequency is too low. Increase the PWM frequency (typically above 500 Hz) and update duty cycles smoothly using timers or lookup tables.
Q: Can low-power modes affect LED blinking?
Yes. In STOP or STANDBY modes, SysTick and some timers may stop. To keep LEDs blinking in low-power modes, use low-power timers (LPTIM) or external RTC-based wakeups.
Q: Can I synchronize blinking with sensors?
Yes. You can read a sensor (e.g., a potentiometer or light sensor) and map that value to your blink delay variable. For example, higher light intensity could trigger a faster blink rate.
Q: Can I implement alternating or random sequences?
Yes. You can use arrays to store sequence patterns or use random() to generate unpredictable intervals. If you need complex “chasing” patterns across many LEDs, a shift register is often the most efficient hardware solution.
Q: How do I make specific patterns like a “heartbeat”?
A heartbeat pattern usually consists of a quick “double beat.” You would program a sequence: On (100ms) -> Off (100ms) -> On (100ms) -> Off (800ms). Using a timer-based state machine (non-blocking code) is the best way to keep this rhythm consistent.
Q: Can I blink LEDs to music?
Yes, LEDs can be synchronized to music on STM32 by sampling an audio signal using the ADC. When the audio amplitude exceeds a predefined threshold, a beat is detected and the LEDs can be triggered. For better performance, ADC sampling is typically driven by a timer and handled via DMA, allowing smooth, real-time music-reactive LED effects.
Q: How can I control the patterns wirelessly?
LED patterns can be controlled wirelessly on STM32 by adding a Bluetooth (HC-05) or Wi-Fi (ESP32/ESP8266) module. The wireless module communicates with the STM32 over a serial interface such as UART. The microcontroller listens for incoming commands (for example, mode selection from a mobile app) and switches the active LED pattern accordingly. This approach allows flexible, real-time wireless control without changing the core LED timing logic.
Q: Does LED blinking accuracy depend on system clock configuration?
Yes. Incorrect system clock, flash latency, or voltage scaling settings can cause unstable timing, missed timer interrupts, or even random resets. Always ensure clock configuration matches the selected system frequency, especially when using high-speed clocks on STM32.
Q: Is it safe to toggle LEDs inside an interrupt?
It is safe but not recommended for complex logic. The best practice is to set a flag inside the ISR and handle LED updates in the main loop to keep interrupts short and predictable.
Related posts:
- STM32 Bare-Metal LED Blink Guide: Blink an LED Without HAL.
- Blinking an LED on STM32: Step-by-Step Guide.
- STM32 GPIO Button and LED Control – Understanding STM32 Button Input.
- STM32 GPIO tutorial: Modes, Functions, and Configuration
- Map-Based Method for GPIO Alternate Function Configuration.