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