Tuesday, January 7, 2014

Developing STM32 microcontroller code on Linux (Part 2 of 8, building the cross-compiler)

The first post of this series covered the steps to build and run code for the STM32. This post is going to cover how to build a cross-compiler for the STM32.

The steps to build a cross-compiler are somewhat covered here and here. In theory, building a cross-compiler is a pretty straightforward process:
  1. Cross compile binutils, to get things like as (assembler), ld (linker), nm (list object symbols), etc.
  2. Cross compile gcc, which gives you a C and C++ compiler.
  3. Cross compile newlib, which gives you a minimal libc-like environment to program in.
However, there is a big gotcha. Not all combinations of binutils, gcc, and newlib work together. Worse, not all combinations of them build on all development environments, which can make this something of a frustrating experience. For instance, it is known that binutils < 2.24 does not build on machines with texinfo 5.x or later. Thus, on modern machines (like Fedora 19), you must use binutils 2.24 or later. Also, I found that the latest newlib of this writing (2.1.0) does not build on Fedora 19. Your mileage may vary, and this will almost certainly change in the future; the best advice I can give is to start with the latest versions of the packages and then slowly back off the ones that fail until you get a relatively recent combination that works. For the purposes of this post, I ended up using binutils 2.24, gcc 4.8.2, and newlib 2.0.0. This combination builds just fine on Fedora 19.

Now onto the steps needed to build the cross compiling environment. We first need to make sure certain tools are installed. We'll install the development tools through yum:

$ sudo yum install gcc make tar wget bzip2 gmp-devel \
mpfr-devel libmpc-devel gcc-c++ texinfo ncurses-devel
Next we fetch the relevant versions of the packages:

$ mkdir ~/cross-src
$ cd ~/cross-src
$ wget ftp://ftp.gnu.org/gnu/binutils/binutils-2.24.tar.gz
$ wget ftp://ftp.gnu.org/gnu/gcc/gcc-4.8.2/gcc-4.8.2.tar.bz2
$ wget ftp://sources.redhat.com/pub/newlib/newlib-2.0.0.tar.gz
Next we set some environment variables. This isn't strictly necessary, but will help us reduce errors in the following steps:

$ export TOPDIR=~/cross-src
$ export TARGET=arm-none-eabi
$ export PREFIX=~/opt/cross
$ export BUILDPROCS=$( getconf _NPROCESSORS_ONLN )
$ export PATH=$PREFIX/bin:$PATH
The TOPDIR environment variable is the directory in which the sources are stored. The TARGET environment variable is the architecture that we want our compiler to emit code for. For ARM chips without an operating system (like the STM32), we want arm-none-eabi. The PREFIX environment variable is the location we want our cross-compile tools to end up in; feel free to change this to something more suitable. The BUILDPROCS environment variable is the number of processors that we can use; we will use all of them while building to substantially speed up the build process. Finally, we need to add the location of the cross-compile binaries to our PATH so that later building stages can find it.

Now we can start building. We first need to build binutils:

$ cd $TOPDIR
$ tar -xvf binutils-2.24.tar.gz
$ mkdir build-binutils
$ cd build-binutils
$ ../binutils-2.24/configure --target=$TARGET --prefix=$PREFIX \
--enable-interwork --disable-nls
$ make -j$BUILDPROCS
$ make install
Basically we are unpacking binutils, doing an out-of-tree build (recommended), and then installing it. The flags to configure deserve some explanation. The --target flag tells binutils what target you want the tools to build for; that is, what kind of code will be emitted by the code. In our case, we want ARM with no operating system. The --prefix flag tells binutils that we want our tools to be installed to $PREFIX. The --enable-interwork flag allows binutils to emit a combination of ARM and THUMB code; if you don't know what that is, don't worry about it for now. Finally, the --disable-nls flag tells binutils not to build translation files, which speeds up the build. Assuming this step went fine on your development machine, there should be a set of tools in ~/opt/cross/bin (or whatever your top-level output directory is) called arm-none-eabi-*. If this didn't work, then you might want to try a newer or older version of binutils; you can't proceed any further without this working.

With binutils built, we can now move on to gcc:

$ cd $TOPDIR
$ tar -xvf newlib-2.0.0.tar.gz
$ tar -xvf gcc-4.8.2.tar.bz2
$ mkdir build-gcc
$ cd build-gcc
$ ../gcc-4.8.2/configure --target=$TARGET --prefix=$PREFIX \
--enable-interwork --disable-nls --enable-languages="c,c++" \
--without-headers --with-newlib \
--with-headers=$TOPDIR/newlib-2.0.0/newlib/libc/include
$ make -j$BUILDPROCS all-gcc
$ make install-gcc
Here we are unpacking gcc and newlib (which is required for building gcc), doing an out-of-tree build of the initial part of gcc, and then installing it. The flags to configure deserve some explanation. The --target flag tells gcc what target you want the tools to emit code for. The --prefix flag tells gcc that we want our tools to be installed to $PREFIX. The --enable-interwork flag allows gcc to emit a combination of ARM and THUMB code. The --disable-nls flag tells gcc not to build translation files, which speeds up the build. The --enable-languages flag tells gcc which compilers we want it to build; in our case, both the C and C++ compilers. The --without-headers --with-newlib and --with-headers flags tells gcc that it not to use internal headers, but rather to use newlib and the headers from newlib. Assuming this step finished successfully, there should be a file called ~/opt/cross/bin/arm-none-eabi-gcc, which is the initial compiler. Again, if it didn't work, then you might want to try a newer or older version of gcc; you can't proceed any further without this.

With the initial compiler built, we can now build newlib:

$ cd $TOPDIR
$ mkdir build-newlib
$ cd build-newlib
$ ../newlib-2.0.0/configure --target=$TARGET --prefix=$PREFIX \
--enable-interwork
$ make -j$BUILDPROCS
$ make install
Since we've already unpacked newlib, we skip that step. Here we are doing an out-of-tree build of newlib, using the compiler that we built in the last step. The configure flags have the same meaning as previously.

With newlib built, we can now go back and finish the build of gcc (the last step!):

$ cd $TOPDIR/build-gcc
$ make -j$BUILDPROCS
$ make install
This finishes the gcc build, and installs it to $PREFIX. That's it! You should now have a $PREFIX directory full of tools and headers useful for building code to run on the STM32.

Update Jan 8, 2014: Updated the formatting so it is more readable.

13 comments:

  1. Fedora 20 should have an arm-none-eabi-gcc with newlib rather soon: https://bugzilla.redhat.com/show_bug.cgi?id=913254

    ReplyDelete
    Replies
    1. Ah, nice to know. Thanks!

      Delete
    2. it's already in repository - try yum install arm-none-eabi-gcc-cs
      arm-none-eabi-gcc-cs-2013.05.23-1.fc20.x86_64.rpm
      arm-none-eabi-binutils-cs-2013.05.23-6.fc20.x86_64.rpm

      Delete
  2. How about getting the toolchain from https://launchpad.net/gcc-arm-embedded, uncompress it, and possibly put symbolic links to the tools in your ~/.local/bin.
    Much easier then building a toolchain yourself and works flawlessly.

    ReplyDelete
    Replies
    1. Sure. I actually started with that. But I wanted to show how to do it from scratch. I think it is educational, as you learn about all of the pieces that need to come together to make it work.

      Delete
    2. Please help me to run "the toolchain from https://launchpad.net/gcc-arm-embedded" on fedora, it works on ubuntu as it said in web page "http://hertaville.com/2013/09/02/stm32f0discovery-part-1-linux/". But I dont want to use ubutnu, with problems of kernel embeded firewall, also I do no trust ubuntu as fedora or redhat. First, I get an error like "arm-none-eabi-gcc: error trying to exec 'cc1': execvp: No such file or directory". After adding PATH exact location of cc1, error has became

      =================== /*********************** ^ cc1: error: unrecognized command line option ‘-mlittle-endian’ cc1: error: unrecognized command line option ‘-mthumb’ cc1: warning: ‘-mcpu=’ is deprecated; use ‘-mtune=’ or ‘-march=’ instead [enabled by default] cc1: error: unrecognized command line option ‘-mthumb-interwork’ cc1: error: unrecognized command line option ‘-mfloat-abi=hard’ cc1: error: unrecognized command line option ‘-mfpu=fpv4-sp-d16’ src/stm32f4xx_it.c:1:0: error: bad value (cortex-m4) for -mtune= switch
      ====================
      please help!!!

      Delete
    3. it seems like "standard" gcc invoked, but not arm-none-eabi-gcc. Usually it should be called explicitly with prefix as arm-none-eabi-gcc and not as shorthand "cc".
      if u need binaries on fedora why dont u just "yum install arm-none-eabi-gcc-cs"?

      Delete
    4. Bacause arm-none-eabi-gcc-cs is based CodeSourcery, I dont want to use it.
      cc1 is not called directly, make file references "arm-none-eabi-gcc", arm-none-eabi-gcc references gcc.

      Delete
  3. Hi Chris,
    I am a novice at this and am trying to compile gcc as mentioned by you .I am facing a few issues.
    The first issue is that in the blog the configure options are hidden.
    On trying to compile I got a few errors which are
    -> gmp,mpfr and mpc libraries missing.
    I overcame this issue by downloading the same,compiling them and finally using the --with-gmp,--with-mpfr flags and --with-mpc flags.
    ->I then compiled gcc(initial version) with the
    $ make -j$BUILDPROCS all-gcc
    $ make install-gcc
    commands. THe first was successful however make install-gcc fails with no rule error.
    Please help..My development environment is the same and I too am trying to use the discovery kit(version 103)

    ReplyDelete
  4. Hi Chris,

    I apologize for the previous comment...I obviously did not successfully install mpfr etc in the first go...but can you please provide the complete yum install....and configure commands...

    ReplyDelete
    Replies
    1. Yeah, sorry about the blog formatting. I couldn't really figure out how to make blogger do what I wanted :(. I'll play around and try to make it a bit more readable.

      Delete
  5. I have a question to ask. Is it possible to use 'glibc' instead of 'newlib'? Or there are some troubles with the ARM code compatibility?

    ReplyDelete
    Replies
    1. Hm, good question. I don't know. glibc is much larger than newlib, which is why newlib is typically preferred in embedded applications like this. However, that doesn't mean that you might not, in theory, be able to use glibc instead. Someone would have to try it out :).

      Delete