dwelch67 / raspberrypi

Raspberry Pi ARM based bare metal examples
2.7k stars 483 forks source link

The main directory examples are for the older/est pi1 boards maybe some attempts at newer boards. These are retained for legacy reasons some folks have direct links. They are still valid for those boards. The boards directory has the first attempt at solving the too many boards issue and that is where I recommend you go. My third string attempt is I have a raspberrypi-zero and raspberrypi-three repos that are for those boards specifically. Little to no verbage though, the rambling text is mostly in this repo.


This repo is getting a bit messy starting with the first released to the public pi and then all that came after. I want to rework it but it is going slowly. Recently started creating directories pi1, piaplus and so on to target the examples specifically to the different cards. Again that is slow going. Likewise this README will be rewritten perhaps but for now...


This repo serves as a collection of low level examples. No operating system, embedded or low level embedded or deeply embedded or bare metal, whatever your term is for this.

I am in no way shape or form associated with the raspberry pi organization nor broadcom. I just happen to own one (some) and am sharing my experiences. The raspberry pi is about education, and I feel bare metal education is just as important as Python programming.

So I started this years ago when got my first ARM11 based raspberry pi maybe we call that a raspberry pi 1, I dont know a good term. But now there is a growing number of variations.

ARM11 based (BCM2835) Raspberry Pi B Raspberry Pi B2 Raspberry Pi A+ Raspberry Pi B+ Raspberry Pi Zero Cortex-A7 based (BCM2836) Raspberry Pi 2 B Cortex-A53 based (BCM2837) Raspberry Pi 3 B

There is also the compute module but I dont have one of those.

General differences that we care about for these examples. The amount of ram varies from board to board. The peripheral base address is different between the BCM2835 and BCM2836. The BCM2835 looks for the file kernel.img the BCM2836 looks for kernel7.img. The ARM11 based Zero is a B with stuff removed and a new layout, but they up/over clocked the processor from 750MHz to 1000MHz, one led on gpio 16. The A+ and B+ they moved the led (or put two) on gpio 35 and 47. The raspberry pi 2 is B+ like but with the different chip, supposedly the BCM2836 is BCM2835 with the ARM11 core removed and replaced with the Cortex A7 and for the most part it appears to be. The raspberry pi 3 is Cortex A8 based, 64 bit. And they moved the LED (the leds) to an i2c gpio expander.

As of this writing I am adding plus and pi2 versions of the examples as many of them are based on the early/original. No guarantees I will do that, just looking at the differences between the blinker01 examples should show you what to do to convert these yourself. In some cases I am intentionally not trying to have one code base build for all three with ifdefs and such, keep it simple stupid then complicate it as needed. The text may say kernel.img but substitute with kernel7.img as needed.

I still have a number of raspberry pi 2 examples to port, and now a bunch of examples to port to the raspberry pi 3. The raspberry pi 3 as of this writing, without a config.txt, is switched to 32 bit compatibility mode. See the aarch64 directory for 64 bit ARM examples.

From what we know so far there is a gpu on chip which:

1) boots off of an on chip rom of some sort 2) reads the sd card and looks for additional gpu specific boot files bootcode.bin and start.elf in the root dir of the first partition (fat32 formatted, loader.bin no longer used/required) 3) in the same dir it looks for config.txt which you can do things like change the arm speed, or change the address where to load kernel.img, and many others 4) it reads kernel.img the arm boot binary file and copies it to memory 5) releases reset on the arm such that it runs from the address where the kernel.img data was written

The memory is split between the GPU and the ARM, I believe the default is to split the memory in half. And there are ways to change that split (to give the ARM more)(using config.txt). Not going to worry about that here.

From the ARMs perspective the kernel.img file is loaded, by default, to address 0x8000. (there are ways to change that, not going to worry about that right now).

Hardware and programming information:

You will want to go here http://elinux.org/RPi_Hardware And the datasheet and schematic. These are moving targets the above elinux link has the datasheet and errata which is important. They didnt give us a full datasheet for the BCM2836 have to go with the BCM2835. You will want to go to http://raspberrypi.org and then the forum tab then slide down to the Bare Metal forum, the first (only) Sticky topic is Bare Metal Resources. There are many more links there for good information. Also go to http://infocenter.arm.com and get the Architectural Reference Manual and the Techincal Reference Manual for the ARM1176JZF-S (BCM2835) and/or the Cortex-A7 (BCM2836).

Early in the BCM2835 document you see a memory map. I am going to operate based on the middle map, this is how the ARM comes up. The left side is the system which we dont have direct access to in that form, the gpu probably, not the ARM. The ARM comes up with a memory space that is basically 0x40000000 bytes in size as it mentions in the middle chart. The bottom of this picture shows total system sdram (memory) and somewhere between zero and the top of ram is a split between sdram for the ARM on the bottom and a chunk of that for the VC SDRAM, basically memory for the gpu and memory shared between the ARM and GPU to allow the ARM to ask the GPU to draw stuff on the video screen. 256MBytes is 0x10000000, and 512MBytes is 0x20000000. Some models of raspberry pi have 256MB, newer models have 512MB total ram which is split between the GPU and the ARM. Assume the ARM gets at least half of this. Peripherals (uart, gpio, etc) are mapped into arm address space at 0x20000000. When you see 0x7Exxxxxx in the manual replace that with 0x20xxxxxx as your ARM physical address. Experimentally I have seen the memory repeats every 0x40000000, read 0x40008000 and you see the data from 0x8000. From the Broadcom doc this looks to be giving us access to the memory with different caching schemes (cached, uncached, etc) depending on which upper address bits you use. Most likely to allow more room for RAM the Raspberry Pi 2 uses a peripheral base address of 0x3Fxxxxxx instead of the 0x20xxxxxx.

I do not normally zero out .bss or use .data so if you do this to my examples

int x; fun() { static int y; }

dont assume x and y are zero when your program starts. Nor if you do this

int x=5; fun() { static int y=7; }

will x=5 or y=7.

See the bssdata directory for more information, you can most likely use the linker script to solve the problem for you since .text, .data, .bss, (.rodata), everything lives in ram.

Nor do I use gcc libraries nor C libraries so you can build most if not all of my examples using a gcc cross compiler. Basically it doesnt matter if you use arm-none-linux-gnueabi or arm-none-eabi. I have not looked in a while but formerly codesourcery.com (now a part of Mentor Graphics) had a free LITE version of their toolchain which was pretty easy to come by. An even easier place is here https://launchpad.net/gcc-arm-embedded to get a cross compiler. Building your own toolchain from gnu sources (binutils and gcc) is fairly straight forward see my build_gcc repository for a build script (Linux only but from that you might get other platforms to build). And also remember that you can run linux on the pi and on that it has a native, not cross, gnu toolchain.

As far as we know so far the Raspberry Pi is not "brickable". Normally what brickable means is the processor relies on a boot flash and with that flash it is possible to change/erase it such that the processor will not boot normally. Brickable and bricked sometimes excludes things like jtag or special programming headers. From the customers perspective a bricked board is...bricked. But on return to the vendor they may have other equipment that is able to recover the board without doing any soldering, perhaps plugging in jtag or some other cable on pins/pads they have declared as reserved. Take apart a tv remote control or calculator, etc and you may see some holes or pads on the circuit board, for some of these devices just opening the battery case you have a view of some of the pcboard. This is no doubt a programming header. Long story short, so far as I know the Raspberry Pi is not brickable because it boots off of an sd card which we can easily remove and replace ourselves. I dont know for sure, a lot more info is out about the GPU since I started with this, but I assume that there is some GPU code that boots off of an internal rom, I doubt with two on chip processors they created pure logic to read the sd card, wade through the filesystem to find a specific bootcode.bin file, load that into ram and run it. If that assumption is true is that on chip rom one time programmable or can it be erased/reprogrammed, and if the latter how lucky do we have to be with a broken program to erase that? So I am not 100% sure but almost 100% sure the Raspberry Pi is not brickable. This is actually a big deal for bare metal programming, in particular if it is your first platform. With decades of experience I still make mistakes from time to time and brick a board, never to be recovered.

To use my samples you do not need a huge sd card. Nor do you need nor want to download one of the linux images, takes a while to download, takes a bigger sd card to use, and takes forever to write to the sd card. I use the firmware from http://github.com/raspberrypi. The minimum configuration you need to get started at this level is:

go to https://github.com/raspberrypi, you DO NOT need to download the repo, they have some large files in there you will not need (for my examples). go to the firmware directory and then the boot directory. For each of these files, bootcode.bin and start.elf (NOT kernel.img, dont need it, too big)(loader.bin is no longer used/required). Click on the file name, it will go to another page then click on View Raw and it will let you download the file. For reference, I do not use nor have a config.txt file on my sd card. I only have the minimum number of files on the sd card, bootcode.bin, start.elf and either kernel.img or kernel7.img (or sometimes both).

My examples are basically the kernel.img file. Not a linux kernel, just bare metal programs. Since the GPU bootloader is looking for that file name, you use that file name. The kernel.img file is just a blob that is copied to memory, dont worry about what they named it.

What I do is setup the sd card with a single partition, fat32. And copy the above files in the root directory. bootcode.bin and start.elf. From there you take .bin files from my examples and place them on the sd card with the name kernel.img. It wont take you very long to figure out this is going to get painful.

1) power off raspi 2) remove sd card 3) insert sd card in reader 4) plug reader into computer 5) mount/wait 6) copy binary file to kernel.img 7) sync/wait 8) unmount 9) insert sd card in raspi 10) power raspi 11) repeat

There are ways to avoid this, one is jtag, which is not as expensive as it used to be. It used to be in the thousands of dollars, now it is under $50 and the software tools are free. Now the raspi does have jtag on the arm, getting the jtag connected requires soldering on older of the older models, but unless you were an early adopter, you dont need to worry about that all the signals are on the P1 connector. How to use the jtag and how to hook it up is described later and in the armjtag sample.

Another method is a bootloader, typically you use a serial port connected to the target processor. That processor boots a bootloader program that in some way, shape, or form allows you to interact with the bootloader and load programs into memory (that the bootloader is not using itself) and then the bootloader branches/jumps to your program. If your program is still being debugged and you want to try again, you reboot the processor the bootloader comes up and you can try again without having to move any sd cards, etc. The sd card dance above is now replaced with the bootloader dance:

1) power off raspi 2) power on raspi 3) type command to load and start new program

Or if you solder on a reset button

1) reset raspi 2) type command to load and start new program

I have working bootloader examples. bootloader05 is currently the last of the xmodem based ones (that basically take a kernel.img file), personally I use bootloader07 which takes an intel hex formatted file which these examples also build. The .bin file would be used with bootloader05, the .hex with bootloader07. But you need more hardware (no soldering is required). For those old enough to know what a serial port is, you CANNOT connect your raspberry pi directly to this port, you will fry the raspberry pi. You need some sort of serial port at 3.3V either a level shifter of some sort (transceiver like a MAX232) or a usb serial port where the signals are 3.3V (dont need to use RS232 just stay at the logic level). The solution I recommend is a non-solder solution:

A recent purchase, experimentally white is RX and green is TX, black GND http://www.nexuscyber.com/usb-to-ttl-serial-debug-console-cable-for-raspberry-pi Sparkfun has one https://www.sparkfun.com/products/12977 As does Adafruit https://www.adafruit.com/products/954 The above, assuming you figure out rx from tx, are all you need. The ones below you may need to solder or may need some jumper wires.

http://www.sparkfun.com/products/9873 plus some male/female wire http://www.sparkfun.com/products/9140

Solutions that may involve soldering http://www.sparkfun.com/products/718 http://www.sparkfun.com/products/8430

Or this for level shifting to a real com port. http://www.sparkfun.com/products/449

Or see the pitopi (pi to pi) directory. This talks about how to take two raspberry pi's and connect them together. One being the host/development platform (a raspberry pi running linux is a native arm development platform, no need to find/get/build a cross compiler) the other being the target that runs your bare metal programs.

Lastly and perhaps the best solution IMO, is the FT4232H or FT2232H mini module from FTDI. It gives you UART and JTAG for under $30. See the armjtag directory README for more and you will want some female/female wire from sparkfun or adafruit or elsewhere https://www.sparkfun.com/products/8430 (I use these F/F wires for most projects, buy/bought the 100 pack)

---- connecting to the uart pins ----

On the raspberry pi, the connector with two rows of a bunch of pins is P1. Starting at that corner of the board, the outside corner pin is pin 2. From pin 2 heading toward the yellow rca connector the pins are 2, 4, 6, 8, 10. Pin 6 connect to gnd on the usb to serial board pin 8 is TX out of the raspi connect that to RX on the usb to serial board. Pin 10 is RX into the raspi, connect that to TX on the usb to serial board. Careful not to have metal items on the usb to serial board touch metal items on the raspberry pi (other than the three connections described). On your host computer you will want to use some sort of dumb terminal program, minicom, putty, etc. Set the serial port (usb to serial board) to 115200 baud, 8 data bits, no parity, 1 stop bit. NO flow control. With minicom to get no flow control you normally have to save the config, exit minicom, then restart minicom and load the config in order for flow control changes to take effect. Once you have a saved config you dont have to mess with it any more.

2 outer corner 4 6 ground 8 TX out 10 RX in

ground is not necessarily needed if both the raspberry pi and the usb to serial board are powered by the same computer (I recommend you do that) as they will ideally have the same ground.

Read more about the bootloaders in their local README files. Likewise if you interested in jtag see the armjatag README file. Other than chewing up a few more GPIO pins, and another thing you have to buy, the jtag solution is the most powerful and useful. My typical setup is the armjtag binary as kernel.img, a usb to jtag board like the amontec jtag-tiny and a usb to serial using minicom.

Update, amontec is history their website is gone. But you can get j-link (or clone) boxes from asia on ebay for around $11, so far I have tried them with ARM JTAG and ARM SWD. Very pleased so far.

If you can solder, the A+, B+, Zero and Pi 2 all have a pair of holes sometimes with the text RUN next to them. I use buttons like this https://www.sparkfun.com/products/97 with two of the legs broken off then the others twisted and adjusted to fit in the holes and soldered down.

As far as these samples go I recommend starting with blinker01 then follow the discovery of the chip into uart01, etc.

The bssdata and baremetal directories attempt to explain a little bit about taking control of the gnu toolchain to build bare metal programs like these examples. As with any bare metal programmer I have my ways of doing things and these two directories hopefully will show you some basics, get you thinking about how these tools really work, take the fear away from using them, as well as some comments on why I take the approach I take (not worrying about .bss nor .data). Since the raspberry pi is from our perspective RAM based (the GPU loads our whole binary into memory), we dont have to deal with the things we would deal with on a FLASH/PROM + RAM system. This RAM only approach makes life a lot easier, but leaves out some important bare metal experiences that you will have to find elsewhere.