Dealing with LEDs using an STM32


This article includes some simple examples to understand how to deal with LEDs when you are approaching STM32 and ChibiOS. The LED can be considered the simplest peripheral output you can connect to a microcontroller. Because of that, usually, every embedded development board is equipped with a LED marked as “User LED” and this means that it is actually connected to a GPIO pin you can drive via software.


To understand this article with proficiency you should match few requirements:

Theory inside

The Light Emitting Diode

The scheme of a Light Emitting Diode

I am quite sure you perfectly know what a LED is and how it works. Anyway, let us do a quick recap. A LED is a diode which emits light when is fed with a current in a proper direction. Like diodes, LEDs have an anode and a cathode:

  • the anode is the electrode through which the current flows-in; on a “through hole” LED, the anode is the longer wire;
  • the cathode is the electrode through which the current flows out; on a “through hole” LED, the cathode is the shorter wire.

To make the current flow in the right direction, the anode shall be connected to the positive voltage and the cathode to the negative. It is important to notice that the LED light is proportional to its current and not to the voltage. This has important consequences. We will explore those in a later article about light dimming.

Basically, microcontrollers and other circuits work on voltage thus we will drive LED by voltage instead of current. Note that the higher is the voltage we put across the LED, the higher the current, the higher light intensity will be. Mind you, too much current would irreversibly break the device.


The thermal drift

A simple circuit to connect a LED to a voltage generator

Driving a LED by voltage instead of current introduces some problems as LEDs suffer from thermal drift. Applying a voltage across the LED, a current will flow in it; now, due to its working principle, the flow heats the LED and this lead to an increase of free carriers which means an increase of current itself, and thus again an increase of temperature which leads to carriers increase and so on until the current in the LED is so high that it is permanently damaged.

A LED cannot be directly connected to a voltage generator because it suffers thermal drift. By the thermal drift a LED can be permanently damaged.

The simplest solution to this problem is a series resistor. Considering the circuit in figure 2 the current flows from VCC across the resistor the LED reaching the ground. According to Kirchhoff Voltage Law, the total voltage provided by our generator is split between the LED and the resistor, i.e.

V_{CC} = V_R + V_{LED}

As before, the current flow heats the LED causing an increase of free carriers and an increase of current itself. Anyway, according to Ohm Law this lead to an increase of voltage drop of the resistor and subsequently a decrease of voltage drop on the LED which reduces the current flow. We can conclude that the resistor introduces such compensation which can limit current in case of thermal drift.

To avoid thermal drift, a LED shall be connected to a voltage generator with a properly sized series resistor.

How to size series resistor

The LED equivalent model

The series resistor directly influences the LED current hence its emitted light intensity. A common problem dealing with LED is to decide which is the proper resistor size. This basically depends on the LED color and main voltage value.

To better understand how this works, we have to consider the V-I curve of a LED: it is basically an exponential curve where the shape depends on materials used to create the LED, and it exhibits a high deviation from LED to LED even if they belong to the same production batch.

To have proper knowledge of a LED, its curve shall be obtained doing measurement using laboratory test equipment. From this curve, one can obtain the LED turn-on voltage and the resistance that LED exhibits.

Considering figure 3, we can see how to get an equivalent model for a LED retrieving turn-on voltage and equivalent on resistance from its characteristic.

With reference to this example, we can spot that turn-on voltage is about 1.6V and that equivalent resistance (when the LED is ON) is


R_{on} = \frac{\Delta V}{\Delta I} \simeq \frac{0.6V}{60mA} = 10 \Omega


The equivalent circuit to connect a LED to a voltage generator

In figure 4, we have replaced LED with its equivalent circuit and now the circuit can be analyzed with ease. Considering known the desired current which should flow inside the LED we can use this model to size the external resistor.


Solving the Kirchoff Voltage Law for this circuit we have


V_{cc} - V_{on} = (R_s + R_{eq}) \cdot I

R_{s} = \frac{(V_{cc} - V_{on})}{I} - R_{eq}


The I-V curve of different coloured LEDs

I use to choose 10mA as current for LEDs because it is not quite far from being the maximum current a LED can manage but nonetheless, with this current, you can get a proper light intensity.

Looking at figure 5 we can spot that turn-on voltage is greater on a blue LED than on red LED: color has a great influence on how resistance shall be sized.

Another thing that influences a lot the selection is also the main power supply voltage level which in case of an embedded system often is 3.3V or 5V.

Doing some computation you would notice that the series resistance can be neglected because is ten/twenty time smaller than the resistance of the external resistor and can be easily confused with component tolerance.

In the next table, I am reporting some conceivable turn-on voltage, equivalent turn-on resistance and series resistance value when desired LED forward current is 10 mA and Vcc is 3.3 and 5 V.


Colour Von [V] Req [Ω] Rs @10mA  (Vcc = 3.3V) @ [Ω] Rs @10mA  (Vcc = 5.0V) [Ω]
Infrared 1.0 7.5 217.5 387.5
Red 1.6 10 160 330
Orange 1.7 11.7 145 315
Green 2.0 13.3 110 280
Yellow 2.1 16.7 100 270
Blue 2.3 16.7 80 250
White 2.5 21.7 55 225
UV 3.3 21.7 impossible 145

These values have been calculated taking figure 5 as a reference and using a simple spreadsheet to compute data

LED Resistance computation

Connecting LED to an STM32

To drive a LED with an STM32 we have to replace the connection to Vcc or GND with a GPIO configured as a digital output.

Possible circuit to connect a LED to an STM32

This solution leads to two different configurations schematized in figure 6:

  • The first configuration is usually called Active High because LED is on when GPIO is set in High Logical State (i.e. Vcc, usually 3.3V on an official STM32 development board).
  • The second one is called Active Low because LED is on when GPIO is set in Low Logical State (i.e. GND)


Blinking a LED

Let us create a simple project with a single thread and a blinking LED.

The solution here depends on the development board we are going to use. Firstly we should understand whereas our development board has a LED connected to a GPIO and which kind of connection is implemented between the two proposed in figure 6.

Information related to our development board is on the board user manual. In here, we can usually find also schematics that show board connections. Actually, every development board is equipped with at least one user LED and the default demo already makes it blinking. For example, all the STM32 Nucleo-64 boards (Nucleo-F401RE, NUCLEO-F303RE, NUCLEO-L476RG) have the same schematic and are equipped with a green LED which is connected to Arduino D13 (i.e. PA5) in active high mode. Browsing the board files we could also notice that PA5 is already configured as OUTPUT PUSH PULL and this explain why how default demo can blink the LED apparently without configuring PA5.

Things are slightly different on other boards like as example STM32F3 Discovery where we have up to 8 user LEDs connected to PE8, PE9, PE10, PE11, PE12, PE13, PE14 and PE15. Anyway, even if things are different, again default demo blinks these LEDs and to reach our goal we could start duplicating the default demo and edit it.

As the goal is to create the simplest LED blinker, we should go for a single threaded solution e.g. using only the main() thread.

Thus let us start duplicating the default demo as shown into the Chapter 5 of the article “A close look to ChibiOS demos for STM32”. As a quick reference here a video which shows how to do that:

So let’s create a new project giving it a proper name, for instance, RT-STM32F401RE-NUCLEO64-Simple_blinker. After editing properly the makefile and debug configuration we can start to edit the main.c:

  1. removing the inclusion of ch_test.h, because actually, we will not use the test suite;
  2. removing the sdStart, because we are not going to use the serial driver which previously was used by the test suite;
  3. removing the chThdCreateStatic, because we are going for a single threaded solution and we do not want to create additional threads;
  4. editing the loop of the main with the loop of Thread1 which already blinks a LED;
  5. deleting the declaration of waThread1 and Thread1 which now are not necessary anymore;
  6. editing the comment within the code (not necessary but is a good habit).

For an STM32 Nucleo-64, the main.c would look like

A screen of projects and its board files.

This code is valid for each STM32 Nucleo-64 because all these boards have a Green LED which line is named (LINE_LED_GREEN). This code could be also used on other development board by editing this line properly.

If you are not aware of what a “Line” is you should read again Using STM32’s GPIO with ChibiOS’ PAL Driver. As a quick reference, remember that all the lines related to your board are declared on the board.h file which is easily reachable through the linked folder inside your project as shown in Fig.1

The code we have written here blinks the LED with a period of 1 second and duty cycle of 50%. This because the palToggleLine() inverts the logic state of the line where the LED is connected and the chThdSleepMilliseconds() adds a pause between each toggle. The next figure is the timing diagram of the main thread as is.

Led blinker 1s 50%
The timing diagram of a thread which blinks a LED with period 1 second and duty cycle 50%
Final remarks

We have learned an important lesson here: how to blink a LED. Usually, in every project I made, there is always a blinking LED because it is a cheap probe inside my firmware: if LED stops to blink the firmware has crashed!

We can still optimise our code: with our modification, we have deleted all the code related to Serial Driver and Test suite. This means we could completely disable all the code related to these parts.

It is possible to disable the Serial Driver acting on halconf.h:

We should also exclude the Test Suite from the makefile reducing the footprint of our code. To do that go into the makefile and comment the entry related to test-suite (a comment in the makefile begins with hash key):

Changing LED speed

Let us edit the newly created project to change the blinking speed.

The suggestion here is to duplicate the newly created project before to edit it. This because we can use an old project as a kind of checkpoint: in case everything starts to go mad we can roll back and re-start from a point where everything was working. This is a good strategy, especially when we are taking our early steps and we are not so knowledgeable with the IDE.

Looking back at the previous timing diagram, we can notice that the timing is kept with “sleep”. Basically to change the speed we just need to act on the chThdSleepMillisecond value.


For example, we can double the blinking speed halving the sleep time. For a generic STM32 Nucleo-64 the implementation would be

This code blinks the LED with a period of half second and duty cycle of 50% and the next figure is the timing diagram of the main thread as is now.

Led blinker 500ms 50%
The timing diagram of a thread which blinks a LED with period 500 millisecond and duty cycle 50%
Final remarks

To conclude this exercise lets point out something about timing. In all the timing diagrams we have stated that the turn-on/turn-off of the Green LED is instantaneous. In reality, the change of the status of a digital output PIN involves some logic circuitry and this means that palTogglePad() requires a non-zero time to propagate the signal. This latency is much bigger the more is high the load. In the case of small loads like LEDs, the execution time is in the order of hundreds of nanoseconds. Thus, in our case, we can confidently say that the turn-on/turn-off of the LED is almost instantaneous.

In the blinker thread what beats the time is the thread sleep. Except in case of extreme CPU load or tight scheduling, the timing provided by a ChibiOS’ sleep is extremely reliable: if the blinker thread is the thread with the highest priority and sleeps are in the order of few milliseconds, even in case of extreme CPU load its schedule could be reliable.

If you are interested in this argument you should absolutely walk through this further reading about scheduling and multi-threading.

Changing the duty cycle

Let us edit the newly created project to change the blinking duty cycle.

Looking back to previous examples we can notice that in both cases we had a 50% duty cycle. This because turn-on and turn-off are timed in the same way.

The duty cycle is a concept which is specific of square waves we are going to deepen in an article related to PWM. For now, let us focus on common sense: the duty cycle is a percentage representing the fraction of a period in which the signal is active. In our case since the LED is active on the high side the duty cycle is

Duty=\frac{T_{High}}{T_{High} + T_{Low}}

Let us assume we would keep the period to 1 second but change the duty cycle from 50% to 25%. This means the LED should be turned on for 250 ms and turned off for 750 ms. In this way the duty would be

Duty=\frac{250ms}{250ms + 750ms}=25\%


To implement this solution we have to slightly change the code in the main loop replacing the palToggleLine. This would allow us to assign different pauses after turn-on and turn-off. For a generic STM32 Nucleo-64 it would sound like this

LED on a STM32 Nucleo-64
The schematic of the Green Led on the STM32 Nucleo-64 board

This code works as expected only if LED is connected as active high or, in other words, the LED stands lighted during the high phase of the signal.

Looking at figure 4 we can see that the LD2 which is the green LED has its cathode connected to the ground and its anode connected in series with a 510Ω resistor to the PA5 (alias Arduino D13). Comparing this scheme with those shown in figure 6 we can notice that this is an active high configuration: palSet means turn-on, palClear means turn-off.

What follows is the timing diagram of the main loop for this example.

Led blinker 1s 25%
The timing diagram of a thread which blinks a LED with period 1 second and duty cycle 25%


To complete this article let me propose you some assignment you should do on your own.

Connecting an external LED

Connect an external LED to your board as active high and make it blinks

  • Read the user manual check the schematic, be sure that the GPIO you are going use is connected to anything else than your LED;
  • Be sure to add a properly sized series resistor to the LED, in case you are not sure of your calculation to use a slightly bigger resistor: the LED would be less bright but would not break down.
  • Configure the GPIO as OUTPUT PUSH PULL

Blink two LEDs in counter-phase

Blink the on board LED and the external LED alternatively (e.g. when one is on the other is off and vice versa)

  • Start from exercise 4.1
  • You can do all with a single-threaded solution.

LED dimming

Reduce the LED bright using only the PAL Driver

  • Read about the persistence of vision
  • It is related to the duty cycle
  • ChibiOS allows sleeps in the order of hundreds of microseconds using the API chThdSleepMicroseconds().

Help and share

Do you need help with exercises? Something is unclear? Comment below or subscribe to our forum. Help us to spread the knowledge sharing this article: share is caring!


Replies to Dealing with LEDs using an STM32

    • I suggest you to buy a development board as it costs something like 10$. Anyway, there is the windows simulator which is used for code coverage purpose. If you need more info about simulator I suggest you to ask on ChibiOS forum as my knowledge about that is extremely poor.

Leave a Reply