IDE im Eigenbau für den STM32 PerformanceStick (Part 1: Toolchain)

Im vierten Semester des Studiengangs “Technische Informatik” findet die Veranstaltung “Embedded Systems” statt. Inhalte der Veranstaltung sind unter anderem Mikrocontroller, Interrupt-Programmierung, Kommunikation mit Peripherie und Schnittstellen und Echtzeit-Systeme. Die für die Übungen verwendete Hardware ist der “STM32-PerformanceStick” und der eingesetzte Compiler ist gnu-gcc, allerdings unter Windows durch Verwendung des “HiTOP IDE/Debugger”. Wie du die ARM-embedded-Toolchain auch unter Linux komfortabel einrichtest, erfährst du hier.

Verwendete Software

(Sollte alles in den Repositories der jeweiligen Distribution zu finden sein)

Cross-Compiler Umgebung

Library

Als Library setzen wir die originale Library von STLink ein, da diese auch Teil der Vorlesung und der abschließenden Klausur ist. Ob wir die Library hier veröffentlichen dürfen, ist unklar. Die Library ist ohne hin bei jedem erstellten Projekt der HiTOP-IDE dabei.

Compiler

Wie bereits oben genannt, verwenden wir die GNU-GCC Toolchain für ARM Embedded. In den meisten Distributionen findet sich diese in der Paketverwaltung. Alternativ kann man sich hier den Quelltext herunterladen und selbst kompilieren.

Projekt-Verzeichnissstruktur

|-- Library
|   |-- inc (H-Dateien)
|   \-- src (C-Dateien)
|-- Settings (Linkerscript)
\-- Source (Projekt-Dateien)

Kompilation eines Programms

Die Kompilation eines Programms ist, wenn man denn weiß was man tut, recht einfach. Die folgenden Compiler-Flags sind nötig:

  • -mthumb: Erstellt ein Binary für das ARM-16-Bit-Instruction Set (Thumb)
  • -mfpu=vfp: Definiert zu verwendende die Gleitkomma-Hardware (ggf. auch Emulation). Wir nutzen VFP (Vector Floating Point Unit)
  • -mcpu=cortex-m3: Legt den zu verwendenen ARM-Prozessor fest

Man erstellt, wie bei einer normalen Binary zunächst Objekt-Dateien aus dem Quellcode und linkt diese daraufhin zusammen. Der Unterschied beim Linken ist jedoch, dass man über ein Linkerscript angibt, wie die Programmdatei schlussendlich aufgebaut wird. Das Linkerscript wird normalerweise durch die HiTOP IDE erzeugt. Da dabei jedoch stets ein identisches Script generiert wird, können wir dieses nach einer kleinen Modifikation selbst verwenden: Die Sektionen SEARCH_DIR ( ... ) und INPUT ( ... ) müssen entfernt und der Pfad zur startup.o gesetzt (./Source/startup.o) werden.

Gelinkt wird also mit folgenden Flags:

  • -Tlinkscript.ld: Nutze das LinkerScript
  • -static: Statisches Linking, keine geteilten (shared) Bibliotheken
  • -nostartfiles: Keine Standardbiliotheken vor den eigentlichen Code schreiben (argc und argv bereitstellen, etc).
  • -lm -lc -lgcc: Math-Library (libm), C-Library (libc) und GCC-Library (libgcc) einbinden

Am Ende wird mittels Obj-Copy aus der .elf Datei eine .bin Datei (manchmal auch .hex genannt) generiert. Diese .bin (oder .hex) ist das Image für den Flash des Controllers. Dieses Image ist quasi ein Speicherabbild der Binary. Alle Symbole und Relocation-Daten werden verworfen.

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

Programm übertragen (Flashen)

Zum Flashen über die JTAG-Schnittstelle verwenden wir OpenOCD. OpenOCD steht für “On-Chip-Debugger” und tut genau das, wonach es sich anhört. Gesteuert wird das ganze über Telnet oder den GDB.

Konfiguration von OpenOCD und erste Verbindung

Über eine Konfigurationsdatei kann man den Port für eine interaktive Telnet-Verbindung festlegen:

telnet_port 4444

source [find board/hitex_stm32-performancestick.cfg]

OpenOCD liefert eine funktionierende Konfigurationsdatei für den Performance Stick mit. Diese liegt unter “/usr/share/openocd/scripts/board/hitex_stm32-performancestick.cfg”. In der OpenOCD Konfiguration genügt allerdings die Angabe des relativen Pfades ab “scripts/”. Das ganze speichern wir nun als openocd.cfg im Ordner des zu flashenden Images. Mittels OpenOCD bauen wir nun eine Verbindung zum Stick auf:

$ openocd
Open On-Chip Debugger 0.7.0 (2013-11-25-16:14)
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.sourceforge.net/doc/doxygen/bugs.html
trst_and_srst separate srst_gates_jtag trst_push_pull srst_open_drain connect_deassert_srst
Info : only one transport option; autoselect 'jtag'
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
jtag_ntrst_delay: 100
cortex_m3 reset_config sysresetreq
adapter speed: 500 kHz
Info : clock speed 500 kHz
Info : JTAG tap: stm32_hitex.cpu tap/device found: 0x3ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x3)
Info : JTAG tap: stm32_hitex.bs tap/device found: 0x16410041 (mfg: 0x020, part: 0x6410, ver: 0x1)
Info : JTAG tap: str750.cpu tap/device found: 0x4f1f0041 (mfg: 0x020, part: 0xf1f0, ver: 0x4)
Info : stm32_hitex.cpu: hardware has 6 breakpoints, 4 watchpoints

Gelegentlich kommt es zu einem Fehler wie:

Info : TAP stm32_hitex.cpu does not have IDCODE
Warn : JTAG tap: stm32_hitex.cpu       UNEXPECTED: 0x00000000 (mfg: 0x000, part: 0x0000, ver: 0x0)
Error: JTAG tap: stm32_hitex.cpu  expected 1 of 1: 0x3ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x3)
Info : JTAG tap: stm32_hitex.bs tap/device found: 0xffffc5fd (mfg: 0x2fe, part: 0xfffc, ver: 0xf)
Warn : JTAG tap: stm32_hitex.bs       UNEXPECTED: 0xffffc5fd (mfg: 0x2fe, part: 0xfffc, ver: 0xf)
Error: JTAG tap: stm32_hitex.bs  expected 1 of 9: 0x06412041 (mfg: 0x020, part: 0x6412, ver: 0x0)
Error: JTAG tap: stm32_hitex.bs  expected 2 of 9: 0x06410041 (mfg: 0x020, part: 0x6410, ver: 0x0)
Error: JTAG tap: stm32_hitex.bs  expected 3 of 9: 0x16410041 (mfg: 0x020, part: 0x6410, ver: 0x1)
Error: JTAG tap: stm32_hitex.bs  expected 4 of 9: 0x06420041 (mfg: 0x020, part: 0x6420, ver: 0x0)
Error: JTAG tap: stm32_hitex.bs  expected 5 of 9: 0x06414041 (mfg: 0x020, part: 0x6414, ver: 0x0)
Error: JTAG tap: stm32_hitex.bs  expected 6 of 9: 0x06418041 (mfg: 0x020, part: 0x6418, ver: 0x0)
Error: JTAG tap: stm32_hitex.bs  expected 7 of 9: 0x06430041 (mfg: 0x020, part: 0x6430, ver: 0x0)
Error: JTAG tap: stm32_hitex.bs  expected 8 of 9: 0x06420041 (mfg: 0x020, part: 0x6420, ver: 0x0)
Error: JTAG tap: stm32_hitex.bs  expected 9 of 9: 0x06428041 (mfg: 0x020, part: 0x6428, ver: 0x0)
Info : JTAG tap: str750.cpu tap/device found: 0xffffffff (mfg: 0x7ff, part: 0xffff, ver: 0xf)
Warn : JTAG tap: str750.cpu       UNEXPECTED: 0xffffffff (mfg: 0x7ff, part: 0xffff, ver: 0xf)
Error: JTAG tap: str750.cpu  expected 1 of 1: 0x4f1f0041 (mfg: 0x020, part: 0xf1f0, ver: 0x4)
Error: Trying to use configured scan chain anyway...
Error: stm32_hitex.cpu: IR capture error; saw 0x0f not 0x01
Warn : Bypassing JTAG setup events due to errors
Warn : Invalid ACK 0x7 in JTAG-DP transaction
Polling target stm32_hitex.cpu failed, GDB will be halted. Polling again in 100ms
Polling target stm32_hitex.cpu failed, GDB will be halted. Polling again in 300ms
Polling target stm32_hitex.cpu failed, GDB will be halted. Polling again in 700ms
[...]

In diesem Fall einfach openocd mittels Strg + C beenden und erneut starten. Dieser Fehler wird noch häufiger auftreten. Uns ist bisher leider keine bessere Lösung, ganz abzusehen von der Ursache, bekannt.

Image übertragen

Zunächst bauen wir über Telnet auf Port 4444 eine Verbindung zu OpenOCD auf.

$ telnet localhost 4444
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
>

Dann setzen wir den Controller zurück und halten ihn im Reset-Zustand.

> reset halt
JTAG tap: stm32_hitex.cpu tap/device found: 0x3ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x3)
JTAG tap: stm32_hitex.bs tap/device found: 0x16410041 (mfg: 0x020, part: 0x6410, ver: 0x1)
JTAG tap: str750.cpu tap/device found: 0x4f1f0041 (mfg: 0x020, part: 0xf1f0, ver: 0x4)
target state: halted
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x080000f0 msp: 0x20004ffc

Mittels flash probe ermitteln wir die Bezeichnung des Flash-Speichers.

> flash probe 0
device id = 0x20016410
flash size = 128kbytes
device id = 0x20016410
flash size = 128kbytes
flash 'stm32f1x' found at 0x08000000

Nun können wir den Flash-Speicher löschen …

> stm32f1x mass_erase 0
stm32x mass erase complete

… und ein neues Image auf Bank 0 an Position 0 schreiben.

> flash write_bank 0 flash.bin 0
wrote 22836 bytes from file flash.bin to flash bank 0 at offset 0x00000000 in 1.093975s (20.385 KiB/s)

Devicezugriff ohne ROOT-Rechte

Versucht man ohne ROOT-Rechte mittels openocd auf das Device zuzugreifen, quittiert openocd das mit der folgenden Fehlermeldung.

[florian0@florian0-PC testproject]$ openocd Open On-Chip Debugger 0.7.0 (2013-11-25-16:14)
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.sourceforge.net/doc/doxygen/bugs.html[...] Error: unable to open ftdi device: usb_open() failed in procedure 'init'

Abhilfe schafft eine UDEV-Regel. Per UDEV weisen wir dem Device entsprechende Zugriffsrechte zu und erstellen praktischerweise gleich noch einen Symlink in /dev.

SUBSYSTEMS=="usb", ATTRS{idVendor}=="0640", ATTRS{idProduct}=="002d", \
    MODE:="0666", \
    SYMLINK+="stm32_%n"

Wem der Modus 666 nicht gefällt, der kann auch mittels OWNER:=”user” und/oder GROUP:=”group” dem Device seinem eigenen Benutzer oder einer Gruppe zuweisen und dann dementsprechend Berechtigungen vergeben.