In this article, we will walk through setting up a simple project on the STM32F446RE Nucleo-64 board where a user button press toggles an onboard LED. The focus will be on manual HAL configuration without relying on interrupts, using a pure polling method.
This exercise covers basic GPIO initialization, clock setup, and handling a simple control flow inside the main loop.
The STM32F446RE Nucleo-64 board comes with an onboard LED and a user push-button:
Electrical Behavior:
GPIO_PIN_SET
) to PA5 turns the LED ON, and writing a low logic level (GPIO_PIN_RESET
) turns it OFF.GPIO_PIN_RESET
) when pressed.Hence, when polling PC13, a logic low indicates that the button is pressed.
The goal is to continuously read the button state and toggle the LED whenever a falling edge (button press) is detected.
The flow of the project can be summarized as:
/*
* main.c
*
* Created on: Apr 24, 2025
* Author: erraj
*/
#include "stm32f4xx_hal.h"
void SystemClock_Config(void);
void GPIO_Config(void);
void Error_Handler(void);
int main(void) {
/** Initialize HAL Driver*/
HAL_Init();
/** Configure System Clock */
SystemClock_Config();
/** Setup GPIO for LED */
GPIO_Config();
while (1) {
if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_RESET) {
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
HAL_Delay(300);
}
}
}
void GPIO_Config(void) {
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = { 0 };
//configure PA5 as output (LED)
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
//configure PC13 as input (User Button B1)
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}
void SystemClock_Config(void) {
RCC_OscInitTypeDef RCC_OscInitStruct = { 0 };
RCC_ClkInitTypeDef RCC_ClkInitStruct = { 0 };
/** Step 1: Enable Power Control clock */
__HAL_RCC_PWR_CLK_ENABLE();
/** Step 2: Set voltage scaling to match 84 MHz operation */
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);
/** Step 3: Configure HSI as PLL source and setup PLL to 84 MHz */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
// PLL Config for SYSCLK = 84 MHz (VCO = 336 MHz)
RCC_OscInitStruct.PLL.PLLM = 16; // HSI = 16 MHz → /16 = 1 MHz
RCC_OscInitStruct.PLL.PLLN = 336; // *336 = 336 MHz
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4; // /4 = 84 MHz (SYSCLK)
RCC_OscInitStruct.PLL.PLLQ = 7; // USB clock = 336 / 7 = 48 MHz ✅
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
Error_Handler(); // Add your error response here
}
/** Step 4: Select PLL as system clock and configure bus clocks */
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; // 42 MHz
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // 84 MHz
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) {
Error_Handler(); // Add your error response here
}
}
void Error_Handler(void) {
// Simple error handler: blink LED rapidly or stay in infinite loop
while (1) {
// Optional: Toggle LED to indicate error
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
HAL_Delay(100); // Fast blink indicates error
}
}
At the beginning of the program, we initialize the HAL driver, configure the system clock, and setup the GPIOs.
int main(void) {
HAL_Init();
SystemClock_Config();
GPIO_Config();
while (1) {
if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_RESET) {
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
HAL_Delay(300);
}
}
}
In the infinite while(1)
loop, we poll the button pin PC13
. If the button is pressed (logic low), the LED connected to PA5
is toggled. A small delay of 300ms is introduced to avoid multiple toggles due to mechanical bounce.
The GPIO_Config()
function sets up both the LED and Button pins.
void GPIO_Config(void) {
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = { 0 };
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}
The SystemClock_Config()
function sets the system clock to 84 MHz using the internal HSI oscillator as a source and PLL for frequency multiplication.
void SystemClock_Config(void) {
RCC_OscInitTypeDef RCC_OscInitStruct = { 0 };
RCC_ClkInitTypeDef RCC_ClkInitStruct = { 0 };
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 16;
RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;
RCC_OscInitStruct.PLL.PLLQ = 7;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) {
Error_Handler();
}
}
In case of clock setup failure or any other critical error, the Error_Handler()
function is used to blink the LED rapidly as an error indication.
void Error_Handler(void) {
while (1) {
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
HAL_Delay(100);
}
}
Peripheral | Pin | Mode | Notes |
---|---|---|---|
LED (LD2) | PA5 | Output, Push-Pull | Active High, toggled manually |
Button (B1) | PC13 | Input | Logic low when pressed |
This simple project demonstrates how to manually configure GPIOs using the STM32 HAL library without relying on the CubeMX graphical tool.
The key point here is polling the button press using HAL_GPIO_ReadPin()
. It is sufficient for simple projects but is not the most efficient way for event handling in real-time applications.
Using interrupts (EXTI lines) for the button press would be a more efficient and scalable approach, which we will cover in future articles.
Polling is a good starting point for beginners to understand GPIOs and basic embedded programming concepts. However, for energy-efficient and real-time designs, polling should be replaced with interrupt-based techniques.
In upcoming articles, we will implement the same LED toggle functionality using interrupts to show the improvement in efficiency and responsiveness.