• Home
  • Button Controlled LED Toggle on STM32F446RE (Polling Method)

Button Controlled LED Toggle on STM32F446RE (Polling Method)

Button Controlled LED Toggle on STM32F446RE (Polling Method)
  • Raja Gupta
  • April 27, 2025

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.

Hardware Overview

The STM32F446RE Nucleo-64 board comes with an onboard LED and a user push-button:

  • LED (LD2): Connected to PA5 (GPIOA, Pin 5)
  • User Button (B1): Connected to PC13 (GPIOC, Pin 13)

Electrical Behavior:

  • The LED (LD2) is active-high. Writing a high logic level (GPIO_PIN_SET) to PA5 turns the LED ON, and writing a low logic level (GPIO_PIN_RESET) turns it OFF.
  • The User Button (B1) is connected to PC13 and is normally pulled high internally through a pull-up resistor. Pressing the button connects PC13 to ground, resulting in a logic low (GPIO_PIN_RESET) when pressed.

Hence, when polling PC13, a logic low indicates that the button is pressed.

Project Overview

  • Microcontroller: STM32F446RE (Cortex-M4)
  • Board: Nucleo-64 MB1136 (STM32 Nucleo-F446RE)
  • IDE: STM32CubeIDE
  • LED: Connected to PA5
  • Button: Connected to PC13

The goal is to continuously read the button state and toggle the LED whenever a falling edge (button press) is detected.

Project Flow

The flow of the project can be summarized as:

  1. Initialize HAL drivers.
  2. Configure system clock to 84 MHz.
  3. Configure GPIO for LED (output) and Button (input).
  4. Poll the button inside an infinite loop.
  5. Toggle the LED on each detected button press.

Full Code

C

/*
 * 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
	}
}

Output

Code Breakdown

main.c

At the beginning of the program, we initialize the HAL driver, configure the system clock, and setup the GPIOs.

C

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.

GPIO Configuration

The GPIO_Config() function sets up both the LED and Button pins.

C

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);
}
  • PA5 is configured as a push-pull output with no pull-up or pull-down resistor.
  • PC13 is configured as an input. No internal pull-up/pull-down resistors are configured here because the board already has a hardware pull-up resistor on PC13.

System Clock Configuration

The SystemClock_Config() function sets the system clock to 84 MHz using the internal HSI oscillator as a source and PLL for frequency multiplication.

C

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();
    }
}

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.

C

void Error_Handler(void) {
    while (1) {
        HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
        HAL_Delay(100);
    }
}

Summary of Pin Configuration

Peripheral Pin Mode Notes
LED (LD2) PA5 Output, Push-Pull Active High, toggled manually
Button (B1) PC13 Input Logic low when pressed

Observations

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.

Conclusion

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.

Leave a Reply

Your email address will not be published. Required fields are marked *