Inhaltsverzeichnis

arm-none-eabi-gcc toolchain

Es ist wie immer im Leben - alles eigentlich ganz einfach, wenn man weiß, wie es geht. In diesem Fall hat es mich allerdings vier Tage gekostet, eben dies heraus zu finden.

Abstrcact:

Es geht darum, die Software-Beispiele, die ST für das STM32F429I-DISCO anbietet mit der ARM-GCC-Toolchain (arm-none-eabi) zu kompilieren. Vorgesehen ist eigentlich, dass man dafür irgendwelche kommerziellen Toolchains/IDEs verwendet und das ist auch einigermaßen dokumentiert. Für andere Boards, gibt es fertige Beispiele mit passenden Makefiles für den GCC, aber für das STM32F429I-DISCO konnte ich nirgendwo etwas passendes finden.

Die Lösung

Man braucht kein Make-File (auch wenn das vielleicht vieles einfacher und schneller macht). Man kann folgenden einfachen Befehl nutzen, um mit GCC die Beispiel-Projekte zu kompilieren:

arm-none-eabi-gcc -Wall -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard -nostdlib \
  -I../../../Libraries/CMSIS/Device/ST/STM32F4xx/Include -I../../../Libraries/CMSIS/Include  \
  -I../../../Utilities/STM32F429I-Discovery -I../../../Libraries/STM32F4xx_StdPeriph_Driver/inc \
  -I./ -T TrueSTUDIO/LTDC_Display_2Layers/STM32F429ZI_FLASH.ld -o display.elf \
  -DUSE_STDPERIPH_DRIVER \
  ../../../Libraries/CMSIS/Device/ST/STM32F4xx/Source/Templates/gcc_ride7/startup_stm32f429_439xx.s *.c \
  ../../../Libraries/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_gpio.c \
  ../../../Libraries/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_rcc.c \
  ../../../Libraries/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_ltdc.c \
  ../../../Utilities/STM32F429I-Discovery/stm32f429i_discovery_lcd.c \
  ../../../Utilities/STM32F429I-Discovery/stm32f429i_discovery_sdram.c \
  ../../../Libraries/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_spi.c \
  ../../../Libraries/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_dma2d.c \
  ../../../Libraries/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_fmc.c \
  ../../../Utilities/STM32F429I-Discovery/stm32f429i_discovery_ioe.c \
  ../../../Libraries/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_i2c.c \
  ../../../Libraries/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_dma.c

Ja, das ist ein Befehl… - wie man sieht ist dieser Befehl für das Beispiel LTDC_Display_2Layers und wegen eines Fehlers in dem Beispiel von ST (aktuelle Version STM32F429I-Discovery_FW_V1.0.1) compiliert es nicht, aber dazu später mehr. Der Eintrag

-T TrueSTUDIO/LTDC_Display_2Layers/STM32F429ZI_FLASH.ld

muss für die anderen Beispiele entsprechend angepasst werden und unter Umständen braucht man da auch noch andere Bibliotheken.

Das so entstandene .elf-binary muss noch ein bisschen massiert werden:

arm-none-eabi-objcopy -O binary display.elf display.bin

Und schon fällt ein binary heraus, das wir direkt an die passende Stelle in den Flash schreiben können:

st-flash write display.bin 0x8000000

LTDC_Display_2Layers

Dieses Beispiel zeigt zwei Bilder, die etwas unbeholfen über den TFT holpern - oder würde das zeigen, wenn ST da ordentlichen Code geschrieben hätte. @ST: Es gibt Betriebssysteme mit Case-Sensitiven Dateisystemen. Man muss also eine kleine Änderung in der main.h vornehmen:

#include "ST_logo1.h"
#include "ST_logo2.h"

wird zu

#include "st_logo1.h"
#include "st_logo2.h"

Und schon klappt es wie oben beschrieben.

Eigene Bilder darstellen

Die beiden ST-Logos sind … auch schön. Ein Blick in st_logo1.h zeigt, dass da offenbar die Daten des Bildes einfach als 19200 32-Bit-HEX-Werte des Bitmap übergeben werden. Aus der main.c kann man dann noch entnehmen, dass es sich offenbar um RGB565 handelt. Es werden also für jedes Pixel zwei Byte übergeben mit dem Aufbau rrrrrggg gggbbbbb. Also jeweils 5 Bit Rot, 6 Bit Grün und 5 Bit Blauwert. Das Display hat eine Auflösung von 320×240 Pixel, die beiden Bilder haben jeweils halbe Display-Höhe, also eine Größe von 240×160 Pixel und wie es der Zufall will kommt das auch recht gut hin: 240 * 160 * 2 Byte = 19200 * 32 Bit.

Also braucht man jetzt nur noch ein Bild der passenden Größe und muss die Pixel noch passend rechnen:

import sys
from PIL import Image
 
im = Image.open(sys.argv[1])
im_list = []
n = 0
for i in range(im.size[1]):
    for j in range(im.size[0]):
        r, g, b, a = im.getpixel((j,i))
        val = int(r * ((1 << 5) - 1) / 255) << 11
        val += int(g * ((1 << 6) - 1) / 255) << 5
        val += int(b * ((1 << 5) - 1) / 255)
        if n % 2 == 0:
            im_list.append(val)
        else:
            im_list[-1] += val << 16
        n += 1
print (",\n".join(map(lambda x: "0x%08X" % (x,), im_list)))

Die Ausgabe garniert man noch mit den passenden Kopf- und Fußzeilen aus den bestehenden st_logo1.h bzw. st_logo2.h und schon hat man da was eigenes - Holleri du dödl di, diri diri dudl dö!

Linux auf dem STM32F429I

Unter https://github.com/jserv/stm32f429-linux-builder gibt es eine sehr einfache Anleitung, wie man Linux auf dem STM32F429I-DISCO zum laufen bringt. Unter Debian Squeeze lässt sich die aktuelle Version von OpenOCD nicht ohne weiteres installieren, da libusb zu alt ist, aber für Arch Linux funktioniert die Anleitung problemlos.

Das Makefile lädt Sourcen für U-Boot, uCLinux und eine Busybox-Umgebung nach. Das fertige System bootet innerhalb von etwa 2 Sekunden in eine Shell auf der Seriellen Schnittstelle auf PC10 (TXD) und PC11 (RXD).

Umgebungsvariablen für die Toolchain setzen

Um gezielt Änderungen an U-Boot, Kernel oder Busybox vornehmen zu können oder eigene kompilieren zu können müssen ein paar Umgebungsvariablen gesetzt werden. Dazu sollte man sich ein kleines Skript als vars.sh erstellen (Pfade müssen natürlich angepasst werden):

vars.sh
#!/bin/bash
 
INSTALL_PATH=${HOME}/stm32f4/stm32f429-linux-builder
export ARCH=arm
export CROSS_COMPILE=arm-uclinuxeabi-
export CROSS_TOOLCHAIN=${HOME}/stm32f4/stm32f429-linux-builder/arm-2010q1/bin
export PATH=$CROSS_TOOLCHAIN:$PATH

Kernel

Man kann die Kernel-Config recht einfach für die eigenen Belange anpassen, sollte sich allerdings vor Augen halten, dass von den 8MB RAM mit Minimalsystem schon nur noch 4MB verfügbar sind.

. vars.sh
cd $INSTALL_PATH/uclinux
make -C $INSTALL_PATH/uclinux O=$INSTALL_PATH/out/kernel menuconfig

Hier tue man, was getan werden muss … und hernach:

make -C $INSTALL_PATH/uclinux O=$INSTALL_PATH/out/kernel xipImage modules
cat $INSTALL_PATH/uclinux/arch/arm/boot/tempfile \
    $INSTALL_PATH/out/kernel/arch/arm/boot/xipImage > $INSTALL_PATH/out/kernel/arch/arm/boot/xipImage.bin
 
$INSTALL_PATH/out/uboot/tools/mkimage -x -A arm -O linux -T kernel -C none -a 0x08020040 -e 0x08020041 \
    -n "Linux-2.6.33-arm1" -d $INSTALL_PATH/out/kernel/arch/arm/boot/xipImage.bin \
    $INSTALL_PATH/out/kernel/arch/arm/boot/xipuImage.bin
cd $INSTALL_PATH

Jetzt kann man den neuen Kernel auf das Board übertragen.

Eigene Module

Wie man Module für den Linux Kernel schreibt erfährt man im schönen Buch Linux Device Drivers. Folgendes Beispiel basiert auf einem Beispiel aus Kapitel 2 der dritten Ausgabe:

stm32f4_blink.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>
#include <mach/gpio.h>
 
MODULE_AUTHOR("Moritz Rosenthal");
MODULE_DESCRIPTION("This module does nothing useful");
MODULE_LICENSE("Dual BSD/GPL");
 
struct timer_list timer1;
unsigned char value;
 
void timer1_routine(unsigned long data)
{
    mod_timer(&timer1, jiffies + HZ); /* restarting timer */
    value ^= 0x01;
    gpio_set_value(110, value);
}
 
static int stm32f4_blink_init (void) {
 
    printk(KERN_ALERT "Starting blink module\n");
    value = 0;
    gpio_set_value(109, 1);
 
    /* initialize a timer */
    init_timer(&timer1);
    timer1.function = timer1_routine;
    timer1.data = 1;
    timer1.expires = jiffies + HZ; /* 1 second */
    add_timer(&timer1);
 
    return 0;
}
 
static void stm32f4_blink_exit (void) {
    printk(KERN_ALERT "No more blinking\n");
    del_timer_sync(&timer1); /* Deleting the timer */
    gpio_set_value(109, 0);
    gpio_set_value(110, 0);
}
 
module_init(stm32f4_blink_init);
module_exit(stm32f4_blink_exit);
Makefile
obj-m:= stm32f4_blink.o

Beide Dateien legt man passender Weise in ein Verzeichnis mit dem Namen stm32f4_blink und wechselt in dieses Verzwichnis. Kompilieren kann man das ganze dann mit folgenden Befehlen:

. ${HOME}/stm32f4/stm32f429-linux-builder/vars.sh
make -C $INSTALL_PATH/uclinux O=$INSTALL_PATH/out/kernel M=`pwd` modules

Die entstehende .ko-Datei kann man zum Test einfach per rx auf das Board übertragen und dann mit insmod laden:

Welcome to                                                                                                                         
          ____ _  _                                                                                                                
         /  __| ||_|                                                                                                               
    _   _| |  | | _ ____  _   _  _  _                                                                                              
   | | | | |  | || |  _ \| | | |\ \/ /                                                                                             
   | |_| | |__| || | | | | |_| |/    \                                                                                             
   |  ___\____|_||_|_| |_|\____|\_/\_/                                                                                             
   | |                                                                                                                             
   |_|                                                                                                                             
                                                                                                                                   
For further information check:                                                                                                     
http://www.uclinux.org/                                                                                                            
                                                                                                                                   
Jan  1 00:00:01 login[27]: root login on 'ttyS2'                                                                                   
~ # cd /var/
/var # rx stm32f4_blink.ko                                                                                                         
C
/var # insmod stm32f4_blink.ko 
Starting blink module
/var # lsmod
Module                  Size  Used by    Not tainted
stm32f4_blink            524  0 
ext2                   33828  1 
mbcache                 2320  1 ext2
/var # rmmod stm32f4_blink
No more blinking

busybox

Das Vorgehen für die Busybox ist im wesentlichen das gleiche wie beim Kernel. Es lohnt z.B. das programm rx (Miscellaneous Utilities) mit in die Busybox hinein zu nehmen, um im laufenden System per X-Modem-Protokoll Dateien in die Ramdisk übertragen zu können.

. vars.sh
make -C $INSTALL_PATH/busybox-1.22.1 O=$INSTALL_PATH/out/busybox menuconfig
make -C $INSTALL_PATH/out/busybox CFLAGS="-march=armv7-m -mtune=cortex-m4 -mlittle-endian -mthumb \
    -Os -ffast-math -ffunction-sections -fdata-sections -Wl,--gc-sections -fno-common \
    --param max-inline-insns-single=1000 -Wl,-elf2flt=-s -Wl,-elf2flt=16384" SKIP_STRIP=y \
    CONFIG_PREFIX=$INSTALL_PATH/out/romfs install

Bootloader, Kernel und ROMFS installieren

Es ist sicherlich auch möglich, einzelne Teile zu installieren, ich mach das aber bisher immer komplett, wie es auch make install aus stm32f429-linux-builder tut:

. vars.sh
cp -af $INSTALL_PATH/rootfs/* $INSTALL_PATH/out/romfs
cp -f $INSTALL_PATH/out/kernel/fs/ext2/ext2.ko $INSTALL_PATH/out/romfs/lib/modules
cp -f $INSTALL_PATH/out/kernel/fs/mbcache.ko $INSTALL_PATH/out/romfs/lib/modules
cd $INSTALL_PATH/out && genromfs -v -V "ROM Disk" -f romfs.bin -x placeholder -d $INSTALL_PATH/out/romfs 2> $INSTALL_PATH/out/romfs.map
cd $INSTALL_PATH/

Jetzt auf dem Board den RESET Knopf drücken und loslassen, sobald make install läuft

make install