Home Articles ARM Przykłady dla STM32 + STM32F10x Standard Peripherals Library
Przykłady dla STM32 + STM32F10x Standard Peripherals Library
User Rating: / 47
PoorBest 
Written by Freddie Chopin   
Wednesday, 11 January 2012 21:35
Article Index
Przykłady dla STM32 + STM32F10x Standard Peripherals Library
Wspólny początek
Sposób 1
Sposób 2
Sprawdzenie konfiguracji
Outro
All Pages

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:

  1. z folderu c:\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\CoreSupport - wszystkie pliki (core_cm3.c i core_cm3.h),
  2. 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ść),
  3. z folderu c:\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver\inc - wszystkie pliki,
  4. 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:

  1. 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),
  2. 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,
  3. w liniach INC_DIRS i SRCS_DIRS dodajemy nazwę stworzonego przez nas katalogu - np. lib,
  4. 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:

  1. 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),
  2. 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,
  3. 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
  4. 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
  5. 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.

Last Updated on Monday, 15 October 2012 22:24