Cross compiling Opencv 3 for ARM

I recently wanted to play around with the Opencv library on a Raspberry Pi. I ran into some issues to get the right version installed, so I decided that the only way forward, was to compile the latest version from source. This article is the result of having to research and scour the web for the relevant information. There is no one source, that had all the answers for my particular case, so I decided to document the process, in case someone else might find it usefull.

The standard way to install Opencv on the Raspberry Pi, would be the following:

sudo apt-get install libopencv-dev

This, however, installs and older, version 2 variety of the library. I wanted to stick with the more modern version 3, hence the reason for this whole excersize.

First of all, the Opencv library is huge. Compiling it on the Pi itself will take many hours, so this was obviously not an option. It has to be cross compiled from a host PC. My desktop PC runs Ubuntu 14.10, but these instructions should also relevant for newer versions.

Before we start. there is a bit of work to do to get all the pieces in the right place.

Download raspberry pi sysroot:
rsync -rl --delete-after --safe-links{lib,usr} /opt/arm/RaspberryPi/sysroot
Replace the x’s above, with your pi’s ip address. However, this method might not be practical. On my Pi, the /usr directory alone, was almost 2.5Gb. rsync would have take all day to copy it. An alternative method, is to put the SD card into a reader and mount the SD card directly on the host pc. Copy the /lib and /usr directories to your sysroot path. When you are done building OpenCV, you can just copy them back to the SD card.

****Fix libz link ../../../lib/arm-linux-gnueabihf/
****Fix link ln -s ../../../lib/arm-linux-gnueabihf/
****Install gstreamer1.0

Get the Raspberry PI tools repository, which contains the compiler:
git clone
In the subdirectory arm-bcm2708/ you will find different toolchains. I used gcc-linaro-arm-linux-gnueabihf-raspbian-x64, since my Ubuntu PC is 64bit. You should choocse the one that fits your architecture.

Point path to compiler in Raspberry pi tools
Make sure that the toolchain you selected, is added to your $PATH environment variable. In my case, I find it easier to add to my .bashrc file. Open it in your favorite text editor and add:
to the end of the file. Make sure the path refelcts your environment.

Configure OpenCV
Now we are ready to configure OpenCV for the build. We are going to use a platform build configuration, so find the file opencv/platforms/linux/arm-gnueabi.toolchain.cmake adn save it as pi.cmake. We will modify pi.cmake for our purpose. This could be done by ccmake, but I prefer to modified the file directly.

Find lines:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mthumb -fdata-sections -Wa,--noexecstack -fsigned-char -Wno-psabi")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mthumb -fdata-sections -Wa,--noexecstack -fsigned-char -Wno-psabi")

Change to:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -Wa,--noexecstack -fsigned-char -Wno-psabi -march=armv6 -mfloat-abi=hard -mfpu=vfp")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdata-sections -Wa,--noexecstack -fsigned-char -Wno-psabi -march=armv6 -mfloat-abi=hard -mfpu=vfp")

I removed the lines:


and replaced it with:

Find the lines:
Remove the version specifier so it looks like:
find_program(CMAKE_C_COMPILER NAMES arm-linux-gnueabi${FLOAT_ABI_SUFFIX}-gcc)
find_program(CMAKE_CXX_COMPILER NAMES arm-linux-gnueabi${FLOAT_ABI_SUFFIX}-g++)

Add archiver location
I had some problems initially, with the first lot of libraries that needs to be built. It turned out to be because cmake is unable to find the archiver, to create the libraries. To fix it, I had to tell cmake explicity where to find it. I am not entirely sure why this is needed, but if you have the same issue, the add the following line to pi.cmake:
set(CMAKE_AR /opt/arm/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-ar CACHE FILEPATH "Archiver")
Only add this if if you get build error.

Set Raspberry pi sysroot path:
set(ARM_LINUX_SYSROOT /opt/arm/RaspberryPi/sysroot CACHE PATH "ARM cross compilation system root")

Create build directory
mkdir build
cd build

cmake \
-D CMAKE_TOOLCHAIN_FILE=../platforms/linux/pi.cmake \
-D CMAKE_INSTALL_PREFIX=/opt/arm/RaspberryPi/sysroot \

make -j4
The -j4 is very handy if you happen to have a multi-core cpu. The 4 tells make, to use 4 cores for the build, to drastically reduce the build time.

sudo make install
This step will copy all the library and include files, to your Raspberry Pi sysroot directory. You can now copy the directories back to the Pi.

This exercise, is an example of cross compiling, the act of using a host platform, to build files that will be used or executed on another platform, different to the host environment. In this case, we used an Intel x64 based system, to build files suited to the ARM platform.

Building an ARM cross compiler from source

There are many ways to get an arm cross compiler installed on your platform of choice, but sometimes using a pre-built binary toolchain is not an option. I had such a case, where I had to use the latest version of GCC because that was what the rest of the project was using. At the time, there were no pre-built toolchains available, which contained the latest version of GCC.

There are tools to make the job easier. One such a tool is crosstool-NG, but to be honest, I did not have much success with it. Anyway, I foolishly decided to do it the hard way because I also wanted to understand the process. What follows, is the recipe I followed to get everything built. I will try to point out some gotchas or tips along the way. Be warned, that this is a tedious process with a million different things that can go wrong. It literally took me weeks to get all the issues ironed out, researching issues as I went along. Anyway, enough waffling, lets get on with it!

First, we need to obtain the source code packages for the different components that will eventually make up the toolchain. These are as follows:

Package Version
Binutils 2.24
Glibc 2.19
GMP 5.1.3
MPFR 3.1.2
Linux kernel
Boost 1.55.0

Technically, Boost is not part of the toolchain, but I am including it since we are building a C/C++ toolchain.

Download the source packages from their respective websites, or source control repositories. I don’t have links to these sites, Google is your friend.

I am using Ubuntu 12.10 as the host platform. Make sure that the build essentials package is installed:
sudo apt-get install build-essential

Also, if the compile fails somewhere along the line, make sure the libgmp-dev, libmpfr-dev and libmpc-dev are installed for your host platform.

The default shell on Ubuntu is Dash, which is incompatible with some of the shell scripts used in the building process. We need to make sure that /bin/sh points to bash, before we can begin:

sudo dpkg-reconfigure dash

A screen will popup, select ‘No’.

This process is loosely based on the description found in the book Building Embedded Linux Systems, but was adapted with the required changes for the latest versions being built. These step by step instructions are meant to be run from a single command line console, in the sequence described here. The whole process takes about an hour and a half, so take your time and concentrate on each step, since it is advisable to start over until the whole process goes through flawlessly.

Here are some steps to prepare the environment:

  1. Create directory from where we will work.
  2. Create two sub directories, one called ‘sources’ and the other one ‘build’.
  3. Extract the source packages that was downloaded, into the ‘sources’ directory. Each package will be in it’s own directory.
  4. Open a command console in the build directory and copy and paste the instructions into the console, as we go along.
  5. We will need write access to the destination path.

The process makes use of environment variables, to keep track of stuff. Each section contains shell code to create a clean directory in the build directory, from where the actual build will take place. This makes it easy to start over if we need to, while keeping the source directory in it’s original state. Modify the following environment variable to reflect your environment and copy them to the console:


We start by building binutils. This contains the tools like the linker, which we will need to link the object files generated by GCC.

Do the configuring:

[ -d $BUILDDIR/binutils ] && rm -rf $BUILDDIR/binutils
mkdir $BUILDDIR/binutils
cd $BUILDDIR/binutils


$BINUTILS_SRC/configure \
--disable-werror \
--build=$BUILDMACH \
--target=$TARGETMACH \
--prefix=$INSTALLDIR \
--with-sysroot=$SYSROOTDIR \

The run the following commands individually:

make install

Kernel headers
The kernel headers will contain the header files and descriptions of things like integer size, of the target system. These commands can all be pasted in one go:


make mrproper
make ARCH=$TARGETARCH integrator_defconfig

[ -d $SYSROOTDIR/usr ] && rm -rf $SYSROOTDIR/usr
mkdir $SYSROOTDIR/usr

make ARCH=$TARGETARCH headers_check

Bootstrap GCC
Now that the headers and binutils are in place, we are going to build the most basic of GCC compilers, called the bootstrap GCC. This compiler does not contain a C library yet, but it is perfectly suitable for writing bare metal code. This compiler will be used to build the C library later on.

Do the configuring:


[ -d $BUILDDIR/bootstrap-gcc ] && rm -rf $BUILDDIR/bootstrap-gcc
mkdir $BUILDDIR/bootstrap-gcc
cd $BUILDDIR/bootstrap-gcc

$GCC_SRC/configure \
--build=$BUILDMACH \
--host=$BUILDMACH \
--target=$TARGETMACH \
--prefix=$INSTALLDIR \
--without-headers \
--enable-bootstrap \
--enable-languages=c \
--disable-threads \
--enable-__cxa_atexit \
--disable-libmudflap \
--with-gnu-ld \
--with-gnu-as \
--disable-libssp \
--disable-libgomp \
--disable-nls \

The following commands must be run one at a time:

make all-gcc install-gcc

make all-target-libgcc install-target-libgcc

ln -s $INSTALLDIR/lib/gcc/$TARGETMACH/4.8.2/libgcc.a $INSTALLDIR/lib/gcc/$TARGETMACH/4.8.2/libgcc_sh.a

GLibC headers
Building the GLibC library is a two step process. The reason for the two steps, is to get around the chicken and egg situation, that the GLibC library needs headers built by the pre processor, but the headers do not exist yet. To get around this, the developers introduced a process whereby only the headers are built. The output of this process will then be used by the actual compiling of the library.

Do the configuring:


[ -d $BUILDDIR/libc ] && rm -rf $BUILDDIR/libc
mkdir -p $BUILDDIR/libc
cd $BUILDDIR/libc

echo "libc_cv_forced_unwind=yes" > config.cache
echo "libc_cv_c_cleanup=yes" >> config.cache

export CC=${CROSS}-gcc
export LD=${CROSS}-ld
export AS=${CROSS}-as

$LIBC_SRC/configure \
--build=$BUILDMACH \
--host=$TARGETMACH \
--prefix=$SYSROOTDIR/usr \
--with-headers=$SYSROOTDIR/usr/include \
--config-cache \
--enable-add-ons \
ports=yes \

Installing the headers:
make -k install-headers cross_compiling=yes install_root=$SYSROOTDIR

We need a symbolic link:
ln -s $INSTALLDIR/lib/gcc/$TARGETMACH/4.8.2/libgcc.a $INSTALLDIR/lib/gcc/$TARGETMACH/4.8.2/libgcc_eh.a

Now that the headers are installed we build the actual C library.

Do the configuring:
[ -d $BUILDDIR/libc ] && rm -rf $BUILDDIR/libc
mkdir -p $BUILDDIR/libc
cd $BUILDDIR/libc

echo "libc_cv_forced_unwind=yes" > config.cache
echo "libc_cv_c_cleanup=yes" >> config.cache

export CC=${CROSS}-gcc
export LD=${CROSS}-ld
export AS=${CROSS}-as

$LIBC_SRC/configure \
--build=$BUILDMACH \
--host=$TARGETMACH \
--prefix=/usr \
--with-headers=$SYSROOTDIR/usr/include \
--config-cache \
--enable-add-ons \
--enable-kernel=2.6.31 \
ports=yes \

Run the following commands one at a time:
make -k install-headers cross_compiling=yes install_root=$SYSROOTDIR


make install_root=${SYSROOTDIR} install

Building the next GCC
Now that we have a working C library, we are ready to build a version of GCC that makes use of it.

Do the configuring:
[ -d $BUILDDIR/final-gcc ] && rm -rf $BUILDDIR/final-gcc
mkdir -p $BUILDDIR/final-gcc
cd $BUILDDIR/final-gcc

echo "libc_cv_forced_unwind=yes" > config.cache
echo "libc_cv_c_cleanup=yes" >> config.cache
export CC=gcc
export LD=ld
export AS=as

$GCC_SRC/configure \
--build=$BUILDMACH \
--target=$TARGETMACH \
--prefix=$INSTALLDIR \
--with-sysroot=${SYSROOTDIR} \
--enable-languages=c,c++ \
--with-gnu-as \
--with-gnu-ld \
--disable-multilib \
--disable-libmudflap \
--with-float=soft \
--disable-sjlj-exceptions \
--disable-nls \
--enable-threads=posix \
--enable-long-longx \

Run the following commands one at a time:
make all-gcc
make install-gcc

Now that we have a version of GCC that contains a C library, we are ready to build the support libraries that will be needed by the final version of GCC.

GMP is a library with all sorts of arithmetic functions, as well as support for big numbers. This library will be needed by the final GCC.

Do the configuring:

[ -d $BUILDDIR/gmp ] && rm -rf $BUILDDIR/gmp
mkdir -p $BUILDDIR/gmp
cd $BUILDDIR/gmp

export CC=${CROSS}-gcc
export LD=${CROSS}-ld
export AS=${CROSS}-as
export CFLAGS=-static

$LIBGMP_SRC/configure \
--build=$BUILDMACH \
--host=$TARGETMACH \
--prefix=$INSTALLDIR \

Run the following two commands one at a time:
make install

The MPFR library contains code that implements precision floating point calculations. This library will be needed by the final version of GCC.

Do the configuring:

[ -d $BUILDDIR/mpfr ] && rm -rf $BUILDDIR/mpfr
mkdir -p $BUILDDIR/mpfr
cd $BUILDDIR/mpfr

export CC=${CROSS}-gcc
export LD=${CROSS}-ld
export AS=${CROSS}-as
export CFLAGS=-static

$LIBMPFR_SRC/configure \
--build=$BUILDMACH \
--host=$TARGETMACH \
--prefix=$INSTALLDIR \

Run the following two commands one at a time:
make install

Building the final GCC
At last we have everything we need to build the final version of the GCC toolchain.

Do the configuring:
[ -d $BUILDDIR/final-gcc-2 ] && rm -rf $BUILDDIR/final-gcc-2
mkdir -p $BUILDDIR/final-gcc-2
cd $BUILDDIR/final-gcc-2

export CC=gcc
export LD=ld
export AS=as
export CFLAGS=

echo "libc_cv_forced_unwind=yes" > config.cache
echo "libc_cv_c_cleanup=yes" >> config.cache

$GCC_SRC/configure \
--prefix=$INSTALLDIR \
--target=$TARGETMACH \
--build=$BUILDMACH \
--with-sysroot=${SYSROOTDIR} \
--bindir=$INSTALLDIR/bin \
--disable-nls \
--enable-shared \
--disable-multilib \
--disable-libmudflap \
--disable-libgomp \
--enable-languages=c,c++ \
--enable-c99 \
--enable-long-long \
--with-float=soft \
--with-mode=arm \
--with-arch=armv5te \
--with-abi=aapcs-linux \
--with-mpfr=$INSTALLDIR \

Run the following two commands one at a time:
make install

If everything went according plan, you should be able to run the following command to test your shiny new toolchain installation:
arm-unknown-linux-gnueabi-gcc -v

You should see something like this:
Using built-in specs.
Target: arm-unknown-linux-gnueabi
Configured with: /home/username/ARM/sources/gcc-4.8.2/configure --prefix=/usr/lib/arm-unknown-linux-gnueabi --target=arm-unknown-linux-gnueabi --build=i686-pc-linux-gnu --with-sysroot=/usr/lib/sysroot/arm-unknown-linux-gnueabi --bindir=/usr/lib/arm-unknown-linux-gnueabi/bin --disable-nls --enable-shared --disable-multilib --disable-libmudflap --disable-libgomp --enable-languages=c,c++ --enable-c99 --enable-long-long --with-float=soft --with-mode=arm --with-arch=armv5te --with-abi=aapcs-linux --with-mpfr=/usr/lib/arm-unknown-linux-gnueabi --with-gmp=/usr/lib/arm-unknown-linux-gnueabi
Thread model: posix
gcc version 4.8.2 (GCC)

Building Boost libraries
As mentioned before, the boost libraries are technically not part of the toolchain. I am adding them, because some of my projects rely on them.

The Boost libraries make use of an annoying proprietary build system, that almost require one to study it completely just for the sake of compiling the source code. I am not going to try and explain the how’s or why’s, because I am not that familiar with it. Allot of Googling was required to figure this one out.

I would suggest, building Boost in a temporary build location. This makes it easier to clean up after the build, you simply delete the build directory. This is achieved by specifying the --build-dir= parameter in the build command.

Firstly, edit the file tools/build/v2/user-config.jam and add the following line under the ‘GCC configuration’ section:
using gcc : arm : arm-unknown-linux-gnueabi-g++ ;

Run the bootstrap shell script that sets everything up:

Now we can perform the actual build:
./b2 install toolset=gcc-arm --build-dir=$HOME/temp/boostbuild --prefix=$INSTALLDIR/usr

We’re almost done. Before we can use the libraries, we need to do some more housekeeping to get around boost peculiarities. A convention seems to exist to add ‘-mt’ to the end of library names to indicate a multi thread safe version of a library. As far as I can ascertain the multithreaded and normal libraries are exactly the same, so we can merely add symbolic links to the original library. Below are the libraries that I have come across, that needs ‘-mt’ versions. You might very well encounter more, just add the links and you should be OK. Just don’t blame me if this is not the case.

ln -s libboost_filesystem.a libboost_filesystem-mt.a
ln -s $INSTALLDIR/usr/lib/ $INSTALLDIR/usr/lib/
ln -s $INSTALLDIR/usr/lib/libboost_regex.a $INSTALLDIR/usr/lib/libboost_regex-mt.a
ln -s $INSTALLDIR/usr/lib/ $INSTALLDIR/usr/lib/
ln -s $INSTALLDIR/usr/lib/libboost_system.a $INSTALLDIR/usr/lib/libboost_system-mt.a
ln -s $INSTALLDIR/usr/lib/ $INSTALLDIR/usr/lib/

In order to create libraries with the -mt suffix, we need to tell the build system at compile time, that we want the multithread safe libraries. The following parameters must be added to the build command above:
threading=multi --layout=tagged

That’s it, I hope you got this far without any major issues. If not, trace your previous steps CAREFULLY for any typos. It is advisable to start from scratch when an error occurred, I know it is tedious but there are no shortcuts in this complex process.

Binary blobs

This is a very short article on a neat feature of the GNU compile tools, that I don’t think many people are aware of. It is often required to include large sets of binary data with an executable. One option is to just include it as a separate data file. But there are cases where you might not have access to a storage medium. Another option is to convert it to a byte array and declare it as a constant array variable, but a large data set will quickly make your source files cumbersome to work with.

A neat feature of the GNU tools, is the ability to link a separate binary file right into the executable. This is ideal for our kernel project, where I included a font file as well a logo file, right into the kernel image file. The way this is done, is to use the objcopy tool, to convert the file into an object file as follows:

objcopy -I binary -O elf-littlearm32 -B arm ./bin/binfile.dat binfile.o

The resulting object file is then linked along with the other object files. The -I tells objcopy that our input file is in binary format. The -O indicates that our output format is elf-littlearm32. The -B indicates that our architecture is arm and the remaining two parameters are the input and out filenames.

Having a binary file linked into the executable is only half of the magic, though. We are not using it yet. The way we reference the data from our code, is a bit awkward at first. The linker resolves the memory occupied by our binary data to standard variables that we can reference from our code. To be exact, three variables are available, a start and end memory address pointer and a length variable. Using the command in previous example, results in the following variables:

unsigned char _binary_bin_binfile_dat_start
unsigned char _binary_bin_binfile_dat_end
unsigned int _binary_bin_binfile_dat_size

A little closer inspection should make it obvious how the linker comes up with variable names, based on the parameters we supplied. To see the variables that the linker provided from our previous example, run the following command:

objdump -x binfile.o

The variables _binary_bin_binfile_dat_start and _binary_bin_binfile_dat_end are pointers and should be used as follows:

unsigned char *ptr_start=(unsigned char*)&_binary_bin_binfile_dat_start;
unsigned char *ptr_end=(unsigned char*)&_binary_bin_binfile_dat_end;

And there you have it! A neat way to handle messy data sets without making your head explode.

Raspberry Pi Frame buffer

In my previous article I introduced you to VarkOS, my experimental OS for learning about the ARM architecture. In this article I will go over the steps involved in getting to the point where we can display text and graphics on the screen. For source code that implements what we are going to do here, head over to my Github repository for a copy of the complete VarkOS project, or view the framebuffer source code here.

The frame buffer is essentially a block of memory, which gets mapped to the screen coordinates, based on some parameters we configure. But before we can get into the nitty gritty of how this work, we need to understand how our platform, the Raspberry Pi, works.

The Raspberry Pi CPU is actually a high performance graphical processor with an ARM CPU strapped to its back. The CPU, ARM1176JZF-S, basically boots by reading the firmware files from the SD card, loads our kernel image into memory and starts executing it. Communication between the ARM CPU and the graphical processor, is by means of a mailbox mechanism. The mailbox is nothing other than a set hardware registers that are accessible by both processors. From our point of view, we write our message to memory and then pass the memory address of our message to the GPU, via the mailbox.


The mailbox’s hardware register addresses are found at the following addresses:

Register Address
Base 0x2000B880
Poll 0x2000B890
Sender 0x2000B894
Status 0x2000B898
Configuration 0x2000B89C
Write 0x2000B8A0

We are only going to concern ourselves with some of these registers. Writing to the mailbox is done as follows:

  1. Read the status register (0x2000B898).
  2. Check if bit 31 (0x80000000) is set.
  3. If not, go back to step 1 else proceed to next step.
  4. The data to be written, has to be left shifted by 4 bits. This converts our address to 28 bits.
  5. The lower 4 bits will contain the mailbox channel number, combine this with 28bit address by performing a logical OR with the 28 bit address from step 4.
  6. Write the data to the Write register (0x2000B8A0).

We read from the mailbox as follows:

  1. Read the status (0x2000B898) register.
  2. Check if bit 30 (0x40000000) is set.
  3. If not, go back to step 1.
  4. Read the base register (0x2000B880).
  5. The lower 4 bits will contain the mailbox channel number of the mailbox that responded. Check that this matches the mailbox we are interested in. If not, then go back to step 1.
  6. The upper 28 bit should contain the same address that we passed in originally.

It is important to note, that the addresses passed to and from the mailbox are left shifted by 4 bits. This is because the first 4 bits contains the mailbox channel number. There are 10 mailbox channels:

  1. Power management
  2. Framebuffer
  3. Virtual UART
  4. VCHIQ
  5. LEDs
  6. Buttons
  7. Touch screen
  8. NA
  9. Mailbox-property-interface – ARM to GPU
  10. Mailbox-property-interface – GPU to ARM

Although channel 1 is marked as being the Framebuffer channel, we will be using channel 8.

Configuring the framebuffer this way, is achieved by querying and setting various tags to the appropriate values. Below is a list of the tags that are relevant to the framebuffer.

Register Address
Allocate buffer 0x00040001
Release buffer 0x00048001
Blank screen 0x00040002
Get physical (display) width/height 0x00040003
Test physical (display) width/height 0x00044003
Set physical (display) width/height 0x00048003
Get virtual (buffer) width/height 0x00040004
Test virtual (buffer) width/height 0x00044004
Set virtual (buffer) width/height 0x00048004
Get depth 0x00040005
Test depth 0x00044005
Set depth 0x00048005
Get pixel order 0x00040006
Test pixel order 0x00044006
Set pixel order 0x00048006
Get alpha mode 0x00040007
Test alpha mode 0x00044007
Set alpha mode 0x00048007
Get pitch 0x00040008
Get virtual offset 0x00040009
Test virtual offset 0x00044009
Set virtual offset 0x00048009
Get overscan 0x0004000a
Test overscan 0x0004400a
Set overscan 0x0004800a
Get palette 0x0004000b
Test palette 0x0004400b
Set palette 0x0004800b

The way these tags are used, is to format a buffer in a way which will be explained soon. The address of this buffer is then sent to the graphics processor via the mailbox mechanism explained above. What is important to understand, is that we are not sending a message and getting response, but rather our buffer is being modified directly to include the response data filled in by the graphics processor. The mailbox analogy could be confusing, by making it seem that we are sending and receiving messages, where in fact we are providing a buffer and some of our original request values get modified with the appropriate response values, by the graphics processor.

Remember that the buffer we are going to use to configure the frame buffer, has to be aligned to a 16 byte boundary, since we will be passing it through the mailbox. It should be declared as an array of unsigned, 32 bit integers. The layout of tags in this buffer is as follows:

Offset Description
0 Total Buffer size (number of bytes)
1 Request/Response indicator
0x00000000 – Request
0x80000000 – Success Response
0x80000001 – Error Response
2 Tag ID
3 Tag value length (number of bytes)
4 Tag Request/Response indicator
0x00000000 – Request
0x80000000 – Success Response
0x80000001 – Error Response
5 Value data
Value data
n 0 – End tag

The steps for configuring basic frame buffer functionality, is as follows:

  1. Query the physical width and height of the frame buffer by using tag 0x00040003. The response should match the supported size of the attached monitor on the HDMI port.
  2. Use the information in step 1 to request a matching frame buffer by setting all the relevant tags IN ONE GO. It is important that concatenated tags are used in a single request, since sending the requests one by one does not provide the correct context for the graphics processor to know what is required, in which case it will just ignore all the parameters you provide. The tags we will set are as follows:
    1. 0x00048003 – Set physical width and height. This was retrieved in step 1.
    2. 0x00048004 – Set virtual width and height. For now, just set it to same values as the physical settings.
    3. 0x00048005 – Set depth. This is how many bits should be used per pixel, to denote colour. I have only tried 16 and 24 so far.
    4. 0x00040001 – Allocate the actual buffer in memory, based on the provide tag values. Provides required alignment in bytes, in our case 16.
  3. Parse the response by checking for errors.
  4. Get the pitch by using tag 0x00040008, which tells us how many bytes are in one row of our framebuffer.

If everything went according to plan, you should now have a frame buffer that you can write to. One thing to note, is the layout of the tag values that allocate the frame buffer, tag 0x00040001. What is interesting here, is that we are actually expecting more values in the response, than what we have populated in the request. As such, we must be sure to leave space for the response value to be filled in. This was not immediately apparent to me and took me longer than I would have liked, to figure out what was going here. We are only populating the required alignment, but in the response we will receive the alignment value as well as the actual frame buffer memory address pointer, that we are after.

The information contained in this article is not entirely all my own, but was collected though tireless research from a number os sources and by examining source code of similar projects.

  1. The Bare Metal section in the Raspberry Pi forum is a great source of information.
  2. Tags are documented in the Raspberry Pi firmware wiki
  3. BCM2835 (SoC) Datasheet
  4. ARM1176JZF-S Technical Reference