W ramach krótkiego wstępu, powiem tylko, że osobiście nie polecam używania biblioteki standardowej dla STM32 (STM32F10x Standard Peripherals Library), z kilku powodów, z których najważniejszym jest to, że tak czy siak nie ominie nas przeczytanie manuala dla danego układu i zrozumienie zasady działania i konfiguracji danego układu peryferyjnego, więc po co dokładać sobie do tego jeszcze dokumentację biblioteki? Z biblioteką ale bez zrozumienia danego układu peryferyjnego nic nie zrobimy, w odwrotnej "konfiguracji" (bez biblioteki, z wiedzą) - można zrobić wszystko co się tylko chce. Generalnie większość zarzutów względem tej biblioteki zebrana została w tym temacie na forum elektrody.
Nie da się jednak zaprzeczyć, że są osoby dla których możliwość użycia tej biblioteki jest ważna i do nich właśnie kierowany jest ten artykuł.
Artykuł ten oparty jest o następujące składniki w następujących wersjach (są to najnowsze wersje na obecną chwilę):
- projekt dla STM32 w postaci archiwum .zip pobrany z działu Download > ARM > Przykłady (artykuł bazuje na wersji stm32_blink_led-1.2.1-120107),
- biblioteka STM32F10x Standard Peripherals Library rozpakowana w dowolnym miejscu (artykuł bazuje na wersji 3.5.0 rozpakowanej w głównym katalogu dysku c:, a więc dostępnej pod ścieżką c:\STM32F10x_StdPeriph_Lib_V3.5.0).
- środowisko skonfigurowane według minimalnie zmodyfikowanego (unowocześnionego, szczegóły opisane w komentarzach pod artykułem i w temacie na forum elektrody) opisu z artykułu ARM toolchain - tutorial:
Sposobów sprzęgnięcia przykładowych projektów z biblioteką jest kilka, przedstawię dwa podstawowe jakie przyszły mi do głowy. Obydwa mają wspólny "początek" i wspólny "koniec" (sprawdzenie konfiguracji).
Wspólny początek
Działania rozpoczynamy od zaimportowania przykładu jako nowego projektu do Eclipse. Po uruchomieniu Eclipse klikamy więc w menu File, a następnie w opcję Import..., z gałęzi General wybieramy opcję Existing Projects into Workspace i klikamy Next. Na kolejnej stronie wybieramy radiobuttona obok Select archive file i wprowadzamy ścieżkę do archiwum z projektem który chcemy zaimportować - ręcznie lub za pomocą przycisku Browse.... Po kliknięciu przycisku Finish w drzewie projektów stworzony zostanie nowy projekt z gotowymi ustawieniami i wszystkimi plikami.
Jeśli w workspace istnieje już projekt o danej nazwie, nie będzie możliwe zaimportowanie go z archiwum. W takim wypadku należy otworzyć istniejący już projekt, zmienić jego nazwę (File > Rename gdy projekt jest zaznaczony lub opcja Rename z menu kontekstowego projektu) i dopiero wtedy zaimportować projekt wg powyższego opisu. Po poprawnym imporcie można projektom zmienić nazwy według uznania lub zostawić takie jak są obecnie.
Teraz należy przystąpić do "odchudzenia" projektu z plików, które będą nam zbędne. Usuwamy więc:
- folder inc wraz z zawartością,
- wszystkie pliki poza hdr_special_registers.h z katalogu hdr (w wersji 1.2.1 skasować więc należy pliki hdr_bitband.h, hdr_gpio.h i hdr_rcc.h),
- wszystkie pliki poza startupem (startup.S), tablicą wektorów (vectors.c), Makefile i skryptem linkera (STM32F103xB_rom.ld lub STM32F107xB_rom.ld), ewentualnie można zostawić Doxyfile (w wersji 1.2.1 skasować więc należy pliki config.h, gpio.c, gpio.h i main.c).
Celem tej operacji jest pozbycie się z projektu funkcji main(), tak więc można się ograniczyć do skasowania samego pliku main.c lub nawet jedynie zmiany nazwy istniejącej w nim funkcji main() na cokolwiek innego.
W tym momencie do projektu możemy dodać kilka plików potrzebnych do funkcjonowania biblioteki. Po zaznaczeniu naszego projektu klikamy więc na opcję Import... z menu File (lub ta sama opcja z menu kontekstowego projektu), z grupy General wybieramy pozycję File System i klikamy na Next. Następnie podajmy ścieżkę do szablonu projektu wchodzącego w skład biblioteki - ręcznie lub przyciskiem Browse - w moim przypadku będzie to c:\STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Template. W prawym okienku wybieramy do zaimportowania (poprzez zaznaczenie checkboxów) pliki stm32f10x_conf.h oraz system_stm32f10x.c i klikamy przycisk Finish.
Do projektu dodajemy też pusty plik main.c - menu File > New > Source File, następnie podajemy nazwę main.c w polu Source file i klikamy Finish (nazwa pliku oczywiście dowolna, byle miał on rozszerzenie .c). W pliku tym umieszczamy pustą (na razie) funkcję main():
int main(void) { return 0; }
Sposób 1 - pliki bazowe z przykładów, biblioteka w folderze projektu
Jest to najprostsza metoda osiągnięcia założonego celu. W sposobie tym kopiujemy po prostu wszystkie potrzebne pliki biblioteki do folderu projektu.
W projekcie (dla większego porządku) tworzymy folder (menu File > New > Folder lub - jak zwykle - menu kontekstowe katalogu projektu) o dowolnej nazwie - np lib - i importujemy do niego (sposób importowania plików przedstawiony został nieco wcześniej w tym artykule) następujące zasoby:
- z folderu c:\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\CoreSupport - wszystkie pliki (core_cm3.c i core_cm3.h),
- z folderu c:\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x - plik stm32f10x.h oraz system_stm32f10x.h (dla porządku można też zaimportować do folderu lib plik system_stm32f10x.c a następnie skasować go z głównego katalogu projektu, lub można go później przenieść),
- z folderu c:\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver\inc - wszystkie pliki,
- z folderu c:\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver\src - wszystkie pliki.
Teraz należy dodać do pliku Makefile kilka opcji, które umożliwią poprawną kompilację plików biblioteki. Po otwarciu pliku Makefile w edytorze dokonujemy w nim następujących zmian:
- w liniach CXX_DEFS i C_DEFS dodajemy następujące definicje: -DUSE_STDPERIPH_DRIVER -DHSE_VALUE=8000000 (wartość HSE_VALUE oczywiście uzależniona od używanego rezonatora kwarcowego), spowoduje to dołączenie do kodu odpowiednich nagłówków (definicja symbolu USE_STDPERIPH_DRIVER) i odpowiednią konfigurację funkcji które korzystają z wartości częstotliwości zewnętrznego rezonatora kwarcowego (wartość symbolu HSE_VALUE),
- w linii C_DEFS dodatkowo dopisujemy jeszcze -DSystemInit=low_level_init_1 - spowoduje to wywołaniem funkcji SystemInit() (z biblioteki) w startupie tuż przed wywołaniem funkcji main() - jest to wymagane przez aktualną konwencję biblioteki,
- w liniach INC_DIRS i SRCS_DIRS dodajemy nazwę stworzonego przez nas katalogu - np. lib,
- z linii C_WARNINGS usuwamy pozycję -Wstrict-prototypes, aby niezgodne z "czystym" standardem języka C deklaracje funkcji z CMSISa nie zasypały nas ostrzeżeniami, że "function declaration isn't a prototype".
W tym momencie możliwa jest już bezbłędna i bezproblemowa kompilacja całego projektu.
Sposób 2 - pliki bazowe z przykładów, biblioteka w innym folderze
W tej metodzie w projekcie wykorzystane są pliki bazowe (startup, skrypt linkera, tablica wektorów i Makefile) z przykładu, a biblioteka znajduje się w dowolnym miejscu.
Jak zwykle najważniejszym etapem jest dodanie do pliku Makefile niezbędnych wpisów. Otwieramy więc plik Makefile w edytorze i wykonujemy w nim następujące czynności:
- w liniach CXX_DEFS i C_DEFS dodajemy następujące definicje: -DUSE_STDPERIPH_DRIVER -DHSE_VALUE=8000000 (wartość HSE_VALUE oczywiście uzależniona od używanego rezonatora kwarcowego), spowoduje to dołączenie do kodu odpowiednich nagłówków (definicja symbolu USE_STDPERIPH_DRIVER) i odpowiednią konfigurację funkcji które korzystają z wartości częstotliwości zewnętrznego rezonatora kwarcowego (wartość symbolu HSE_VALUE),
- w linii C_DEFS dodatkowo dopisujemy jeszcze -DSystemInit=low_level_init_1 - spowoduje to wywołaniem funkcji SystemInit() (z biblioteki) w startupie tuż przed wywołaniem funkcji main() - jest to wymagane przez aktualną konwencję biblioteki,
- w linii INC_DIRS dodajemy następujące ścieżki z potrzebnymi nagłówkami:
- c:\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\CoreSupport
- c:\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x
- c:\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver\inc
- w linii SRCS_DIRS dodajemy następujące ścieżki z potrzebnymi plikami źródłowymi:
- c:\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\CoreSupport
- c:\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver\src
- z linii C_WARNINGS usuwamy pozycję -Wstrict-prototypes, aby niezgodne z "czystym" standardem języka C deklaracje funkcji z CMSISa nie zasypały nas ostrzeżeniami, że "function declaration isn't a prototype".
Podając wiele parametrów można rozdzielić je na klika linii przy pomocy znaku backslasha "\" lub powtarzać daną zmienną operatorem "+=" w kolejnych liniach VAR1 = value1 \ value2 \ value3 VAR2 = value1 VAR2 += value2 VAR2 += value3
W tym momencie projekt można już skompilować i powinno się to odbyć bez błędów, a więc zasadniczo wszystko jest w teorii gotowe.
Sprawdzenie konfiguracji
W ramach sprawdzenia można sobie stworzyć prosty programik testowy - nieśmiertelne miganie diodą LED, w którym skorzystamy z funkcji udostępnianych przez Standard Peripheral Library. Całą zawartość pliku z funkcją main() zastępujemy więc przedstawionym poniżej kodem, dostosowując definicje portów i pinów do posiadanej przez nas konfiguracji.
#include "stm32f10x.h"
#define LED_GPIO GPIOB #define LED_GPIO_RCC RCC_APB2Periph_GPIOB #define LED_Pin GPIO_Pin_1
int main(void) { volatile uint32_t count, count_max = 3000000; GPIO_InitTypeDef GPIO_InitStructure;
/* LED_GPIO Periph clock enable */ RCC_APB2PeriphClockCmd(LED_GPIO_RCC, ENABLE);
/* Configure LED pin in output pushpull mode */ GPIO_InitStructure.GPIO_Pin = LED_Pin; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(LED_GPIO, &GPIO_InitStructure);
while (1) { /* Set */ GPIO_SetBits(LED_GPIO, LED_Pin); /* Delay */ for (count = 0; count < count_max; count++); /* Reset */ GPIO_ResetBits(LED_GPIO, LED_Pin); /* Delay */ for (count = 0; count < count_max; count++); } }
Po skompilowaniu całości można ją wgrać do układu i debuggować - uruchamiając dostępne w projekcie predefiniowane skróty do OpenOCD (menu Run > External Tools > External Tool Configurations...) i GDB (menu Run > Degug Configurations... - należy wybrać skrót do GDB Hardware Debugging z końcówką "+ load").
Niekiedy może być konieczne dostosowanie konfiguracji do dostępnej wersji OpenOCD, konfiguracji sprzętowej lub innych szczegółów.
Po wgraniu i uruchomieniu program powinien migać wybraną diodą z częstotliwością około 1Hz (jeśli rdzeń pracuje na 72MHz, co jest domyślnym ustawieniem biblioteki).
Jeśli podczas kompilacji pliku core_cm3.c (szczególnie z włączoną optymalizacją) kompilator zasygnalizuje błąd assemblera: Error: registers may not be the same -- `strexb r0,r0,[r1]', Error: registers may not be the same -- `strexh r0,r0,[r1]', należy w pliku tym (po wyłączeniu atrybutu read-only) zmienić "=r" na "=&r" w następujących funkcjach: uint32_t __STREXB(uint8_t value, uint8_t *addr), uint32_t __STREXH(uint16_t value, uint16_t *addr), uint32_t __STREXW(uint32_t value, uint32_t *addr). Ciekawostką jest to, że ARM w stworzonym przez siebie pliku nie trzyma się swojej własnej dokumentacji... No cóż...
Jeśli pomimo poprawnej kompilacji projektu w konsoli Eclipse zasypuje nas błędami typu Semantic Error o treści "... could not be resolved" (gdy otwarty jest problematyczny plik źródłowy) należy wykonać następujące kroki: 1. skompilować projekt tak aby powstał plik wynikowy z rozszerzeniem .elf, 2. zamknąć wszystkie zakładki edytora, 3. zamknąć i otworzyć projekt, 4. odświeżyć zawartość folderu projektu (menu File > Refresh) - powinien pojawić się "folder" Binaries, 5. odbudować indeks projektu (menu kontekstowe projektu > Index > Rebuild).
Outro
Na podstawie opisanych powyżej dwóch sposobów można oczywiście opracować jeszcze kilka innych kombinacji, z których najbardziej interesująca wydaje się być wersja z wykorzystaniem jedynie pliku Makefile z przykładów, podczas gdy pozostałe pliki (startup, skrypt linkera, ...) będą pochodziły (np.) z paczki z biblioteką. Taka konfiguracja byłaby jedynie nieznacznie zmieniona względem drugiego z opisanych tu sposobów, trzeba tylko pamiętać o dwóch drobnostkach z pliku Makefile:
- rozszerzenie plików assemblerowych (parametr AS_EXT) skonfigurowane jest jako .S, natomiast w paczce pochodzącej od ST pliki te mają rozszerzenie .s (mała litera),
- parametr USES_CXX nie ma znaczenia dla startupa nie pochodzącego z przykładów.
W razie pytań lub niejasności, problemów i nieścisłości - jak zwykle - kontaktujcie się ze mną przez komentarze pod artykułem, formularz kontaktowy strony lub przez temat na forum elektrody poświęcony temu artykułowi. |