STM32 dla minimalistów
STM32 to popularna i niedroga implementacja mikrokontrolerów ARM Cortex-M. Występują w setkach odmian i wariantów bogato wypchanych peryferiami. Oczywiście, jako że procesory popularne, to i dokumentacji, tutoriali, podręczników i materiałów video zatrzęsienie. Oprócz tego są wypasione zestawy uruchomieniowe i środowiska developerskie. Osobiście preferuję jednak narzędziowy minimalizm, bo to pozwala mi na skupienie się na rzeczach istotnych i poznanie sprzętu od podszewki. Dlatego przyjąłem sobie następujące założenia:
- Minimalistyczny zestaw uruchomieniowy, mój wybór padł na Nucleo z procesorem STM32F103. Dlaczego akurat ten? Mam zamiar zrobić na nim konkretny projekt i jego zestaw peryferiów, moc obliczeniowa, dostępne obudowy i cena mi odpowiadają. Płytka Nucleo nie posiada praktycznie żadnych zewnętrznych peryferiów: jedna dioda LED, jeden przycisk użytkownika, przycisk reset. Ma za to wyprowadzone na piny praktycznie wszystko z procesora. Mniej się więc nadaje jako sprzęt do dydaktyki, ale znakomicie do prototypowania własnych projektów. Oprócz tego ma w gratisie programator ST-Link, który potem można używać do programowania własnych układów już na samodzielnie zaprojektowanych PCB.
- Programowanie w asemblerze, żeby „poczuć sprzęt” i nie być odizolowanym warstwami abstrakcji. Wszystkie te ułatwiacze i wypasione środowiska, pisanie w C czy nawet C++ – to jest fajne i miłe, oraz oczywiście szybkie w użyciu, ale mam wtedy wrażenie, że ślizgam się po powierzchni zagadnienia.
- Ma działać na Linuksie.
Zestaw narzędzi
st-link
Przy powyższych założeniach do pracy wystarczą dwa niewielkie pakiety oprogramowania. Pierwszym z nich jest pakiet st-link↑, który służy do obsługi programatora, a więc wgrywania programu do pamięci flash procesora, zrzucania tej pamięci, kasowania, debugowania i tak dalej. Niestety akurat dla Debiana i pochodnych dystrybucji (a więc i mojego ulubionego XUbuntu) nie ma gotowej paczki i trzeba sobie skompilować ze źródeł. Proces jest dokładnie opisany w dokumentacji i przebiegł jak na Linuksa w miarę gładko. Zacząłem od sklonowania repozytorium z GitHuba (oczywiście gita miałem już na kompie zainstalowanego):
git clone https://github.com/texane/stlink.git
Oprócz tego do skompilowania st-linka będzie niezbędny kompilator GCC (natywny). Ten też miałem wcześniej, natomiast nie miałem niezbędnych pakietów cmake i libusb, których potrzebuje st-link. Próba kompilacji zakończy się w razie ich braku niepowodzeniem, dlatego proponuję zainstalować je przed tą próbą:
sudo apt-get install cmake libusb-1.0.0-dev
Wreszcie można wejść do głównego katalogu st-linka i wpisać
make release
a po zakończonej sukcesem kompilacji
cd build/Release
sudo make install
Po tym wszystkim do dyspozycji mamy trzy narzędzia: st-flash, st-info i st-util. Ale nie tak od razu, próba uruchomienia któregokolwiek spowoduje piękny komunikat:
st-flash: error while loading shared libraries:
libstlink.so.1: cannot open shared object file:
No such file or directory
Problem jest zdaje się specyficzny dla dystrybucji opartych na Debianie. Rozwiązanie polega na tym, że trzeba kazać systemowi przeskanować katalogi z bibliotekami współdzielonymi, żeby zauważył nowe. Właściwe rozwiązania tego problemu są dwa: restart systemu albo
sudo ldconfig
Po tej akcji podłączyłem swoje Nucleo kabelkiem USB i sprawdziłem jego wykrywalność (numer seryjny pozwoliłem sobie wyciąć...).
~$ st-info --probe
Found 1 stlink programmers
serial: <cenzura>
openocd: <cenzura>
flash: 131072 (pagesize: 1024)
sram: 20480
chipid: 0x0410
descr: F1 Medium-density device
binutils
Drugi niezbędny pakiet zawiera asembler, disasembler, konwerter plików wykonywalnych i pomniejsze narzędzia. Ten na szczęście jest gotową paczką więc instalacja sprowadza się do:
sudo apt-get install binutils-arm-none-eabi
Teraz wszystko już mamy i można zabrać się za...
Pierwszy program
Kod
Na początek ambitne zagadnienie: zaświecenie zielonej diody LED na Nucleo, jest ona podłączona do jednej z linii GPIO. Wybaczcie, że dam tylko suchy listing, jednak ten wpis poświęcony jest narzędziom, a nie samemu programowaniu ARM-a.
CLR2 = 0x02 ;blok GPIO konfiguracja
BSRR = 0x10 ;blok GPIO set/reset
.thumb
.word 0x20005000 ;dno stosu
.word start + 1 ;wektor reset
.word endless + 1 ;wektor NMI
.word endless + 1 ;wektor HardFault
.word endless + 1 ;wektor MemFault
.word endless + 1 ;wektor BusFault
.word endless + 1 ;wektor UsageFault
start:
LDR r1,RCC_APB2ENR
MOV r0,#4
STR r0,[r1] ;włączam port A
LDR r1,GPIO_A
MOV r0,#24
STRB r0,[r1,#CLR2] ;konf. pinu PA5
MOV r0,#0x0020
STR r0,[r1,#BSRR] ;ustawiam 1 na pinie
endless:
B endless
.align 4
RCC_APB2ENR:
.word 0x40021018
GPIO_A:
.word 0x40010800
Asemblacja
Zapisany w pliku tekstowym dioda.asm kod źródłowy asemblujemy poniższym poleceniem:
arm-none-eabi-as -mcpu=cortex-m3 dioda.asm -o dioda.o
Z ciekawości możemy wykonać disasemblację wygenerowanego pliku *.o.
arm-none-eabi-objdump -d dioda.o
Oto wynik:
Disassembly of section .text:
00000000 <start-0x1c>:
0: 20005000 .word 0x20005000
4: 0000001d .word 0x0000001d
8: 00000039 .word 0x00000039
c: 00000039 .word 0x00000039
10: 00000039 .word 0x00000039
14: 00000039 .word 0x00000039
18: 00000039 .word 0x00000039
0000001c <start>:
1c: 4904 ldr r1, [pc, #16] ; (30 <RCC_APB2ENR>)
1e: 2004 movs r0, #4
20: 6008 str r0, [r1, #0]
22: 4904 ldr r1, [pc, #16] ; (34 <GPIO_A>)
24: 2018 movs r0, #24
26: 7088 strb r0, [r1, #2]
28: 2020 movs r0, #32
2a: 6108 str r0, [r1, #16]
0000002c <endless>:
2c: e7fe b.n 2c <endless>
2e: bf00 nop
00000030 <RCC_APB2ENR>:
30: 40021018 .word 0x40021018
00000034 <GPIO_A>:
34: 40010800 .word 0x40010800
Konwersja do pliku binarnego
Asembler wygenerował nam plik w formacie ELF. Plik w tym formacie zawiera szereg nagłówków, podział na sekcje i tym podobne. Oczywiście nie nadaje się do bezpośredniego wgrania do pamięci flash procesora. Najpierw należy go skonwertować na surowy plik binarny:
arm-none-eabi-objcopy -O binary dioda.o dioda.bin
Sprawdźmy zawartość pliku binarnego, pamiętając o tym, że ARM używa zapisu little-endian, a więc najmłodszy bajt przodem. Porównując to z wynikiem disasemblacji zauważymy, że plik binarny zawiera dokładnie to, o co nam chodzi.
00000000: 00 50 00 20 1d 00 00 00
00000008: 2d 00 00 00 2d 00 00 00
00000010: 2d 00 00 00 2d 00 00 00
00000018: 2d 00 00 00 04 49 04 20
00000020: 08 60 04 49 18 20 88 70
00000028: 20 20 08 61 fe e7 00 bf
00000030: 18 10 02 40 00 08 01 40
Flashowanie
Nie pozostaje nam nic innego, jak wgrać program do pamięci flash i zaobserwować jego działanie. Pamięć flash zaczyna się od adresu $0800 0000.
st-flash write dioda.bin 0x08000000
I jeszcze jedna uwaga – dlaczego nigdzie w kodzie nie ma dyrektywy ustawiającej adres startu kodu na $0800 0000? W standardowej konfiguracji procesora po resecie pamięć flash jest również widziana od adresu 0 i tam właśnie procesor poszukuje tablicy wektorów przerwań i adresu początku kodu użytkownika. Asembler zaś domyślnie generuje kod zaczynając od adresu 0, więc wszystko się zgadza.
ostatnia aktualizacja: 19 lipca 2018