Skip to main content

Task 5.1: Binary Counter with Button

Enhance Lab 5 by adding button control. Each button press increments the counter and displays the new value on LEDs.

Learning Objectives

By the end of this task, you will:

  • 🎯 Combine button input with LED output
  • 🎯 Implement counter increment on button events
  • 🎯 Apply debouncing in event-driven code
  • 🎯 Manage state transitions in firmware
  • 🎯 Build interactive embedded applications

Prerequisites

  • ✅ Complete Lab 5 (binary counter)
  • ✅ Complete Lab 4 (software debouncing)
  • ✅ Understand button press detection

Hardware Required

ComponentDetails
MicrocontrollerSTM32F407VG
ButtonPA0
LEDsPD12-PD15 (counter display)

Theory: Event-Driven Updates

Instead of continuous counting, we now:

Loop forever:
if (button pressed before):
├─ Debounce press
├─ Confirm real press
├─ Increment counter
├─ Display new value
├─ Wait for release
├─ Debounce release
└─ Ready for next press

else:
└─ Display current counter value

Demo

Binary Counter with Button

Press button: counter increments and displays new binary value on LEDs

Complete Code

#define RCC_BASE 0x40023800UL
#define RCC_AHB1ENR *(volatile unsigned int*)(RCC_BASE + 0x30U)

#define GPIO_D_BASE 0x40020C00UL
#define GPIOD_MODER *(volatile unsigned int*)(GPIO_D_BASE + 0x00U)
#define GPIOD_ODR *(volatile unsigned int*)(GPIO_D_BASE + 0x14U)

#define GPIO_A_BASE 0x40020000UL
#define GPIOA_MODER *(volatile unsigned int*)(GPIO_A_BASE + 0x00U)
#define GPIOA_IDR *(volatile unsigned int*)(GPIO_A_BASE + 0x10U)

void led_delay(void) {
for (volatile int i = 0; i < 300000; i++);
}

void display_value(int value) {
// Clear LED bits
GPIOD_ODR &= ~(0xF << 12);
// Display value (wraps at 16)
GPIOD_ODR |= ((value & 0xF) << 12);
}

int main(void) {
// Enable clocks
RCC_AHB1ENR |= (1U << 0); // GPIOA
RCC_AHB1ENR |= (1U << 3); // GPIOD

// PA0 as input
GPIOA_MODER &= ~(3U << 0);

// PD12-PD15 as outputs
GPIOD_MODER &= ~(0xFF << 24);
GPIOD_MODER |= (0x55 << 24);

int counter = 1; // Start at 1
display_value(counter);

while (1) {
// Wait for button press
if (GPIOA_IDR & (1 << 0)) {
led_delay(); // Debounce press

// Confirm
if (GPIOA_IDR & (1 << 0)) {
counter++; // Increment
if (counter > 15) counter = 0; // Wrap around

display_value(counter);

// Wait for release
while (GPIOA_IDR & (1 << 0));
led_delay(); // Debounce release
}
}
}

return 0;
}

Algorithm

Initialize: counter = 1, display on LEDs

Loop forever:
if (button pressed):
Debounce
if (still pressed):
counter = (counter + 1) % 16
display_value(counter)
Wait for release
Debounce release
else:
Display current counter

Expected Output

Initial: LEDs show 0001 (decimal 1)

Button Press 1: LEDs show 0010 (decimal 2)
Button Press 2: LEDs show 0011 (decimal 3)
Button Press 3: LEDs show 0100 (decimal 4)
...
Button Press 15: LEDs show 0000 (wraps around)
Button Press 16: LEDs show 0001 (repeats)

Common Mistakes

IssueSolution
Counter wraps to 0 at 15, then 16Use modulo: if (counter > 15) counter = 0
Doesn't incrementCheck button if condition - maybe inverted
Fast multiple incrementsDebounce delay too short - increase to 300000
Wraps to wrong valueUse counter & 0xF to mask to 4 bits

Key Takeaways

Remember:

  1. Event-driven code responds to user input
  2. Debouncing both edges ensures reliable button control
  3. State variables (counter) persist across loop iterations
  4. Modulo operator (%) or bit masking wraps values
  5. Display updates only when counter changes

Challenge Exercises

Challenge 1: Decrement Mode

Add another button (PA1) to decrement counter instead of increment.

Challenge 2: Display Hex

Show hexadecimal letters (A-F) for values 10-15, not just binary.

Challenge 3: Counter Speed Control

Add button to change delay between increments (fast/slow modes).

Next Steps

🚀 Ready for Lab 6? Learn to generate sound with a buzzer using GPIO timing!

Prerequisites for Lab 6: GPIO output timing, Understanding frequency