Thomas makes, writes

2006-01-01

Embedded artists Game/Bluetooth board

bluetooth board

I wrote this article on using a microcprocessor board with GNU tools for an embedded software class back in 2006. It's written in Dutch.

Inleiding

Dit voorbije semester heb ik vooral een ontwikkelomgeving voor de ARM bestudeerd, waarbij zoveel mogelijk vrije software in aanmerking kwam. Deze ontwikkelomgeving heb ik gevonden in een combinatie van de GNU C Compiler (voor arm), de GNU Debugger en OpenOCD (deze laatste maakt het mogelijk om via een USB JTAG kabel de arm in-circuit te debuggen). Het was zeker geen lachertje deze opstelling aan de praat krijgen, de onvolledige en onduidelijke documentatie op internet heeft zeker niet in mijn voordeel gewerkt. Het is dan ook mijn bedoeling om in de toekomst dit werk nog verder uit te breiden tot een naslagwerk voor iedereen die met dit platform aan de slag wil.

Het Bluetooth board en verassingen

Ik heb een LPC2104 Color LCD Game with Bluetooth board gebruikt voor mijn experimenten. Met het geniale idee dat ik de hardware zonder problemen uit kon breiden met een JTAG in-circuit debugging cable, had ik al een interessant knutselplatform voor ogen.

Revisie 1.0 van het board wordt verkocht zonder on-board JTAG naar USB hardware. De reden hiervoor ben ik echter op de harde manier te weten gekomen. Ik merkte reeds wat nattigheid toen de controller consistent - in JTAG debug modus - de leds L1 en L2 deed oplichten en het LCD niet initialiseerde.

Er zit dus een onverwacht addertje onder de blauwe lak waardoor het debuggen via JTAG niet te combineren valt met een toepassing die het lcd, de leds L1 en L2, of de schakelaars gebruikt.

Na lang zoeken in de user manual, heb ik dan ook een teleurstellende vaststelling gedaan.

Als de controller op de normale manier in JTAG debugmodus wordt gezet (m.b.v. DBGSEL/RTCK), wordt de primaire JTAG pinnengroep geconfigureerd als JTAG poort. De secundaire pinnengroep wordt onvermijdelijk voor Embedded Trace Mode (ETM) gebruikt. Het is evident dat men op deze manier 15 pinnen verliest! (Om deze reden wordt in sommige toepassingen enkel de secundaire pinnengroup gebruikt, het is mogelijk om in dat geval de primaire pinnengroep als I/O te gebruiken.)

Klaarblijkelijk heeft men de print ontworpen met de LPC2103 in gedachten (die geen Embedded Trace Mode functionaliteit heeft), maar omdat deze met zijn 8kb ram te bekrompen bleek, heeft men dan maar de LPC2104 bestukt. Men heeft dan maar de JTAG schakeling onbestukt gelaten, en het kind verkocht zonder kapsel.

Langs de positieve kant bekeken, heb ik echter wel zes vrije I/O pinnen ontdekt, waar externe hardware op kan worden aangesloten. Dit valt toch mee, aangezien het gebrek hieraan in de eerste plaats een kleine afknapper was.

jtag soldering

De bedrading tussen de solder pads van het pcb en de JTAG box-connector.

board

Connections made to solder pads of absent smd resistors.

board

Along with the JTAG debug connector board.

Het gereedschap - installatie

De volledige set gereeschap bestaat uit:

  • binutils; de ARM assembler en tools om met objectbestanden te werken
  • gcc; de C compiler
  • newlib; de C library
  • gdb; de gnu debugger
  • openocd; daemon die de arm-usb-ocd kabel bestuurt
  • lpc2isp, .hex file downloader

Binutils, gcc, newlib, gdb

Dankzij de inspanningen van vrijwilligers en bedrijven heeft de Gnu compiler collectie degelijke support voor ARM cores. Bij het configureren van de build van de cross compiler moet expliciet worden opgegeven dat ondersteuning voor thumb modus, software floating point math, intwerworking (switchen tussen ARM en THUMB instructieset) en een niet standaard libc gewenst is.

Dit wordt bekomen door de ./configure commando's de argumenten --enable-interwork --enable-multilib --with-float=soft mee te geven. Merk op dat alvorens deze argumenten herkend worden, het bestand gcc/config/arm/t-arm-elf uit de gcc source tree gepatcht dient te worden, een gepatcht t-arm-elf bestand is te vinden op de http://www.gnuarm.com website.

Het bouwen van binutils gcc, de newlib C-library en gdb heb ik geautomatiseerd met een script dat te vinden is in de bijlage.

PATH-variabele

Ik heb al deze software geïnstalleerd onder prefix /opt/gnuarm-4.1.1, deze directory staat niet standaard in de $PATH variabele. Bijgevolg worden de compileraanroepen zoals arm-elf-gcc niet herkend door de shell.

Gentoo Linux maakt gebruik van een flexibel systeem om de omgevingsvariabelen in te stellen. Om de gnuarm compiler system-wide beschikbaar te maken is de volgende configuratie in een nieuw bestand /etc/env.d/09arm-elf opgenomen

PATH=/opt/gnuarm-4.1.1/bin
ROOTPATH=/opt/gnuarm-4.1.1/bin
LDPATH=/opt/gnuarm-4.1.1/lib

Een andere manier is om in ~/.bashrc PATH="$PATH:/opt/gnuarm/bin" te zetten.

lpc2isp

Lpc2isp bestaat uit een enkel bronbestand, lpc2isp.c, en kan door de host compiler eenvoudig worden gecompileerd:

#versie 1.32 van embedded artists uitpakken
#compileren met host-gcc
gcc lpc2isp -o /usr/bin/lpc2isp
#uitvoerbaar maken
chmod +x /usr/bin/lpc2isp

Het gebruik is heel simpel:

lpc21isp test.hex /dev/ttyUSB0 115200 14746

OpenOCD

Ik heb de OpenOCD versie uit de cvs gebruikt

#unmasken van libftdi in gentoo
echo dev-embedded/libftdi >>/etc/portage/package.keywords
#libftdi installeren
emerge libftdi

#bron afhalen
svn checkout svn://svn.berlios.de/openocd/trunk
#make met ondersteuning voor de chip in de arm-usb-ocd
./configure --enable-ft2232_libftdi
make
make install

Broncode

Makefile

Bij software waar meer dan enkele bronbestanden aan te pas komen, gebruikt men Makefiles om het compilatieproces te automatiseren. Als deze Makefiles flexibel opgebouwd zijn, laat dit toe, door het wijzigen van enkele variabelen of declaraties, heel het compilatieproces te beïnvloeden. Zo zullen er variabelen zijn die het formaat van de resulterende binary gaan bepalen (elf/intel hex/binair), of er THUMB of ARM instructies gaan gegenereerd worden (compiler flags), welke optimalisatie flags gebruikt gaan worden en welk link script gebruikt gaat worden. Kortom alle instellingen die je bij een (commerciële) IDE in het project properties dialoogvenster zou terugvinden.

Start-up code

Tussen het moment dat de klokfrequentie gestabiliseerd is (en de programmateller begint te lopen), en de uitvoering van main(), dienen nog wat formaliteiten te gebeuren. Zo dienen andere de PLL klok vermenigvuldiger en de MAM geconfigureerd te worden, de stack voor de verschillende operating modes ingesteld te worden. Dit neemt de startup code (die in assembler geschreven is) voor zijn rekening, ook worden hier de interrupt vectors gedefinieerd.

De complexiteit van de startup code van de bluetooth-board-demo grenst ongeveer aan het onmenselijke. Er wordt namelijk gebruik gemaakt van de C preprocessor. Dit heeft tot gevolg dat op basis van de instellingen in het bestand config.h, de PLL en MAM juist geconfigureerd worden. Enkel oppervlakkige dingen, zoals gewenste kristal- en klokfrequentie, dienen opgegeven te worden.

Linkerscript

Het linkerscript wordt door de Linker (het programma dat de objectbestanden van de compiler en de gebruikte libraries combineert tot een solide executable) gezien als zijn opdracht. Met dit bestand kunnen we bepalen welke geheugengebieden gebruikt gaan worden (zowel flash als ram), en waar uiteindelijk de code uitgevoerd gaat worden (soms is het wenselijk de code vanuit ram uit te voeren, omwille van de lage toegangstijd). Hier volgt een chronologische bespreking van het linkerscript, aangezien dit script zeer cruciaal is.

De executable vangt aan bij de _startup: label

ENTRY(_startup)

De declaratie van de memory map is als volgt voor de LPC2104 controller (128k flash en 16k ram):

MEMORY
{
  FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00020000
  RAM   (rw) : ORIGIN = 0x40000000, LENGTH = 0x00004000
}

Vervolgens wordt in de SECTIONS{} clausule gespecifieerd welke delen van de code waar moeten komen. Men vangt aan met de .text sectie, die gebruikt wordt voor de startupcode (*startup.o (.text)) en de programmacode (*(.text))

.text :
{
  *startup.o (.text)
  *(.text)
  *(.glue_7)
  *(.glue_7t)
} > FLASH = 0
. = ALIGN(4);

De ALIGN(4) statement zorgt ervoor dat na de programmacode de locatieteller wordt verhoogd tot de volgende 32-bit grens

De constantes volgen:

.rodata :
{
  *(.rodata)
  *(.rodata*)
} > FLASH
. = ALIGN(4);

Vervolgens wordt het symbool _etext gedefinieerd als het adres na de laatste codebyte.

_etext = . ;
PROVIDE (etext = .);

Geïnitialiseerde variabelen volgen in de flash na de constantes, dus op het adres _etext. Deze zijn bedoeld voor de ram (Ox4...80), en dienen hier dan ook naar gecopieerd te worden (> RAM). Ook hierna wordt er een symbool gedefinieerd.

.data 0x40000080: AT (_etext)
{
  _data = . ;
  *(.data)
  SORT(CONSTRUCTORS)
} > RAM
. = ALIGN(4);

_edata = . ;
PROVIDE (edata = .);

Ongeïnitialiseerde variabelen, de .bss sectie, hoeven niet in het ram te worden geladen.

.bss (NOLOAD):
{
  _bss          = . ;
  __bss_start   = . ;
  __bss_start__ = . ;
  *(.bss)
  *(COMMON)
  . = ALIGN(4);
} > RAM
. = ALIGN(4);
__bss_end   = . ;
__bss_end__ = . ;
_end = . ;
PROVIDE (end = .);

Dit besluit het linkerscript.

Conclusie

Het valt te betwisten of deze ontwikkelomgeving een goede keuze vormt voor studenten van de richting elektronica-ICT. Spelen met al deze ingewanden is toch wel te hoog gegrepen met onze achtergrond. Met mijn eindwerk -waar ik gcc-arm ga gebruiken- in het verschiet is deze investering echter toch niet voor niets geweest. Vanuit mijn achtergrond was dit alles vrij nieuw, maar echte informatici hebben over deze materie natuurlijk al enkele dikke cursussen achter de kiezen.

Toepassing van de tools

lpc21isp

Het gebruik van de lpc21isp tool wijst zichzelf uit. Toch kan ik twee opmerkingen maken. De handshake signalen van de USB-serial converter zijn via jumpers verbonden met de controller (Reset aan DTR, EnableBootLoader aan RTS). Men mag dus niet vergeten -control te gebruiken bij het aanroepen van het programma!

lpc21isp -control test.hex /dev/ttyUSB0 115200 14746

Als in de commandoregel ook -term opgenomen wordt, gaat de programmer in terminal modus als het downloaden klaar is. Dit is handig in sommige gevallen.

OpenOCD debugger

Openocd is een daemon die het mogelijk maakt de ARM via JTAG te bedienen. Er kan gebruik worden gemaakt van simpele hardware die aan de printerpoort is aangesloten. Kant en klare hardware zoals de arm-usb-ocd, die met behulp van een synchrone uart met usb interface de JTAG chain aanstuurt, is echter een betere optie.

OpenOCD implementeert twee communicatiekanalen met de gebruiker: een telnet daemon en een gdb server, waardoor de Gnu debugger gebruikt kan worden.

In bijlage is de gebruikte configuratie te bekijken. De OpenOCD daemon moet met root permissies worden gestert (met sudo), omdat het toegang tot de usb poort nodig heeft. Als argument krijgt het ook -f configuratiebestand mee.

gdb

Gdb is een commandoregelgestuurde debugger. Men start hem door in de shell het commando arm-elf-gdb te geven. Vervolgens dienen er enkele formaliteiten te gebeuren vooraleer er aan de slag kan worden gegaan. Deze commando's heb ik opgenomen in een bestand. Nadat gdb gestart is, kunnen ze worden uitgevoerd door source gdbinit in te typen.

directory startup
symbol-file hutsepot.elf
target remote localhost:3333
monitor reset
monitor sleep 500
monitor poll
monitor soft_reset_halt
monitor arm7_9 sw_bkpts enable

Zoals u ziet gaan we een remote target debuggen. In dit geval niet een gdbserver, maar de Openocd software, die draait op poort 3333. Aangezien de bestanden startup.S, framework.c en consol.c zich in de map 'startup' bevinden, dient deze map met een 'directory' statement aan het zoekpad te worden toegevoegd. De symbolen gaan we van het .elf bestand lezen. Met het monitor statement kunnen we OpenOCD commando's geven die hij op zijn telnet interface accepteert. Een voorbeeld hiervan is monitor mdw 0xE01FC040. Dit zal de waarde van het MEMMAP register tonen (dat bepaalt waar de interuptvectors gelezen gaan worden).

Hier volgt een samenvatting van de meestgebruikte commando's:

p variabele:
de waarde van de variabele tonen
l [lijn]:
broncodelisting tonen van de huidige lijn, of van de [opgegeven lijn]
i all:
de inhoud van de processor registers tonen
i s:
de stack tonen
w variabele:
watchpoint zetten op een variabele, executie wordt onderbroken als ze verandert.
i b:
de breakpoints tonen.
b [lijn]:
een breakpoint op de huidige lijn, of de [opgegeven lijn] zetten.
d [n]:
laatste breakpoint of [breakpoint n] verwijderen.

Voorbeeldsessie

thomas@bauknecht ~/hutsepot $ arm-elf-gdb
GNU gdb 6.6
(gdb) source gdbinit
main () at main.c:70
70      }
target state: halted
target halted in ARM state due to debug request, current mode: System
cpsr: 0x4000001f pc: 0x400036dc
requesting target halt and executing a soft reset
software breakpoints enabled
0xe01fc040: 00000001
(gdb) list
65                      IOSET = LED_RED_PIN;
66                      printf("B");
67                      while (RTC_SEC == MIR_SEC) {}
68                      MIR_SEC = RTC_SEC;
69              }
70      }
(gdb) watch MIR_SEC
Hardware watchpoint 1: MIR_SEC
(gdb) continue
Continuing.
Hardware watchpoint 2: MIR_SEC

Old value = 6 '\006'
New value = 45 '-'
0x000002d6 in main () at main.c:70
70      }
(gdb) continue
Continuing.
Hardware watchpoint 2: MIR_SEC

Old value = 45 '-'
New value = 49 '1'
0x000002aa in main () at main.c:64
64                      IOCLR = LED_GREEN_PIN;
(gdb) print MIR_SEC
$2 = 49 '1'
(gdb) quit

Democode Bluetoothboard

De demosoftware van het bluetoothboard is gebaseerd op een pre-emptive realtime operating system. Dit is goed zichtbaar in de broncode. Het gebruikte rtos heeft echter wel een gemakkelijke API, zeker in vergelijking met het FreeRTOS (waarvan ik de code zeer aandachtig heb bekeken en geprobeerd heb de voorbeelden te laten werken op het bluetoothboard).

stdio.h

Zoals bekend, wordt in de C programmeertaal doorgaans gebruik gemaakt van de printf() en scanf() functies om te communiceren met een gebruiker aan een terminal. Indien er een onderliggend operating system is, kan er gebruik worden gemaakt van de standard io streams die deze biedt. In het geval van een stand-alone toepassing op een microcontroller is dit echter anders. Zo dient de gebruiker zelf printf() te implementeren, ofwel dienen zelfgeschreven putc() getc() functies aan de (in dit geval newlib) C-library te worden gekoppeld. Het 'framework' van de democode implementeert de putc() functie zelf. Er wordt gebruik gemaakt van UART 0 of 1. In de config.h headerfile kan gekozen worden of de printf() functie van newlib wordt gebruikt of een dieet functie van het huis. In dit laatste geval wordt 22kb aan flash gespaard.

Testprogramma

Bij wijze van oefening heb zelf een testprogramma samengesteld. Ik heb de startupcode en configuratiebestanden uit het demoprogramma genomen, en deze aangepast waar nodig. Aangezien ik nog een wekker zoek te maken met dit board, heb ik de RTC geïnitialiseerd. Ook heb ik geprobeert de UART te initialiseren, ik kwam echter tot de vaststelling dat hiervoor reeds een functie in consol.h bestaat.

Realtime clock

De realtimeclock wordt gedreven door een 32khz referentie. Deze wordt bekomen door de periferieklok door een deler te halen. Aangezien de PLL vermenigvuldigingsfactor gelijk is aan de deler die gebruikt wordt om de periferiebus te klokken, wordt de kristalwaarde bij de berekening gebruikt. (de berekening is met python in interactieve modus gedaan)

>>> pclk = 14745600
>>> PREINT= int((pclk/32768)) - 1
>>> PREFRAC = int(pclk - ((PREINT+1) * 32768))
>>> print "PREINT  = 0x%.8x\nPREFRAC = 0x%.8x" % (PREINT,PREFRAC)
PREINT  = 0x000001c1
PREFRAC = 0x00000000

UART

Het framework van embedded artists gebruikt de C preprocessor om de juist waarden te berekenen van de twee divisor latches van de UART. In config.h zijn de nodige gegevens gedefinieerd:

#define CONSOL_BITRATE      115200
#define FOSC 14745600

De baudrateberekening zelf is verspreid over meerdere bestanden, de relevante code ziet er als volgt uit:

#define CCLK (FOSC * PLL_MUL)
#define PCLK (CCLK / PBSD)
#define UART_DLL_VALUE (unsigned short)((PCLK / (CONSOL_BITRATE * 16.0)) + 0.5)
UART_DLL = (unsigned char)(UART_DLL_VALUE & 0x00ff);
UART_DLM = (unsigned char)(UART_DLL_VALUE>>8);

Listings

  • Script om de crosscompiler te bouwen.
  • Configuratiescript OpenOCD
  • main.c, rtc.c