Step03 – Bare Metal Programming in C Pt3

Fork me on GitHub

Introducing CMake

If you really don’t want to know anything about the [CMake]( build system,
just skip this section of the tutorial. There’s no ARM bare metal in it – just compiling the same
armc-09 tutorial with a build system! However, to continue with the tutorials, you’ll need CMake
installed to configure + build the tutorials.

CMake is an excellent build system, I much prefer it to autotools and certainly prefer it in
comparison to hand-crafting Makefile’s!
CMake’s documentation
is great, so feel free to peruse it whilst reading this tutorial. The tutorial isn’t going to
cover CMake, other than showing how it can help and some rudimentary stuff which shows how CMake
can be setup to do exactly what we were doing in our simple command-line build.

CMake generally detects the host information, that is the computer you’re compiling on and will
assume that the target is the same as the host. However, it’s very easy to tell CMake not to use
that assumption and instead use a system that we’re describing or hinting at. We can hint for
example that the target is MinGW when the host is Linux and CMake will setup a lot of settings,
knowing that they are useful for targetting Windows. On embedded systems we’re describing the
system ourself. It’s very easy to describe to CMake because it doesn’t need to know much. It
needs to know what the toolchain filename’s are and where they’re located. Let’s look at the
toolchain-arm-none-eabi-rpi.cmake that I’ve added to the arm010 folder:


# A CMake toolchain file so we can cross-compile for the Rapsberry-Pi bare-metal


# usage
# cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-arm-none-eabi.cmake ../

# The Generic system name is used for embedded targets (targets without OS) in
# CMake
set( CMAKE_SYSTEM_NAME          Generic )

# Set a toolchain path. You only need to set this if the toolchain isn't in
# your system path. Don't forget a trailing path separator!
set( TC_PATH "" )

# The toolchain prefix for all toolchain executables
set( CROSS_COMPILE arm-none-eabi- )

# specify the cross compiler. We force the compiler so that CMake doesn't
# attempt to build a simple test program as this will fail without us using
# the -nostartfiles option on the command line

# We must set the OBJCOPY setting into cache so that it's available to the
# whole project. Otherwise, this does not get set into the CACHE and therefore
# the build doesn't know what the OBJCOPY filepath is
    CACHE FILEPATH "The toolchain objcopy command " FORCE )

# Set the CMAKE C flags (which should also be used by the assembler!
set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mfpu=vfp" )
set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mfloat-abi=hard" )
set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv6zk" )
set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mtune=arm1176jzf-s" )


If you’ve not used CMake before, some things may look a bit strange – but it’s only syntatical.
I think you can read CMake files relatively easily even when you’ve never seen it before.

The toolchain file describes our toolchain. The CMAKE_SYSTEM_NAME is essential, and Generic is
used by CMake to essentially set the system type to something CMake doesn’t yet know about.

The CMAKE_SYSTEM_PROCESSOR is just a string describing the processor type, CMake doesn’t use this
setting on a system it doesn’t know.

The TC_PATH (toolchain path) is blank because the toolchain is expected to be in the path. If
it’s not, then set TC_PATH to something sensible. If you do have to set this – make sure you add a
trailing path separator!

The CROSS_COMPILE settings is simply the toolchain executable prefix.

The CMAKE_FORCE_C_COMPILER setting is the executable name of the c compiler and it’s type (GNU),
it’s mainly built up of variables that we set earlier. We use the FORCE c compiler variable to
tell CMake that it shouldn’t try to use the compiler before we tell it to. Normally CMake trys to
build a blank test program with the compiler to make sure it’s working correctly. However, we need
the -nostartfiles flag in order to build something that succeeds and so CMake’s test would fail
and it would not it would think the compiler was broken.

The CMAKE_OBJCOPY setting looks a bit fancier doesn’t it!? CMake knows that CMAKE_C_COMPILER needs
to be accessible by the entire project, so it adds it automatically to the CACHE. The CACHE is
available to all of the CMake files in the build system. If we set a variable in this toolchain
file it only has local scope (meaning only this file can see the value we set to this variable).
Therefore we force CMake to add this to the cache. The cache requires a “type” for the variable so
CMake knows how to use it. In fact most things in CMake are strings. See the documentation for
further information on these settings (Look at commands->set->CACHE in the documentation).

The CMAKE_C_FLAGS settings creates a list of flags that are passed to the compiler when compiling
a c source file. Here, we just use the processor-specific flags and again as with OBJCOPY we want
to put the CMAKE_C_FLAGS setting from here in the CACHE so that the entire project has this
variable. We can add more generic flags like optimisation level to the c flags later on because
they’re not specific to a particular processor. So, notice that we also have
toolchain-arm-none-eabi-rpibplus and toolchain-arm-none-eabi-rpi2 for the other board types

As with autotools, CMake uses a configuration step which works out what should be built and how.
The result of the configuration step, if successful, is a Makefile which we can use with make
(On Windows you must use mingw32-make) to build our project.

CMake uses a file in each directory that needs building called CMakeLists.txt. It’s a simple text
file as it’s extension suggests. Here’s the CMakeLists.txt file for arm010:


cmake_minimum_required( VERSION 2.8 )

# Mark the language as C so that CMake doesn't try to test the C++
# cross-compiler's ability to compile a simple program because that will fail
project( armc-010-tutorial C ASM )

# We had to adjust the CMAKE_C_FLAGS variable in the toolchain file to make sure
# the compiler would work with CMake's simple program compilation test. So unset
# it explicitly before re-setting it correctly for our system
set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -nostartfiles" )

add_executable( armc-010
    armc-010-start.S )

    TARGET armc-010 POST_BUILD
    COMMAND ${CMAKE_OBJCOPY} ./armc-010${CMAKE_EXECUTABLE_SUFFIX} -O binary ./kernel.img
    COMMENT "Convert the ELF output file to a binary image" )

We specify a minimum version required, I just tend to start at 2.8 because most people have it,
but our simple examples in this tutorial can most probably be used with earlier versions than

The project() call tells CMake what the project is called and what languages are required.
The languages can be left out, and frequently are – but there’s a few reasons why we need to
tell CMake explicitly what languages we’re using here.

  1. If we don’t do this, CMake tests the C++ compiler to make sure it can compile a simple
    program to validate the C++ compiler. However, as our system is bare metal and requires
    specific linker steps this fails and CMake falls over at the first hurdle. CMake assumes
    that C and C++ are the project’s languages if you do not tell it any languages explicitly.
  2. Assembler files (.S) in the source list are ignored completely by CMake unless we
    “enable” the assembler by telling CMake in the project definition that we’re using assembly
    files. When we enable assembler, CMake detects gcc as the assembler rather than as – this
    is good for us because we then only need one set of compilation flags.

As we’ll see in a bit, to successfully configure, we need to pass some C flags to CMake when
configuring. This is so that the C compiler sanity check passes by enabling the C compiler to
successfully compile a small C program.

We then set the C flags that CMake will use when compiling C ( and assembler! ) files. These
are exactly the same as we were passing to the gcc invocation in our bat or sh file to compile.

We set the linker flags for when CMake links an executable. Again, this is the same as we were
passing to the Linker through gcc when compiling and linking in one command. So CMake will pass
this setting to the linker which will make sure we’re using our linker script.

Then we add an executable target. A target in CMake is basically something to build. An
executable means that CMake knows this target is an executable. If we don’t apply any special
settings to the executable, the target name becomes the output filename for the target, plus
any default prefix or suffix (for example Windows executables have the .exe suffix added).
After the target name, we simply list the source files (including header files so that
CMake can work out the dependencies for the build process).

Finally we add a custom command associated with the executable target we’ve just defined. We
declare it as being required to be run after building the executable target. This uses objcopy
from our toolchain file to extract the binary image from the ELF file to a kernel image.


So now building the example is slightly different. Navigate into the build sub-directory of the
tutorial and run the batch file or bash script called configure. You can look in the script file
to see how the configure call is done with CMake. The result of this configure command will be a
CodeBlocks project file that you can use to edit, and build the tutorial, and there’s a Makefile
which the CodeBlocks project uses. You can use this Makefile directly to build the tutorial.

From now on, when I use the term ${MAKE} I mean on Linux use the make command, and on Windows use
the mingw32-make command. If you’ve not got mingw32-make on Windows then you best go get it now.
I suggest getting the a copy from the mingw-builds project – just download one of the latest
toolchains and it will come with mingw32-make.

Build the project – it’s the same as the arm009 tutorial, but built with the CMake build system
instead. So long as it works for you, this is all then CMake I want to go into – this is an ARM
bare metal tutorial, not a build-systems tutorial. However, it’s important because soon the
number of files we’ll have will increase and we do not want to be having a hugely complicated
command line to build each tutorial ( or at least, I don’t! )

System Timer Blinking LED

Let’s now look at using another peripheral in the Raspberry-Pi processor. We’ve so far used the
GPIO controller to turn an LED on and off, we’ll keep that going, but the rate at which we’ve
been turning it on and off is unknown to us. We’ve just used a volatile loop so the processor
takes some amount of time to do the work we’ve requested (loop and increment a variable!) and
then it changes the state of the LED. It’d be much better if we could request that the processor
pause for a certain amount of time that is easy to determine in software.

The Raspberry-Pi processor (both BCM2835/6) has a peripheral called the System Clock. It’s a
64-bit counter that continuously runs at a fixed rate. It’s the introduction of this timer in
ARM cores that made porting operating systems (which all need to be time-aware for thread context
switching, etc.) easy!! Previously porting an operating system to a new processor, even in the
same family of parts could require considerable re-writes.

Again, referring to the
on page 172 is the System Timer peripheral. This is a free-running 64-bit counter as we’ve
already seen.

We need to access these registers, and of the whole peripheral all we have to do is keep an eye
on the counter value as it increases. Each increment takes 1us, so keeping track of time using
the System Timer is very easy. In C, we can write a “driver” for this peripheral very easily for
this basic functionality. We don’t even have to initialise the peripheral.

Firstly, we’ll need the base address of the system timer:

#define RPI_SYSTIMER_BASE       0x20003000

Remember from Pt1 of the tutorial that the physical address 0x7E00000 is mapped to the ARM
physical address 0x2000000, so 0x7E003000 becomes 0x20003000 to the processor!

Then we can generate a struct which is structured the same as the registers for the peripheral:

typedef struct {
    volatile uint32_t control_status;
    volatile uint32_t counter_lo;
    volatile uint32_t counter_hi;
    volatile uint32_t compare0;
    volatile uint32_t compare1;
    volatile uint32_t compare2;
    volatile uint32_t compare3;
    } rpi_sys_timer_t;

In the driver code, we can declare this type of structure at memory address RPI_SYSTIMER_BASE
by using a variable:

static rpi_sys_timer_t* rpiSystemTimer = (rpi_sys_timer_t*)RPI_SYSTIMER_BASE;

and then finally, we can implement our delay function:

void RPI_WaitMicroSeconds( uint32_t us )
    volatile uint32_t ts = rpiSystemTimer->counter_lo;

    while( ( rpiSystemTimer->counter_lo - ts ) < us )
        /* BLANK */

Here, all we do is get the current value of the counter wait until the counter is the required
number of us higher than when the function was called before returning. As per the Cambridge
tutorials, this version ignores the upper 32-bits of the counter. It will take over an hour
before the timer increases above 32-bits anyway, so I’m not expecting us to test a blinking LED
for that long!


#include <string.h>
#include <stdlib.h>

#include "rpi-gpio.h"
#include "rpi-systimer.h"

/** GPIO Register set */
volatile unsigned int* gpio = (unsigned int*)GPIO_BASE;

/** Main function - we'll never return from here */
void kernel_main( unsigned int r0, unsigned int r1, unsigned int atags )
    int loop;

    /* Write 1 to the GPIO16 init nibble in the Function Select 1 GPIO
       peripheral register to enable GPIO16 as an output */
    gpio[GPIO_GPFSEL1] |= ( 1 << LED_GPFBIT );

    /* Never exit as there is no OS to exit to! */

        /* Set the GPIO16 output high ( Turn OK LED off )*/

        /* Wait half a second */
        RPI_WaitMicroSeconds( 500000 );

        /* Set the GPIO16 output low ( Turn OK LED on )*/

        /* Wait half a second */
        RPI_WaitMicroSeconds( 500000 );

Now we have a slightly slower blinking LED where the time is known – 0.5 seconds before toggling
the LED. The system timer can be extremely useful. This is definitely the easiest way to get
using a timer on the Raspberry-Pi!

Don’t forget to build armc-011 tutorial you need CMake installed. Then you can build with:

cd arm011\scripts

or on Linux

cd arm011/scripts

I think in the next tutorial part we’ll introduce interrupts. The code in this tutorial is far
from ideal. Function calls that wait half a second before returning are not what we want.
Interrupts can help keep our system more responsive so in that 0.5s we can get on and do other
stuff and just let the processor interrupt the code when the 0.5s is up.

I’ve also included a more fancy use of the timer functions to allow some PWM dimming of the LED
so you can check out armc-012 yourself. It’s at least a little bit more interesting compared to
a normal flashing LED!

When you’re ready,
head over to Pt4…

25 thoughts on “Step03 – Bare Metal Programming in C Pt3

  1. poofjunior

    Many thanks! This is very well-explained!
    One question, though: in the RPI_WaitMicroseconds function, how do we know the rate at which the system timer increments? It looks like the datasheet doesn’t mention anything about rate in that section. (I’m guessing it’s probably nearly as fast as the input clock speed of the Pi, but I think most Pi’s are 700[MHz], but the input int, a value of microseconds has no scaling to account for this.)

    1. Brian_S Post author


      I would love to be able to reference a document from Broadcom, but unfortunately their information is severely lacking. Instead, I gleaned this information from the web and also proved it by timing the GPIO line with a scope on a known loaded value and it figures out okay. The System Timer runs independently of the processor clock so altering the processor clock does not alter this System Timer rate.

      Google a bit and you’ll see multiple references to this on webpages, but no references to documentation proving the fact. I’ll add some information about that to the tutorial. Thanks for the feedback!

  2. Sujai Moses


    It’s been quite a while since part 3 was posted. Please post part 4 soon.

    And by the way, thanks for this series. Absolutely brilliant! 🙂

    1. Brian_S Post author

      I realise it’s been a while, but I’m writing up pt4 now and it should be online in the next week. I had some problems getting interrupts working which turned out to be a misreading of the documentation so it took longer than I was expected to do the code.

      Thanks for reading, and hopefully you’ll have some more to read soon. Comments like yours always spur me on, so thank-you!

  3. h11sam

    I hope you finish pt4. I’m thinking of doing some OS programming for the raspberry pi as a summer project, now that I’m done with uni this year.


  4. jiadong

    hey, first of all, thx for nice explanation. it’s really useful for beginners, like me to pick up. I am an EE undergraduate student and really enjoy learning these amazing geek stuff. Btw, really looking forward to ur pt5. 🙂 cheers.

  5. shawn

    Hey Brian,

    can you explain me why i am getting below 2 errors when i am trying to run make file after program successfully builds?

    shawn@theWarfarePC:/media/undead/arm_eabi_gcc practice/arm011/build$ make
    Scanning dependencies of target armc-011
    [ 20%] Building C object CMakeFiles/armc-011.dir/armc-011.c.obj
    [ 40%] Building C object CMakeFiles/armc-011.dir/armc-011-cstartup.c.obj
    [ 60%] Building C object CMakeFiles/armc-011.dir/armc-011-cstubs.c.obj
    [ 80%] Building ASM object CMakeFiles/armc-011.dir/armc-011-start.S.obj
    [100%] Building C object CMakeFiles/armc-011.dir/rpi-systimer.c.obj
    Linking C executable armc-011
    arm-none-eabi-gcc: error: practice/arm011/rpi.x: No such file or directory
    make[2]: *** [armc-011] Error 1
    make[1]: *** [CMakeFiles/armc-011.dir/all] Error 2
    make: *** [all] Error 2

    it has rpi.x file inside but still it is showing no such file or directory.

    1. Brian_S Post author

      Hi Shawn,

      Please remove the space from your directory name “arm_eabi_gcc practice” and things should work. Sorry, the CMakeLists.txt file is not fully safe against spaces in paths…

      Best Regards, Brian.

      1. shawn

        Thank you very much Brian, it was my silly mistake to add space in directory name. Your tutorials are very helpful. Thank you again.

  6. Reda Baydoun

    Hi Brian,

    I have successfully build all the projects up until now but I’m always finding myself obligated to comment out this line from the toolchain-arm-none-eabi-rpi.cmake file:

    set( CMAKE_C_FLAGS “${CMAKE_C_FLAGS} -mfloat-abi=hard” )

    I’ve had difficulties in the part 2 as well, having to remove the “-mfloat-abi=hard” parameter to have the project compile and link. Could you please shed some light on this issue I’m having? I’m building on a Windows machine.

    Much thanks for your help and awesome tutorial!

    Best regards,


  7. Mahesh Sreekandath

    Thanks for the excellent documentation Brian!

    Just to add, Raspberry Pi 2 (BCM2836) System Timer Base address calculation is not very straightforward. Adding 0x3000 to the GPIO base address gives us 0x3F203000, but the actual base address in fact 0x3F003000. This was extracted from the the Linux device tree file.

    1. Mahesh Sreekandath

      Please disregard my above comment, for some reason I equated GPIO base address to I/O peripheral base address.

      BCM2835 & BCM2836 peripheral base addresses are 0x20000000 and 0x3F000000 respectively. Which actually makes System Timer base address calculation quite straightforward — 0x20000000/0x3F000000 + 0x3000

  8. Federico

    In raspberry pi 2 there is no more status register in the timer, so the control_status makes the code to wait eternally. Removing the line

    volatile uint32_t control_status;

    makes the code work; also the base of the timer changed, for rasp pi 2 is: 0x4000001c.
    Thank you for your tutorial, hope this is helpful to people with no working timer for the led even if the tutorial is great

  9. Markus

    Hi Brian,
    I wasn’t able to configure cmake on lesson 010.
    “configure_rpi2.bat” gives me the following output:

    E:\Documents\PV\RaspBerry_Arduino\Raspberry\workspace\baremetal\arm-tutorial-rpi-master\part-3\arm010\scripts>cmake -G “CodeBlocks – MinGW Makefiles” -DCMAKE_TOOLCHAIN_FILE=../toolchain-arm-none-eabi-rpi2.cmake ../
    — The ASM compiler identification is unknown
    — Found assembler: arm-none-eabi-gcc
    CMake Error at CMakeLists.txt:36 (project):


    is not a full path to an existing compiler tool.

    Tell CMake where to find the compiler by setting either the environment
    variable “CC” or the CMake cache entry CMAKE_C_COMPILER to the full path to
    the compiler, or to the compiler name if it is in the PATH.

    On the cmake file i defined “set( TC_PATH “C:/GNUARM/arm-none-eabi/bin/” )”. I set the path because it gave me the same message as above, like this:



    is not a full …

    even so i set the path: C:\Program Files (x86)\CMake\bin;C:\MinGW\mingw32\bin;C:\GNUARM\arm-none-eabi\bin

    What is wrong here can you help?

  10. MOd

    So the path should be something like C:\GNUARM\bin\ and not C:\GNUARM\bin\arm-none-eabi\bin\
    After several trial and error, i was removing the path i added manually to the system path
    then configured with an empty set( TC_PATH “” ) in the cmake file. Dunno whats going on but it worked i finally got my makefile.

    After successfully building and linking of the code with the makefile, it then failed to convert the ELF file to the img file. Finally i started from scratch, deleted everything i set and generated so far and added the paths as they were missing. Took me a while but now IT WORKS.

    And when you edit your system path, you need to restart your comand line tool. This and the mistake mentioned above kept me busy (the fact that there are two “bin” folders confuses me). Aside that, it should have worked right the first try, just following your tutorial.

    Very happy to conclude on part 3, cannot wait for part 4 🙂

  11. Ton

    Hi Brian,

    I am using a model A+. To make arm011 I did run (the bplus variant is also good for model a plus) and the kernel.img works fine.
    Then I decided to use an older raspberry pi model with a ‘big’ SD card adapter, so that the model A ‘small’ SD socket would not be used over and over.
    I did run to make a new build environment. However, it did not work.
    In /arm-tutorial-rpi/part-3/arm011/scripts/CMakeFiles/armc-011.dir I see a file called flags.make that had not been updated when I ran (It contains the C_DEFINES = -DRPIBPLUS=1)

    So, when you configure for one model, re-configuring does not automatically go well.

    Is there some sort of configure clean? Or is that already solved in further parts?

    Great show BTW 🙂

  12. Ton

    Solution is to remove everything from the /scripts directory except for the original config-*.bat/sh files. Then re-run the right config job.

  13. Alexandre

    Hi Brian, all,

    Another great part I enjoyed until the last sip 🙂

    I’m running a PI2 and playing with the timers I found a mismatch in the GPIO for the led.
    When running armc-011 with asymmetric timers, it’s like the state of the led in inverted compared to the GPIO as explained in the section 1 of this tutorial. According to the schematic the led is turned on when the GPIO is turned OFF.

    /* Set the GPIO16/47 output high ( Turn OK LED off )*/

    /* Keep OFF for 200ms */
    RPI_WaitMicroSeconds( 200000 );

    /* Set the GPIO16/47 output low ( Turn OK LED on )*/

    /* Keep ON for one second */
    RPI_WaitMicroSeconds( 1000000 );

    My expectation for this code is the led is ON longer than is is OFF.
    My observation however is different: the led is OFF for 1 second and shortly turns ON.

    So I wonder where is the trick.
    Is it the macro in rpi-gpio.h which is incorrect (PI2):
    #define LED_ON() do { gpio[LED_GPCLR] = ( 1 << LED_GPIO_BIT ); } while( 0 )
    #define LED_OFF() do { gpio[LED_GPSET] = ( 1 << LED_GPIO_BIT ); } while( 0 )
    Is it the PCB layout which is different from the schematic ?
    Is it the GPIO peripheral of the BCM2836 which does no behave as described in the "BCM2835 ARM Peripherals" document ?

    As often read here and there, It's a pity the documentation of the PI project is so confusing and poorly updated. It's far for the openness I expected when I started with it…

    Any other one who experienced that ? Or am I really too tired and mixed up ? :))


  14. David

    Thank you for these great tutorials. They are well written and don’t make assumptions about the skill level of the reader. Being a newb to embedded programming I have fount this very helpful. With that being said, there were a couple of thing that I found which may help others of my skill level. All, I believe, are for Windows.
    1) If you specify the toolchain path (TC_PATH) you have to use a slash instead of a backslash for the directory separator.


    3) You can use directories with spaces, however you will have to enter the short name into the .cmake files. If you don’t know the short name, you can get it using dir/x on the command line.

    Now off to part 4…

  15. Leon de Boer

    Brian sorry me again 🙂

    From arm-11 on you define this in rpi-systimer.h

    extern void RPI_WaitMicroSeconds( uint32_t us );

    Yet you provide the function within the C code of rpi-systimer.c why are you declaring it extern????

    It probably doesn’t matter to you because you are using a makefile process but it has the nasty effect on GCC of fouling the automatic compiler order. I have only rarely seen that form of interface declaration done when for some strange reason you want to use the function somewhere else but not provide the header file.

    In fact the only time I can ever remember seeing that was with a clash of a pile of other function names between different units and it was a hack to provide the single function without including the header file and getting the clash.

    I removed it and everything was fine but I am wondering if you had some reason for that?

Leave a Reply to Jerry Cancel reply