Table of Contents

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:

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