03. UART "Hello World" - First Output from Our OS¶
Now that our kernel boots, let's add UART (serial communication) to see "Hello World!" on the screen.
What is UART?¶
UART (Universal Asynchronous Receiver/Transmitter) is a serial communication protocol. On the Raspberry Pi 4B, we use the Mini UART (UART1) which is mapped to GPIO pins 14 (TX) and 15 (RX).
Hardware Setup¶
Connecting a USB-to-TTL Cable¶
| USB-TTL Cable | Raspberry Pi GPIO |
|---|---|
| GND (Black) | Pin 6 (GND) |
| RX (White) | Pin 8 (GPIO 14, TX) |
| TX (Green) | Pin 10 (GPIO 15, RX) |
Don't Connect 5V!
The Raspberry Pi GPIO operates at 3.3V. Connecting 5V can damage the board.
Opening a Serial Terminal¶
Use PuTTY or Tera Term: - Port: COM3 (or whatever shows up) - Baud: 115200
Understanding Memory-Mapped I/O (MMIO)¶
How does our code talk to the hardware? In Bare Metal, we use something called Memory-Mapped I/O.
The "Magic Mailbox" Analogy¶
Imagine a huge wall of mailboxes (Memory/RAM). - Most mailboxes are for storing letters (Data). - But some special mailboxes at the top are connected to hardware devices.
If you put a letter in the "Printer" mailbox, the printer starts printing. You don't "send" data; you just write to a specific memory address, and the hardware reacts.
graph LR
CPU[CPU]
subgraph "Memory Space"
RAM["RAM (0x0 - 0xFDFFFFFF)"]
MMIO["MMIO (0xFE000000+)"]
end
subgraph "Hardware"
UART["UART Controller"]
GPIO["GPIO Controller"]
end
CPU -->|Load/Store| RAM
CPU -->|Load/Store| MMIO
MMIO -->|Control| UART
MMIO -->|Control| GPIO
Raspberry Pi 4B Memory Map¶
| Address Range | Description |
|---|---|
0x00000000 - 0xFDFFFFFF |
RAM (System Memory) |
0xFE000000 - 0xFFFFFFFF |
MMIO (Peripherals) |
Pi 3 vs Pi 4 MMIO Base
- Raspberry Pi 3: MMIO starts at
0x3F000000 - Raspberry Pi 4: MMIO starts at
0xFE000000(This is why we need different drivers!)
Implementing Mini UART¶
We will implement a driver for the Mini UART (UART1), which is easier to set up than the PL011 UART.
Initialization Steps¶
- Map MMIO: Define the base address (
0xFE000000). - GPIO Setup: Configure GPIO pins 14 (TX) and 15 (RX).
- UART Setup: Enable UART, set baud rate (115200), and 8-bit mode.
File: drivers/uart.c
UART Register Map¶
Implementing the UART Driver¶
File: drivers/uart.c
GPIO Configuration (The Switchboard)¶
The Raspberry Pi's pins (GPIO 14 & 15) can do many things. By default, they might be just simple inputs. We need to tell the GPIO Controller to connect these pins to the UART module.
This is called setting the Alternate Function.
graph TD
Pin["Pin 14 (Physical)"]
Switch{"Function Switch"}
GPIO_In["Input Mode"]
GPIO_Out["Output Mode"]
UART_TX["UART TX (Alt5)"]
Pin --- Switch
Switch -->|Default| GPIO_In
Switch -->|Alt5| UART_TX
First, we configure GPIO pins 14 and 15 to use "Alt Function 5" (UART mode):
Sending Characters¶
The Main Function¶
File: kernel/main.c
Simplified Code
The code above is a simplified version to demonstrate the core concept. The actual kernel/main.c in the repository includes additional features like a shell, command processing, and support for other hardware (which we'll add in later chapters).
Building and Testing¶
1. Build the Kernel¶
Verify kernel8.img was created.
2. Prepare the SD Card¶
Copy these files to the SD card:
- bootcode.bin
- start4.elf
- fixup4.dat
- kernel8.img (your kernel)
3. Connect Serial Cable¶
Connect the USB-to-TTL cable as described above.
4. Power On¶
- Insert the SD card
- Open the serial terminal (
screen /dev/ttyUSB0 115200) - Power on the Raspberry Pi
You should see:
5. Test Echo¶
Type any key in the terminal. It should echo back!
Troubleshooting¶
Mini UART Baud Rate Calculation
The Mini UART (UART1) baud rate depends on the GPU core frequency, not a fixed clock. The formula is:
For Raspberry Pi 4 at 500MHz core frequency:
Therefore: AUX_MU_BAUD = 541
Critical Requirements:
- Your config.txt must include core_freq_min=500 to lock the GPU core frequency
- Without this, dynamic frequency scaling will cause garbled output even if your code is correct
- See Article 02 for complete config.txt configuration
If changing baud rate or core frequency:
- For 9600 baud @ 500MHz: AUX_MU_BAUD = 6509
- For 115200 @ 250MHz: AUX_MU_BAUD = 270
- Always recalculate using the formula above
| Problem | Solution |
|---|---|
| No output | Check wiring (TX/RX might be swapped) |
| Garbled characters | Verify config.txt has core_freq_min=500 and initial_turbo=0 (see warning above) |
| Wrong baud rate | Recalculate AUX_MU_BAUD using formula with actual core frequency |
| Garbled text | Wrong baud rate (should be 115200) |
| Raspberry Pi won't boot | Missing firmware files (start4.elf, etc.) |
Complete Source Code¶
The full implementation is available in the simpian-os repository:
boot/boot.S- Assembly entry pointdrivers/uart.c- UART driverkernel/main.c- Main kernel entryboot/linker.ld- Linker script
What's Next?¶
Congratulations! You've successfully: - ✅ Set up a bare-metal development environment - ✅ Written ARM64 assembly boot code - ✅ Implemented a UART driver - ✅ Printed "Hello World" from your own OS!
Future topics: - Exception handling and interrupts - Timers - Memory management (MMU) - Multitasking
Stay tuned for more articles!