1.1 Programowanie mikrokontrolerów 2.0 Wstęp Marcin Engel Marcin Peczarski Instytut Informatyki Uniwersytetu Warszawskiego 16 kwietnia 2015
Co to jest mikrokontroler? Układ scalony integrujący następujące elementy (zależnie od modelu): jednostka obliczeniowa (8-, 16-, 32-bitowa) pamięć danych (SRAM, EEPROM, FRAM) pamięć programu (Flash, ROM, EEPROM) układy taktujące (generator kwarcowy, generator RC, pętla synchronizacji fazy lub częstotliwości PLL, FLL) kontroler przerwań kontroler DMA liczniki przetworniki analogowo-cyfrowe przetworniki cyfrowo-analogowe interfejsy szeregowe (UART, USART, LIN, IrDA, SPI, I 2 C, I 2 S, CAN, SDIO, 1-Wire, USB, Ethernet,... ) interfejsy równoległe (LCD, kamera, SRAM, DRAM, Flash)...
Co to jest mikrokontroler?... interfejs do debugowania (JTAG, SWD) układ nadzorujący, strażnik (ang. watchdog) układ zarządzania poborem prądu zegar czasu rzeczywistego RTC (ang. Real Time Clock) generator liczb losowych akcelerator kryptograficzny (AES, SHA,... )... mikrokontroler = komputer w jednym układzie scalonym
Popularne mikrokontrolery 8051 firmy Intel i jego liczne klony PIC firmy Microchip Technology 68HC firmy Motorola (obecnie Freescale) Z8 firmy Zilog AVR firmy Atmel MSP430 firmy Texas Instruments ARM, produkowane przez wiele firm, np.: Atmel Freescale NXP Samsung STMicroelectronics Texas Instruments...
ARM = Advanced RISC Machines Międzynarodowa firma, mająca główną siedzibę w Cambrdge w Wielkiej Brytanii Projektuje i sprzedaje licencje na rdzenie ARM Nie produkuje krzemu Większość urządzeń mobilnych, w tym głównie telefonów komórkowych, i systemów wbudowanych zawiera rdzenie ARM
Rozwój 32-bitowej architektury ARM Źródło: http://www.arm.com 1.6
64-bitowa architektura ARM Źródło: http://www.arm.com 1.7
ARM Cortex-A i Cortex-R Rdzenie Cortex-A przeznaczone są do stosowania w urządzeniach wymagających dużych mocy obliczeniowych i umiarkowanego zużycia energii smartfony tablety Rdzenie Cortex-R przeznaczone są do stosowania w systemach czasu rzeczywistego (ang. real time), gdzie wymagana jest szybka reakcja na zdarzenia, wysoka niezawodność, spora moc obliczeniowa motoryzacja lotnictwo energetyka
ARM Cortex-M Cortex-M7 przeznaczony do systemów wbudowanych wymagających bardzo dużej wydajności Cortex-M4 przeznaczony do systemów wbudowanych wymagających dużej wydajności FPU SP i DSP tylko w rdzeniach M4 i M7 Cortex M3 przeznaczony do szerokiego stosowania w systemach wbudowanych Cortex-M1 przeznaczony do implementacji w FPGA (ang. Field Programmable Gate Array) ASIC (ang. Application Specific Integrated Circuit) ASSP (ang. Application Specific Standard Product) Cortex-M0+ przeznaczony do urządzeń o małym zużyciu energii ograniczony zestaw instrukcji uproszczona architektura alternatywa dla architektur 8- i 16-bitowych Cortex-M0 to poprzednia (mniej udana) wersja Cortex-M0+
ARM SecureCore SC300 rdzeń Cortex-M3 SC100 rdzeń ARM7TDMI SC000 rdzeń Cortex-M0 Główny obszar zastosowań to karty czipowe SIM płatnicze do dekoderów TV identyfikacyjne
Rodzina mikrokontrolerów STM32 Źródło: http://www.st.com/stm32 1.11
Jak zacząć zabawę? Trzeba kupić: mikrokontroler troszkę innych elementów elektronicznych (rezonator kwarcowy, diody świecące, rezystory, kondensatory, mikroprzełączniki, złącza,... ) programator laminat, wytrawiacz, lutownicę i inne narzędzia Ponadto należy: przygotować komputer z oprogramowaniem nauczyć się projektować obwody drukowane Ale obiecaliśmy, że nie będziemy lutować...
Nucleo I I I I Szybkie tworzenie układów testowych Programowanie i debugowanie przez USB Dostępne różne ekspandery Dodatkowe układy podłączane za pomocą kabelków 1.13
Nucleo-F411RE Mikrokontroler STM32 32-bitowy mikrokontroler STMicroelectronics F4 Cortex-M4 o maks. częst. takt. 84 180 MHz 11 oznaczenie modelu, wbudowane peryferie R rozmiar obudowy: 64 wyprowadzenia E rozmiar pamięci Flash: 512 KiB T typ obudowy: LQFP 6 zakres temperatur pracy: 40... 85 C STM32F411RET6 maks. częst. takt. 100 MHz, 128 KiB SRAM
Interfejsy do programowania i debugowania w układzie JTAG (ang. Joint Test Action Group) wyprowdzenia JTMS (PA13), JTCK (PA14), JTDI (PA15), JTDO (PB3), NJTRST (PB4) SWD (ang. Serial Wire Debug) wyprowdzenia SWDIO (PA13), SWCLK (PA14) Przykładowe adaptery JTAG/SWD USB ARM-USB-TINY-H J-Link ST-LINK/V2-1 Adapter korzysta też z wyprowadzenia NRST, zerującego mikrokontroler
Środowisko programistyczne GCC: polecenie arm-eabi-gcc Binutils: przydatne programy arm-eabi-ar, arm-eabi-as, arm-eabi-ld, arm-eabi-objcopy, arm-eabi-objdump,... GDB: debuger arm-eabi-gdb OpenOCD (ang. Open On Chip Debugger): polecenie openocd Newlib: standardowa biblioteka C dla systemów wbudowanych Program make Dostępne w labie po dodaniu do.bash_profile ścieżki poszukiwań plików PATH=$PATH:/opt/arm/bin export PATH Instrukcja i skrypt instalujący zestaw narzędzi dostępne w labie w katalogu /opt/arm/stm32/doc
Dokumentacja PM0214 Programming manual, STM32F3 and STM32F4 Series Cortex-M4 RM0383 Reference manual, STM32F411xC/E advanced ARM-based 32-bit MCUs STM32F411xC/E Data sheet STM32F411xC/E Errata sheet, device limitations UM1724 User manual, STM32 Nucleo boards W labie dostępna w katalogu /opt/arm/stm32/doc Aktualne wersje do ściągnięcia ze strony producenta http://www.st.com/stm32
Ogólne własności architektur ARMv6 do ARMv8 Rdzenie ARM mogą być cienkokońcówkowe lub grubokońcówkowe, w niektórych jest możliwość wyboru Cortex-M jest wyłącznie cienkokońcówkowy Jednolita przestrzeń adresowa architekura typu Princeton Osobne szyny do pamięci danych i programu organizacja typu Hardward Zestawy instrukcji maszynowych ISA (ang. Instruction Set Architecture): ARM, Thumb, Thumb-2 Cortex-M nie obsługuje zestawu ARM
Rejestry w Cortex-M4 32-bitowe: R0 do R12 rejestry ogólnego przeznaczenia SP (R13, MSP, PSP) wskaźnik stosu LR (R14) adres powrotu PC (R15) licznik programu PSR (APSR, IPSR, EPSR) rejestr znaczników PRIMASK, FAULTMASK, BASEPRI rejestry maskujące przerwania CONTROL rejestr sterujący trybami pracy rdzenia S0 do S31 rejestry zmiennoprzecinkowe 64-bitowe: D0 do D15 rejestry zmiennoprzecinkowe, mapowane na pary rejestrów S0 do S31; tylko przesyłanie wartości rdzeń nie obsługuje arytmetyki podwójnej precyzji
Zestaw instrukcji maszynowych Wzorowany na RISC, aby łatwo potokować ldr r3, [r1, #36] adds r2, r3, #1 RISC-owe wołanie procedur bl bx etykieta lr Prawie każda instrukcja może być wykonywana warunkowo movge r7, r4 Argument można przesunąć przed wykonaniem operacji orr r1, r1, r4, lsl #12 str r2, [r4, r0, lsl #2] Występują instrukcje typowo CISC-owe push pop {r4, r5, lr}; prolog funkcji {r4, r5, pc}; epilog funkcji
Pierwszy program, plik first_main.c static int a[4]; static int b[4] = {1, 2, 3, 4}; static const int c[4] = {9, 8, 7, 6}; int main() { static int d[4]; static int e[4] = {1, 3, 5, 7}; static const int f[4] = {8, 1, 7, 2}; int g[4], i; g[0] = a[0] + b[0] + c[0] + d[0] + e[0] + f[0]; for (i = 1; i < 4; ++i) g[i] = a[i] + b[i] + c[i] + d[i] + e[i] + f[i] + g[i - 1]; return g[3]; }
Jak skompilować? Plik makefile CC = arm-eabi-gcc OBJCOPY = arm-eabi-objcopy FLAGS = -mthumb -mcpu=cortex-m4 \ -mfloat-abi=softfp -mfpu=fpv4-sp-d16 CPPFLAGS = -DSTM32F411xE CFLAGS = $(FLAGS) -Wall -g -I/opt/arm/stm32/inc \ -O0 -ffunction-sections -fdata-sections LDFLAGS = $(FLAGS) -Wl,--gc-sections -nostartfiles \ -L/opt/arm/stm32/lds -Tstm32f411re.lds vpath %.c /opt/arm/stm32/src
Jak skompilować? Plik makefile, cd..phony: all clean.secondary: first.elf first_main.o startup_stm32.o all: first.bin %.elf : first_main.o startup_stm32.o $(CC) $(LDFLAGS) $^ -o $@ %.bin : %.elf $(OBJCOPY) $< $@ -O binary clean : rm -f *.bin *.elf *.hex *.d *.o *.bak *~
Jak zaprogramować mikrokontroler? Podłączyć, znajdujący sią na płytce Nucleo, ST-LINK/V2-1 do gniazda USB w komputerze Pierwszy sposób Użyć OpenOCD w trybie wsadowym (w bieżącym katalogu musi być obraz pamięci *.bin) za pomocą skryptu /opt/arm/stm32/ocd/qfn4 Drugi sposób Uruchomić OpenOCD jako GDB-serwer za pomocą skryptu /opt/arm/stm32/ocd/dbgn4 w katalogu domowym musi być plik.gdbinit zawierający coś takiego target remote :3333 monitor reset halt W katalogu, w którym mamy plik wykonywalny, uruchomić GDB poleceniem arm-eabi-gdb *.elf Następnie w GDB użyć polecenia load
Debugujemy Fragment sesji z GDB (gdb) p &a $1 = (int (*)[4]) 0x20000020 <a> (gdb) p &b $2 = (int (*)[4]) 0x20000000 <b> (gdb) p &c $3 = (const int (*)[4]) 0x80002dc <c> (gdb) p &d $4 = (int (*)[4]) 0x20000030 <d> (gdb) p &e $5 = (int (*)[4]) 0x20000010 <e> (gdb) p &f $6 = (const int (*)[4]) 0x80002ec <f> (gdb) p &g $7 = (int (*)[4]) 0x2001ffdc Jak wytłumaczyć takie rozmieszczenie danych w pamięci?
1.26 Organizacja pamięci programu Flash sidata = etext.data.rodata RAM stack heap estack end ebss.text.bss edata = sbss 0x08000000.isr vector.data sdata = 0x20000000
1.27 Jak to działa w Cortex-M? Element 0 tablicy przerwań zawiera adres estack Element 1 tablicy przerwań zawiera adres, od którego ma się rozpocząć wykonywanie programu funkcja Reset Handler Po włączeniu zasilania lub wyzerowaniu rejestr SP jest inicjowany adresem estack rejestr PC jest inicjowany adresem Reset Handler Zadaniem funkcji Reset Handler jest skopiowanie sekcji.data z Flash do RAM wyzerowanie sekcji.bss wywołanie funkcji main obsłużenie wartości zwróconej przez funkcję main
1.28 Jak to działa w Cortex-M? Początkowe adresy Flash i RAM oraz ich rozmiary zdefiniowano w pliku stm32f411re.lds Rozmieszczenia sekcji w pamięci zdefiniowano w pliku cortex-m.lds Pliki te są dostępne w labie w katalogu /opt/arm/stm32/lds Tekst źródłowy funcji Reset Handler znajduje się w pliku startup_stm32.c Pliki ten jest dostępny w labie w katalogu /opt/arm/stm32/src Tablicę przerwań zdefiniowano w plikach interrupt_vector_stm32.c i interrupt_vector_stm32f411xe.c Pliki te są dostępne w labie w katalogu /opt/arm/stm32/inc
Optymalizacja Pierwszy program kompilowaliśmy z wyłączoną optymalizacją: opcja -O0 Mikrokontrolery mają ograniczoną pamięć i ograniczoną częstotliwość taktowania Otymalizacje przyspieszające wykonywanie kodu zwykle zmniejszają też jego rozmiar W większości przypadków najodpowiedniejsza jest opcja -O2 Zmniejszenie rozmiaru kodu wykonywalnego można uzyskać, stosując opcje -ffunction-sections i -fdata-sections biblioteki muszą być skompilowane z tymi opcjami konsolidator musi być wywołany z opcją --gc-sections Podglądanie efektu pracy kompilatora: arm-eabi-objdump -M reg-names-std -d *.elf
Jak zaimplementować prostą funkcję opóźniającą? Prosty pomysł void Delay(unsigned count) { while (count--); } Dlaczego count--, a nie --count? Delay(0); Dlaczego ta implementacja jest zła? 00000000 <Delay>: 0: 4770 bx lr 2: bf00 nop
Jak zaimplementować prostą funkcję opóźniającą? Użyjmy słowa kluczowego volatile, żeby zablokować optymalizację void Delay(volatile unsigned count) { while (count--); } Dlaczego ta implementacja jest zła? 00000000 <Delay>: 0: b082 sub sp, #8 2: 9001 str r0, [sp, #4] 4: 9b01 ldr r3, [sp, #4] 6: 1e5a subs r2, r3, #1 8: 9201 str r2, [sp, #4] a: 2b00 cmp r3, #0 c: d1fa bne.n 4 <Delay+0x4> e: b002 add sp, #8 10: 4770 bx lr 12: bf00 nop 1.31
1.32 Jak zaimplementować prostą funkcję opóźniającą? Spróbujmy inaczej void Delay(unsigned count) { while (count--) { NOP(); NOP(); } } Dlaczego ta implementacja jest dobra? 00000000 <Delay>: 0: 1e43 subs r3, r0, #1 2: b120 cbz r0, e <Delay+0xe> 4: bf00 nop 6: bf00 nop 8: f113 33ff adds.w r3, r3, #4294967295 ; -1 c: d2fa bcs.n 4 <Delay+0x4> e: 4770 bx lr
Jak zaimplementować prostą funkcję opóźniającą? Nasza ostateczna wersja funkcji opóźniającej void Delay(unsigned count) { while (count--) { NOP(); NOP(); } } Dlaczego dwie instrukcje NOP? Jak dobrać wartość argumentu, aby otrzymać żądane opóźnienie? 250tf gdzie t to żądany czas opóźnienia w ms, a f to częstotliwość taktowania mikrokontrolera w MHz