Les 3
Toolchain en simulator
Cross-compileren met avr-gcc, wat een linkerscript doet, flashen met avrdude en firmware draaien in de simulator simavr.
🎯 Na deze les kun je
- uitleggen wat cross-compileren is
- een firmwarebestand bouwen met avr-gcc en avr-objcopy
- in grote lijnen uitleggen wat het linkerscript en de opstartcode doen
- firmware draaien in simavr en flashen met avrdude
Op je laptop compileer je een programma voor je laptop. Firmware voor een microcontroller compileer je óók op je laptop, maar voor een heel andere processor. Dat heet cross-compileren. In deze les zet je de keten van gereedschappen (de toolchain) op, bouw je je eerste firmware en draai je hem in een simulator.
De toolchain
| Gereedschap | Doet |
|---|---|
avr-gcc |
cross-compiler: C → AVR-machinecode |
avr-libc |
C-standaardbibliotheek en headers voor AVR (<avr/io.h>) |
avr-objcopy |
ELF-bestand omzetten naar Intel HEX voor het flashen |
avr-size |
laat zien hoeveel flash en RAM je programma gebruikt |
avrdude |
flasht de HEX naar een echt bordje via USB |
simavr |
simuleert een ATmega328P op je pc |
Op Debian/Ubuntu: sudo apt install gcc-avr avr-libc binutils-avr avrdude simavr. In de lab-container staat alles al klaar.
Eerste build
main.c:
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
DDRB |= (1 << DDB5); /* PB5 (Arduino D13) als uitgang */
for (;;) {
PORTB ^= (1 << PORTB5); /* LED omdraaien */
_delay_ms(500);
}
}
Wat de registers precies doen, leer je in les 4 en 5. Nu gaat het om het bouwen:
avr-gcc -mmcu=atmega328p -DF_CPU=16000000UL -Os -Wall -Wextra -std=gnu11 -o blink.elf main.c
avr-size --mcu=atmega328p -C blink.elf
avr-objcopy -O ihex -R .eeprom blink.elf blink.hex
| Optie | Betekenis |
|---|---|
-mmcu=atmega328p |
voor welke chip; bepaalt instructies, geheugengroottes en registeradressen |
-DF_CPU=16000000UL |
kloksnelheid; _delay_ms rekent hiermee |
-Os |
optimaliseer op grootte (flash is krap) |
-std=gnu11 |
C11 met GNU-uitbreidingen, die avr-libc gebruikt |
avr-size toont iets als:
Program: 176 bytes (0.5% Full)
(.text + .data + .bootloader)
Data: 0 bytes (0.0% Full)
(.data + .bss + .noinit)
Program is flash, Data is SRAM vóór de stack. Houd deze getallen in de gaten; in les 15 duiken we erin.
_delay_ms werkt alleen goed met optimalisatie aan (-Os of -O2) en met een correcte F_CPU. Klopt F_CPU niet, dan knippert je LED op de verkeerde snelheid.
Wat gebeurt er vóór main?
Op een pc regelt het besturingssysteem het laden van je programma. Op een microcontroller doet een stukje opstartcode (startup code, uit avr-libc) dat, direct na een reset:
- de stackpointer op het einde van het SRAM zetten;
- de beginwaarden van geïnitialiseerde globale variabelen (
.data) van flash naar SRAM kopiëren; - niet-geïnitialiseerde globale variabelen (
.bss) op nul zetten; main()aanroepen.
Het linkerscript vertelt de linker waar alles komt: code en constanten in flash vanaf adres 0, variabelen in SRAM vanaf 0x100. Bij avr-gcc kiest -mmcu het juiste linkerscript automatisch; op ARM-chips schrijf of kopieer je hem vaak zelf. Je hoeft hem nu niet te kunnen schrijven, wel te weten dat hij bestaat.
main eindigt bij embedded nooit: er is geen besturingssysteem om naar terug te keren. Vandaar de oneindige for (;;).
Draaien in de simulator
simavr -m atmega328p -f 16000000 blink.elf
simavr voert je firmware instructie voor instructie uit. Om te zien wat de pinnen doen, laat je simavr een VCD-trace (value change dump) schrijven en open je die in GTKWave. In de lab-container zit een wrapper die dat voor je doet:
beetje sim blink.elf --trace PB5 --tijd 3s
t=0.000 s PB5=1
t=0.500 s PB5=0
t=1.000 s PB5=1
Een handige browser-simulator voor thuis is Wokwi: je ziet daar een virtuele Uno met LED's en knoppen.
Flashen naar een echt bordje
Heb je een Arduino Uno, dan flash je via de bootloader die er al op staat:
avrdude -p atmega328p -c arduino -P /dev/ttyACM0 -b 115200 -U flash:w:blink.hex:i
-c arduino kiest het protocol van de Arduino-bootloader, -P de seriële poort (op Windows bijvoorbeeld COM3). Na het flashen start je firmware automatisch.
Een Makefile
Na les 13 van de C & C++-cursus weet je hoe je dit vastlegt:
MCU = atmega328p
F_CPU = 16000000UL
CC = avr-gcc
CFLAGS = -mmcu=$(MCU) -DF_CPU=$(F_CPU) -Os -Wall -Wextra -std=gnu11
blink.hex: blink.elf
avr-objcopy -O ihex -R .eeprom $< $@
blink.elf: main.c
$(CC) $(CFLAGS) -o $@ $^
avr-size --mcu=$(MCU) -C $@
flash: blink.hex
avrdude -p $(MCU) -c arduino -P /dev/ttyACM0 -b 115200 -U flash:w:$<:i
.PHONY: flash
Typische fouten
gccin plaats vanavr-gcc:avr/io.h: No such file or directory.-mmcuvergeten: registers alsDDRBzijn onbekend ('DDRB' undeclared), want welke registers er zijn hangt van de chip af.avrdude: stk500_recv(): programmer is not responding: verkeerde poort, de seriële monitor staat nog open, of verkeerde-b(oude Nano-bootloaders gebruiken 57600).
Oefeningen
- Waarom kun je de
blink.elfniet gewoon met./blink.elfop je laptop starten? - Verander de vertraging naar 100 ms en bouw opnieuw. Verandert de grootte in
avr-size? - Wat gebeurt er met de knippersnelheid als je
-DF_CPU=8000000ULmeegeeft, maar de chip echt op 16 MHz loopt?
Modelantwoord
- Het bestand bevat AVR-machinecode; je laptop heeft een x86- of ARM-processor. Alleen een AVR (of een simulator) kan het uitvoeren.
- Nauwelijks of niet: de vertraging is een constante die in een lus-teller terechtkomt.
_delay_ms(500)denkt dat de klok half zo snel is en wacht daarom half zo veel klokslagen. Op 16 MHz duurt dat 250 ms: de LED knippert twee keer zo snel.
Lab: eerste build in de simulator
- Bouw
blink.elfenblink.hexmet de Makefile in de lab-map. - Laat de LED op PB5 knipperen met 1 Hz (500 ms aan, 500 ms uit).
- Controleer met
beetje sim blink.elf --trace PB5 --tijd 3sdat de wisselingen op het juiste moment gebeuren. beetje check embedded-c-les3
Samenvatting
- Cross-compileren: bouwen op je pc voor een andere processor, met
avr-gcc -mmcu=.... avr-sizetoont flash- en RAM-gebruik;avr-objcopymaakt een HEX om te flashen.- Opstartcode en linkerscript regelen alles vóór
main;maineindigt nooit. - Testen kan in simavr of Wokwi; flashen met
avrdude.
Volgende les: bits, registers en volatile, zodat je begrijpt wat DDRB |= (1 << DDB5) precies doet.
Lab
Eerste build in de simulator
💻 Dit lab doe je op je eigen computer met Docker. Start de lab-container met (geeft de opdracht een eigen startcommando, gebruik dan dat):
$ docker run -it --rm registry.wizg.it/webhosting/beetje-labs:embedded-c-les3
Klaar? Controleer je werk met:
$ beetje check embedded-c-les3