05. Functions · Header Splitting · #define Macros
Firmware is split into modules — "the GPIO part", "the UART part", "the app logic". The tools are functions (units of action), header(.h)/source(.c) separation (interface/implementation), and the preprocessor (#include·#define·include guards). We extract bit logic into a ledbank module and let main use only its interface, covering include guards and the macro-parenthesis trap. This modular sense carries through lesson 23 (HAL abstraction).
What you'll learn
- 1Distinguish a function declaration (prototype) from its definition
- 2Split header (.h, interface) from source (.c, implementation) and build together
- 3Prevent double inclusion with include guards (#ifndef/#define/#endif)
- 4Tell apart constant vs function-like macros and avoid the parenthesis trap
- 5Branch by platform/option with conditional compilation (#ifdef)
Introduction
When code grows you can't pile it into one file. As the last basics lesson, we extract bit logic into a ledbank module (ledbank.h + ledbank.c) so main only sees the interface.
Key concepts
1) Functions / header vs source
| File | Role | Holds |
|---|---|---|
| ledbank.h | interface | #define·types·function declarations |
| ledbank.c | implementation | function definitions (bodies) |
| main.c | use | #include "ledbank.h" then call |
Build the .c files you use together: `gcc main.c ledbank.c -o main`. In μVision, add both .c files to the Source Group.
2) Include guard — prevent double inclusion
#ifndef LEDBANK_H /* if not yet defined */
#define LEDBANK_H /* mark defined and */
... header contents ...
#endif /* LEDBANK_H */ /* skip entirely the second time */#pragma once does the same but isn't guaranteed by all compilers, so the #ifndef guard is preferred for portability.
3) #define — constant vs function-like macro
#define LED_COUNT 8u /* constant macro */
#define BIT(n) (1u << (n)) /* function-like macro */
#define SQ(x) x*x /* bad: SQ(a+1) → a+1*a+1 */
#define SQ(x) ((x)*(x)) /* good: wrap args and the whole expr */Macros have no type checks and evaluate args multiple times (SQ(i++) is risky). For anything beyond simple constants/expressions, a static (or inline) function is safer.
4) Conditional compilation #ifdef
#ifdef USE_UART
uart_send(msg); /* included only in the UART build */
#endifCore example
ledbank.h holds the interface (declarations), ledbank.c the implementation (definitions), and main expresses intent with function names only.
/* ledbank.h — interface */
#ifndef LEDBANK_H
#define LEDBANK_H
#include <stdint.h>
#define BIT(n) (1u << (n))
uint8_t led_set(uint8_t bank, uint8_t pos);
uint8_t led_toggle(uint8_t bank, uint8_t pos);
uint8_t led_count_on(uint8_t bank);
#endif /* LEDBANK_H *//* ledbank.c — implementation */
#include "ledbank.h"
uint8_t led_set(uint8_t b, uint8_t p) { return (uint8_t)(b | BIT(p)); }
uint8_t led_toggle(uint8_t b, uint8_t p) { return (uint8_t)(b ^ BIT(p)); }
uint8_t led_count_on(uint8_t b) {
uint8_t pos, n = 0;
for (pos = 0; pos < 8u; pos++) n = (uint8_t)(n + ((b >> pos) & 1u));
return n;
}gcc main.c ledbank.c -o main && ./main # build both .c togetherCommon mistakes
Q. I get multiple definition / redefinition errors.
A. Putting a function definition (body) in a header and including it from several .c files creates multiple definitions. Put declarations in the header, definitions in .c, and don't drop the include guard.
Q. I get undefined reference to led_set at link time.
A. You didn't build ledbank.c too. The header only declares; compile/link the .c with the implementation: gcc main.c ledbank.c.
Q. #define SQ(x) x*x gives weird values.
A. Missing parentheses. SQ(a+1) expands to a+1*a+1. Wrap args and the whole expression: #define SQ(x) ((x)*(x)). For side-effecting args, use a function.
Summary
- Split actions into functions; declarations in headers (.h), definitions in sources (.c)
- Build the .c files you use together to link
- Prevent double inclusion with include guards (#ifndef/#define/#endif)
- Wrap function-like macro args and expressions in parentheses — use a function when complex
- Pick platform/option code with #ifdef
Exercises
- Add led_clear() to the ledbank module and call it from main
- Remove the include guard and #include the header twice to reproduce the error, then fix with the guard
- Write a MIN(a,b) function-like macro following the parenthesis rule and explain why MIN(x++, y) is risky
All lecture materials and example code (with homework and answers) are openly available on GitHub.
View on GitHub ↗