23. HAL Abstraction · Module Separation · Portability
If register code (GPIOC->BRR=...) is scattered through app logic, switching chips means rewriting everywhere. A HAL (Hardware Abstraction Layer) splits "what to do" (turn on the LED) from "how" (write a specific register). We run the same app.c against three HAL implementations — PC (printf), STM32 (registers), and Arduino Uno (AVR ATmega328P) — and see the app code stay unchanged even as the architecture switches from ARM to AVR.
What you'll learn
- 1Explain why to split firmware into app/HAL/hardware layers
- 2Separate interface (header) from implementation (source)
- 3Port the same app across multiple HAL implementations
- 4Keep the dependency direction (app→HAL, never reverse)
- 5Unit-test app logic with a fake PC HAL
Introduction
When you switch from STM32 to 8051, or want to test on PC, register code embedded in the app forces you to fix everything. A HAL solves this by splitting the app from the implementation.
Key concepts
1) Layering and dependency direction
app (app.c) "control LED per pattern" — knows no hardware
↓ (calls)
HAL interface (hal.h) contract like "hal_led_set(on)"
↓
HAL implementation hal_pc.c / hal_stm32.c / hal_arduino.c
↓
hardware PC stdout / STM32 GPIO / AVR GPIODependency direction: app → HAL allowed, HAL → app forbidden. If the lower layer knows the upper, separation breaks and you can't reuse it.
2) Portability — swap only the implementation
PC test: pc_test.c + app.c + hal_pc.c (printf)
STM32: main.c + app.c + hal_stm32.c (ARM, PC13)
Uno: main + app.c + hal_arduino.c (AVR, PB5/D13)
/* app.c is byte-for-byte identical in all three */3) AVR (Uno) register mapping
| Action | AVR register | STM32 equivalent |
|---|---|---|
| as output | DDRB |= (1<<5) | GPIOC->CRH mode |
| High (on) | PORTB |= (1<<5) | GPIOC->BSRR/BRR |
| Low (off) | PORTB &= ~(1<<5) | GPIOC->BSRR/BRR |
DDRx(direction)·PORTx(output)·PINx(input) are AVR GPIO's three registers. Using them directly instead of digitalWrite keeps the direct-register philosophy on AVR too. (The Uno is AVR, so it can't build in Keil — use avr-gcc/Arduino IDE.)
Core example
/* hal.h — contract (the app depends only on this) */
void hal_led_init(void);
void hal_led_set(uint8_t on); /* 1=on, 0=off */
/* app.c — hardware-independent. Not one line of register code */
void app_blink_pattern(const uint8_t *p, uint16_t len) {
uint16_t i; hal_led_init();
for (i = 0; i < len; i++) hal_led_set(p[i] ? 1u : 0u);
}/* the same hal.h implemented three ways */
void hal_led_set(uint8_t on){ printf("LED %s\n", on?"ON":"OFF"); } /* PC */
void hal_led_set(uint8_t on){ if(on)GPIOC->BRR=(1u<<13);else GPIOC->BSRR=(1u<<13);} /* STM32 */
void hal_led_set(uint8_t on){ if(on)PORTB|=(1u<<5);else PORTB&=~(1u<<5);} /* Uno AVR */Verify: gcc pc_test.c app.c hal_pc.c -o pc_test → prints LED ON/OFF per pattern. In Keil, swap hal_pc.c for hal_stm32.c and the same app.c drives PC13.
Common mistakes
Q. I made a HAL but the app still has register code.
A. All register access must move into the HAL implementation. One line of GPIOC->... in app.c breaks portability.
Q. Linking several implementations together errors out.
A. The same functions get defined multiple times. Link exactly one (hal_pc.c for PC, hal_stm32.c for STM32, hal_arduino.c for Uno).
Q. Too much abstraction made it slow and complex.
A. Abstraction isn't free. Put a HAL only at boundaries that change often or need porting·testing, and don't over-layer (balance).
Summary
- Splitting firmware into app/HAL/hardware boosts portability and testability
- Header is the contract (interface), source the implementation — app depends only on the header
- Swap only the HAL implementation when changing chips; keep the app
- The same app.c runs on PC·STM32(ARM)·Uno(AVR)
- Dependency direction is one-way: app→HAL (never reverse)
Exercises
- Add hal_button_read() to hal.h and implement it three ways (PC fake input·STM32 IDR·AVR PINx)
- Audit that app.c has no register code at all
- Explain which file to change to add a second LED
All lecture materials and example code (with homework and answers) are openly available on GitHub.
View on GitHub ↗