Hlavní navigace

Přístupy k programování STM32

Michal Dudka

Ještě než se pustíte do programování STM32, není od věci seznámit se s přístupy, jakými je možné programovat. Díky tomu, že existuje několik typů knihoven, existuje i několik programovacích stylů.

Soustředím se pouze na to, abych vám předvedl jednoduchou úlohu blikání LEDkou několika různými styly. Doufám, že si díky tomu uděláte představu a vyberete si pro začátek ten styl který je vám nejbližší. Pojďme si tyto přístupy nejprve stručně představit.

CMSIS

Programování pouze s knihovnou CMSIS se velice podobá programovacím stylům 8bitových mikrokontrolérů. Programujete tím stylem, že přímo zapisujete a čtete z registrů (SFR). Vystačíte si s hlavičkovým souborem, který obsahuje stovky maker a definic usnadňujících vám přístup k řídicím registrům čipu.

Během programování je nezbytně nutné stále nahlížet do datasheetu na funkci jednotlivých bitů v registrech. Je to tedy velice pracná a zdlouhavá metoda. Jedinou její výhodou (a to velice spornou) je fakt, že se nemusíte učit, jak pracují knihovní funkce. Využitelná je v aplikacích, které jsou úzce vázané na periferie čipu. Tedy v aplikacích zaměřených primárně na hardware.

SPL

Programování s pomocí knihoven SPL (Standard Peripheral Library) je postup, který nemá budoucnost, protože podpora SPL byla před více než rokem ukončena. Ke starším čipům, jako třeba STM32F407 (a mnoha dalším), jsou SPL k dispozici. Pokud ale začínáte, je pro vás výhodnější učit se knihovny LL nebo HAL.

LL API

Nízkoúrovňové (Low Level) knihovny slouží pro kompletní ovládání periferií. Odstiňují vás od přímé práce s registry, aniž by ale obětovaly některé z funkcí periferií. Jejich blízký vztah k periferiím snižuje teoreticky přenositelnost programu. Na druhou stranu umožňuje plně rozvinout schopnosti periferií. Jim se budu ve svém tutoriálu věnovat.

HAL API

Vysokoúrovňové knihovny (High Abstraction Layer) jsou dobře přenositelné. Odstiňují programátora od detailní práce s periferiemi. Díky tomu je konfigurace periferií rychlejší. Nejsou ale tak efektivní jako LL nebo SPL a já se jim v tutoriálu nejspíš nebudu věnovat. Čímž nechci naznačit že jsou špatné, ale leží příliš daleko od těžiště mé práce a tedy i mých zkušeností.

Jednoduchá ukázka programování s CMSIS

V ukázkách si zablikáme LEDkami, které máme na Discovery boardu (STM32F051 Discovery) připojeny na pinech PC8(modrá) a PC9(zelená) přes odpory 330R resp. 660R proti GND. A vy se zkuste soustředit jen na to, jak je pro vás zdrojový kód čitelný. Ve všech třech příkladech děláme totéž. Spustíme clock periferii GPIOC, nastavíme PC8 a PC9 jako výstupy (s maximální rychlostí přeběhu). Poté s pomocí tupé čekací smyčky (s dobou čekání okolo 1s) obě LED zhasneme a jednu po druhé rozsvítíme. Pojďme se tedy nejprve podívat na ukázku CMSIS.

// A) ukázka programování pouze s pomocí CMSIS, blikání LED
// procesor STM32F051R8T6 na desce STM32F051 Discovery
// LEDky na PC8 a PC9
#include "stm32f0xx.h"

void delay(uint32_t del); // hloupý delay
#define DELAY_VAL 1000000

int main(void){
// pustit clock do GPIOC
RCC->AHBENR |= RCC_AHBENR_GPIOCEN;
// nastavit PC8 a PC9 jako výstupy
GPIOC->MODER = GPIO_MODER_MODER9_0 | GPIO_MODER_MODER8_0;
// nastavit rychlost přeběhu na PC8 a PC9 na maximum (jen pro legraci)
GPIOC->OSPEEDR = GPIO_OSPEEDR_OSPEEDR8_Msk | GPIO_OSPEEDR_OSPEEDR9_Msk;

  while (1){
    GPIOC->BRR = GPIO_BRR_BR_9 | GPIO_BRR_BR_8; // zhasni obě LED
    delay(DELAY_VAL); // chvíli počkej
    GPIOC->BSRR = GPIO_BSRR_BS_8; // Rozsviť LED na PC8
    delay(DELAY_VAL); // chvíli počkej
    GPIOC->BSRR = GPIO_BSRR_BS_9; // Rozsviť LED na PC9
    delay(DELAY_VAL); // chvíli počkej
  }
}

// hloupý delay
void delay(uint32_t del){
uint32_t i;
for(i=0;i<del;i++){};
}

Všimněte si struktury příkazů. Registr ke kterému přistupujeme se stává z bázové adresy periferie a offsetu příslušného registru. Na konkrétním příkladě příkazu:
RCC->AHBENR |= RCC_AHBENR_GPIOCEN;
můžete vidět, že v periferii RCC (Reset and Clock Control) chceme přistupovat do registru AHBENR. Za rovnítkem je pak makro, kterým přistupujeme ke konkrétnímu bitu v tomto registru a nastavujeme ho do log.1. V situacích, kdy bychom chtěli nastavovat celou skupinu bitů zároveň, hledáme makra zakončená koncovkou _Msk (těchto maker využijete typicky při nulování skupiny bitů). A to je v podstatě vše, s čím si musíte vystačit. Další zdrojový kód bude ke stejnému účelu využívat LL knihovny v plné výbavě.

// B) ukázka programování s pomocí LL API (s využitím USE_FULL_LL_DRIVER), blikání LED
// Styl velmi podobný SPL
// procesor STM32F051R8T6 na desce STM32F051 Discovery
// LEDky na PC8 a PC9
#include "stm32f0xx.h"
#include "stm32f0xx_ll_bus.h" // kvůli fcím pro povolování clocku periferiím
#include "stm32f0xx_ll_gpio.h" // kvůli fcím pro práci s GPIO

void delay(uint32_t del); // hloupý delay
#define DELAY_VAL 1000000

int main(void){
// deklarace struktury pro konfiguraci pinů
LL_GPIO_InitTypeDef gpio;
// spustit clock pro GPIOC
LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOC);
// naplníme strukturu gpio (s vybranou konfigurací pinů)
gpio.Mode = LL_GPIO_MODE_OUTPUT; // vybrané piny jsou výstupy
gpio.Speed = LL_GPIO_SPEED_HIGH; // rychlost přeběhu maximální
gpio.OutputType = LL_GPIO_OUTPUT_PUSHPULL; // typ výstupu push-pull
gpio.Pull = LL_GPIO_PULL_NO; // pull-up ani pull-down resistor nechceme
gpio.Pin = LL_GPIO_PIN_8 | LL_GPIO_PIN_9; // piny 8 a 9
// aplikujeme zvolenou konfiguraci na GPIOC
LL_GPIO_Init(GPIOC,&gpio);

 while (1){
  LL_GPIO_ResetOutputPin(GPIOC,LL_GPIO_PIN_8 | LL_GPIO_PIN_9); // zhasni obě LED
  delay(DELAY_VAL); // chvíli počkej
  LL_GPIO_SetOutputPin(GPIOC,LL_GPIO_PIN_8); // Rozsviť LED na PC8
  delay(DELAY_VAL); // chvíli počkej
  LL_GPIO_SetOutputPin(GPIOC,LL_GPIO_PIN_9); // Rozsviť LED na PC9
  delay(DELAY_VAL); // chvíli počkej
 }
}

// hloupý delay
void delay(uint32_t del){
uint32_t i;
for(i=0;i<del;i++){};
}

Pro použití knihoven musíme potřebné soubory inkludovat, což je patrné z prvních řádků ukázky. Veškeré programování se odehrává pomocí čitelných funkcí a skrývá tak před vámi přímý přístup do registrů i nutnost listovat datasheetem a sledovat, jak jednotlivé bity v registrech konfigurovat. Stejně tak ani nemusíte hledat mezi makry, protože funkce jsou velmi dobře komentované a v záhlaví každé funkce je seznam všech povolených argumentů. Což výrazně zrychluje programování. Pro konfiguraci periferií slouží přehledné struktury, které před-plníme čitelnými parametry a zavoláním příslušné funkce se toto nastavení aplikuje. V našem příkladě takto nastavujeme parametry pinům PC8 a PC9 (všimněte si že to můžeme dělat hromadně pro více pinů). Abyste tyto struktury mohli používat, musíte si v překladači mezi definované symboly zařadit „USE_FULL_LL_DRIVER“ (o čemž se dozvíte přečtením prvních stránek dokumentace ke knihovnám). Proč je tu možnost struktury nepoužívat, netuším. V dalším příkladě pro úplnost předvedu, jak by to vypadalo bez používání inicializačních struktur.

// C) ukázka programování s pomocí LL API (bez využití USE_FULL_LL_DRIVER), blikání LED
// procesor STM32F051R8T6 na desce STM32F051 Discovery
// LEDky na PC8 a PC9
#include "stm32f0xx.h"
#include "stm32f0xx_ll_bus.h" // kvůli fcím pro povolování clocku periferiím
#include "stm32f0xx_ll_gpio.h" // kvůli fcím pro práci s GPIO

void delay(uint32_t del); // hloupý delay
#define DELAY_VAL 1000000

int main(void){
// spustit clock pro GPIOC
LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOC);
// nastavíme PC8 jako výstup
LL_GPIO_SetPinMode(GPIOC,LL_GPIO_PIN_8,LL_GPIO_MODE_OUTPUT);
// nastavíme PC9 jako výstup
LL_GPIO_SetPinMode(GPIOC,LL_GPIO_PIN_9,LL_GPIO_MODE_OUTPUT);
// nastavíme rychlost přeběhu na PC8
LL_GPIO_SetPinSpeed(GPIOC,LL_GPIO_PIN_8,LL_GPIO_SPEED_FREQ_HIGH);
// nastavíme rychlost přeběhu na PC9
LL_GPIO_SetPinSpeed(GPIOC,LL_GPIO_PIN_9,LL_GPIO_SPEED_FREQ_HIGH);

 while (1){
  LL_GPIO_ResetOutputPin(GPIOC,LL_GPIO_PIN_8 | LL_GPIO_PIN_9); // zhasni obě LED
  delay(DELAY_VAL); // chvíli počkej
  LL_GPIO_SetOutputPin(GPIOC,LL_GPIO_PIN_8); // Rozsviť LED na PC8
  delay(DELAY_VAL); // chvíli počkej
  LL_GPIO_SetOutputPin(GPIOC,LL_GPIO_PIN_9); // Rozsviť LED na PC9
  delay(DELAY_VAL); // chvíli počkej
 }
}

// hloupý delay
void delay(uint32_t del){
volatile uint32_t i;
for(i=0;i<del;i++){};
}

Tady si všimněte, že bez využití struktur ztrácíte možnost konfigurovat více pinů zároveň. Čitelnost programu tím však nijak moc netrpí. Teď už je jen na vás, který styl si zvolíte. Já budu ve svých tutoriálech používat druhý z nich, tedy LL knihovny s plnou podporou. Přirozeně vám nikdo nebrání tyto přístupy libovolně míchat. Což najde opodstatnění v situacích, kdy budete potřebovat některé části programu optimalizovat pro rychlost. Tam pak prostě kritické rutiny napíšete pomocí CMSIS a zbytek programu bude pro jednoduchost napsán s použitím LL nebo HAL knihoven. Doufám, že jste si udělali hrubou představu, jak bude programování vypadat, a těším se na shledanou u dalšího tutoriálu…

STM32 v True AtollicSTUDIO

STM32 můžete programovat v mnoha různých vývojových prostředích. Bez omezení velikosti kódu to lze minimálně v Atollic TrueStudiu a v CoIDE. Správně bych se zde ani neměl zabývat tím, jak celý vývojový řetězec zprovoznit. Nemám na to totiž kvalifikaci a pořádně tomu nerozumím. Protože to ale není úplně snadné, zkusím vám dát pár tipů a budu doufat že se tím prokoušete. Pokud se vám to podaří, bude na vás čekat řada tutoriálů, osvětlujících práci s jednotlivými periferiemi STM32. Doufám, že se na českém webu najde někdo povolanější a tutoriál na toto téma zpracuje.

O ST-LINKu už byla řeč, zmíním tedy pouze to, že k jeho přímému ovládání slouží aplikace ST-LINK Utility, kterou si určitě stáhněte. Ta vám umožní provádět základní operace jako je mazání, čtení a programování čipu. A bude vám velmi užitečná, když si nějakou hloupou chybou vypnete programovací rozhraní. Jinak ovládání ST-LINKu zvládá IDE. Některé modulky obsahují vylepšený ST-LINK, který krom role programátoru / debuggeru zastoupí také roli USB->UART bridge a po nahrání příslušných driverů můžete v PC rozběhnout virtuální seriový port a zprostředkovat tak komunikaci mezi PC a STM32 bez potřeby připojovat externí USB->UART převodník (jako třeba čipy od FTDI). O tom, která verze ST-LINKu na desce je, se dozvíte z její dokumentace. Já o této funkci vím jen z doslechu.

Pro zprovoznění toolchainu si budete muset stáhnout Atollic TrueSTUDIO a nainstalovat ho. Během instalace by se vám do PC měly nahrát drivery k ST-LINKu. Studio samo o sobě vám ale moc možností nedává, čipy budete moci programovat pouze s CMSIS knihovnami. Pokud budete chtít používat SPL, LL a nebo HAL knihovny, musíte si je stáhnout. Pro jejich stažení googlete STM32Cube a nebo si vyberte na ST.com. Každá rodina má svůj vlastní balík knihoven. Aby to nebylo tak přehledné, tak v balíku najdete krom knihoven také kopu vzorových projektů pro různá vývojová prostředí, vzorové příklady a spoustu dalšího. Takže si ji po stažení určitě prohlédněte a trochu se v ní zorientujte.

Jak stvořit nový projekt v TrueSTUDIu nemá smysl moc komentovat, protože s každou novou verzí se postup drobně upravuje, odkážu vás proto buď na user manual a nebo na postarší stránky emcu.it, kde je obrázkový návod pro starší verze. Jakmile se vám to podaří, můžete směle programovat s knihovnami CMSIS. To ale chtít asi nebudete, takže musíte zvládnout ještě jeden krok: implementaci knihoven LL nebo HAL [PDF]. Do strany 7 se návod zabývá nástrojem CubeMX, o kterém bude řeč později. To, co potřebujete, začíná na straně 8.

  • Krok jedna je v balíčku knihoven, který jste si stáhli, najít složku STM32F0×x_HAL_Driver (pro jinou rodinu čipů než F0 se jméno drobně odlišuje) a její obsah nakopírovat do stejnojmenné složky v projektu (tato složka by měla vzniknout spolu s vytvořením projektu). Ke knihovnám je potřeba ještě dodat aktuální hlavičkové soubory. Ty se nachází ve složce CMSIS/Device Obsah této složky (který by měla tvořit jen skupinka hlavičkových souborů) opět nakopírujete do stejnojmenné složky ve vašem projektu. Nic dalšího byste z balíčku teď potřebovat neměli. Ve studiu by mělo být možné knihovny pouze „nalinkovat“ a nekopírovat je (což neumím a musím se naučit !).
  • Krok dvě je povolení plných funkcí LL knihoven. V trueSTUDIu jděte do Project->Properties->C/C++ Build->Settings a v C Compiler->Smybols přijdete definici USE_FULL_LL_DRIVER (přesně tak jak vidíte na straně 9) ve výše zmíněném návodu.
  • Poslední krok je pak v souboru main.c v němž píšete zdrojový kód, inkludovat použité knihovny (strana 11 v návodu). Pokud víte že budete používat pouze LL knihovny, můžete krok jedna upravit a nekopírovat celý obsah složky STM32F0×x_HAL_Driver, ale pouze LL knihovny. Nebo dokonce pouze vybrané LL knihovny :)
  • Pokud vám překladač hlásí nějaké chybějící makro nebo definici, skoro určitě máte jinou verzi knihoven než CMSIS. Jinak řečeno knihovny očekávají,že máte novější hlavičkové soubory.

Alternativní možností je stáhnout aplikaci CubeMX, ve které lze přehledným způsobem nakonfigurovat periferie i clock čipu a nechat si od ní vygenerovat adresářovou strukturu, kterou pak nahrajete do složky s projektem. Spolu s tím totiž nevědomky uděláte krok jedna – to je také zmíněno v návodu. To je ode mě k tomuto tématu všechno. Možná to bylo stručné, ale zcela to odpovídá mým vědomostem na toto téma. Víc jsem totiž nepotřeboval.

Relevantní odkazy

Našli jste v článku chybu?