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β
| LED | Pin | Connection |
|---|---|---|
| Green | PD12 | GPIOD bit 12 |
| Orange | PD13 | GPIOD bit 13 |
| Red | PD14 | GPIOD bit 14 |
| Blue | PD15 | GPIOD bit 15 |
Demo: Sequential LED Blinkingβ

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β
| Aspect | Lab 1 | Lab 1.1 |
|---|---|---|
| Lines of Code | 30+ | 15+ |
| Readability | Low | High |
| Maintainability | Hard | Easy |
| Reusability | No | Yes |
| Bug Proneness | High | Low |
| Extensibility | Difficult | Trivial |
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β
| Mistake | Effect | Fix |
|---|---|---|
| Forgot to clear MODER bits | Pin not configured as output | Use &= ~(3 << ...) first |
| Function doesn't match usage | Compilation or runtime errors | Check function signatures |
| Wrong bit positions | Controlling different pins | Verify bit calculations |
| Missing initialization loop | LEDs behave unpredictably | Configure 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