How to run a C program with no OS on the Raspberry Pi?
Asked Answered
O

4

42

I'd like to experiment using the Raspberry Pi for some different low level embedded applications. The only problem is that, unlike the AVR and PIC microcontroller boards available, Raspberry Pi typically runs an OS (like Raspbian) that distributes CPU time across all running programs and makes it impractical for certain real time applications.

I've recently learned that, assuming you have a bootloader like GRUB installed, running a C program on x86 (in the form of a kernel) takes very little actual setup, just an assembly program to call the main function and the actual C code.

Is there a way to achieve this with a Raspberry Pi? It'd be a great way to learn about low level ARM programming, and it already has a few complex peripherals to mess around with (USB, Ethernet, etc.)

Olivine answered 24/4, 2015 at 2:48 Comment(7)
If you don't use even a minimal linux kernel, wouldn't you end up having to write a whole bunch of kernel functionality just to interact with all the peripherals?Discounter
Not really. It depends on what you want to do. If all you want to do is blink a light, then you really don't need much code. But talking to USB, Ethernet, etc. would be a lot of work.Suffix
Can‘t you just try a few lightweight RTOSes before making a decision?Career
you should check out raspberrypi.stackexchange.com/questions/1408/… before ditching the OS. on PICs etc you don't run an OS because you often don't have room for it! on the Pi you do, and there's ways to run realtimeUnhandy
There is a "bare-metal" forum for RPi at raspberrypi.org/forums/viewforum.php?f=72 which you may find useful.Spreadeagle
@KeithNicholas there are many reasons to NOT run an OS. You cannot accurately count cycles or develop anything with cycle accuracy when you have to ask the OS for permission.Oech
You can compile pascal to run directly on a Pi using Ultibo Core. Search for Ultibo demo 1.0.0 on YouTube. We wrote some stuff to emulate an elm327 adapter with Ultibo on a Pi zero and it worked very well.Blondellblondelle
I
29

Fully automated minimal bare metal blinker example

Tested on Ubuntu 16.04 host, Raspberry Pi 2.

https://github.com/dwelch67/raspberrypi is the most comprehensive example set I've seen to date (previously mentioned on this now deleted answer), but this is a minimal easy to setup hello world to get you started quickly.

Usage:

  1. Insert SD card on host

  2. Make the image:

    ./make.sh /dev/mmblck0 p1
    

    Where:

    • /dev/mmblck0 is the device of the SD card
    • p1 is the first partition of the device (/dev/mmblck0p1)
  3. Inset SD card on PI

  4. Turn power off and on

enter image description here

GitHub upstream: https://github.com/cirosantilli/raspberry-pi-bare-metal-blinker/tree/d20f0337189641824b3ad5e4a688aa91e13fd764

start.S

.global _start
_start:
    mov sp, #0x8000
    bl main
hang:
    b hang

main.c

#include <stdint.h>

/* This is bad. Anything remotely serious should use timers
 * provided by the board. But this makes the code simpler. */
#define BUSY_WAIT __asm__ __volatile__("")
#define BUSY_WAIT_N 0x100000

int main( void ) {
    uint32_t i;
    /* At the low level, everything is done by writing to magic memory addresses.
    The device tree files (dtb / dts), which are provided by hardware vendors,
    tell the Linux kernel about those magic values. */
    volatile uint32_t * const GPFSEL4 = (uint32_t *)0x3F200010;
    volatile uint32_t * const GPFSEL3 = (uint32_t *)0x3F20000C;
    volatile uint32_t * const GPSET1  = (uint32_t *)0x3F200020;
    volatile uint32_t * const GPCLR1  = (uint32_t *)0x3F20002C;

    *GPFSEL4 = (*GPFSEL4 & ~(7 << 21)) | (1 << 21);
    *GPFSEL3 = (*GPFSEL3 & ~(7 << 15)) | (1 << 15);
    while (1) {
        *GPSET1 = 1 << (47 - 32);
        *GPCLR1 = 1 << (35 - 32);
        for (i = 0; i < BUSY_WAIT_N; ++i) { BUSY_WAIT; }
        *GPCLR1 = 1 << (47 - 32);
        *GPSET1 = 1 << (35 - 32);
        for (i = 0; i < BUSY_WAIT_N; ++i) { BUSY_WAIT; }
    }
}

ldscript

MEMORY
{
    ram : ORIGIN = 0x8000, LENGTH = 0x10000
}

SECTIONS
{
    .text : { *(.text*) } > ram
    .bss : { *(.bss*) } > ram
}

make.sh

#!/usr/bin/env bash

set -e

dev="${1:-/dev/mmcblk0}"
part="${2:-p1}"
part_dev="${dev}${part}"
mnt='/mnt/rpi'

sudo apt-get install binutils-arm-none-eabi gcc-arm-none-eabi

# Generate kernel7.img
arm-none-eabi-as start.S -o start.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding -c main.c -o main.o
arm-none-eabi-ld start.o main.o -T ldscript -o main.elf
# Get the raw assembly out of the generated elf file.
arm-none-eabi-objcopy main.elf -O binary kernel7.img

# Get the firmware. Those are just magic blobs, likely compiled
# from some Broadcom proprietary C code which we cannot access.
wget -O bootcode.bin https://github.com/raspberrypi/firmware/blob/597c662a613df1144a6bc43e5f4505d83bd748ca/boot/bootcode.bin?raw=true
wget -O start.elf https://github.com/raspberrypi/firmware/blob/597c662a613df1144a6bc43e5f4505d83bd748ca/boot/start.elf?raw=true

# Prepare the filesystem.
sudo umount "$part_dev"
echo 'start=2048, type=c' | sudo sfdisk "$dev"
sudo mkfs.vfat "$part_dev"
sudo mkdir -p "$mnt"
sudo mount "${part_dev}" "$mnt"
sudo cp kernel7.img bootcode.bin start.elf "$mnt"

# Cleanup.
sync
sudo umount "$mnt"

QEMU friendly bare metal examples

The problem with the blinker is that it is hard to observe LEDs in QEMU: https://raspberrypi.stackexchange.com/questions/56373/is-it-possible-to-get-the-state-of-the-leds-and-gpios-in-a-qemu-emulation-like-t

Here I describe some bare metal QEMU setups that may be of interest: How to make bare metal ARM programs and run them on QEMU? Writing to the UART is the easiest way to get output out from QEMU.

How well QEMU simulates the Raspberry Pi can be partially inferred from: How to emulate Raspberry Pi Raspbian with QEMU? Since even the Linux terminal shows up, it is likely that your baremetal stuff will also work.

Bonus

Here is an x86 example for the curious: How to run a program without an operating system?

Ison answered 15/10, 2016 at 18:59 Comment(0)
B
21

While bare metal is possible on the Pi, I would avoid it since Linux is getting so lightweight and handles a whole bunch of stuff for you.

Here's a tutorial to get you started if you want to still learn bare metal stuff: http://www.valvers.com/open-software/raspberry-pi/step01-bare-metal-programming-in-cpt1/

With all that said, I would just load up your favorite embedded linux distro (RT patched might be preferred based on your requirements) and call it good.

Bowlin answered 24/4, 2015 at 3:0 Comment(2)
Thanks for the info. I'll probably use the bare metal programming for now, as the project has no time constraints, but I'll keep the embedded linux distro in mind!Olivine
Unfortunately Valvers has been taken offline for being too useful. You will need to access it via google cache now :/Buffum
K
4

https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/ is a great tutorial, and as they'll tell you the best quick and dirty way to run code on bare metal is to hijack a linux distro, to do that, just compile to kernel.img (with the appropriate architecture options) and use it to replace the existing one in the linux distro for just this section of the tutorial you can go to: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/ok01.html#pitime

Kop answered 24/4, 2015 at 3:17 Comment(0)
N
3

The Pi may be a bit suboptimal for what you are wanting to do, since the SoC design is such that the ARM CPU is a second-class citizen - meaning there are some hoops to jump through to get a bare metal program running on it.

However, you could cheat a bit and use the U-Boot API to give you access to some of the features U-Boot provides but be able to add your own features on the side.

Norvun answered 24/4, 2015 at 10:27 Comment(2)
Actually, the Pi is easier to get started on than ARM-only systems; the GPU boot loader takes care of starting the system, initialising DRAM refresh, loading the boot image, etc. All you need to do is to drop a binary image with the right name onto the SD card and it'll be loaded and run. You can even make RPC calls to the GPU to do things like initialise a graphics framebuffer in a handful of instructions. See here for a pile of examples.Troxler
Let me add that the Pi can never be bricked. A few ARM-only system can become so corrupted after a JTAG mishap that the system won't boot anymore - being bricked. It is exactly because the ARM core is a second-class citizen that the Pi can never be bricked as there is no chip writing allowed, so a corrupted system can be easily replaced with a SD card swap.Tildy

© 2022 - 2024 — McMap. All rights reserved.