LED Blinking Tutorial STM32F4 Discovery Board – GPIO Pins with HAL Library

This STM32F4 Discovery Board LED blinking tutorial teaches you how to use the GPIO pins of the STM32F4 Discovery Board to blink the four onboard LEDs using the HAL (Hardware Abstraction Layer) library in Keil uvision IDE for ARM. It is the perfect starting point for learning STM32F407 programming, because LED blinking is the embedded “Hello World” — once you can toggle a GPIO pin with the HAL driver, the same pattern applies to buttons, relays, buzzers, motors, and almost every other output peripheral on the board. 

In this guide we will use the onboard user LEDs of the latest STM32F4DISCOVERY board (MB997D / MB9970), but the same code, pin mappings, and concepts apply to every earlier revision of the board. By the end of this tutorial you will have set up a complete Keil uvision project for the STM32F407VGT6, installed the HAL drivers, written your first C program, and seen the LEDs blink on real hardware.

STM32F4 Discovery Board LED blinking example using HAL library in Keil uvision

What You Will Learn

  • How GPIO pins of the STM32F407VGT6 are organized into ports and which ports are broken out on the Discovery header pins
  • Which onboard LEDs (LD3, LD4, LD5, LD6) are connected to which PORTD pins and in what color
  • How to install STM32F4 HAL drivers and board support packages inside Keil uvision
  • How to create a new Keil project targeting the STM32F407VG microcontroller
  • How to write STM32F4 LED blinking code using HAL_GPIO_TogglePin() and HAL_GPIO_WritePin()
  • How the GPIO_InitTypeDef and GPIO_TypeDef structures work under the hood
  • How to troubleshoot the most common LED blinking issues on the STM32F4 Discovery Board

Prerequisites and Required Components

Before you start this STM32F4 LED blinking project, make sure you have the following hardware and software ready:

  • STM32F4 Discovery Board (STM32F407G-DISC1, MB997D or MB9970)
  • Mini USB cable to connect the board to your computer (the board uses the onboard ST-LINK/V2 for both programming and power)
  • Keil uvision IDE (MDK-ARM) — the free community edition supports STM32F4 with a code size limit that is more than enough for this tutorial
  • STM32F4 HAL device family pack (installed through Keil’s Pack Installer)
  • Basic familiarity with C programming

Because the onboard LEDs are already wired to fixed pins on the Discovery Board, you do not need a breadboard, external LEDs, or current limiting resistors for this tutorial. That is one of the biggest advantages of using the Discovery kit to learn STM32 programming.

We use the HAL libraries throughout this series for a simple reason: HAL is officially developed and maintained by STMicroelectronics. Code written for the STM32F4 HAL can be ported to the F0, F1, F2, F3, F7, L0, L4, G0, and G4 families with minimal changes, which makes this LED blinking tutorial a transferable skill, not just a Discovery Board exercise.

STM32F4 Discovery Board GPIO Pins

The STM32F4 Discovery board features a 32-bit STM32F407VGT6 microcontroller, which belongs to the F4 family of ST microcontrollers and is based on ARM Cortex-M4 architecture with DSP and FPU. Microcontrollers in the STM32F407xx family support up to 140 GPIO pins with interrupt capability — of which up to 136 can operate as fast GPIOs up to 84 MHz, and up to 138 are 5 V-tolerant. The STM32F407VGT6 specifically exposes 82 GPIO pins, arranged into ports PORTA, PORTB, PORTC, PORTD, PORTE, PORTF, PORTG, PORTH, and PORTI. Each port contains up to 16 pins (pin 0 through pin 15). 

Note: Not all ports have the full 16 GPIO pins broken out, because some pins are reserved for other functions such as oscillator inputs, JTAG/SWD, and power supply pins. 

On the STM32F4 Discovery Board itself, five GPIO ports (PORTA, PORTB, PORTC, PORTD, PORTE) are brought out to the two 50-pin male headers on the sides of the board, giving you a total of 80 GPIO pins to work with for external connections, as shown in the figure below:

STM32F4 Discovery Board GPIO header pins pinout diagram

Onboard LEDs on the STM32F4 Discovery Board

The Discovery board comes with four user LEDs connected to PD12, PD13, PD14, and PD15 of PORTD, each through a current-limiting resistor. This is why the LED blinking example focuses on PORTD pins 12 through 15 — the wiring is already done for you on the PCB. 

STM32F4 Discovery onboard LED pins connection diagram schematic
LED ColorLED NamePin Number
GreenLD4PD12
OrangeLD3PD13
RedLD5PD14
BlueLD6PD15
  • LD3: orange LED — connected to GPIO pin PD13 of the STM32F407VGT6
  • LD4: green LED — connected to GPIO pin PD12 of the STM32F407VGT6
  • LD5: red LED — connected to GPIO pin PD14 of the STM32F407VGT6
  • LD6: blue LED — connected to GPIO pin PD15 of the STM32F407VGT6
STM32F4 Discovery onboard LEDs LD3 LD4 LD5 LD6

Because all four LEDs sit on the same port (PORTD), we can enable the clock for a single GPIO port and control all four LEDs with one initialization block — a small efficiency that becomes important when you start writing more complex firmware.

In this LED blinking tutorial, we will see how to blink all four of these onboard LEDs using the HAL GPIO drivers in Keil uvision IDE. 

Writing Your First Program for STM32F4 Discovery Board in Keil uvision

In this section you will create your first Keil uvision project for the STM32F4 Discovery Board from scratch, install the required HAL packages, and configure the run-time environment so the project compiles against the STM32F407VG device.

Step 1: Install STM32F4 HAL Drivers and Board Support Packages in Keil

First, download and install Keil uvision IDE (MDK-ARM) on your system. If you have not done this yet, follow our dedicated install guide: 

Once Keil is installed, open it and launch the Pack Installer to install the STM32F4 software packages and HAL libraries. Click the Pack Installer icon in the Keil toolbar:

Open Keil Pack Installer to install STM32F4 HAL drivers

The Pack Installer window will appear. In the left pane, expand STMicroelectronics → STM32F4 Series. When you double-click the STM32F4 series, the required software packages are listed on the right-hand side. 

From the right pane select Device Specific and install the drivers for STM32F4 (and the STM32F4 Nucleo pack if you ever plan to use a Nucleo board too) by clicking the Install buttons one by one. This downloads the HAL drivers and board support packages for the entire STM32F4 family of microcontrollers, including the STM32F407VGT6 used on the Discovery Board. 

Install STM32F4 Discovery Board package and HAL drivers in Keil uvision Pack Installer

Once the installation finishes, close the Pack Installer. You are now ready to create your first STM32F4 Discovery project in Keil uvision. 

Step 2: Create a New Project in Keil uvision

To create a new project for the STM32F4 Discovery Board, go to the Project menu and click New uvision Project

Create new uvision project in Keil for STM32F4

Next, choose the location where you want to save your project. It is good practice to create a new folder dedicated to this project and give it a clear name (for example, STM32F4_LED_Blink):

Save Keil uvision project for STM32F4 LED blinking

After saving, the “Select Target Device” window appears. In the search bar type STM32F407VG, select the microcontroller from the filtered options, and click OK

Select STM32F407VG microcontroller of Discovery board in Keil uvision

The Run-Time Environment window will appear next. This is where you pick which HAL modules your project needs. For STM32F4 LED blinking, make the following selections: 

  • CMSIS → tick CORE
  • Device → tick Startup 
  • Device → STM32Cube Framework (API) → select Classic
  • Device → STM32Cube HAL → tick the peripheral modules you need. For this tutorial we only need GPIO, RCC, PWR, Cortex, and Common. You can add more later (ADC, SPI, I2C, UART, etc.) as your projects grow. 

Selecting these options may pull in additional software components. Any missing dependencies are shown at the bottom of the window. Click the Resolve button and Keil uvision will automatically enable the required dependencies for you. 

Because this LED blinking tutorial uses the GPIO pins of the STM32F4 Discovery Board, make sure the GPIO HAL module is ticked under STM32Cube HAL before continuing. 

Select GPIO HAL driver for STM32F4 in Keil uvision run time environment

After confirming your settings, click OK. The project is created and the Keil workspace should look like this: 

New STM32F4 Keil uvision project created with HAL drivers

Now add the main source file to the project. Right-click on Source Group 1, choose Add New Item to Group, select a C File (.c), and name it main. We will write the STM32F4 LED blinking code inside this file. 

STM32F4 Discovery Board LED Blinking Program (HAL_GPIO_TogglePin)

The following LED blinking program toggles all four onboard LEDs (PD12, PD13, PD14, PD15) at a rate of 1 second. It uses the GPIO HAL module driver and the HAL_GPIO_TogglePin() function, which flips the current state of the specified pin on every call.

#include "stm32f4xx_hal.h"

void Init_OnBoard_LEDs(void);
void Delay_ms(volatile int time_ms);
int main(void)
{
	Init_OnBoard_LEDs();
	while(1)
	{
	HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15);
	Delay_ms(1000);
	}
}

void Init_OnBoard_LEDs(void)
{
	 __HAL_RCC_GPIOD_CLK_ENABLE();
	GPIO_InitTypeDef BoardLEDs;
	BoardLEDs.Mode = GPIO_MODE_OUTPUT_PP;
	BoardLEDs.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
	HAL_GPIO_Init(GPIOD, &BoardLEDs);
}
void Delay_ms(volatile int time_ms)
{
	      int j;
        for(j = 0; j < time_ms*4000; j++)
            {}  /* excute NOP for 1ms */
}

The Init_OnBoard_LEDs() function performs three essential steps: it enables the peripheral clock for PORTD using __HAL_RCC_GPIOD_CLK_ENABLE(), fills in a GPIO_InitTypeDef structure with the pin mode and pin mask, and passes that structure to HAL_GPIO_Init(). Without the clock-enable step, the GPIO registers will simply not respond — this is the single most common reason why STM32 LED blinking code compiles cleanly but does nothing on the board.

STM32F4 GPIO HAL Driver Explained

The HAL driver provides a clean, portable API to control the GPIO pins of the STM32F4 family of microcontrollers. Any GPIO pin can be configured in one of the following modes: 

  • Digital Input mode — read a logic level from the outside world (push buttons, logic signals)
  • Analog mode — connect the pin to the ADC or DAC peripheral
  • Digital Output mode — drive a logic high or low (LEDs, relays, transistors)
  • Alternate Function mode — hand the pin over to a peripheral such as USART, SPI, I2C, TIM, or USB
  • External Interrupt / Event lines — trigger an ISR on a rising, falling, or both-edge transition (EXTI)

The stm32f4xx_hal.h header file includes function prototypes and type definitions for all the HAL peripheral modules available on STM32F4 microcontrollers. Including this single header gives your code access to GPIO, ADC, SPI, I2C, UART, TIM, and every other HAL peripheral API. 

The GPIO_InitTypeDef C structure is used to initialize and set the mode of GPIO pins: 

typedef struct
{
  uint32_t Pin;       /*!< Specifies the GPIO pins to be configured.
                           This parameter can be any value of @ref GPIO_pins_define */

  uint32_t Mode;      /*!< Specifies the operating mode for the selected pins.
                           This parameter can be a value of @ref GPIO_mode_define */

  uint32_t Pull;      /*!< Specifies the Pull-up or Pull-Down activation for the selected pins.
                           This parameter can be a value of @ref GPIO_pull_define */

  uint32_t Speed;     /*!< Specifies the speed for the selected pins.
                           This parameter can be a value of @ref GPIO_speed_define */

  uint32_t Alternate;  /*!< Peripheral to be connected to the selected pins. 
                            This parameter can be a value of @ref GPIO_Alternate_function_selection */
}GPIO_InitTypeDef;

Another important C structure is GPIO_TypeDef. Its members map directly to the configuration and control registers of a GPIO port — which means when you write to a field in this struct, you are really writing to a memory-mapped register inside the MCU. 

typedef struct
{
  __IO uint32_t MODER;    /*!< GPIO port mode register,               Address offset: 0x00      */
  __IO uint32_t OTYPER;   /*!< GPIO port output type register,        Address offset: 0x04      */
  __IO uint32_t OSPEEDR;  /*!< GPIO port output speed register,       Address offset: 0x08      */
  __IO uint32_t PUPDR;    /*!< GPIO port pull-up/pull-down register,  Address offset: 0x0C      */
  __IO uint32_t IDR;      /*!< GPIO port input data register,         Address offset: 0x10      */
  __IO uint32_t ODR;      /*!< GPIO port output data register,        Address offset: 0x14      */
  __IO uint32_t BSRR;     /*!< GPIO port bit set/reset register,      Address offset: 0x18      */
  __IO uint32_t LCKR;     /*!< GPIO port configuration lock register, Address offset: 0x1C      */
  __IO uint32_t AFR[2];   /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
} GPIO_TypeDef;

Configure GPIO Pins as Digital Output

The HAL GPIO library provides functions to initialize, control, and de-initialize GPIO pins. The most important of these for LED blinking is HAL_GPIO_Init()

The HAL_GPIO_Init() function initializes the GPIOx port according to the configuration supplied via a GPIO_InitTypeDef struct. Here GPIOx can be GPIOA, GPIOB, GPIOC … up to GPIOI, depending on which port you want to configure. 

void HAL_GPIO_Init(GPIO_TypeDef  *GPIOx, GPIO_InitTypeDef *GPIO_Init)

In the LED blinking code above, we use GPIOD to control the onboard LEDs PD12, PD13, PD14, and PD15. To configure them as digital outputs, we first create a struct variable BoardLEDs of type GPIO_InitTypeDef

GPIO_InitTypeDef BoardLEDs;

Using the members of GPIO_InitTypeDef, we set the mode of BoardLEDs to push-pull digital output and select the four LED pins by OR-ing the pin bitmasks together: 

BoardLEDs.Mode = GPIO_MODE_OUTPUT_PP;
BoardLEDs.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;

We then pass a reference to the BoardLEDs struct to HAL_GPIO_Init() along with the GPIO port (GPIOD). This initializes PD12–PD15 as digital output pins in push-pull mode. Push-pull means the pin can actively drive both a high and a low logic level, which is exactly what you want for driving an LED.

HAL GPIO Toggle LED Function

The HAL GPIO driver provides the convenience function HAL_GPIO_TogglePin(), which can be used to toggle any GPIO pin on the STM32F4 Discovery Board. This is the easiest way to blink an LED — every call simply inverts the current state of the pin, so you do not have to track it in software. 

To use this function, pass the GPIO port and the pin number (or a mask of multiple pins) that you want to toggle. 

void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

For example, to toggle pins PD12–PD15 of GPIOD we pass GPIOD as the first argument and the OR-ed pin mask as the second argument. These two lines sit inside the while(1) loop so they repeat forever. The 1-second delay in between gives the eye enough time to see the LEDs turn on and off, producing a visible 1 Hz blink rate on all four onboard LEDs. 

HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15);
Delay_ms(1000);

HAL GPIO Write Pin Function 

HAL_GPIO_WritePin() is used to set or clear a specified pin on a GPIO port. The first argument is the port name, the second is the pin number (or OR-ed pin mask) of the selected port, and the third is the state of the pin — either GPIO_PIN_RESET or GPIO_PIN_SET

  • GPIO_PIN_RESET → drives the pin low (0 V, LED off for active-high wiring)
  • GPIO_PIN_SET → drives the pin high (3.3 V, LED on for active-high wiring)
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)

To blink the onboard LEDs of the STM32F4 Discovery Board using HAL_GPIO_WritePin(), you set the corresponding pins for one second and then reset them for one second, giving the same 1 Hz blink rate as the toggle version but with a little more explicit control over the on/off timing. 

HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_SET);
Delay_ms(1000);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET);
Delay_ms(1000);

STM32F4 LED Blinking Code with HAL_GPIO_WritePin()

Here is the complete alternative version of the LED blinking code, this time using HAL_GPIO_WritePin() to turn the green, orange, red, and blue LEDs on and off.

#include "stm32f4xx_hal.h"

void Init_OnBoard_LEDs(void);
void Delay_ms(volatile int time_ms);
int main(void)
{
	Init_OnBoard_LEDs();
	while(1)
	{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_SET);
Delay_ms(1000);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET);
Delay_ms(1000);
	}
}

void Init_OnBoard_LEDs(void)
{
	 __HAL_RCC_GPIOD_CLK_ENABLE();
	GPIO_InitTypeDef BoardLEDs;
	BoardLEDs.Mode = GPIO_MODE_OUTPUT_PP;
	BoardLEDs.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
	BoardLEDs.Pull = GPIO_NOPULL;
	HAL_GPIO_Init(GPIOD, &BoardLEDs);
}
void Delay_ms(volatile int time_ms)
{
	      int j;
        for(j = 0; j < time_ms*4000; j++)
            {}  /* excute NOP for 1ms */
}

Uploading the Code to the STM32F4 Discovery Board

To flash the LED blinking firmware onto the STM32F4 Discovery Board, connect the board to your computer using a Mini USB cable plugged into the ST-LINK USB port (the one labeled CN1 on the top of the board, not the OTG port). The onboard ST-LINK/V2 programmer provides both programming and power — no external power supply is required.

In Keil uvision, open the project options (Project → Options for Target), go to the Debug tab, and select ST-Link Debugger. Then press F7 to build the project and F8 (Load) to flash the binary to the STM32F407VGT6. If the build succeeds and the flash operation completes without errors, all four onboard LEDs will immediately start blinking at 1 Hz.

Troubleshooting STM32F4 LED Blinking Issues

If you uploaded the code but the onboard LEDs of the STM32F4 Discovery Board are not blinking, one of these issues is almost certainly the cause:

  • GPIO clock not enabled. If you forget __HAL_RCC_GPIOD_CLK_ENABLE(), writes to the GPIO registers are ignored silently. Double-check this line is called before HAL_GPIO_Init().
  • Wrong GPIO port. The onboard LEDs are on PORTD — not PORTA or PORTB. Make sure every reference in your init and toggle code uses GPIOD.
  • Pin mode set to input. The default value of an uninitialized GPIO_InitTypeDef is typically 0 (input). Always assign Mode = GPIO_MODE_OUTPUT_PP explicitly.
  • ST-LINK not detected. If Keil shows “No ST-LINK detected”, try a different USB cable — many Mini USB cables are charge-only with no data lines. The jumpers CN3 (labeled Discovery) must both be in place for the onboard ST-LINK to target the onboard MCU.
  • Board resets repeatedly. Watchdog enabled accidentally or stack overflow. For this simple blink program, leave the default startup file untouched.
  • Code compiles but LEDs stay off. You may have accidentally selected the wrong device (e.g., STM32F405 instead of STM32F407VG) in the target settings, causing the linker to use the wrong memory map.

Frequently Asked Questions

Which pins control the onboard LEDs on the STM32F4 Discovery Board?

The STM32F4 Discovery Board (STM32F407G-DISC1) has four user LEDs wired to pins PD12 (green, LD4), PD13 (orange, LD3), PD14 (red, LD5), and PD15 (blue, LD6) of PORTD on the STM32F407VGT6 microcontroller.

Why do I need to enable the GPIOD clock before blinking LEDs?

STM32 microcontrollers use clock gating to save power — every peripheral, including each GPIO port, has its clock disabled at reset. If you do not call __HAL_RCC_GPIOD_CLK_ENABLE() before HAL_GPIO_Init(), writes to the GPIOD registers silently do nothing and the LEDs will stay off even though your code appears correct.

What is the difference between HAL_GPIO_TogglePin() and HAL_GPIO_WritePin()?

HAL_GPIO_TogglePin() flips the current state of a GPIO pin in a single call — it is ideal for simple blink loops. HAL_GPIO_WritePin() drives the pin to an explicit state (GPIO_PIN_SET or GPIO_PIN_RESET) regardless of the previous state, which is more appropriate when you need exact control — for example in state machines, button-responsive code, or when you are mirroring the state of an external input.

Can I use this LED blinking code on the STM32 Nucleo or STM32 Blue Pill?

The HAL functions are identical across the STM32 families, but the onboard LED pin differs. On the STM32 Nucleo-F446RE the user LED is on PA5, and on the STM32 Blue Pill (STM32F103C8T6) the user LED is on PC13 (active low). Change the port, pin, and clock-enable macro accordingly and the same tutorial logic applies. We have dedicated guides for the STM32 Nucleo and STM32 Blue Pill for this reason.

Is the software delay in this tutorial accurate?

No — the Delay_ms() function used here is a crude busy-wait loop whose timing depends on the compiler’s optimization level and the clock configuration. For production code you should use HAL_Delay() (which is driven by SysTick) or a hardware timer. The busy-wait version is kept here only because it makes this first tutorial completely self-contained and free of SysTick setup.

Conclusion

You now have a fully working STM32F4 Discovery Board LED blinking project in Keil uvision using the HAL library. You installed the STM32F4 device pack, created a new Keil project, configured the GPIO HAL module, wrote and flashed the blink firmware, and learned how HAL_GPIO_Init(), HAL_GPIO_TogglePin(), and HAL_GPIO_WritePin() work internally. The same three-step pattern — enable the peripheral clock, fill an init struct, call the HAL init function — is the foundation for every other peripheral you will use on the STM32F407, from ADC and DAC to UART, SPI, and timers.

2 thoughts on “LED Blinking Tutorial STM32F4 Discovery Board – GPIO Pins with HAL Library”

  1. Very nice explanation by you. It has resolved many of my doubts.

    Thankyou so much for such wonderful tutorial.

    Reply

Leave a Comment