====== 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 [[http://www.st.com/web/en/catalog/tools/PF259429#|Software-Beispiele]], die ST für das [[http://www.st.com/web/catalog/tools/FM116/SC959/SS1532/LN1848/PF259090|STM32F429I-DISCO]] anbietet mit der [[https://launchpad.net/gcc-arm-embedded|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 320x240 Pixel, die beiden Bilder haben jeweils halbe Display-Höhe, also eine Größe von 240x160 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**): #!/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 [[https://lwn.net/Kernel/LDD3/|Linux Device Drivers]]. Folgendes Beispiel basiert auf einem Beispiel aus Kapitel 2 der dritten Ausgabe: #include #include #include #include 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); 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