Płytka została oddana do produkcji, więc ostatnio było trochę czasu, żeby usiąść do softu. W tym tygodniu udało mi się zrobić kilka rzeczy:

  • postawić projekt na STM32F4,
  • dodać najnowszą wersję systemu FreeRTOS – 9.0.0,
  • uruchomić framework do unit testów CppUTest.

Aktualny kod źródłowy projektu można znaleźć na moim GitHubie.

Projekt STM32

Szablon projektu na STM32F4 opisywałem kiedyś w oddzielnym artykule, a źródła umieściłem na GitHubie. Szablon z artykułu był robiony pod płytkę STM32F4 Discovery zawierającą procesor STM32F407VG. W tym projekcie używam słabszego procesora STM32F401RB posiadającego mniej pamięci FLASH, mniej RAM, niższe taktowanie, mniej peryferiów i mniej nóżek. Aby dostosować projekt do innego procesora należało wykonać kilka zmian:

1. Zmodyfikować w skrypcie linkera rozmiary używanych pamięci. Rozmiary pamięci rom i ram zostały zmienione, aux_ram, ccm_ram i bkp_ram zostały usunięte. Usunąłem dla nich także symbole start, size i end.

/******************************************************************************
 * Defines for memory regions for chip: STM32F401RB
 ******************************************************************************
 */

MEMORY
{
	rom (rx)		: org = 0x08000000, len = 128k
	ram (rwx)		: org = 0x20000000, len = 64k
}

/******************************************************************************
 * Definitions of the beginning, end, and size of various regions.
 *****************************************************************************/
__rom_start = ORIGIN(rom);
__rom_size = LENGTH(rom);
__rom_end = __rom_start + __rom_size;

__ram_start = ORIGIN(ram);
__ram_size = LENGTH(ram);
__ram_end = __ram_start + __ram_size;

/******************************************************************************
 * Exporting definitions to be used in program. They are available if program
 * doesn't define variable with the same name.
 *****************************************************************************/
PROVIDE(__rom_start = __rom_start);
PROVIDE(__rom_size = __rom_size);
PROVIDE(__rom_end = __rom_end);

PROVIDE(__ram_start = __ram_start);
PROVIDE(__ram_size = __ram_size);
PROVIDE(__ram_end = __ram_end);

2. Wprowadzić zmiany w pliku hw/core_init/core_init.c umożliwiające pracę z taktowaniem 84MHz zamiast 168MHz. W tym celu należało zmienić definicje dzielników dla PLL:

/** PLL divider for USB - 42 MHz. */
#define PLL_Q	4
/** PLL divider before VCO - 2 MHz */
#define PLL_M	4
/** PLL multiplier - 168 MHz */
#define PLL_N	84
/** PLL divider after VCO - 84 MHz */
#define PLL_P	2

Poza tym należało zmienić ilość wait states dla pamięci FLASH. Dla prędkości taktowania procesora 84MHz poprawna wartość to 2WS.

/* FLASH configuration*/
FLASH->ACR = FLASH_ACR_ICEN |   /* instruction cache */
	FLASH_ACR_DCEN |        /* data cache */
	FLASH_ACR_PRFTEN |      /* prefetch enable */
	FLASH_ACR_LATENCY_2WS;  /* 2 wait states */

Pozostały kod może zostać bez zmian. Należy tylko pamiętać, że procesor STM32F401RB nie obsługuje wszystkich przerwań, jakie zostały zapisane w wektorze. Nie wpływa to jednak na działanie programu, ponieważ zamiast nieużywanych funkcji przerwań, w wektorze znajdują się puste miejsca.

FreeRTOS

Aby dodać do projektu system czasu rzeczywistego FreeRTOS, należy ściągnąć najnowszą wersję kodu ze strony producenta. Następnie skopiować do projektu pliki .c z folderu Source i pliki .h z folderu Source/include. Znajdują się tam pliki z implementacją systemu FreeRTOS. Potrzebujemy jeszcze plików specyficznych dla wykorzystywanej przez nas architektury procesora, czyli ARM Cortex M4 z wykorzystaniem FPU. Potrzebne pliki znajdziemy w folderze Source/portable/GCC/ARM_CM4F.

Potrzebny będzie nam także mechanizm zarządzania dynamiczną alokacją pamięci. Dostępne implementacje znajdują się w folderze Source/portable/MemMang, więcej informacji na temat dostępnych implementacji tutaj – link. Ja w swoich projektach używam heap_4.c, czyli implementacji umożliwiającej alokację i zwalnianie pamięci, a także łączenie zwolnionych obszarów. Dobrym wyborem może być też heap_3.c będący wrapperami na standardowe funkcje systemowe malloc i free. Jest jeszcze nowy heap_5.c, którego nigdy nie używałem i nie wiem jak działa, ale z tego co widzę jest to rozszerzona wersja czwórki.

FreeRTOS do działania wymaga również pliku nagłówkowego FreeRTOSConfig.h zawierającego ustawienia konfiguracyjne w postaci define’ów. Najlepiej ten plik skopiować z przykładów udostępnianych przez FreeRTOS np. z pliku Demo/CORTEX_M4F_STM32F407ZG-SK/FreeRTOSConfig.h i zedytować. Podczas edycji warto zwrócić uwagę na takie parametry jak maksymalna liczba priorytetów, minimalny rozmiar stosu dla wątku, całkowity rozmiar heapa, szybkość taktowania. Mój plik konfiguracyjny znajduje się w src/external/FreeRTOS/include/FreeRTOSConfig.h.

Ostatnim potrzebnym plikiem jest implementacja hooków wykorzystywanych przez FreeRTOS. Są to funkcje, do których program wchodzi w przypadku błędu alokacji pamięci, przepełnienia stosu, albo będąc w idle tasku. Moja implementacja znajduje się w pliku src/external/FreeRTOS/hooks.c.

CppUTest

CppUTest jest frameworkiem do unit testów napisanym w C++ również z myślą o testowaniu systemów wbudowanych. Framework ten jest używany w książce „Test Driven Development for Embedded C” Jamesa Grenninga. Autor książki jest również jednym z autorów frameworka. Do tej pory do unit testów używałem frameworka Unity napisanego w C. Używam go już od kilku lat i mogę spokojnie wszystkim polecić. W tym projekcie jednak chciałem spróbować czegoś nowego, dlatego wybór padł na CppUTest. Dodatkowym impulsem była chęć napisania testów w C++. Dzięki temu powinienem się czegoś nowego nauczyć, a sam kod powinien być bardziej przejrzysty i łatwiejszy do utrzymania. Zobaczymy, jak to będzie wyglądało w praktyce.

Pierwszym problemem z jakim przyszło mi się zmierzyć jest rozmiar frameworka i liczba wykorzystywanych w nim plików. Unity składało się jedynie z dwóch plików .c i pięciu plików .h. Dlatego CppUTest na początku wydał mi się nieco przytłaczający, sam folder include/CppUTest z podstawowymi headerami zawiera 27 plików, do tego headery z include/CppUTestExt i źródła z src/CppUTest i src/CppUTestExt. Łącznie ponad 80 plików źródłowych.

Trzeba by to teraz jakoś zbuildować. Projekt zawiera swoje własne makefile i skrypty bashowe. Próbowałem skompilować projekt zgodnie z instrukcją za pomocą skryptu configure i make. Niestety operacja się nie powiodła. Problemem było MINGW, którego używałem do kompilacji. Twórcy projektu zalecają do tego celu Cygwina. Postanowiłem więc skompilować projekt przy pomocy własnych skryptów make. Trochę czasu musiałem poświęcić na walkę, ale w końcu się udało. Aby kompilacja się powiodła, należało wyłączyć z niej plik src/CppUTestExt/IEEE754ExceptionsPlugin.cpp i dodać plik UtestPlatform.cpp do folderu src/CppUTest. Plik ten zawiera implementacje funkcji takich jak skoki, alokacja pamięci, czy funkcje do obsługi wątków zależne od wykorzystywanego systemu.

Swój plik UtestPlatform.cpp wziąłem z folderu src/Platforms/Gcc, musiałem go jednak dodatkowo zedytować. Edycja polegała na wyrzuceniu linijki

#include <pthread.h>

i zastąpieniu kodu obsługującego mutexy znajdującego się na końcu pliku pustymi funkcjami:

static PlatformSpecificMutex DummyMutexCreate(void)
{
    return 0;
}

static void DummyMutexLock(PlatformSpecificMutex mtx)
{
}

static void DummyMutexUnlock(PlatformSpecificMutex mtx)
{
}

static void DummyMutexDestroy(PlatformSpecificMutex mtx)
{
}

PlatformSpecificMutex (*PlatformSpecificMutexCreate)(void) = DummyMutexCreate;
void (*PlatformSpecificMutexLock)(PlatformSpecificMutex) = DummyMutexLock;
void (*PlatformSpecificMutexUnlock)(PlatformSpecificMutex) = DummyMutexUnlock;
void (*PlatformSpecificMutexDestroy)(PlatformSpecificMutex) = DummyMutexDestroy;

Po tych zabiegach udało mi się skompilować bibliotekę CppUTest wraz z przykładowym testem i uruchomić program. Bibliotekę dodałem do projektu tak, aby była kompilowana do biblioteki statycznej .a i dodawana do wszystkich buildów unit testowych w projekcie. Targety unit testów są kompilowane za pomocą C++, ale testują kod produkcyjny napisany w C. We wspomnianej wyżej książce autor twierdził, że taka sytuacja jest w porządku, a nawet ma pewne zalety. Prawdopodobnie więcej będę mógł powiedzieć na ten temat, kiedy już napiszę trochę testów.