Bare Metal Raspberry Pi 3:Blinking LED

Introduction: Bare Metal Raspberry Pi 3:Blinking LED

About: Hey I am a computer science student who enjoys making things! Feel free to msg me about any questions you might have about any of my instructables.

Welcome to the BARE METAL pi 3 Blinking LED tutorial!

In this tutorial we will go through the steps, from start to finish, to get a LED blinking using a Raspberry PI 3, a breadboard, a resistor, an led, and a blank SD card.

So what is BARE METAL? BARE METAL is no frills programming. Bare metal means we are fully in control of what the computer will do down to the bit. So it basically means that the code will be completely written in assembly, using the Arm instruction set. By the end we will have created a program that will blink an LED by accessing the physical address of one of the Raspberry Pi's GPIO pins and configuring it to output and then toggling it on and off. Attempting this project, is a great way to get started with embedded programming and hopefully provide a better understanding of how a computer works.

What do you need?

Hardware

  • Raspberry PI 3
  • SD card pre-loaded with a bootable image
  • Breadboard
  • Male Female jumper wires
  • Male Male jumper wires
  • LED
  • 220 ohm resistor (doesn't have to be exactly 220 ohms, most any resistor will work)
  • mini sd card
  • mini sd card pre loaded with raspberry pi operating system (usually included with the pi)

Software

Alright lets Get STARTED!

Step 1: SETTING THINGS/STUFF UP

Okay so... the first step is to acquire to hardware. You could buy the parts separately or there is a kit that comes with more than enough parts. LINK

This kit comes with everything needed to setup the raspberry pi 3 and more! the only thing not included in this kit is an extra mini sd card. Wait! Don't buy another one just yet. If you don't plan on using the linux installation preloaded on the card then just copy the contents of the included mini sd card for later and re format the card (more on that later). IMPORTANT NOTE: make sure you keep the files on the included card you will NEED them for later!

Next it's time to setup the software. This tutorial will not include detailed instructions on how to install the software. There are many resources and tutorials online on how to install these:

WINDOWS USERS:

  • Download and install gcc.

LINUX/MAC

  • Linux distributions come with gcc pre-installed
  • Download and install GNU ARM embedded toolchain.

Okay so if all goes well then you should be able to open the terminal(linux/mac) or cmd line(windows) and try typing

  • arm-none-eabi-gcc

The output should look similar to the first picture. This is just to verify that it is installed correctly.

Alright now that the pre-requisites are out of the way, it's time to get started with the fun stuff.

Step 2: CIRCUIT

Circuit time! The circuit for this is simple. We will connect a led to GPIO 21 (pin 40) on pi (see picture 2 and 3). A resistor is also connected in series to prevent the led from being damaged. The resistor will be connected to the negative column on the breadboard which will be connected to GND (pin 39) on the pi. When connecting the led be sure to connect the short end to the negative side. See the last picture

Step 3: BOOTABLE Mini SD

There are three steps to get your pi 3 to recognize your blank mini sd card. We need to find and copy bootcode.bin,start.elf, and fixup.dat. You can get these files on the included mini sd card if you bought the canakit or make a bootable sd card for the pi 3 with a linux distribution. Either way these files are necessary to allow the pi to recognize the sd card as a bootable device. Next, format the mini sd to fat32(most mini sd cards come formatted in fat32. I used a cheap mini sd card from sandisk), move bootcode.bin, start.elf, fixup.dat onto the sd card. And your done! Okay one more time and in the order of the pictures the steps are:

  1. Find bootcode.bin, start.elf,fixup.dat.
  2. Make sure your sd card is formatted to fat32.
  3. Move bootcode.bin,start.elf, and fixup.dat onto the formatted sd card.

Here's how I figured this out, link .

Step 4: CHECK Mini SD

Alright, we have a bootable mini sd card, and hopefully, you have a pi 3 at this point. So now we should test it to make sure the pi 3 is recognizing the mini sd card as bootable.

On the pi, near the mini usb port there are two small led's. One is red. This is the power indicator. When the pi is receiving power this light should be on. So if you plug your pi in right now with no mini sd card it should light up red. Okay now unplug your pi and put in your bootable mini sd card that was created in the previous step and plug the pi in. Do you see another light? There should be a green light, right next to the red one, that indicates that it is reading the sd card. This led is called the ACT led. It will illuminate when there is a viable sd card inserted. It will flash when it is accessing your mini sd card.

Okay so two things should have happened after you inserted the bootable mini sd card and plugged the pi in:

  1. The red led should be illuminated indicating power reception
  2. The green led should be illuminated indicating that it has booted into the mini sd card

If something went wrong try repeating the previous steps or click on the link below for more information.

Link here is a good reference.

Step 5: CODE1

This project is written in ARM assembly language. A basic understanding of ARM assembly is assumed in this tutorial, but here is a few things you should know:

  • .equ: assigns a value to a symbol i.e abc .equ 5 abc now represents five
  • ldr: loads from memory
  • str: writes to memory
  • cmp: compares two values by performing a subtraction. Sets flags.
  • b: branch to label
  • add: performs arithmetic

If you don't have any experience with Arm assembly watch this video. It will give you a good understanding of the Arm assembly language.

Okay so right now we have a circuit that is connected to our raspberry pi 3 and we have an sd card that the pi recognizes, so our next task is figuring out how to interact with the circuit by loading the pi with an executable program. In general, what we need to do is tell the pi to output a voltage from GPIO 21(pin connected to the red wire). Then we need a way to toggle the led to make it blink. To do this we need more information. At this point we have no idea how to tell GPIO 21 to output which is why we must read the datasheet. Most micro-controllers have data-sheets that specify exactly how everything works. Unfortunately, the pi 3 does not have official documentation! However, there is an unofficial data-sheet. Here are two links to it:

  1. https://github.com/raspberrypi/documentation/files...

  2. https://web.stanford.edu/class/cs140e/docs/BCM2837...

Okay at this point, you should take a few minutes before moving onto the next step to look through the data-sheet and see what information you can find.

Step 6: CODE2:Turn_Led_ON

The raspberry pi 3 53 registers to control the output/input pins (peripherals). The pins are grouped together and each group is assigned to a register. For GPIO we need to be able to access the SELECT register, SET register, and CLEAR registers. To access these registers we need the physical address's of these registers. When you are reading the data-sheet you only want to note the offset of the address (lo byte) and add that to the base address. You have to do this because the datasheet is listing the linux virtual address which are basically values that the operating systems assign. We are not using an operating system so we need to access these registers directly by using the physical address. To this you need the following information:

  • Base Address of Peripherals: 0x3f200000. The pdf (page6) says that the base address is 0x3f000000, however, this address will not work. Use 0x3f200000
  • Offset of FSEL2(SELECT) not the full address of the register. The pdf lists FSEL2 at 0x7E20008 but this address refers to the linux virtual address. The offset will be same so that is what we want to note. 0x08
  • Offset of GPSET0(SET):0x1c
  • Offset of GPCLR0(CLEAR):0x28

So you probably noticed that the data-sheet lists 4 SELECT registers, 2 SET registers, and 2 CLEAR registers so why did I choose the ones I did? This is because we want to use GPIO 21 and FSEL2 controls GPIO 20-29, SET0 and CLR0 controls GPIO 0-31. The FSEL registers assigns three bits for every GPIO pin. Since we are using FSEL2 that means bits 0-2 control GPIO 20, and bits 3-5 control GPIO 21 and so on. The Set and CLR registers assign a single bit to every pin. For example, bit 0 in SET0 and CLR0 controls GPIO 1. To control GPIO 21 you would set bit 21 in SET0 and CLR0.

Okay so we've talked about how to access these registers, but what does it all mean?

  • FSEL2 register will be used to set GPIO 21 to output. To set a pin to output you need to set the lo order bit of the three bits to 1. So if bits 3-5 control GPIO 21 that means we need to set the first bit, bit 3 to 1. This will tell the pi that we want to use GPIO 21 as an output. So if we were to look at the 3 bits for GPIO 21 they should look like this after we set it to output, b001.
  • GPSET0 tells the pi to turn on the pin(output a voltage). To do this we just toggle the bit that corresponds with the GPIO pin we want. In our, case bit 21.
  • GPCLR0 tells the pi to turn off the pin(no voltage). To turn off the pin set the bit to the corresponding GPIO pin. In our case bit 21

Before we get to a blinking led, first lets make a simple program that will simply turn on the led.

To start off we need to add two directives to the top of our source code.

  • .section .init tells the pi where to put the code
  • .global _start

Next, we need to layout all the addresses we will be using. Use .equ to assign readable symbols to the values.

  • .equ GPFSEL2,0x08
  • .equ GPSET0, 0x1c
  • .equ GPCLR0, 0x28
  • .equ BASE, 0x3f200000

Now we are going to create masks to set the bits that we need to be set.

  • .equ SET_BIT3,0x08 This will set bit three 0000_1000
  • .equ SET_BIT21,0x200000

Then we need to add our _start label

  • _start:

Load base address into register

  • ldr r0,=BASE

Now we need to set bit3 of GPFSEL2

  • ldr r1,SET_BIT3
  • str r1,[r0,#GPFSEL2] This instruction says to write back the bit 0x08 to the address of GPFSEL2

Finally we need to set GPIO 21 to on by setting bit 21 in the GPSET0 register

  • ldr r1,=SET_BIT21
  • str r1,[r0,#GPSET0]

The final product should look something like the code pictured.

The next step is to compile the code and create a .img file that the pi can run.

  • Download the attached makefile, and kernel.ld and if you want the turn_led_on.s source code.
  • Put all the files in the same folder.
  • If you are using your own source code edit the makefile and replace the code = turn_led_on.s with code = .s
  • Save the makefile.
  • Use the terminal(linux) or cmd window(windows) to navigate to your folder containing the files and type make and hit enter
  • The make file should generate a file called kernel.img
  • Copy kernel.img onto your mini sd card. Your cards contents should be as pictured(pic 3):bootcode.bin, start.elf, fixup.dat, and kernel.img.
  • Eject the mini sd card and insert it into the pi
  • Plug pi into power source
  • LED should light up!!!

SLIGHTLY IMPORTANT NOTE: Apparently instructables had a problem with the makefile not having an extension, so I re-uploaded it with a .txt extension. Please remove the extension when you download it for it to work properly.

Step 7: CODE3:BLINKY_LED

And finally, it is time to make to led blink!

This is relatively simple compared to the previous steps. All we need to do is write an infinite loop and inside that loop turn the LED on then DELAY then turn the LED off delay and loop.

  • Open your turn_led_on.s, then save it as blinky_led.s
  • in the .equ section add .equ COUNTER,0xf0000. We will use this for the delays.
  • next somewhere before the loop add ldr r2,=COUNTER
  • At the end of the file add a new label Inifinite_loop:
  • on the next line add b Inifinite_loop. This means that the code will branch back to the Inifinite_loop label indefinitely.
  • In the loop turn the led on: str r1,[r0,#GPSET0] and then put 0 in r10: mov r10,#0
  • Add label delay:
  • on the nextline add: add r10,r10,#1 this will increment r10 by 1
  • on the nextline add: cmp r10,r2 this will compare r10 with the value COUNTER
  • bne delay this means that the program will be stuck in a loop until r10 increments to COUNTER this will delay the program
  • Now we need to turn off the led: str r1,[r0,#GPCLR0] this sets bit 21 in the CLEAR register. Turns the led off.
  • Next copy the delay loop from above and change delay to delay2
  • open the makefile and change turn_on_led.s on line 4 to blinky_led.s or the filename you choose.
  • execute make like the previous step.
  • replace kernel.img on the sd card with the new kernel.img
  • plug sd card into pi and power up!
  • Led should blink at a rate of about 1 blink per second!

SLIGHTLY IMPORTANT NOTE: Apparently instructables had a problem with the makefile not having an extension, so I re-uploaded it with a .txt extension. Please remove the extension when you download it for it to work properly.

Step 8: FINISH

So that's it. Hopefully, you now have a blinking led!

I hoped enjoyed this instructable!

Share

    Recommendations

    • Metalworking Contest

      Metalworking Contest
    • Tiny Home Contest

      Tiny Home Contest
    • Creative Misuse Contest

      Creative Misuse Contest

    8 Discussions

    0
    None
    Bagels1b

    Question 2 months ago

    Thanks for the information. Yeah, I see the 0x8000 in the elf file but the binary starts at address 0. I can see the .data and .bss sections are now just offset from 0 instead of 0x8000 in the binary so their allocated space is retained. I still don't know how to put the starting address in the binary though. I don't think the raspberry pi needs it because the GPU handles initial boot but other setups might.

    I'll check out your blinking row of LED's after I get done playing around with this tutorial.

    I'm not able to download either makefile. I get a Failed - forbidden error. Maybe the "null" extension of the file is causing a problem?

    2 replies

    Sorry about that. Instructables had a problem with the file not having an extension. I re-uploaded it as makefile.txt, just rename it "makefile" without the .txt and you should be good to go.

    Edit: thanks for letting me know!

    1
    None
    Bagels1b

    Question 2 months ago

    In the .s file there is no .text section and the .ld file sets the address of .text to be 0x8000. Looking at the elf file the 0x8000 address isn't there. Should there be a .text section in the .s code (or .init address in the .ld file)?

    Even if the 0x8000 is setup for the .text (or .init?) section and it shows up in the elf does it have any effect on the binary that is created after objcopy?

    1 more answer

    That is a good question. I didn't really understand why I had to do this either for this project either. According to a git hub tutorial, link, it has to do with allowing room for setting up the stack and some debugging stuff. I don't think it should matter for this project because it doesn't use the stack, however when I tried to compile it without it didn't work.

    I have another instructable where I make a row of leds blink. In that it is more clear what the purpose of the kernel.ld is. In that project, the source code I wrote does have a .text section in it because to use the stack you have manually set aside space for it. So all of you code is inserted after 0x8000 and then sp (stack pointer) is moved to 0x8000. You can see the effect of it on the kernel.elf in the attached image. Everything before the 0x8000 line is 0 or null, then you can see that after that line the code is inserted.

    The objcopy just translates the .elf into a binary format. This is done because the raspberry pi looks for a binary image file. So even though the .img does not have a 0x8000 address it is still telling the raspberry pi to partition the memory the way you told it to. So .text or code after 0x8000 and everything before 0x8000 is left empty.

    I hope that sort of answered your questions. Feel free to message me with any other questions or post them here and I will try my best to answer them. Unfortunately writing bare metal assembly for the raspberry pi 3 isn't very well documented.

    ghex.png

    Well done. Not many people left who can do the job in assembly.

    1 reply

    Thanks! I know it is sort of reinventing the wheel especially since this could be done with like 5 lines of python code but I enjoy learning about how things work on a low level.