Saturday 6 March 2021

Developing for Cortex MCUs on a Raspberry Pi

Recently, on the Gitter Ada Programming Language lobby, there have been discussions on cross-compiling for Cortex M micro-controllers, specifically the BBC micro:bit, using the Raspberry Pi.

TL;DR: it's not going to be easy, unfortunately. Maybe later (or earlier?) Raspbian releases will help.

The Pi uses an ARM processor, and its native compiler is configured for ARM (its target is arm-linux-gnueabihf - the hf means ‘hard floating-point support’). The GCC-8 compiler is capable of generating code for many ARM CPUs: gcc -mcpu=x generates

arm2 arm250 arm3 arm6 arm60 arm600 arm610 arm620 arm7 arm7d arm7di
arm70 arm700 arm700i arm710 arm720 arm710c arm7100 arm7500 arm7500fe
arm7m arm7dm arm7dmi arm8 arm810 strongarm strongarm110 strongarm1100
strongarm1110 fa526 fa626 arm7tdmi arm7tdmi-s arm710t arm720t arm740t
arm9 arm9tdmi arm920 arm920t arm922t arm940t ep9312 arm10tdmi 
arm1020t arm9e arm946e-s arm966e-s arm968e-s arm10e arm1020e 
arm1022e xscale iwmmxt iwmmxt2 fa606te fa626te fmp626 fa726te 
arm926ej-s arm1026ej-s arm1136j-s arm1136jf-s arm1176jz-s 
arm1176jzf-s mpcorenovfp mpcore arm1156t2-s arm1156t2f-s 
cortex-m1 cortex-m0 cortex-m0plus cortex-m1.small-multiply 
cortex-m0.small-multiply cortex-m0plus.small-multiply 
generic-armv7-a cortex-a5 cortex-a7 cortex-a8 cortex-a9 cortex-a12 
cortex-a15 cortex-a17 cortex-r4 cortex-r4f cortex-r5 cortex-r7 
cortex-r8 cortex-m7 cortex-m4 cortex-m3 marvell-pj4 
cortex-a15.cortex-a7 cortex-a17.cortex-a7 cortex-a32 cortex-a35 
cortex-a53 cortex-a57 cortex-a72 cortex-a73 exynos-m1 xgene1 
cortex-a57.cortex-a53 cortex-a72.cortex-a53 cortex-a73.cortex-a35 
cortex-a73.cortex-a53 cortex-a55 cortex-a75 cortex-a75.cortex-a55 
cortex-m23 cortex-m33 cortex-r52

which certainly includes the cortex-m0 in the micro:bit (version 1; cortex-m4 in version 2 boards).

As an introduction to this, consider Inspirel’s Ada and SPARK on ARM Cortex-M on-line tutorial, the executable commands in which are written to be run on a Pi (for example, in AdaCore and FSF compilers, the ARM cross-compiler is called arm-eabi-gcc, whereas on the Pi it’s just gcc).

The sources, slightly adapted from Chapter 3, are (spec):

package Program is
   procedure Run
   with
     Export,
     Convention => C,
     External_Name => "run";
end Program;

{body):

package body Program is
   procedure Run is
   begin
      loop
         null;
      end loop;
   end Run;
end Program;

(loader script):

OUTPUT_FORMAT("elf32-littlearm")
OUTPUT_ARCH(arm)

MEMORY
{
    flash (RX)  : ORIGIN = 0x00000000, LENGTH = 256K
    sram (RWX)  : ORIGIN = 0x20000000, LENGTH = 16K
}

SECTIONS
{
    .vectors :
    {
        LONG(_estack)
        LONG(run + 1)
        FILL(0)
    } > flash

    .text :
    {
	. = ALIGN(512);
        *(.text)
    } > flash

    PROVIDE(_estack = ORIGIN(sram) + LENGTH(sram));

}

which built as

$ gcc -c -mcpu=cortex-m0 -mthumb -mfloat-abi=soft program.adb
$ nm program.o
         U __aeabi_unwind_cpp_pr0
00000000 D program_E
00000000 T run
$ ld -T flash.ld -o program.elf program.o 
ld: program.o:(.ARM.exidx+0x0): undefined reference to `__aeabi_unwind_cpp_pr0'

Hmm.
After considerable research, I find that I can get the same result on the Mac with GCC 10, but only by specifying -funwind-tables.

You’d think that I could suppress this using -fno-unwind-tables, and get back to the Inspirel results, but it turns out that on this Raspbian (6.3 desktop, GCC 8.3 compiler) gnat1 (the actual compiler) inserts the unwind tables regardless of the setting of the switch! cc1, the C compiler, behaves as you would expect.

As a more challenging project, I tried working with Cortex GNAT RTS. To use this, you also need FreeRTOS, at version 10.0.1 (that will unpack to FreeRTOS-Kernel-10.0.1; if you do that in your home directory, you’ll either have to change the directory name to FreeRTOSv10.0.1, set the environment variable FREERTOS_RELEASE to FreeRTOS-Kernel-10.0.1, or edit cortex-gnat-rts/FreeRTOS.gpr to suit).

Starting in the cortex-gnat-rts/microbit directory, to build the RTS, make fails with

$ make
gprbuild -p -P build_runtime.gpr
gprconfig: can't find a toolchain for the following configuration:
gprconfig: language 'c', target 'arm-eabi', default runtime
gprconfig: can't find a toolchain for the following configuration:
gprconfig: language 'ada', target 'arm-eabi', runtime '/home/pi/cortex-gnat-rts/microbit'
Ambiguous variable substitution, need to specify the language (in TARGET)
Invalid setup of the gprconfig knowledge base
GNAT-TEMP-000001.TMP:1:01: "project" expected
gprbuild: processing of configuration project "/tmp/GNAT-TEMP-000001.TMP" failed
make: *** [Makefile:22: all] Error 4

The problem here is that build-runtime.gpr specifies

for Target use "arm-eabi";

which gprbuild (actually, gprconfig) uses to decide which compiler to use; it does this (see $prefix/share/gprconfig/compilers.xml) by looking for *-gnatls or just gnatls, and then in that executable’s tree for Ada RTS files under lib/gcc/$TARGET/$gcc_version.

On the Pi (with GNAT installed), there is indeed a suitable compiler, with target arm-linux-gnueabihf.

You might think, why would I specify that target when I’m compiling for a bare board with a cortex-m0 processor? The answer is, that left to its own devices, the compiler generates code for the Pi’s own CPU, but that doesn’t prevent it generating code for other CPUs on request.

We could say gprbuild -P build_runtime.gpr --target=arm-linux-gnueabihf, which picks up the actual target CPU switches from this in runtime.xml:

   package Compiler is
      Common_Required_Switches :=
         ("-mlittle-endian", "-msoft-float",
          "-mcpu=cortex-m0", "-mthumb");

though an alternative is to define TARGET on the make command line:

$ make TARGET=arm-linux-gnueabihf
gprbuild -p -P build_runtime.gpr
Compile
   [C]            port.c
In file included from /usr/include/features.h:448,
                 from /usr/include/arm-linux-gnueabihf/bits/libc-header-start.h:33,
                 from /usr/include/stdint.h:26,
                 from /usr/lib/gcc/arm-linux-gnueabihf/8/include/stdint.h:9,
                 from /home/pi/FreeRTOSv10.0.1/FreeRTOS/Source/include/FreeRTOS.h:49,
                 from /home/pi/FreeRTOSv10.0.1/FreeRTOS/Source/portable/GCC/ARM_CM0/port.c:33:
/usr/include/arm-linux-gnueabihf/gnu/stubs.h:7:11: fatal error: gnu/stubs-soft.h: No such file or directory
 # include <gnu/stubs-soft.h>
           ^~~~~~~~~~~~~~~~~~
compilation terminated.
gprbuild: *** compilation phase failed

Oh dear.

The Pi CPU has hard floating-point support, so the system includes have no need to supply the soft support.

Worse, these are the includes for the Pi! We need bare-metal includes, as you would get with Newlib.

Anyway, to cut a long story short(er), I tried the same with Cortex GNAT RTS’s STM34 runtime, and found that, although an executable was generated, it didn’t run, because it included Pi-related code.

No comments:

Post a Comment