Skip to main content

Boot & HAL

Memory Map

Flash (0x0000_0000, 64 KB):
[0x0000_0000 - 0x0000_003F] .vector_table (64 bytes)
[0x0000_0040 - 0x0000_EAF8] .text + .rodata (60,261 bytes)
[0x0000_EAF8 - 0x0000_F364] .data (LMA copy) (2,768 bytes)
Remaining: ~2.5 KB free

RAM (0x2000_0000, 8 KB):
[0x2000_0000 - 0x2000_0ACE] .data + .bss (2,776 bytes)
[0x2000_0ACE - 0x2000_1A00] Unallocated gap (3,880 bytes)
[0x2000_1A00 - 0x2000_2000] .stack (1,536 bytes, grows down)
[0x2000_2000] Top of SRAM (initial SP)

Stack reserved: 1,536 B (1.5 KB). Stack grows downward from 0x2000_2000.

Boot Sequence (Layer 3)

The reset handler in boot.rs performs three steps: zero .bss via ptr::write_bytes, copy .data from Flash LMA to RAM VMA via ptr::copy_nonoverlapping (zero bytes in the current build — no initialised data section), then jump to crate::start().

The vector table occupies 64 bytes at Flash base — initial stack pointer, Reset vector, and 14 exception handlers. Any unhandled exception spins in DefaultHandler. There is no bootloader; firmware is loaded one-shot through JTAG/SWD only. The linker script (link.x) defines FLASH (0x0, 64K), RAM (0x20000000, 8K), and _stack_size = 3K.

HAL Layer (Layer 4)

The HAL is the only crate permitted to touch hardware registers. All unsafe MMIO is confined to mmio.rs, which exposes read_register, write_register, and set_register_bits — thin wrappers around volatile pointer reads and writes.

ModuleRegisters / BaseProtocolPins
uart.rsUART0 (0x4000_C000)8N1, 115200 baud, polling TX/RXPA0=RX, PA1=TX
i2c.rsI2C0 (0x4002_0000)Master, 100 kHz, send_byte/send_bytesPB2=SCL, PB3=SDA
gpio.rs6 ports (A-F)AFSEL, DIR, DEN, ODR registersAlt func config
clock.rsRCC registers12 MHz internal oscillator (QEMU), RCGC gating
oled.rsSSD0303 (0x3D)I2C, 5-command init, render, clear, set_pixelI2C0

uart.rs implements the numcore::hal::Uart trait (putchar, getchar, poll_byte, transmit_bytes) by polling the FIFO status registers. Baud rate: divisor = 12 MHz / (16 ×\times 115200) \approx 6.51; IBRD = 6, FBRD = 33.

i2c.rs provides master-mode sends to the OLED, handling START/ACK/STOP protocol in software. Timer period: TPR = (12 MHz / (20 ×\times 100 kHz)) - 1 = 5.

gpio.rs holds port base addresses and configures alternate functions, direction, digital enable, and open-drain mode.

clock.rs runs at 12 MHz (internal oscillator; no PLL configured). Gates peripheral clocks via the RCGC registers and provides a spin-loop delay.

oled.rs drives an SSD0303 display at address 0x3D over I2C: sends the 5-command init sequence (DISPLAY_OFF, START_LINE_0, SEG_REMAP_NORMAL, NORMAL_DISPLAY, DISPLAY_ON), uploads a 192-byte framebuffer on render, blanks the screen on clear, and sets individual pixels. The visible column offset is 36 (accounting for the SSD0303 controller RAM layout).

Uart Trait

pub trait Uart {
fn init();
fn transmit_bytes(bytes: &[u8]);
fn transmit_byte(byte: u8);
fn poll_byte() -> Option<u8>;
}

Display Trait

pub trait Display {
type Buffer: AsMut<[u8]> + AsRef<[u8]>;
const WIDTH: usize;
const HEIGHT: usize;
fn init();
fn new_buffer() -> Self::Buffer;
fn render(fb: &Self::Buffer);
fn set_pixel(fb: &mut Self::Buffer, col: usize, row: usize, on: bool);
}

Porting Checklist

To port NumCore to a new MCU:

  1. Create hal-<mcu>/ implementing Uart and Display.
  2. Create numcore-<mcu>/ with Cargo.toml, main.rs, boot.rs, link.x.
  3. Add target-specific rustflags in .cargo/config.toml.
  4. Add the new crate to the workspace Cargo.toml and the Makefile.

No changes to the numcore/ core crate are required.

The WASM simulator (hal-wasm/) provides a browser-based Uart + Display implementation that runs the unmodified numcore crate through wasm32-unknown-unknown. It can be used as a reference for porting the HAL traits to new environments.