My Journey Into Bare Metal Programming on Raspberry Pi
Introduction
I’m a Java developer who usually works with high-level languages like Java and Python. For my hobby projects, I’ve used boards like the Raspberry Pi 3, 4, 5, ESP8266, and Arduino. These boards are easy to work with, especially when you have an operating system or pre-built libraries to handle the low-level details.
One day, I started thinking about how the Raspberry Pi boots up and what happens before the Linux kernel starts. I wondered, “Can I run the Raspberry Pi without using Raspbian OS?” This curiosity led me to the idea of writing my own simple OS — not a complete OS, but something basic that could print “Hello, World” on the UART or blink an LED.
That’s how I got introduced to the concept of bare-metal programming. Unlike normal development, where you have an operating system to manage the hardware, bare-metal programming runs the code directly on the hardware without any OS. It gives full control of the hardware, but it also means you have to manage everything yourself. This sounded like an exciting challenge, and I decided to give it a try.
The First Attempt
My first goal was to blink an LED using the Raspberry Pi. I started by learning how the Raspberry Pi’s boot process works. I wrote a simple C program to blink an LED, compiled it into a custom kernel (kernel.img
), and replaced the Raspberry Pi’s default kernel with my custom one. I powered on the Raspberry Pi, expecting to see the LED blink.
But nothing happened.
I checked everything — the SD card contents, file names, GPIO pin connections, and power supply — but the LED still wouldn’t blink. I didn’t know what was wrong. Without an OS, there were no error logs or debug messages to help me understand the issue. I realized I needed a way to debug the boot process.
The Challenge — No HDMI, No Output
I initially tried to connect an HDMI display, but since this was a bare-metal program, there were no drivers to control the HDMI output. It became clear that I needed a new approach to debug the system.
After some research, I found that UART (Universal Asynchronous Receiver/Transmitter) was a solution. UART allows data to be sent serially through GPIO pins, and I could view the data on my desktop.
The Solution — Setting Up UART Communication
To view UART output on my desktop, I needed a way to connect the Raspberry Pi’s UART pins to my desktop. Since my desktop doesn’t have UART ports, I purchased a USB-to-TTL converter. This converter allows the Raspberry Pi’s UART pins to be connected to a USB port on my desktop.
After wiring the USB-to-TTL converter to the Raspberry Pi’s GPIO pins, I opened Minicom on my desktop. Minicom is a terminal program that reads data from the USB port, allowing me to view UART output.
I powered on the Raspberry Pi and expected to see something in Minicom.
But nothing appeared on the screen.
I decided to flash Raspbian OS onto the SD card to test if UART was working correctly. Again, I connected the USB-to-TTL converter to the Raspberry Pi’s GPIO pins and opened Minicom, but still, I didn’t see anything on the screen.
After reviewing the Raspberry Pi documentation, I discovered that UART is not enabled by default. I had to edit the config.txt
file on the SD card and add the following line:
enable_uart=1
I rebooted the Raspberry Pi, and this time I saw some output, but it was scrambled text. I initially thought it was a baud rate mismatch, so I double-checked Minicom’s baud rate configuration. It was set to 115200, which is the standard baud rate for UART on the Raspberry Pi.
At this point, I was confused.
After more research, I found that Raspberry Pi 3 has two UART controllers:
- UART0 — A proper UART with a fixed clock, providing a stable baud rate.
- UART1 — A mini-UART that relies on the CPU clock, meaning the baud rate changes dynamically.
By default, GPIO pins are connected to UART1, which explains why the baud rate wasn’t stable. I found two possible solutions to this problem:
- Disable Bluetooth, which would allow UART0 to be used on the GPIO pins.
- Set the core frequency to a constant value, stabilizing UART1’s baud rate.
For Raspbian, I chose the first option. I disabled Bluetooth so that UART0 could be used on the GPIO pins.
For bare-metal programming, I went with the second option. I added the following line to config.txt
:
core_freq=250
This setting locks the system clock frequency to 250 MHz, ensuring the UART1 baud rate remains stable. After rebooting the Raspberry Pi, I could now see the Raspberry Pi console in Minicom.
Printing “Hello, World” on UART and Blinking an LED
After successfully setting up UART communication in Raspbian, I moved to bare-metal programming. My first goal was to print “Hello, World” on UART1 using a simple C program. I referenced the raspi3-tutorial GitHub repository to understand how to control UART1 directly.
Once I successfully printed “Hello, World”, I moved on to my original goal of blinking an LED. I used the same repository as a guide to control the GPIO pins. I focused on GPIO17 for LED control. After ensuring the kernel was compiled and loaded properly, I finally saw the LED blink.
This was a significant milestone in my bare-metal programming journey.
What I Learned
- UART is useful for debugging — UART allowed me to see what was happening during boot.
- Start with small tasks — Instead of trying to blink an LED right away, I should have started with printing “Hello, World”.
- Read the documentation — The Raspberry Pi documentation helped me solve many of my issues, especially regarding
config.txt
properties. - Be patient — Figuring out the scrambled UART text issue was confusing, but I eventually solved it.
What’s Next?
With the basics of UART and GPIO under control, my next step in this bare-metal programming journey is to connect the W5500 Ethernet controller and establish a simple network connection. This will involve initializing the W5500, configuring its registers, and sending/receiving simple network packets.
What’s Coming Next on My Blog
In my upcoming blog posts, I’ll share the step-by-step tutorials on how I achieved each of the following:
- Setting up UART-to-USB Communication — How to connect UART from Raspberry Pi to a USB port on a PC for debugging and logging.
- Printing ‘Hello, World’ on UART — Full details on how to write C code that prints “Hello, World” using UART.
- Blinking an LED Using GPIO — A simple step-by-step guide on how to control an LED connected to GPIO17.
I’ll also share the source code for each of these steps so you can follow along and try it yourself. If you’re curious about bare-metal programming or want to learn how hardware works at a low level, stay tuned for more articles.
This is just the beginning of my bare-metal journey, and I’m excited to share everything I learn along the way.