06 lipca 2018

SysTick - timer systemowy

Skoro już pojawiły się przerwania wspomnę jeszcze o bardzo przydatnym module timera systemowego. Jest to 24-bitowy licznik, który można wykorzystać np. do generowania zdarzeń systemowych - tick-ów, stąd jego nazwa.
Sam timer jest podobnie jak NVIC nadzorowany bezpośrednio przez ARM-a, więc dokumentacji moża szukać tutaj: Cortex-M4 Programming manual
Aby skorzystać z tego timera wystarczy ustawić wartość dwóch rejestrów oraz napisać procedurę obsługi przerwania.
Najpierw omówmy rejestr kontrolny, czyli SysTick->CTRL. Znajdziemy w nim następujące, interesujące nas bity:
  • CLKSOURCE - ustawienie sprawia, że licznik używa bezpośrednio zegara systemowego (AHB), wyzerowanie włącza dzielnik /8
  • TICKINT - włącza generowanie przerwania
  • ENABLE - uruchamia licznik
Zanim uruchomimy licznik musimy jeszcze ustawić częstotliwość generowania przerwań. W tym celu do rejestru SysTick->LOAD wpisujemy pomniejszoną o 1 liczbę cykli zegara po których ma pojawić się przerwanie.
Pamiętamy, że domyślnym źródłem taktowania mikrokontrolera jest wbudowany generator 16MHz - teraz można napisać prosty przykładowy program.

#include <stdint.h>
#include <stdbool.h>
#include "stm32f429xx.h"

volatile uint32_t ms_counter = 0;

void SysTick_Handler(void)
{
        if (ms_counter)
                ms_counter--;
}

void delay_ms(uint32_t ms)
{
        ms_counter = ms;
        while (ms_counter) ;
}

int main(int argc, char *argv[])
{
        RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOGEN;

        GPIOG->MODER |= GPIO_MODER_MODE13_0|GPIO_MODER_MODE14_0;

        SysTick->LOAD = 16000-1;
        SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
                        SysTick_CTRL_TICKINT_Msk |
                        SysTick_CTRL_ENABLE_Msk;

        while (1) {
                GPIOG->BSRR = GPIO_BSRR_BS13;
                GPIOG->BSRR = GPIO_BSRR_BR14;
                delay_ms(500);

                GPIOG->BSRR = GPIO_BSRR_BS14;
                GPIOG->BSRR = GPIO_BSRR_BR13;
                delay_ms(500);
        }

        return 0;
}

Procedury delay_ms() wykorzystują omawiany timer do wprowadzania opóźnień w programie. To znacznie lepsza metoda niż używane wcześniej pętle.

Przerwania zewnętrzne

Skoro odczyt stanu przycisku opanowany, czas uruchomić coś bardzo blisko powiązanego - czyli przerwania do linii GPIO. Układy STM32 mają dość specyficznie rozwiązane przyjmowanie przerwań. Teoretycznie można obsługiwać przerwania od każdej linii, ale nie od wszystkich jednocześnie. Pin o danym numerze może być źródłem przerwania tylko od jednego portu na raz. Czyli jeśli wejście PA0 (port A, pin 0), ustawimy jako przerwanie, inne porty nie będą mogły obsługiwać przerwań na ich pinie 0. Tak to sobie inżynierowie projektujący układ wymyślili.
W przypadku płytki ewaluacyjnej nie jest to problem - i tak tylko jeden przycisk jest dostępny. Ale projektując nieco bardziej skomplikowany układ trzeba takie ograniczenia brać pod uwagę.
Większość programu będzie taka sama, albo chociaż bardzo podobna do poprzeniego, opiszę więc tylko to co nowe.
Na początek warto wybrać port, którego pin 0 będzimy używać. Do konfiguracji używany jest rejestr (rejestry) SYSCFG->EXTICRx



To właśnie w tym rejestrze wybieramy, który port będzie przypisany do przerwania na danym wejściu - dlatego wbierając jeden z portów tracimy możliwość użycia określonego pinu na pozostałych.
Wybieram PA0, czyli piszę:

SYSCFG->EXTICR[0] &= ~SYSCFG_EXTICR1_EXTI0;

Teraz wypada skonfigurować przerwania zewnętrzne. Moduł EXTI jest wyposażony w 6 rejestrów. Rejestry RTSR oraz FTSR pozwalają ustawić wyzwalanie przerwania odpowiednio po wykryciu zbocza narastającego i opadającego.


Naciśnięcie przycisku zmienia stan niski na wysoki, mamy więc zbocze narastające:

EXTI->RTSR |= EXTI_RTSR_TR0;

Teraz trzeba włączyć generowanie przerwania dla pinu 0. Odpowiada za to rejestr IMR:


Kod wygląda tak:

EXTI->IMR |= EXTI_IMR_MR0;

Następnie uruchomiamy samo przerwanie. Kontroler przerwań w przypadku Cortex-M4 nazywa się NVIC i został zaprojektowany przez firmę ARM (więc jest taki sam dla wszystkich mikrokontrolerów tej rodziny). Oznacza to niestety, że dokumentacji musimy poszukać na stronach ARM, a nie ST.
Na szczęście w naszym przypadku chcemy tylko zapalić jeden bit w rejestrze ISER. Uruchamiamy przerwanie kodem:

NVIC->ISER[EXTI0_IRQn / 32] |= 1u << (EXTI0_IRQn % 32);

Na koniec trzeba jeszcze przygotować procedurę obsługi przerwania. Sam wektor przerwań jest zdefiniowany w pliku startup_stm32f429xx.s.


/******************************************************************************
*
* The minimal vector table for a Cortex M3. Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
* 
*******************************************************************************/
   .section  .isr_vector,"a",%progbits
  .type  g_pfnVectors, %object
  .size  g_pfnVectors, .-g_pfnVectors
   
g_pfnVectors:
  .word  _estack
  .word  Reset_Handler

  .word  NMI_Handler
  .word  HardFault_Handler
  .word  MemManage_Handler
  .word  BusFault_Handler
  .word  UsageFault_Handler
  .word  0
  .word  0
  .word  0
  .word  0
  .word  SVC_Handler
  .word  DebugMon_Handler
  .word  0
  .word  PendSV_Handler
  .word  SysTick_Handler
  
  /* External Interrupts */
  .word     WWDG_IRQHandler                   /* Window WatchDog              */                                        
  .word     PVD_IRQHandler                    /* PVD through EXTI Line detection */                        
  .word     TAMP_STAMP_IRQHandler             /* Tamper and TimeStamps through the EXTI line */            
  .word     RTC_WKUP_IRQHandler               /* RTC Wakeup through the EXTI line */                      
  .word     FLASH_IRQHandler                  /* FLASH                        */                                          
  .word     RCC_IRQHandler                    /* RCC                          */                                            
  .word     EXTI0_IRQHandler                  /* EXTI Line0                   */                        
  .word     EXTI1_IRQHandler                  /* EXTI Line1                   */                          
  .word     EXTI2_IRQHandler                  /* EXTI Line2                   */ 

Funkcja EXTI0_IRQHandler odpowiada naszej procedurze obsługi przerwania - możemy oczywiście zmienić jej nazwę, jednak żeby nie komplikować programu zostawimy domyślną.
Procedurę napisałem żeby przetestować eliminację drgań - po każdym przyciśnięciu zmieniam stan świecenia diod. Jest to prosty test czy przerwania pojawiają się wielokrotnie po naciśnięciu przycisku.
Jeszcze jedna uwaga - po odebraniu przerwania należy wyzerować flagę w rejestrze EXTI->PR. Dzięki temu możliwe będzie obsłużenie kolejnego naciśnięcia.
Cały program wygląda następująco:


#include <stdint.h>
#include <stdbool.h>
#include "stm32f429xx.h"

void EXTI0_IRQHandler(void)
{
        static bool set = false;

        if (set) {
                GPIOG->BSRR = GPIO_BSRR_BS13;
                GPIOG->BSRR = GPIO_BSRR_BR14;
                set = false;
        } else {
                GPIOG->BSRR = GPIO_BSRR_BS14;
                GPIOG->BSRR = GPIO_BSRR_BR13;
                set = true;
        }

        EXTI->PR = EXTI_PR_PR0;
}

int main(int argc, char *argv[])
{
        RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOGEN;

        GPIOG->MODER |= GPIO_MODER_MODE13_0|GPIO_MODER_MODE14_0;
        GPIOA->PUPDR |= GPIO_PUPDR_PUPD0_1;

        SYSCFG->EXTICR[0] &= ~SYSCFG_EXTICR1_EXTI0;

        EXTI->RTSR |= EXTI_RTSR_TR0;
        EXTI->IMR |= EXTI_IMR_MR0;

        NVIC->ISER[EXTI0_IRQn / 32] |= 1u << (EXTI0_IRQn % 32);

        while (1) {
        }

        return 0;
}


05 lipca 2018

Odczyt stanu przycisku

Odczytywanie stanu wejścia jest jeszcze łatwiejsze niż sterowanie wyjściami. Nie ma problemu z atomowymi dostępami, wystarczy po prostu pobrać zawartość rejestru IDR.


Oczywiście trzeba najpierw sprawdzić do którego portu i pinu jest podłączony przycisk. Odpowiednia informacja jest w dokumentacji płytki:

Czyli port A, pin 0. Na schemacie widać układ eliminujący drgania styków oraz rezystor pull-down (zawsze mnie ciekawi jak to się po polsku nazywa...).
Co więcej wciśnięcie przycisku odpowiada stanowi wysokiemu, czyli logicznej "1". To mniej popularne rozwiązanie niż zwieranie do masy, ale w sumie na jedno wychodzi.
Skoro już przy rezystorach podciągających i tych, no... pull-down jesteśmy, warto zwrócić uwagę że sam mikrokontroler też posiada takie rezystory. Można ich nie używać (co jest domyślnym ustawieniem), albo włączyć jeden z nich. Podobnie jak w przypadku rejestru MODER, teraz również do konfiguracji pinu przeznaczone są dwa bity. Znajdziemy je w rejestrze PUPDR:





Czyli chcąc uruchomić rezystor pull-down dla pinu 0, moglibyśmy napisać:

GPIOA->PUPDR |= GPIO_PUPDR_PUPD0_1;

Teraz można już napisać prosty program, który zmieni stany obu diod gdy naciśniemy przycisk:

#include <stdint.h>
#include "stm32f429xx.h"

int main(int argc, char *argv[])
{
        volatile uint32_t dly;

        RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOGEN;

        GPIOG->MODER |= GPIO_MODER_MODE13_0|GPIO_MODER_MODE14_0;
        GPIOA->PUPDR |= GPIO_PUPDR_PUPD0_1;

        while (1) {
                if (GPIOA->IDR & 0x0001) {
                        GPIOG->BSRR = GPIO_BSRR_BS13;
                        GPIOG->BSRR = GPIO_BSRR_BR14;
                } else {
                        GPIOG->BSRR = GPIO_BSRR_BS14;
                        GPIOG->BSRR = GPIO_BSRR_BR13;
                }
        }

        return 0;
}

Program jest gotowy, a oczytywanie stanu wejścia jak widać bardzo proste. Warto jeszcze na moment wrócić do pliku Makefile. Pierwsza wersja używała na stałe zapisanej nazwy pliku wynikowego "test01". Ponieważ nowy program ma nazwę test02, więc musiałbym w kilku miejscach zmieniać plik - łatwiej zdefiniować odpowiednią zmienną. Nowy plik Makefile wygląda następująco:

CROSS_COMPILE=arm-none-eabi-

AS=$(CROSS_COMPILE)as
CC=$(CROSS_COMPILE)gcc
SIZE=$(CROSS_COMPILE)size

OUTPUT=bin/test02

CFLAGS=-mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -std=gnu11 -O0 \
        -ffunction-sections -fdata-sections -g -fstack-usage -Wall -specs=nano.specs

LDFLAGS=$(CFLAGS) -Wl,--gc-sections -specs=nosys.specs -Wl,-cref,-u,Reset_Handler \
        -Wl,-Map=$(OUTPUT).map -Tsrc/stm32f4_flash.ld

all: $(OUTPUT).elf
        $(SIZE) $<

$(OUTPUT).elf: obj/main.o obj/startup_stm32f429xx.o
        $(CC) $(LDFLAGS) -o $@ $^

obj/main.o: src/main.c
        $(CC) $(CFLAGS) -o $@ -c $<

obj/startup_stm32f429xx.o: src/startup_stm32f429xx.s
        $(CC) $(CFLAGS) -o $@ -c $<

debug: $(OUTPUT).elf
        openocd -f board/stm32f429discovery.cfg -f interface/stlink-v2.cfg \
                -c "init; sleep 200; reset halt; wait_halt; \
                flash write_image erase $(OUTPUT).elf; \
                reset run; sleep 10; shutdown"

clean:
        rm -f bin/* obj/*

W kolejnych kilku wpisach będę używał takiego pliku zmieniając tylko nazwy programów wynikowych.






04 lipca 2018

Parę słów wyjaśnienia

Pisząc pierwszy wpis chciałem skoncentrować się na uruchomieniu kompletnego programu. Okazało się, że tekstu wyszło całkiem sporo, więc niewiele zostało miejsca na wyjaśnienie kodu samego programu.
Teraz spróbuję nadrobić te zaległości, opisać jego działanie oraz samych linii wejscia-wyjścia.

Zacznijmy od taktowania procesora. Wspomniałem o zakomentowaniu wywołania SystemInit(). Standardowo ta funkcja konfiguruje pętlę PLL jako źródło taktowania, jej pominięcie pozostawia domyślne źródło - czyli wbudowany generator RC o częstotliwości 16 MHz.

Kolejna sprawa to porty wejścia-wyjścia, czyli GPIO. Opisywany mikrokontroler ma całkiem sporo wyprowadzeń. Porty grupują piny po maksymalnie 16 sztuk, numerowane są od 0 do 15. Same porty oznaczane literami - od A, aż do K.

Na płytce ewaluacyjnej znajdziemy dwie diody, których stanem możemy sterować - są podłączone do wyprowadzeń PG13 i PG14.

Domyślnie większość modułów peryferyjnych mikrokontrolera jest wyłączona - dzięki temu układ pobiera mniej prądu. Oznacza to, że port G do którego podłączone są diody, należy najpierw uruchomić. W tym celu trzeba najpierw sprawdzić do jakiej magistrali jest podłączony. Wszystkie porty są wpięte w AHB1, więc kod:

RCC->AHB1ENR |= RCC_AHB1ENR_GPIOGEN;

Sprawia, że port G zaczyna działać (tzn. jest podłączany do źródła sygnału zegarowego).
Domyślnie (prawie) wszystkie piny są ustawione w tryb wejścia. Wcześniej opisywałem mikrokontroler STM32F103, w nim ustawianie trybów było nieco skomplikowane. Nowsze układy z rodziny STM32 są pod tym względem znacznie łatwiejsze do użycia.
Rejestr MODER przechowuje konfigurację wszystkich pinów danego portu. Działanie pinu opisują dwa bity (stąd 32 bitowy rejestr bez problemu obsługuje cały port).


Jak widać jest to prawie tak proste jak użycie rejestru DDRx w układach AVR. Po uruchomieniu lub resecie rejestr MODER dla portu G zawiera wartość 0 więc linie podłączone do diod są wejściami.
Chcąc sterować ich świeceniem trzeba przestawić tryb na "01", czyli zapalić zerowy bit dla pinu 13 i 14. Plik nagłówkowy stm32f429xx.h definiuje odpowiednie stałe, więc wystarczy napisać:

GPIOG->MODER |= GPIO_MODER_MODE13_0|GPIO_MODER_MODE14_0;

Teraz można przejść do samego sterowania stanem pinów. Rejestr wyjściowy to ODR, teoretycznie więc można byłoby napisać coś takiego:

GPIOG->ODR |= 1u << 13;

Jest to jednak bardzo złe rozwiązanie - kod może źle działać jeśli w czasie jego wykonywania pojawi się przerwanie. Najlepiej zapamiętać, żeby nigdy tak nie robić (albo chociaż prawie nigdy).
Są jeszcze co najmniej dwa, znacznie lepsze rozwiązania. Pierwsze to zastosowanie bit-bandingu. Dzięki tej technice można odwoływać się do pojedynczych bitów. Taka metoda była dość popularna w układach rodziny F1, jednak producent układów czyli firma ST powoli wydaje się odchodzić o tej techniki.
Pozostaje jeszcze jedna możliwość, czyli użycie rejestru BSRR (bit set/reset register).
Jest to rejestr tylko do zapisu, a znaczenie mają jedynie bity zapalone (logiczne 1). Zapis do dolnych 16 bitów powoduje wystawienie stanu wysokiego na odpowiednim pinie, a do górnych - niskiego.


 Czyli aby zapalić diodę możemy napisać:
 
GPIOG->BSRR = 1u << 13;

Natomiat aby zgasić:
 
GPIOG->BSRR = 1u << 29;

Nie ma tutaj odczytu wartości, więc opracja jest atomowa i przerwania nie są już groźne. Plik nagłówkowy stm32f429xx.h dostarcza odpowiednie definicje, więc ładniejsza wersja kodu zapalającego diodę to:

GPIOG->BSRR = GPIO_BSRR_BS13;

Natomiast do jej zgaszenia używamy:

GPIOG->BSRR = GPIO_BSRR_BR13;

Teraz można już bez problemu przeanalizować cały program, który na wszelki wypadek jeszcze raz umieszczę:

#include <stdint.h>
#include "stm32f429xx.h"

int main(int argc, char *argv[])
{
        volatile uint32_t dly;

        RCC->AHB1ENR |= RCC_AHB1ENR_GPIOGEN;

        GPIOG->MODER |= GPIO_MODER_MODE13_0|GPIO_MODER_MODE14_0;

        while (1) {
                GPIOG->BSRR = GPIO_BSRR_BS13;
                GPIOG->BSRR = GPIO_BSRR_BR14;
                for (dly = 0; dly < 500000; dly++)
                        ;
                GPIOG->BSRR = GPIO_BSRR_BS14;
                GPIOG->BSRR = GPIO_BSRR_BR13;
                for (dly = 0; dly < 500000; dly++)
                        ;
        }

        return 0;
}

   

28 czerwca 2018

Pierwszy projekt - opcja minimum

Jak poprzednio napisałem Atollic TrueSTUDIO jakoś nie wzbudziło mojego szaleńczego entuzjazmu. Nie pozostaje więc nic innego niż zakasać rękawy i przygotować projekt bez tego narzędzia. W sumie to zestaw nakładek, na eclipse, gcc, gdb, openocd, więc nie powinno być problemu z poradzeniem sobie bez niego.
Na początek kompilator. Pod linuxem jest łatwo - arm-none-eabi-gcc jest dostępny w paczce. Więc wystarczy odpalić:

apt install gcc-arm-none-eabi

I gotowe, kompilator już jest. Pod windowsem można pobrać gcc dla arm-a i zainstalować bez problemu, ale nie mam teraz tego systemu pod ręką, zostanę więc przy linuksie.
Teraz pojawia się problem - jakie opcje do kompilacji są potrzebne. gcc posiada niesamowitą liczbę opcji, na początek to nie ułatwia. Ale skoro TrueStudio sobie radzi, a używa narzędzi open-source to może warto "podpatrzeć" co ono robi. Zaglądam do loga kompilacji przykładowego kodu:

arm-atollic-eabi-gcc -c ../Libraries/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_usart.c -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -std=gnu11 -DSTM32F42_43xxx -DUSE_STDPERIPH_DRIVER -DHSE_VALUE=8000000 -DUSE_STM32F429I_DISCO -I../src -I../Libraries/CMSIS/Include -I../Libraries/Device/ST/STM32F4xx/Include -I../Libraries/STM32F4xx_StdPeriph_Driver/inc -I../Utilities/Common -I../Utilities/STM32F429I-Discovery -O0 -ffunction-sections -fdata-sections -g -fstack-usage -Wall -specs=nano.specs -o Libraries/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_usart.o

W pierwszej chwili wygląda, że tego sporo, ale wszystkie -I -D oraz ścieżki do plików można pominąć - wtedy zostaje:

arm-atollic-eabi-gcc -c stm32f4xx_usart.c -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -std=gnu11 -O0 -ffunction-sections -fdata-sections -g -fstack-usage -Wall -specs=nano.specs -o stm32f4xx_usart.o

Nie ma co się bać nazwy z "attolic" to i tak zwykły gcc. Zostają więc opcje:

-mthumb 
-mcpu=cortex-m4 
-mfloat-abi=hard 
-mfpu=fpv4-sp-d16 
-std=gnu11 
-O0 
-ffunction-sections 
-fdata-sections 
-g 
-fstack-usage 
-Wall 
-specs=nano.specs 

Warto byłoby dokładnie wszystkie sprawdzić, ale na razie zostawię ten problem "na później".
Jest jeszcze jeden ważny element - linkowanie aplikacji. Znowu zaglądam do logów TrueStudio - tym razem pominę pełną wersję, była bardzo długa, ale po usunięciu plików .o wygląda tak:

arm-atollic-eabi-gcc -o test01.elf -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -T../stm32f4_flash.ld -specs=nosys.specs -static -Wl,-cref,-u,Reset_Handler -Wl,-Map=test01.map -Wl,--gc-sections -Wl,--defsym=malloc_getpagesize_P=0x80 -Wl,--start-group -lc -lm -Wl,--end-group -specs=nano.specs 



Czyli użyte są następujące opcje:

-mthumb 
-mcpu=cortex-m4 
-mfloat-abi=hard 
-mfpu=fpv4-sp-d16 
-T../stm32f4_flash.ld 
-specs=nosys.specs 
-static 
-Wl,-cref,-u,Reset_Handler 
-Wl,-Map=test01.map 
-Wl,--gc-sections 
-Wl,--defsym=malloc_getpagesize_P=0x80 
-Wl,--start-group 
-lc 
-lm 
-Wl,--end-group 
-specs=nano.specs 

Warto zwrócić uwagę na opcję -T która wskazuje na skrypt linkera stm32f4_flash.ld - będzie nam potrzebny.
Poza tym -specs występuje dwa razu, jak widać autorzy TrueSTUDIO sami się gubią w swoim przerośniętym narzędziu.

Wiadomo już jak TrueSTUDIO kompiluje projekty. Można, a nawet warto najpierw przetestować działanie arm-none-eabi-gcc z linii poleceń. Jednak po chwili staje się to nudne i niewygodne.
Na szczęście dostępne są różne narzędzia wspomagające kompilację kodu. Ja wybrałem starego (nawet bardzo starego), ale dobrego make-a.
Pierwsza wersja mojego Makefile-a wygląda następująco:

CROSS_COMPILE=arm-none-eabi-
AS=$(CROSS_COMPILE)as
CC=$(CROSS_COMPILE)gcc
SIZE=$(CROSS_COMPILE)size

CFLAGS=-mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -std=gnu11 -O0 \
        -ffunction-sections -fdata-sections -g -fstack-usage -Wall -specs=nano.specs

LDFLAGS=$(CFLAGS) -Wl,--gc-sections -specs=nosys.specs -Wl,-cref,-u,Reset_Handler \
        -Wl,-Map=bin/test01.map -Tsrc/stm32f4_flash.ld

all: bin/test01.elf
        $(SIZE) $<

bin/test01.elf: obj/main.o obj/startup_stm32f429xx.o
        $(CC) $(LDFLAGS) -o $@ $^

obj/main.o: src/main.c
        $(CC) $(CFLAGS) -o $@ -c $<

obj/startup_stm32f429xx.o: src/startup_stm32f429xx.s
        $(CC) $(CFLAGS) -o $@ -c $<

debug: bin/test01.elf
        openocd -f board/stm32f429discovery.cfg -f interface/stlink-v2.cfg \
                -c "init; sleep 200; reset halt; wait_halt; \
                flash write_image erase bin/test01.elf; \
                reset run; sleep 10; shutdown"

clean:
        rm -f bin/* obj/*

Do programowania płytki stm32f429-discovery używam openocd co widać w załączonym pliku. Pod Debianem, na którym pracuję wystarczyło zainstalować gotowy pakiet. OpenOCD jest oczywiście dostępny w innych dystrybucjach oraz systemach, więc pod Windą, czy Mac OS nie powinno być z nim problemów.

Teraz pozostaje zebrać pliki źródłowe. Potrzebujemy:
  • startup_stm32f429xx.s - zawiera kod uruchamiany po resecie mikrokontrolera 
  • stm32f4_flash.ld - skrypt linkera
  • main.c - pierwsza aplikacja

Pierwsze dwa pliki możemy "podebrać" z TrueSTUDIO, albo lepiej pobrać bibliotekę Cube HAL dla stm32f4: https://www.st.com/en/embedded-software/stm32cubef4.html
Pobierając bibliotekę szybko odkryjemy, że straciliśmy kolejne 2GB przestrzeni dyskowej... Zaczynam mocno podejrzewać, że ST podukuje jakieś układy do dysków twardych i bardzo dba o generowanie zapotrzebowania na nie.
W każdym razie pliki należy skopiować i nieco zmienić pierwszy, czyli startup_stm32f429xx.s. Znajdziemy w nim wywołanie funkcji SystemInit (przed main). Nie mamy tej funkcji zdefiniowanej, więc najlepiej na razie zakomentować linijkę która ją wywołuje.
 
Przydadzą się również pliki nagłówkowe:
  • stm32f429xx.h
  • cmsis_gcc.h
  • core_cmFunc.h
  • core_cmSimd.h 
  • core_cm4.h
  • core_cmInstr.h
Pierwszy zawiera definicje rejestrów mikrokontrolera - jest bardzo ważny i wart dodania do projektu. Pozostałe to praktycznie śmieci, o przepraszam biblioteka CMSIS... Są włączane przez pierwszy, więc na razie warto je dodać - w wolnej chwili może uda się ich pozbyć.
Plik stm32f429xx.h poza włączaniem wspomnianych śmieci stara się również dodać plik system_stm32f4xx.h. Tego pliku nie potrzebujemy, najlepiej więc usunąc linię która się do niego odwołuje.

Na koniec pozostaje napisać pierwszy program i zapisać go jako main.c:

#include <stdint.h>
#include "stm32f429xx.h"

int main(int argc, char *argv[])
{
        volatile uint32_t dly;

        RCC->AHB1ENR |= RCC_AHB1ENR_GPIOGEN;

        GPIOG->MODER |= GPIO_MODER_MODE13_0|GPIO_MODER_MODE14_0;

        while (1) {
                GPIOG->BSRR = GPIO_BSRR_BS13;
                GPIOG->BSRR = GPIO_BSRR_BR14;
                for (dly = 0; dly < 500000; dly++)
                        ;
                GPIOG->BSRR = GPIO_BSRR_BS14;
                GPIOG->BSRR = GPIO_BSRR_BR13;
                for (dly = 0; dly < 500000; dly++)
                        ;
        }

        return 0;
}


27 czerwca 2018

Szybki falstart, czyli Atollic TrueSTUDIO

Do niedawna dość dobrym, a co najważniejsze darmowym środowiskiem do pisania programów dla STM32 był OpenSTM32 (nazywany również AC6): http://www.openstm32.org/HomePage
Poprzednio o nim wspominałem. Sporo czasu jednak minęło i podobno jest to już mocno przestarzałe rozwiązanie. ST ponoć przestał wspierać inicjatywę, zainwestował za to w produkt firmy Atollic - albo raczej w całą firmę.
W każdym razie słyszałem, że teraz wskazane jest używanie Atollic TrueSTUDIO (https://atollic.com/truestudio/). Postanowiłem to sprawdzić.
Na początek trzeba pobrać pakiet instalacyjny. Podobnie jak OpenSTM32 również TrueStudio to nakładka na Eclipse. Jednak producent mocniej stara się to ukrywać i trzeba pobrać cały pakiet - w wersji dla Linuxa to 740MB, jakoś sporo, ale mamy XXI wiek więc trzeba się przyzwyczaić.
Po rozpakowaniu i instalacji jest jeszcze gorzej - 2.1GB dysku zajęte przez wtyczkę do Eclipse. Miejmy nadzieję, że to chociaż dobra wtyczka.
Uruchomienie programu nie ujawnia niespodzianek - zwykły Eclipse, nic specjalnego.
Tworzę pierwszy projekt i tutaj miłe zaskoczenie. Dostępne są szablony projektów dla bardzo wielu mikrokontrolerów STM32, nawet płytki ewaluacyjne są do wyboru. Super, wybieram więc moją stm32f429-discovery, cierpliwie przyciskam next, next, next i gotowe, mam pierwszy projekt!


Tutaj pierwsza myśl - przegapiłem wybór biblioteki. Co więcej projekt używa StdPeriph, do dobra biblioteka, ale nieco wiekowa jak na nowy wyrób.
Spróbowałem więc ponownie i zonk! TrueSTUDIO zawsze używa StdPeriph. Nie można sobie w Cube HAL projektu przygotować. Pewnie autorzy uznali, że od tego jest Cube MX, ale ja mam uczulenie na ten strasznie niedopracowany wyrób programo-podobny.
Jako ciekawostkę wygenerowałem projekt dla nowszego układu, który nie jest wspierany przez StdPeriph - wtedy TrueSTUDIO po cichu zmienia bibliotekę i stosuje Cube... Jednym słowem pakiet ma własne AI i wie lepiej czego chcemy.
Wniosek z eksperymentu jest taki - TrueSTUDIO nawet działa i pewnie można z niego korzystać. Ale jeśli nie chcemy żeby komputer był inteligentniejszy od nas, lepiej je chyba potraktować jako ciekawostkę która tylko marnuje miejsce na dysku. W każdym razie taki mam plan. Od następnego wpisu już z tego środowiska nie zamierzam korzystać.

Powrót do pisania bloga i zmiana platformy

Jak ten czas szybko biegnie. Na chwilę znudziło mi się pisanie bloga, a jak popatrzę na daty wpisów już ponad rok czasu minął. Dlaczego mi się znudziło? Jak zwykle powodów jest wiele, więc właściwie to ciężko powiedzieć. W sumie to chyba głównie dlatego, że obiecałem dokończyć kurs stm32f103 na HAL z Cube (https://forbot.pl/blog/kurs-stm32-f1-migracja-na-hal-wstep-spis-tresci-id23580) i w efekcie ten mikrokontroler obrzydł mi już całkiem. Nie dlatego, że to zły układ - może trochę dlatego że nie przepadam za bibliotekami, ale na pewno dlatego że to już drugi kurs (poprzedni wykorzystywał StdPeriph). Więc po dwóch kursach oraz początku bloga mogę chyba już mieć stm32f103 serdecznie dosyć.
Postanowiłem więc wrócić do pomysłu pisania programów z minimalną liczbą bibliotek, ale tym razem na trochę innym mikrokontrolerze. Mój wybór padł na stm32f429 oraz płytkę ewaluacyjną discovery: https://www.st.com/en/evaluation-tools/32f429idiscovery.html


Dlaczego akurat ta płytka? Po pierwsze zadecydowała cena - jest bardzo przyzwoita w porównaniu z nowszymi stm32f7xx. Poza tym na płytce znajdziemy peryferia, które niełatwo podłączyć samemu: ekran LCD oraz pamięć SDRAM. Dostępny jest również interfejs USB - więc zabawa płytką może dostarczyć sporo emocji w porównaniu z innymi produktami w większości oferującymi bardziej standardowe interfejsy.