Convert Figma logo to code with AI

bztsrc logoraspi3-tutorial

Bare metal Raspberry Pi 3 tutorials

2,716
332
2,716
3

Top Related Projects

Learning operating system development using Linux kernel and Raspberry Pi

Raspberry Pi ARM based bare metal examples

1,828

A C++ bare metal environment for Raspberry Pi with USB (32 and 64 bit)

Build a minimal multi-tasking OS kernel for ARM from scratch

Quick Overview

The bztsrc/raspi3-tutorial repository is a collection of tutorials and examples for the Raspberry Pi 3 platform. It covers a wide range of topics, including bare-metal programming, device drivers, and operating system development.

Pros

  • Comprehensive Coverage: The repository provides a thorough introduction to Raspberry Pi 3 development, covering a diverse range of topics.
  • Hands-on Approach: The tutorials include practical examples and code snippets, making it easier for beginners to understand and apply the concepts.
  • Active Maintenance: The repository is actively maintained, with regular updates and bug fixes.
  • Community Support: The project has a dedicated community of contributors and users, providing a valuable resource for troubleshooting and collaboration.

Cons

  • Steep Learning Curve: The content may be challenging for absolute beginners, as it assumes a certain level of programming and computer science knowledge.
  • Specific to Raspberry Pi 3: The tutorials are focused on the Raspberry Pi 3 platform, which may limit the applicability to other Arm-based systems.
  • Lack of Structured Curriculum: The tutorials are organized as individual examples, rather than a structured curriculum, which may make it harder for learners to follow a clear learning path.
  • Limited Documentation: While the code examples are well-commented, the overall documentation could be more comprehensive and user-friendly.

Code Examples

The bztsrc/raspi3-tutorial repository contains a variety of code examples, including:

  1. Bare-metal Programming:
// example/hello.c
#include "mbox.h"

void kernel_main() {
    mbox_write(MBOX_CH_PROP, MBOX_TAG_SETPOWER, 0, 0, 0);
    mbox_read(MBOX_CH_PROP);
    mbox_write(MBOX_CH_PROP, MBOX_TAG_GETPOWER, 0, 0, 0);
    unsigned int power = mbox_read(MBOX_CH_PROP);
    uart_puts("Power state: ");
    uart_puthex(power);
    uart_puts("\n");
}

This code demonstrates how to use the Mailbox interface to interact with the Raspberry Pi 3's power management system.

  1. Device Drivers:
// example/i2c.c
#include "i2c.h"

void i2c_init() {
    gpio_set_function(2, GPIO_FUNC_ALT0);  // SDA
    gpio_set_function(3, GPIO_FUNC_ALT0);  // SCL
    i2c_write_reg(0x68, 0x6B, 0x00);      // PWR_MGMT_1 register
}

int i2c_read_reg(uint8_t addr, uint8_t reg) {
    i2c_start(addr << 1);
    i2c_write(reg);
    i2c_start((addr << 1) | 1);
    int data = i2c_read(0);
    i2c_stop();
    return data;
}

This code demonstrates how to initialize the I2C interface and read from a device register on the Raspberry Pi 3.

  1. Operating System Development:
// example/kernel.c
#include "mmu.h"

void kernel_main() {
    mmu_init();
    mmu_map_section(0x00000000, 0x00000000, 0x10000, 0b10C0);
    mmu_map_section(0x40000000, 0x40000000, 0x10000, 0b10C0);
    mmu_enable();

    // Rest of the kernel code
}

This code demonstrates how to initialize the Memory Management Unit (MMU) and map memory regions on the Raspberry Pi 3.

Getting Started

To get started with the bztsrc/raspi3-tutorial repository, follow these steps:

  1. Clone the repository:
git clone https://github.com/bztsrc/raspi3-tutorial.

Competitor Comparisons

Pros of pico-examples

  • Specifically designed for Raspberry Pi Pico, offering tailored examples
  • Comprehensive coverage of Pico's features, including PIO and multicore programming
  • Official repository maintained by Raspberry Pi, ensuring up-to-date and accurate information

Cons of pico-examples

  • Limited to Raspberry Pi Pico, not applicable to other Raspberry Pi models
  • Focuses on C/C++ examples, with less emphasis on assembly language programming
  • May be overwhelming for absolute beginners due to its extensive coverage

Code Comparison

pico-examples (C++):

#include "pico/stdlib.h"

int main() {
    const uint LED_PIN = PICO_DEFAULT_LED_PIN;
    gpio_init(LED_PIN);
    gpio_set_dir(LED_PIN, GPIO_OUT);

raspi3-tutorial (Assembly):

.globl _start
_start:
    // set up stack
    mov     sp, #0x8000

    // set GPIO pin function to output
    mov     r0, #1
    lsl     r0, #18

This comparison highlights the different approaches and target platforms of the two repositories. pico-examples is more accessible for C/C++ programmers and focuses on the Pico's specific features, while raspi3-tutorial provides low-level assembly programming examples for Raspberry Pi 3.

Learning operating system development using Linux kernel and Raspberry Pi

Pros of raspberry-pi-os

  • More comprehensive coverage of OS development concepts
  • Includes detailed explanations and theory alongside code
  • Supports multiple architecture versions (32-bit and 64-bit)

Cons of raspberry-pi-os

  • Less focused on bare-metal programming
  • May be more complex for beginners
  • Slower progression through basic concepts

Code Comparison

raspi3-tutorial (boot.S):

.section ".text.boot"
.global _start
_start:
    // Disable all cores except core 0
    mrc p15, 0, r1, c0, c0, 5

raspberry-pi-os (boot.S):

#include "mm.h"

.section ".text.boot"

.globl _start
_start:
    mrs    x0, mpidr_el1
    and    x0, x0, #0xFF

Both repositories provide valuable resources for learning Raspberry Pi OS development, but they cater to different audiences and learning styles. raspi3-tutorial focuses more on bare-metal programming and provides a step-by-step approach suitable for beginners. raspberry-pi-os offers a more comprehensive look at OS development concepts, making it ideal for those seeking a deeper understanding of operating system internals.

The code comparison shows differences in assembly syntax and initialization approaches, reflecting the repositories' distinct focuses and target architectures.

Raspberry Pi ARM based bare metal examples

Pros of raspberrypi

  • More comprehensive coverage of Raspberry Pi models, including older versions
  • Includes examples for various peripherals and interfaces (UART, SPI, I2C)
  • Provides assembly language examples alongside C code

Cons of raspberrypi

  • Less structured learning path compared to raspi3-tutorial
  • Documentation may be less beginner-friendly
  • Some examples might be outdated for newer Raspberry Pi models

Code Comparison

raspi3-tutorial (main.c):

#include "uart.h"

void main()
{
    uart_init();
    uart_puts("Hello World!\n");
}

raspberrypi (blinker05/blinker05.c):

extern void PUT32 ( unsigned int, unsigned int );
extern unsigned int GET32 ( unsigned int );
extern void dummy ( unsigned int );

int notmain ( void )
{
    unsigned int ra;

The raspi3-tutorial example demonstrates a simpler, more abstracted approach to UART communication, while the raspberrypi example shows lower-level hardware manipulation. This reflects the different focus of each repository, with raspi3-tutorial aiming for a more beginner-friendly approach and raspberrypi providing more in-depth, hardware-specific examples.

1,828

A C++ bare metal environment for Raspberry Pi with USB (32 and 64 bit)

Pros of Circle

  • More comprehensive and feature-rich, offering a complete bare metal environment
  • Supports a wider range of Raspberry Pi models, including newer versions
  • Provides a higher-level abstraction, making it easier for beginners to get started

Cons of Circle

  • Larger codebase, which may be overwhelming for those seeking a minimal tutorial
  • Less focused on explaining low-level details compared to raspi3-tutorial
  • May abstract away some hardware-specific concepts that raspi3-tutorial explicitly covers

Code Comparison

raspi3-tutorial (kernel.c):

void kernel_main(uint32_t r0, uint32_t r1, uint32_t atags)
{
    // Declare as unused
    (void) r0;
    (void) r1;
    (void) atags;

Circle (kernel.cpp):

CKernel::CKernel (void)
:   m_Screen (m_Options.GetWidth (), m_Options.GetHeight ()),
    m_Timer (&m_Interrupt),
    m_Logger (m_Options.GetLogLevel (), &m_Timer)
{
    m_ActLED.Blink (5);    // show we are alive

The raspi3-tutorial code focuses on a minimal bare-metal entry point, while Circle provides a more structured, object-oriented approach with built-in functionality for common tasks.

Build a minimal multi-tasking OS kernel for ARM from scratch

Pros of mini-arm-os

  • Focuses on a broader range of ARM-based systems, not limited to Raspberry Pi
  • Provides a more comprehensive overview of operating system concepts
  • Includes task scheduling and context switching implementations

Cons of mini-arm-os

  • Less detailed hardware-specific explanations compared to raspi3-tutorial
  • Fewer graphics and UART examples
  • May be more challenging for absolute beginners due to its broader scope

Code Comparison

mini-arm-os (context switching):

void activate(task_t *next) {
    if (current && current->state == TASK_RUNNING)
        current->state = TASK_READY;
    current = next;
    current->state = TASK_RUNNING;
}

raspi3-tutorial (UART initialization):

void uart_init()
{
    register unsigned int r;
    r=*GPFSEL1;
    r&=~((7<<12)|(7<<15)); // gpio14, gpio15
    r|=(2<<12)|(2<<15);    // alt5
    *GPFSEL1 = r;
}

The mini-arm-os example demonstrates task management, while the raspi3-tutorial focuses on hardware-specific initialization. This reflects the different goals of each project, with mini-arm-os emphasizing OS concepts and raspi3-tutorial concentrating on Raspberry Pi hardware interaction.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

Bare Metal Programming on Raspberry Pi 3

Hello there! This tutorial series are made for those who would like to compile their own bare metal application for the Raspberry Pi.

The target audience is hobby OS developers, who are new to this hardware. I'll give you examples on how to do the basic things, like writing to the serial console, reading keystrokes from it, setting screen resolution and draw to the linear frame buffer. I'm also going to show you how to get the hardware's serial number, a hardware-backed random number, and how to read files from the boot partition.

This is not a tutorial on how to write an OS. I won't cover topics like memory management and virtual file systems, or how to implement multi-tasking. If you plan to write your own OS for the Raspberry Pi, I suggest to do some research before you continue. This tutorial is strickly about interfacing with the hardware, and not about OS theory. For that, I'd recommend raspberry-pi-os.

I assume you have a fair GNU/Linux knowledge on how to compile programs and create disk and file system images. I won't cover those in detail, although I'll give you a few hints about how to set up a cross-compiler for this architecture.

Why Raspberry Pi 3?

I've choosen this board for several reasons: first of all, it's cheap and easy to get. Second, it's a 64 bit machine. I gave up programming for 32 bit long long time ago. The 64 bit is so much more interesting, as it's address space is increadibly huge, bigger than the storage capacity which allows us to use some interesting new solutions. Third, uses only MMIO which makes it easy to program.

For 32 bit tutorials, I'd recommend:

Cambridge tutorials (ASM and 32 bit only),

David Welch's tutorials (mostly C, with some 64 bit examples),

Peter Lemon's tutorials (ASM only, also for 64 bit) and

Leon de Boer's tutorials (C and ASM, also for 64 bit, more complex examples like USB and OpenGL).

What about Raspberry Pi 4?

More or less the same hw with the same peripherals, only the MMIO_BASE address is different (as long as this tutorial concerned, most of the differences are irrelevant for us). For RPi4 tutorials I'd recommend

rpi4-osdev tutorials

Why not C++?

The C language in "freestanding" mode allows us to develop directly to the hardware. With C++ this is not possible, because that requires a runtime library. If you are interested in this, then I suggest to take a look at the brilliant Circle C++ library, which not only contains the mandatory C++ runtime, but also implements every Raspberry Pi functionalities we're about to discuss in these tutorials (and even more).

Why not Rust?

Simply because my personal opinion is that Rust is a much higher level language than preferable for bare metal, something like with C++. But if you provide the required runtime libraries, you can do it. My multiplatform system boot loader has an example Rust kernel too and @andre-richter ported these tutorials to Rust. He has added considerably more code to his Rust repository which is a very good start if you're interested in this language.

A few questions popped up on ycombinator regarding this. First, please note that I've started the sentance with "my personal opinion" (and I mean Rust has a considerably larger and more complex grammar than C, and it's easy to forget with cargo that you actually must compile in all library dependencies). Second, and please don't get this the wrong way, but if you find clicking on the "Rust port" link too complicated then maybe low-level programming is not the best hobby for you!

Prerequisites

Before you can start, you'll need a cross-compiler (see 00_crosscompiler directory for details) and a Micro SD card with firmware files on a FAT filesystem.

Every directory has a Makefile.gcc and a Makefile.clang. Make sure the Makefile symlink points to the version according to the cross-compiler of your choosing. I'd like to say thanks to @laroche for testing these tutorials for the first time with Clang too.

I recommend to get a Micro SD card USB adapter (many manufacturers ship SD cards with such an adapter), so that you can connect the card to any desktop computer just like an USB stick, no special card reader interface required (although many laptops have those these days). If you dislike dd, then for writing images I recommend USBImager which is simple GUI app with a portable executable available for Windows, MacOSX and Linux.

You can create an MBR partitioning scheme on the SD card with an LBA FAT32 (type 0x0C) partition, format it and copy bootcode.bin, start.elf and fixup.dat onto it. Or alternatively you can download a raspbian image, dd it to the SD card, mount it and delete the unnecessary .img files. Whichever you prefer. What's important, you'll create kernel8.img with these tutorials which must be copied to the root directory on the SD card, and no other .img files should exists there.

I'd also recommend to get an USB serial debug cable. You connect it to the GPIO pins 14/15, and run minicom on your desktop computer like

minicom -b 115200 -D /dev/ttyUSB0

Emulation

Unfortunately official qemu binary does not support Raspberry Pi 3 yet. But good news, I've implemented that, so it's coming soon (UPDATE: available in qemu 2.12). Until then, you have to compile qemu from the latest source. Once compiled, you can use it with:

qemu-system-aarch64 -M raspi3b -kernel kernel8.img -serial stdio

Or (with the file system tutorials)

qemu-system-aarch64 -M raspi3b -kernel kernel8.img -drive file=$(yourimagefile),if=sd,format=raw -serial stdio

-M raspi3b The first argument tells qemu to emulate Raspberry Pi 3 Model B hardware.

-kernel kernel8.img The second tells the kernel filename to be used.

-drive file=$(yourimagefile),if=sd,format=raw In second case this argument tells the SD card image too, which can be a standard rasbian image as well.

-serial stdio

-serial null -serial stdio Finally the last argument redirects the emulated UART0 to the standard input/output of the terminal running qemu, so that everything sent to the serial line will be displayed, and every key typed in the terminal will be received by the vm. Only works with the tutorials 05 and above, as UART1 is not redirected by default. For that, you would have to add something like -chardev socket,host=localhost,port=1111,id=aux -serial chardev:aux (thanks @godmar for the info), or simply use two -serial arguments (thanks @cirosantilli).

!!!WARNING!!! Qemu emulation is rudimentary, only the most common peripherals are emulated! !!!WARNING!!!

About the hardware

There are lots of pages on the internet describing the Raspberry Pi 3 hardware in detail, so I'll be brief and cover only the basics.

The board is shipped with a BCM2837 SoC chip. That includes a

  • VideoCore GPU
  • ARM-Cortex-A53 CPU (ARMv8)
  • Some MMIO mapped peripherals.

Interestingly the CPU is not the main processor on the board. When it's powered up, first GPU runs. When it's finished with the initialization by executing the code in bootcode.bin, it will load and execute the start.elf executable. That's not an ARM executable, but compiled for the GPU. What interests us is that start.elf looks for different ARM executables, all starting with kernel and ending in .img. As we're going to program the CPU in AArch64 mode, we'll need kernel8.img only, which is the last to look for. Once it's loaded, the GPU triggers the reset line on the ARM processor, which starts executing code at address 0x80000 (or more precisely at 0, but the GPU puts an ARM initialisation and jump code there first).

The RAM (1G for the Raspberry Pi 3) is shared among the CPU and the GPU, meaning one can read what the other has written into memory. To avoid confusion, a well defined, so called mailbox interface is established. The CPU writes a message into the mailbox, and tells the GPU to read it. The GPU (knowing that the message is entirely in memory) interprets it, and places a response message at the same address. The CPU has to poll the memory to know when the GPU is finished, and then it can read the response.

Similarily, all peripherals communicates in memory with the CPU. Each has it's dedicated memory address starting from 0x3F000000, but it's not in real RAM (called Memory Mapped IO). Now there's no mailbox for peripherals, instead each device has it's own protocol. What's common for these devices that their memory must be read and written in 32 bit units at 4 bytes aligned addresses (so called words), and each has control/status and data words. Unfortunately Broadcom (the manufacturer of the SoC chip) is legendary bad at documenting their products. The best we've got is the BCM2835 documentation, which is close enough.

There's also a Memory Management Unit in the CPU which allows creating virtual address spaces. This can be programmed by specific CPU registers, and care must be taken when you map these MMIO addresses into a virtual address space.

Some of the more interesting MMIO addresses are:

0x3F003000 - System Timer
0x3F00B000 - Interrupt controller
0x3F00B880 - VideoCore mailbox
0x3F100000 - Power management
0x3F104000 - Random Number Generator
0x3F200000 - General Purpose IO controller
0x3F201000 - UART0 (serial port, PL011)
0x3F215000 - UART1 (serial port, AUX mini UART)
0x3F300000 - External Mass Media Controller (SD card reader)
0x3F980000 - Universal Serial Bus controller

For more information, see Raspberry Pi firmware wiki and documentation on github.

https://github.com/raspberrypi

Good luck and enjoy hacking with your Raspberry! :-)

bzt