Skip to main content

Task 1.1: Multiple LED Blink (Modular Functions)

Now that you understand GPIO basic operation, let's apply professional software engineering practices by refactoring the blinking code into modular, reusable functions.

Learning Objectives​

By the end of this task, you will:

  • 🎯 Implement reusable GPIO functions
  • 🎯 Understand function abstraction in embedded systems
  • 🎯 Control multiple GPIO pins with elegant code
  • 🎯 Apply DRY principle (Don't Repeat Yourself)
  • 🎯 Write clean, maintainable embedded code

Prerequisites​

  • βœ… Complete Lab 1 (understand GPIO basics)
  • βœ… Familiar with C functions
  • βœ… Understand bitwise operations

Theory: Modular Design​

Why Modular Code?​

Non-Modular (Lab 1):
β”œβ”€ 100+ lines of repetitive code
β”œβ”€ Hard to add a new LED
└─ Error-prone copy-paste

Modular (Lab 1.1):
β”œβ”€ Reusable functions
β”œβ”€ Change one function = affects all LEDs
└─ Scalable and maintainable

Function Abstraction Pattern​

// Instead of:
GPIOD_MODER &= ~(3 << (12*2));
GPIOD_MODER |= (1 << (12*2));

// Create a function:
void configure_output(int pin) {
GPIOD_MODER &= ~(3 << (pin*2));
GPIOD_MODER |= (1 << (pin*2));
}

// Usage:
configure_output(12);
configure_output(13);
configure_output(14);
configure_output(15); // Scales elegantly!

Hardware Reference​

LEDPinConnection
GreenPD12GPIOD bit 12
OrangePD13GPIOD bit 13
RedPD14GPIOD bit 14
BluePD15GPIOD bit 15

Demo: Sequential LED Blinking​

Multiple LED Blinking Animation

LEDs light up sequentially in a flowing pattern

The Code​

// ==================== REGISTER DEFINITIONS ====================
#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)

// ==================== FUNCTION DEFINITIONS ====================

/**
* Delay function - creates visible pause
* ~150000 iterations β‰ˆ 1 second
*/
void led_delay(void) {
for (volatile int i = 0; i < 150000; i++);
}

/**
* Configure a GPIO pin as output
*
* Parameter:
* - n: Pin number (12, 13, 14, 15)
*
* Operation:
* 1. Clear the 2-bit mode field for pin n
* 2. Set to 01 mode (General Purpose Output)
* 3. Initialize pin to LOW (OFF state)
*/
void configure_output(int n) {
// Clear the 2 bits for mode configuration
GPIOD_MODER &= ~(3 << (n * 2));

// Set to output mode (01)
GPIOD_MODER |= (1 << (n * 2));

// Initialize to LOW (LED off)
GPIOD_ODR &= ~(1 << n);
}

/**
* Turn on an LED (set pin HIGH)
*
* Parameter:
* - n: Pin number
*
* Side effect: Includes delay for visible effect
*/
void led_on(int n) {
GPIOD_ODR |= (1 << n);
led_delay();
}

/**
* Turn off an LED (clear pin to LOW)
*
* Parameter:
* - n: Pin number
*
* Side effect: Includes delay for visible effect
*/
void led_off(int n) {
GPIOD_ODR &= ~(1 << n);
led_delay();
}

/**
* Toggle an LED state (ON→OFF or OFF→ON)
*
* Parameter:
* - n: Pin number
*/
void led_toggle(int n) {
GPIOD_ODR ^= (1 << n);
}

// ==================== MAIN PROGRAM ====================

int main(void) {
// ========== SYSTEM INITIALIZATION ==========
// Enable clock for GPIOD port
RCC_AHB1ENR |= (1 << 3);

// ========== GPIO PIN CONFIGURATION ==========
// Configure all four LED pins as outputs
configure_output(12); // Green
configure_output(13); // Orange
configure_output(14); // Red
configure_output(15); // Blue

// ========== MAIN LOOP - Sequential Blinking ==========
while (1) {
// Turn on all LEDs
led_on(12);
led_on(13);
led_on(14);
led_on(15);

// Turn off all LEDs
led_off(12);
led_off(13);
led_off(14);
led_off(15);
}

return 0;
}

Execution Flow​

Start
↓
Enable GPIOD Clock
↓
Configure Pins 12-15 as Outputs (all initialized LOW)
↓
Loop:
β”œβ”€ led_on(12) β†’ PD12 HIGH, wait
β”œβ”€ led_on(13) β†’ PD13 HIGH, wait
β”œβ”€ led_on(14) β†’ PD14 HIGH, wait
β”œβ”€ led_on(15) β†’ PD15 HIGH, wait
β”œβ”€ led_off(12) β†’ PD12 LOW, wait
β”œβ”€ led_off(13) β†’ PD13 LOW, wait
β”œβ”€ led_off(14) β†’ PD14 LOW, wait
β”œβ”€ led_off(15) β†’ PD15 LOW, wait
└─ Repeat infinitely

Code Improvements Over Lab 1​

Before (Lab 1)​

// Repetitive, error-prone
GPIOD_MODER &= ~(3 << (12*2)); GPIOD_MODER |= (1 << (12*2));
GPIOD_MODER &= ~(3 << (13*2)); GPIOD_MODER |= (1 << (13*2));
GPIOD_MODER &= ~(3 << (14*2)); GPIOD_MODER |= (1 << (14*2));
GPIOD_MODER &= ~(3 << (15*2)); GPIOD_MODER |= (1 << (15*2));

After (Lab 1.1)​

// Clean, maintainable
configure_output(12);
configure_output(13);
configure_output(14);
configure_output(15);

Key Benefits​

AspectLab 1Lab 1.1
Lines of Code30+15+
ReadabilityLowHigh
MaintainabilityHardEasy
ReusabilityNoYes
Bug PronenessHighLow
ExtensibilityDifficultTrivial

Alternative: Knight Rider Pattern​

// Create a "cylon scanner" effect
void knight_rider(void) {
while (1) {
for (int i = 12; i <= 15; i++) {
GPIOD_ODR &= ~(0xF << 12); // Clear all
GPIOD_ODR |= (1 << i); // Turn on one
led_delay();
}

for (int i = 14; i >= 13; i--) {
GPIOD_ODR &= ~(0xF << 12); // Clear all
GPIOD_ODR |= (1 << i); // Turn on one
led_delay();
}
}
}

Common Mistakes​

MistakeEffectFix
Forgot to clear MODER bitsPin not configured as outputUse &= ~(3 << ...) first
Function doesn't match usageCompilation or runtime errorsCheck function signatures
Wrong bit positionsControlling different pinsVerify bit calculations
Missing initialization loopLEDs behave unpredictablyConfigure all pins before loop

Advantages of This Approach​

βœ… Scalability: Adding a 5th LED is one line: configure_output(16);

βœ… Maintainability: Bug fix in led_on() benefits all uses

βœ… Readability: Code reads like English: led_on(12); led_off(12);

βœ… Testing: Each function can be tested independently

βœ… Professional: This is how production embedded code is organized

Challenge Exercises​

Challenge 1: All-At-Once Control​

/**
* Create an all_leds_on() function
* Turns on all four LEDs simultaneously
* Hint: Use bit-OR to set multiple bits at once:
* GPIOD_ODR |= (0xF << 12);
*/
void all_leds_on(void) {
// YOUR CODE HERE
}

// Same for all_leds_off()

Challenge 2: Custom Patterns​

void pattern_pulse(void) {
// Each LED turns on for 0.5s then off, in sequence
// 12 ON β†’ OFF β†’ 13 ON β†’ OFF β†’ etc.
}

void pattern_chase(void) {
// LEDs light up like a chaser: 12β†’13β†’14β†’15β†’12β†’...
}

Challenge 3: Blinking Separately​

/**
* Make two LEDs blink at different rates:
* LED 12: 0.5s on, 0.5s off
* LED 15: 1s on, 1s off (slower)
* Hint: You'll need to structure the timing differently
* (This is still tricky without timersβ€”we'll solve this in Lab 8!)
*/

Expected Output​

Time:  0s                  1s                  2s
↓ ↓ ↓
Pin 12 [HIGH]........ β†’ [LOW]

Pin 13 [HIGH]........ β†’ [LOW]

Pin 14 [HIGH]........ β†’ [LOW]

Pin 15 [HIGH]........ β†’ [LOW]

Visual: Sequential lighting, then all-off, repeats

Best Practices Learned​

✨ Key Points:

  1. DRY Principle - Don't Repeat Yourself

    • Write once, use many times
  2. Function Abstraction - Hide complexity

    • Caller doesn't need to know register details
  3. Clear Naming - Code readability

    • led_on() is obvious; GPIOD_ODR |= is not
  4. Parameter-Driven - Generic functions

    • Same led_on() works for any pin
  5. Documentation - Comment the interface

    • Future you will thank present you

Next Steps​

πŸš€ Ready for Lab 2? You'll learn Bitwise operations with dual LED toggling and create interesting visual patterns!

Skills You'll Need for Lab 2​

  • βœ… Comfortable with functions
  • βœ… Understand XOR operation (^)
  • βœ… Can modify delays

Pro Tip: Save this function library! You'll reuse these functions throughout all future labs!