Bare-Metal C Course using SDCCRationale: The preprocessor is a text substitution tool. Strict syntax prevents precedence errors and side effects.
( ).#define OFFSET 0x10U + 0x02U#define OFFSET (0x10U + 0x02U)#define BASE_ADDR ((volatile uint8_t *) 0x8000U)U” Suffix Usage.
U for Bit-level Data: Hardware addresses, bit masks, and patterns must use the U suffix (Unsigned 16-bit) to prevent sign-extension bugs.
0x8000U, 0xFFU, (1U << 15)i < 10, delay(5000)static inline functions instead to avoid side-effect bugs.#define ADDR_OFFSET(x) (BASE + x)static inline uint8_t* get_addr(uint8_t x) { return BASE + x; }Rationale: Z80 int size varies by compiler. Explicit types prevent ambiguity. The C99 standard does not specify whether char by itself should be signed or unsigned; avoid using char as well.
<stdint.h> types.
int, short, or long, and char.char: Use uint8_t instead of char. It is guaranteed to be 8-bit unsigned.uint16_t (Z80 address bus is 16-bit).uint8_t for 8-bit values and uint16_t for 16-bit values.int8_t or int16_t only when dealing with signed data (2’s complement number), whether for calculation or reading from IO address.bool from <stdbool.h>.
true / false for logic flags.Rationale: “Writing to IO” is simply writing to a specific memory address.
volatile keyword is mandatory.
volatile so the compiler does not optimize away the access.#define NAME ((volatile type *) addressU)#define LED_PORT ((volatile uint8_t *) 0x4000U)
*LED_PORT = 0xFFU;
Rationale: Memory is finite (SRAM) and there is no OS.
malloc(), calloc(), free().const for Read-Only Data.
const to save RAM.Rationale: The Z80 stack is small and easy to overflow.
main() must end with while(true).Function prototypes must include argument names alongside data types. This clarifies the purpose of each argument (Self-Documentation) and enables better IDE support.
void lcd_set_cursor(uint8_t, uint8_t);void lcd_set_cursor(uint8_t row, uint8_t col);default clause in a switch statement must always end with a break;. This prevents accidental fall-through if new cases are added later (Defensive Coding).default: err = 1; }default: err = 1; break; }Rationale: Hardware control requires changing specific bits without affecting others.
(1U << 3). Avoid bit-fields.*PORT = *PORT | (1U << 3);*PORT = *PORT & (~(1U << 3));Rationale: SDCC optimizations are tailored for 8-bit systems.
for loops: for (uint8_t i = 0; ...)static or global to avoid stack overflow.__asm__, except for __asm__("nop");.Rationale: Explicit operations prevent ambiguity and ensure the read-modify-write cycle is visible.
+=, -=, &=, <<=, ++, or --. The long form is clearer.d++; 2. m <<= 2; 3. k ^= 0x70U;d = d + 1; 2. m = m << 2; 3. k = k ^ 0x70U;/*
* Hardware: Z80 simulated with memory mapped LED at 0x8000
* Goal: Blink LED
*/
#include <stdint.h>
#include <stdbool.h>
// Rule 3.2: Defined as macro with cast.
// Rule 1.2: Address is a bit-pattern, so use U suffix.
#define IO_LED ((volatile uint8_t *) 0x8000U)
void delay_loops(uint16_t count) {
while (count > 0) {
// Rule 8.1: Explicit subtraction (no count--)
count = count - 1;
// Rule 7.3: NOP for delay
__asm__("nop");
}
}
void main(void) {
uint8_t pattern = 0x01U;
// Rule 5.2: Infinite Super Loop
while (true) {
// 1. Write to Data Bus
*IO_LED = pattern;
// 2. Wait
// Rule 1.2: '5000' is a count/scalar, so NO U suffix.
delay_loops(5000);
// 3. Logic: Shift pattern left
if (pattern == 0x80U) {
pattern = 0x01U;
} else {
// Rule 8.1: Explicit shift
pattern = pattern << 1;
}
}
}
C99 Style GuideRead the C99 Style Guide next.