STM32 Firmware: GPIO and Buttons

Build responsive LED firmware on STM32H750B-DK using non-blocking timing.

Introduction

30 Second Summary

Every device you interact with daily, from a phone vibrating in your pocket to a car's dashboard indicators, runs firmware that responds to inputs instantly. The difference between amateur and professional firmware is whether the system stays responsive while doing its work.

In this project, you will program an STM32 microcontroller to blink an LED and respond to button presses using the non-blocking timing patterns that professional embedded engineers use every day.

What You'll Build

When you press the blue button on your board, the green LED instantly changes its blink speed, proving your firmware never misses an input no matter where it is in its timing cycle.

By the end of this project, you'll have:

  • A green LED blinking at a steady rhythm using non-blocking timing instead of blocking delays, the pattern every professional embedded system uses.
  • A responsive button that instantly toggles between slow and fast blink with zero missed presses, proving your main loop never gets stuck.
  • Confidence navigating the STM32 reference manual to understand what your code configures at the hardware register level, the core skill embedded employers test for.
  • Secret Mission: Replace button polling with a hardware EXTI interrupt so presses are detected by the CPU automatically.

Are there any prerequisites?

You need basic C programming knowledge (variables, if statements, while loops, functions). Arduino experience is helpful but not required.

You also need an STM32H750B-DK Discovery board, a USB Micro-B cable, and both STM32CubeIDE and STM32CubeMX installed on your computer.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters. This step is your chance to commit to the goal before any hands-on work begins.

Create and Flash Your First Project

Before you can write any firmware, you need a project that compiles, flashes, and runs on your specific board.

STM32CubeMX generates all the initialization code for you, including the clock tree, GPIO defaults, and startup files. You tell it which board you have, and it handles the rest so you can focus on application logic.

In this step, get ready to:
  • Select the STM32H750B-DK board and initialize its peripherals in STM32CubeMX.
  • Generate a project named GPIO_Basics and open it in STM32CubeIDE.
  • Build and flash the project to confirm the toolchain works end-to-end.
Select your board in STM32CubeMX

STM32CubeMX needs to know which board you have so it can pre-configure the correct pins for your onboard hardware.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type STM32CubeMX and press Enter to open it.
  • On the home screen, click Access to Board Selector under the New Project section.
  • In the search field at the top, type STM32H750B-DK.
  • Select the STM32H750B-DK board from the results list, then click Start Project.
  • A dialog asks whether to initialize all peripherals with their default mode. Click Yes.

The CubeMX pinout viewer opens, showing the STM32H750 chip with pins already colour-coded and assigned.

What does initializing with defaults do?

The STM32H750B-DK board has hardware-wired connections between specific pins and onboard components. Initializing with defaults tells CubeMX to pre-configure those pins so you do not have to set them manually.

Pin PJ2 is set as a GPIO output because it connects to the green LED (LD7). Pin PC13 is set as a GPIO input because it connects to the blue user button (B1). These are the two peripherals you will use throughout this project.

Generate and open the project
  • Click the Project Manager tab at the top of the CubeMX window.
  • In the Project Name field, type GPIO_Basics.
  • Set the Toolchain/IDE dropdown to STM32CubeIDE.
  • Click Generate Code in the top toolbar.

CubeMX generates the project files, including all HAL driver sources, startup assembly, and the linker script.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type STM32CubeIDE and press Enter to open it.
  • In STM32CubeIDE, click File in the menu bar.
  • Click STM32 Project Create/Import.
  • Under Import STM32 Project, select STM32CubeMX/STM32CubeIDE Project and click Next.
  • Browse to the GPIO_Basics folder that CubeMX created, then click Finish.

The project appears in the Project Explorer on the left side of CubeIDE.

Don't see the project in the explorer?

Make sure you selected STM32CubeIDE (not EWARM or Makefile) in the CubeMX Toolchain/IDE dropdown before generating. If you picked the wrong toolchain, return to CubeMX, change it, and click Generate Code again. Then re-import in CubeIDE.

Help me import my CubeMX project into STM32CubeIDE

Build and flash to the board

Your project is a generated skeleton with no application code yet. Building it proves that the toolchain, HAL sources, and linker script all work together.

  • Connect your STM32H750B-DK board to your computer with the USB Micro-B cable. Plug the cable into the ST-LINK USB connector (labelled CN15 on the board).
  • In STM32CubeIDE, click Project in the menu bar, then click Build Project.

Before you check the result, what do you expect to see in the Console panel at the bottom?

  • Look at the Console panel at the bottom of the CubeIDE window. You should see a build log ending with 0 errors.

What just got compiled?

CubeMX generated hundreds of source files: the HAL drivers for every peripheral you might use, a SystemClock_Config function that sets up the 480 MHz clock tree, and a main.c with an empty while(1) loop. The build compiled all of it into a single binary small enough to fit in the STM32H750's 128 KB internal flash.

  • Click Run in the menu bar, then click Debug.
  • If a perspective switch dialog appears, click Switch to enter the Debug perspective.

The firmware flashes to the board and the debugger halts execution at the first line of main(). You should see the code editor highlighting a line inside Core/Src/main.c.

  • Click the red Terminate button (the red square in the toolbar) to end the debug session.

The green LED (LD7) on your board is not blinking yet. That is expected. You have a blank project running, and you will add LED control in the next step.

Build has errors or debugger won't connect?

  • If you see linker errors mentioning missing HAL files, return to CubeMX and confirm the Toolchain/IDE is set to STM32CubeIDE. Regenerate and re-import.
  • If the debugger says "No ST-LINK detected", check that the USB cable is plugged into the ST-LINK connector (CN15), not the USB OTG connector.
  • If the debugger connects but immediately disconnects, make sure no other debug session is still running. Click Terminate first, then retry.

Help me get the STM32 debugger to connect to my board

Your toolchain is fully working. You have a blank firmware running on real hardware. Next up, you will write your first lines of C to make that green LED blink.

Blink the LED

Your project compiles and flashes cleanly. Now it's time to make the hardware do something visible.

A blinking LED proves your entire toolchain works: the clock tree, the GPIO pin configuration, and the flash process. You'll also crack open the reference manual to see exactly what CubeMX configured in the hardware registers.

In this step, get ready to:
  • Write your first HAL function calls to toggle the onboard green LED.
  • Navigate the STM32H750 reference manual to understand GPIO registers.
  • Discover why blocking delays are a problem for embedded input handling.
Add the blink code and flash to the board

The HAL library gives you ready-made functions for controlling GPIO pins. Two calls are all you need for a basic blink: one to flip the pin state and one to wait before flipping again.

  • In STM32CubeIDE, open Core/Src/main.c from the Project Explorer on the left.
  • Find the while (1) loop. Locate the comment /* USER CODE END WHILE */ inside it.
  • Add the following two lines directly above that comment:
    HAL_GPIO_TogglePin(GPIOJ, GPIO_PIN_2);
    HAL_Delay(500);

What does this code do?

  • HAL_GPIO_TogglePin(GPIOJ, GPIO_PIN_2) flips pin PJ2 (the green LED) between HIGH and LOW. If the LED is on, it turns off. If off, it turns on.
  • HAL_Delay(500) pauses execution for 500 milliseconds. The CPU does nothing else during this time.
  • Together, the loop toggles the LED every 500 ms, creating a 1-second blink cycle (500 ms on, 500 ms off).

Before you run this, what do you expect to see on the board once the code flashes?

  • Build the project by clicking Project in the menu bar, then Build Project.
  • Confirm 0 errors in the Console panel at the bottom.
  • Flash the firmware by clicking Run in the menu bar, then Run (or press the green play button in the toolbar).
  • Look at your board. The green LED (LD7) should now blink on and off at roughly 1-second intervals.

LED not blinking?

  • Check the Console for build errors. A typo in GPIOJ or GPIO_PIN_2 causes an undefined identifier error.
  • Make sure your code is above the /* USER CODE END WHILE */ comment, not below it. Code outside the USER CODE markers gets deleted when CubeMX regenerates.
  • Confirm the USB cable is connected to the ST-LINK port (the Micro-B connector near the board's edge), not the USB OTG port.

Why isn't my LED blinking after flashing?

✔️ Awesome, I've got everything!

Great. Double check you've saved main.c and that your LED is blinking steadily.

ⓧ I'd like to double check the full code

Here is the full user code in main.c so far. Compare your file against this:

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* USER CODE BEGIN PV */
/* USER CODE END PV */

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/* USER CODE BEGIN WHILE */
  while (1)
  {
    HAL_GPIO_TogglePin(GPIOJ, GPIO_PIN_2);
    HAL_Delay(500);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
Navigate the GPIO registers in the reference manual

Your LED blinks, but what did CubeMX actually configure in the hardware to make that pin an output? The reference manual tells you exactly which register bits were set.

  • Download or open the STM32H750 reference manual (RM0433) from the ST website.
  • Navigate to Chapter 11: General-purpose I/Os (GPIO).
  • Find the GPIO port mode register description (GPIOx_MODER). Read how each pair of bits controls a pin's mode.
  • Find the GPIO output data register (GPIOx_ODR). This is the register that HAL_GPIO_TogglePin flips to change your LED state.

What did CubeMX configure for PJ2?

In GPIOx_MODER, each pin gets a 2-bit field. CubeMX set PJ2's bits to 01 (binary), which means "General purpose output mode." That single register write is what makes PJ2 capable of driving the LED.

When you call HAL_GPIO_TogglePin, the HAL reads the current value of GPIOx_ODR, flips the bit for pin 2, and writes it back. One register read-modify-write is all it takes to toggle the LED.

Why read the reference manual?

HAL hides these registers behind function calls, which is convenient. But in job interviews and debugging sessions, you need to know what's underneath. Understanding MODER and ODR means you can read a datasheet for any STM32 chip and configure it confidently.

Discover the blocking delay problem

Your LED blinks beautifully. But your board also has a blue user button (B1). What do you think happens if you press it right now?

  • While your LED is blinking, press the blue button (B1) on your board several times with quick taps.
  • Observe what happens to the LED.

Nothing changes. The LED keeps blinking at the same rate no matter how many times you press B1.

Why does the button do nothing?

Your code never checks the button. But even if it did, there's a deeper problem: HAL_Delay(500) locks the CPU in a busy-wait loop for 500 ms. During that time, the processor cannot execute any other code.

If a button press happens during that 500 ms window, the CPU is completely blind to it. This is why blocking delays are considered harmful in embedded systems. The next two steps solve this problem.

Your LED is blinking, proving the full toolchain works end to end. But the blocking delay makes your firmware deaf to user input. Next, you'll add button reading and feel that limitation firsthand.

Read the Button and Discover the Blocking Problem

Your LED blinks. That proves your output works. Now it is time to add an input: the blue user button (B1) on your board.

The simplest way to check a button is polling: reading the pin state every time through the loop. You will add button reading to toggle the blink speed between slow and fast. But you will also discover a serious limitation of HAL_Delay that makes this approach unreliable for real embedded systems.

In this step, get ready to:
  • Add button polling code that toggles the LED blink speed.
  • Discover that blocking delays cause missed button presses.
  • Read the GPIOx_IDR register description in RM0433 to understand why.
Add a blink speed variable

Before you can change the blink speed at runtime, you need a variable to hold the current delay value instead of a hardcoded number.

  • In STM32CubeIDE, open Core/Src/main.c.
  • Find the /* USER CODE BEGIN PV */ comment near the top of the file.
  • Add a global variable between the PV comments by typing this code:
/* USER CODE BEGIN PV */
uint32_t blink_delay = 500;
/* USER CODE END PV */

What does this variable do?

The variable blink_delay stores the current blink interval in milliseconds. It starts at 500 (the same value you had hardcoded).

Declaring it outside main() as a global variable means any code in this file can read and change it. You will toggle it between 500 and 100 when the button is pressed.

Add button polling and replace the hardcoded delay

Button B1 is connected to pin PC13. CubeMX already configured this pin as a GPIO input when you initialized the board with defaults. When the button is pressed, the pin reads HIGH (GPIO_PIN_SET).

  • Scroll down to the while (1) loop inside main().
  • Find these two existing lines:
    HAL_GPIO_TogglePin(GPIOJ, GPIO_PIN_2);
    HAL_Delay(500);
  • Replace those two lines with the following code, which adds a button check before the toggle and uses your new variable for the delay:
    /* Check if user button B1 is pressed (active HIGH on PC13) */
    if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET)
    {
      /* Toggle between slow (500 ms) and fast (100 ms) blink */
      blink_delay = (blink_delay == 500) ? 100 : 500;
    }

    /* Toggle LED and wait for the current blink interval */
    HAL_GPIO_TogglePin(GPIOJ, GPIO_PIN_2);
    HAL_Delay(blink_delay);

What does this code do?

  • HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) reads the current state of pin PC13 (button B1). It returns GPIO_PIN_SET when pressed.
  • The ternary expression (blink_delay == 500) ? 100 : 500 flips the delay between the two speeds each time a press is detected.
  • HAL_Delay(blink_delay) now uses the variable instead of a hardcoded value, so the speed changes take effect immediately.
  • Save main.c with Ctrl+S.
Build, flash, and discover missed presses

Before you run this, think about what you expect to happen. If you tap the blue button quickly, do you think every single press will register?

  • Build the project by clicking Project in the menu bar, then Build Project.
  • Confirm 0 errors in the console output at the bottom of STM32CubeIDE.
  • Flash the firmware by clicking Run in the menu bar, then Debug. Once the debugger halts, press F8 to resume execution.
  • Try pressing the blue button (B1) with quick, short taps.

You will notice that most quick taps are completely ignored. The blink rate only changes if you happen to hold the button down for a long time. You might need to hold it for up to 500 ms before the press registers.

Why are presses missed?

The CPU spends almost all its time stuck inside HAL_Delay(blink_delay). During those 500 ms, no other code runs. Your HAL_GPIO_ReadPin check only executes for a tiny instant between delays.

If you press and release the button while the CPU is blocked in the delay, the press is completely invisible to your code. This is the fundamental problem with blocking delays in embedded systems.

LED not blinking at all after flashing?

  • Check that your code is inside the while (1) loop, between the USER CODE BEGIN WHILE and USER CODE END WHILE comments. Code placed outside these markers gets overwritten by CubeMX.
  • Make sure you pressed F8 (Resume) after the debugger halted. Without resuming, the program stays paused at the first breakpoint.
  • Verify the build showed 0 errors. A warning about an unused variable is fine, but errors mean the code did not compile.

Help me figure out why my LED stopped blinking after adding button code.

Understand why by reading GPIOx_IDR

You have seen the problem. Now open the reference manual to understand the hardware reason behind it.

  • Open the STM32H750 reference manual (RM0433) and navigate to Chapter 11: General-purpose I/Os (GPIO).
  • Find the GPIO port input data register (GPIOx_IDR) description.

What is GPIOx_IDR?

GPIOx_IDR is the register that HAL_GPIO_ReadPin reads under the hood. Each bit corresponds to one pin. Bit 13 of GPIOC_IDR holds the current electrical state of PC13 (your button).

The critical detail: IDR reflects the pin state only at the exact instant you read it. It does not store or remember past states. If your code cannot reach the read instruction because it is stuck in a delay, any button press during that window is permanently lost.

This is the core limitation. Your code can only see the button when it actually executes the ReadPin call. With a 500 ms blocking delay, you get one chance every half-second to notice a press.

✔️ Awesome, I've got everything!

Great. Double check that you have saved main.c and your LED is still blinking on the board.

ⓧ I'd like to double check the full code

Here is the complete user code in main.c at this point. Compare it against yours:

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* USER CODE BEGIN PV */
uint32_t blink_delay = 500;
/* USER CODE END PV */

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/* USER CODE BEGIN WHILE */
  while (1)
  {
    if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET)
    {
      blink_delay = (blink_delay == 500) ? 100 : 500;
    }
    HAL_GPIO_TogglePin(GPIOJ, GPIO_PIN_2);
    HAL_Delay(blink_delay);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

You now have direct evidence that blocking delays make your system unresponsive to inputs. In the next step, you will replace HAL_Delay with a non-blocking timing pattern that keeps the loop spinning fast, so every button press is detected instantly.

Fix with Non-Blocking Timing

In the last step, you proved that HAL_Delay makes your CPU blind to button presses. The fix is the single most important pattern in professional embedded firmware.

Instead of blocking the CPU for hundreds of milliseconds, you check elapsed time on every loop iteration using HAL_GetTick(). The loop spins thousands of times per second, checking both the LED timing and the button state on every pass. Nothing is ever missed.

In this step, get ready to:
  • Replace the blocking HAL_Delay with a non-blocking timing pattern using HAL_GetTick().
  • Add software debouncing so quick button taps register reliably every time.
  • Read the SysTick section of the reference manual to understand what drives the tick counter.
Replace HAL_Delay with the non-blocking LED blink

The non-blocking pattern works by remembering when the LED last toggled and comparing that to the current tick count. If enough time has passed, toggle. If not, skip and keep looping.

  • In Core/Src/main.c, find the /* USER CODE BEGIN PV */ section.
  • Find this existing code:
/* USER CODE BEGIN PV */
uint32_t blink_delay = 500;
/* USER CODE END PV */
  • Add uint32_t last_toggle = 0; on the line after blink_delay, so the section looks like this:
/* USER CODE BEGIN PV */
uint32_t blink_delay = 500;
uint32_t last_toggle = 0;
/* USER CODE END PV */
  • Now scroll down to the while (1) loop. Delete everything between /* USER CODE BEGIN WHILE */ and /* USER CODE END WHILE */ (the button check, toggle, and HAL_Delay lines).
  • Replace the deleted code with this non-blocking LED blink pattern:
    /* Non-blocking LED blink */
    if (HAL_GetTick() - last_toggle >= blink_delay)
    {
      HAL_GPIO_TogglePin(GPIOJ, GPIO_PIN_2);
      last_toggle = HAL_GetTick();
    }

What does this code do?

HAL_GetTick() returns the number of milliseconds since the board booted. Subtracting last_toggle from the current tick gives you elapsed time since the last LED toggle.

When the elapsed time reaches blink_delay (500 ms), the LED toggles and last_toggle resets. The key difference from HAL_Delay: the CPU never stops. It just checks and moves on.

  • Save main.c.
  • Build the project (Project > Build Project) and flash it (Run > Debug, then click the Resume button to let the code run).
  • The green LED (LD7) should blink at a steady 500 ms rhythm, just like before.

LED not blinking?

  • Make sure you deleted the old HAL_Delay and HAL_GPIO_TogglePin lines completely. If both the old and new toggle code exist, the LED may appear always-on.
  • Check that last_toggle is declared in the PV section (before main()), not inside the while loop. Declaring it inside the loop resets it to 0 on every iteration.
  • Confirm you clicked Resume after flashing. The debugger halts at the first line of main() by default.

Having trouble with your build? Help me fix a build error in my non-blocking LED code

Add button debouncing

The LED blinks, but pressing B1 does nothing right now because you removed the button code. You need to add it back, but this time with a debounce guard that prevents false triggers from electrical noise.

  • Return to the /* USER CODE BEGIN PV */ section. Add uint32_t last_press = 0; on the line after last_toggle, so the full section reads:
/* USER CODE BEGIN PV */
uint32_t blink_delay = 500;
uint32_t last_toggle = 0;
uint32_t last_press = 0;
/* USER CODE END PV */
  • Scroll back to the while (1) loop. Add the debounced button check directly below the LED blink block (after the closing } of the if (HAL_GetTick() - last_toggle ...) block):
    /* Button polling with debounce */
    if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET)
    {
      if (HAL_GetTick() - last_press >= 200)
      {
        blink_delay = (blink_delay == 500) ? 100 : 500;
        last_press = HAL_GetTick();
      }
    }

What does the debounce do?

When you press a physical button, the metal contacts bounce rapidly for a few milliseconds, generating multiple electrical transitions. Without debouncing, your code could read 3-5 "presses" from a single tap.

The 200 ms guard (HAL_GetTick() - last_press >= 200) ensures the code ignores any reads within 200 ms of the last accepted press. This is long enough to filter bounce but short enough to feel instant to your finger.

  • Save main.c.
  • Build and flash the project.

Before you run the code, predict: will quick button taps register this time, or will you still miss presses like before?

  • Click Resume to run the firmware.
  • Tap the blue button (B1) quickly several times. Every single tap should toggle the blink rate between slow (500 ms) and fast (100 ms) instantly.

Every press registers now. The loop spins thousands of times per second, checking the button on every pass, so there is no window where a press can be missed.

Button presses still unreliable?

  • Confirm you are checking GPIO_PIN_SET (not GPIO_PIN_RESET). The B1 button on the STM32H750B-DK is active HIGH.
  • Make sure last_press is declared as a global in the PV section, not inside the loop.
  • Verify the debounce guard uses 200 (milliseconds), not 200000. A value too large makes the button feel unresponsive.

Still stuck? Help me debug my button debounce code

✔️ Awesome, I've got everything!

Great. Double check you saved main.c and that your board is responding to every button tap.

ⓧ I'd like to double check the full code

Confirm the following sections in your main.c match what you built above, in order:

  • The /* USER CODE BEGIN PV */ section declares three variables: blink_delay, last_toggle, and last_press.
  • The while (1) loop contains the non-blocking LED blink chunk (checking HAL_GetTick() - last_toggle >= blink_delay).
  • Immediately after, the button polling with debounce chunk (checking HAL_GPIO_ReadPin and HAL_GetTick() - last_press >= 200).
  • No HAL_Delay call exists anywhere in the loop.
Understand SysTick in the reference manual

Your non-blocking pattern depends on HAL_GetTick() returning an accurate millisecond count. But where does that count come from?

  • Open the STM32H750 reference manual (RM0433) and navigate to the SysTick section.
  • Find the description of the SysTick timer. This is an ARM Cortex-M7 core timer that generates a periodic interrupt.

What is SysTick?

SysTick is a 24-bit countdown timer built into every ARM Cortex-M core. CubeMX configures it to fire an interrupt every 1 ms.

Each time SysTick_Handler fires, the HAL increments a global variable called uwTick. When you call HAL_GetTick(), it simply returns the current value of uwTick. This is why your non-blocking pattern works without you configuring any timer manually.

The unsigned subtraction (HAL_GetTick() - last_toggle) also handles the rare case where uwTick wraps around after approximately 49.7 days of uptime. Unsigned arithmetic in C handles this correctly.

Why not just use a hardware timer?

You could configure TIM2 or TIM6 as a dedicated blink timer, but SysTick is already running and costs nothing extra. For simple timing comparisons, HAL_GetTick() is the professional default. Reserve dedicated hardware timers for PWM, input capture, or microsecond-precision tasks.

You now have the foundational timing pattern that every professional embedded system uses. Your main loop spins freely, checking inputs and managing outputs without ever blocking. Next up, you will take this further in the Secret Mission by replacing button polling entirely with hardware interrupts.

Secret mission

Hardware Interrupts with EXTI

Your button detection works, but the main loop still has to spin and check the pin every cycle. What if the hardware could detect the press for you and call your code automatically? In this secret mission, you will replace polling with a hardware EXTI interrupt so the CPU is notified the instant B1 is pressed.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely on local hardware with free software, so there are no ongoing costs.

Resources you used:

  • GPIO_Basics project files in your STM32CubeIDE workspace.
  • Firmware flashed to the STM32H750B-DK board.

Keep everything running

No action needed. Choose this if you plan to keep building on this project or want it as a reference for future STM32 work.

  • Your GPIO_Basics project stays in your STM32CubeIDE workspace, ready to open any time.
  • The firmware remains on your STM32H750B-DK board. It runs whenever the board is powered via USB.
  • Unplug the USB cable when you are not using the board to avoid unnecessary wear.

Pause - I'll come back to this later

Shut down running processes and disconnect the board to free up your desk and USB port.

  • Terminate any active debug session in STM32CubeIDE (click the red square stop button in the toolbar).
  • Unplug the USB Micro-B cable from the board. The firmware stays in flash memory and will run again next time you plug it in.
  • Close STM32CubeIDE. Your project is saved automatically.

Delete - I don't want to use this again

Remove all project files and start fresh. Your board hardware is unaffected.

  • In STM32CubeIDE, right-click the GPIO_Basics project in the Project Explorer and select Delete. Check the box to delete project contents on disk, then click OK.
  • Alternatively, delete the project folder directly from your filesystem. Find your STM32CubeIDE workspace folder and remove the GPIO_Basics directory.
  • The firmware on your board remains in flash. It does nothing harmful. The next time you flash a new project, it will be overwritten automatically.

Nice Work!

Nice Work!

You just built a responsive embedded system on real hardware. Your STM32H750B-DK Discovery board now runs firmware that blinks an LED and responds to button presses instantly, using the same non-blocking timing pattern that professional embedded engineers rely on every day.

You learned how to:

  • Built and flashed STM32 firmware from scratch using STM32CubeMX and STM32CubeIDE, controlling the onboard LED (LD7, PJ2) through GPIO output registers.
  • Implemented the professional non-blocking timing pattern using HAL_GetTick() with software debouncing, replacing blocking delays so button presses are never missed.
  • Navigated the STM32H750 reference manual (RM0433) across GPIO, SysTick, and EXTI chapters to understand what HAL configures under the hood.
  • Secret Mission: Configured EXTI hardware interrupts to detect button presses without polling, using HAL_GPIO_EXTI_Callback driven by the CPU's interrupt hardware.

Ready to quiz yourself?