
V Snažil jsem se přiblížit hobby elektronikům, kteří vyrostli z kalhotek Arduino, jak a proč by měli číst datasheety a další dokumentaci k mikrořadičům. Text se ukázal být rozsáhlý, proto jsem slíbil praktické příklady ukázat v samostatném článku. No co, nazval jsem se houbou...
Dnes ukážu, jak pomocí datasheetů vyřešit poměrně jednoduché, ale nezbytné úkoly pro mnoho projektů na kontrolerech STM32 (Blue Pill) a STM8. Všechny demo projekty jsou věnovány mým oblíbeným LED diodám, rozsvěcovat je budeme ve velkém množství, k čemuž musíme zapojit různé zajímavé periferie.
Text opět vyšel obrovský, proto pro pohodlí dělám obsah:
Disklaimer: nejsem inženýr, netvrdím, že mám hluboké znalosti elektroniky, článek je určen pro podobné nadšence jako jsem já. Ve skutečnosti jsem cílovou skupinou zvažoval sebe před dvěma lety. Kdyby mi tehdy někdo řekl, že čtení datasheetů k neznámému čipu není děsivé, neušetřil bych spoustu času hledáním kousků kódu na internetu a vymýšlením nouzových řešení s nůžkami a náplastmi.
V centru tohoto článku jsou datasheety, a ne projekty, proto kód nemusí být příliš uhlazený a často na štíru. Samotné projekty jsou velmi jednoduché, i když vhodné pro první seznámení s novým čipem.
Doufám, že můj článek pomůže někomu na podobné cestě do hobby.
STM32
16 LED diod s DM634 a SPI
Malý projekt s využitím Blue Pill (STM32F103C8T6) a LED ovladače DM634. Pomocí datasheetů se podíváme na ovladač, IO porty STM a nastavíme SPI.
DM634
Taiwanský čip s 16 16-bitovými PWM výstupy, lze spojovat do řetězců. Mladší 12-bitový model je znám z domácího projektu . Když jsem si vybíral mezi DM63x a dobře známým TLC5940, rozhodl jsem se pro DM z několika důvodů: 1) TLC na Aliexpressu je určitě padělaný, zatímco tento není; 2) DM má samostatný PWM se svým generátorem frekvence; 3) mohl jsem ho levně koupit v Moskvě, místo abych čekal na balíček z Ali. No a samozřejmě jsem měl zájem sám se naučit řídit čip, místo abych používal hotovou knihovnu. Čipy jsou nyní většinou v pouzdře SSOP24, není těžké je připájet na adaptér.
Protože výrobce je tchajwanský, popis k čipu je napsán v čínské angličtině, a tím pádem to bude zajímavé. Nejprve se podíváme na pinout (Pin Connection), abychom pochopili, k jaké noze co připojit, a popis pinů (Popis pinů). 16 vývodů:

Zdroje přivádějící stejnosměrný proud (otevřený drenáž)
Sink / Open-drain output – drenáž; zdroj přivádějícího proudu; výstup, v aktivním stavu připojený k zemi, – diody se připojují k ovladači katodami. Elektricky to samozřejmě žádná 'otevřená drenáž' není (open drain), ale v datasheetech se takové označení pro vývody v režimu drenáže často objevuje.
![]()
Externí rezistory mezi REXT a GND pro nastavení výstupního proudu
Mezi pinem REXT a zemí se umisťuje referenční rezistor, který řídí vnitřní odpor výstupů, viz graf na str. 9 datasheetu. V DM634 lze tímto odporem také programově řídit, nastavující celkovou jasnost (global brightness); v tomto článku se do podrobností pouštět nebudu, prostě sem dám rezistor o hodnotě 2.2 – 3 kΩ.
Abychom pochopili, jak řídit čip, podíváme se na popis rozhraní zařízení:

Aha, tady je, čínská angličtina v celé své kráse. Překládat to je problematické, pochopit to při přání možné je, ale je tady jiná cesta – podívat se, jak je popsáno připojení v datasheetu k funkčně blízkému TLC5940:

… K zadání dat do zařízení jsou potřeba pouze tři piny. Rychlý náběh signálu SCLK posune data z pinu SIN do vnitřního registru. Po nahrání všech dat krátký vysoký signál XLAT zafixuje po sobě jdoucí přenesená data ve vnitřních registrech. Vnitřní registry – spínané úrovní signálu XLAT zajišťují zámek. Všechna data se přenášejí s nejvyšším bitem vpřed.
Latch – blokovací/zádržná/zajišťovací funkce.
Rising edge – vzestupný náběh impulzu
MSB first – posunout s nejstarším (nejlevějším) bitem dopředu.
přenos dat – předávat data sekvenčně (bit po bitu).
Slovo latch se často vyskytuje v dokumentaci čipů a překládá se různě, proto si dovolím pro pochopení
malý výkladLED driver – v podstatě posuvný registr. «Posun» (shift) v názvu znamená posun dat uvnitř zařízení: každý nový vložený bit tlačí celou řadu před sebou dopředu. Protože během posunu nikdo nechce sledovat chaotické blikání LED, proces probíhá v bufferových registrech oddělených od pracovního prostoru uzávěrkou (latch) – to je jakýsi předpokoj, kde se bity řadí do správné sekvence. Když je vše připraveno, uzávěrka se otevře a bity začnou pracovat, nahrazují předchozí sestavu. Slovo latch v dokumentaci k mikročipům téměř vždy znamená takovou uzávěrku, bez ohledu na to, v jakých kombinacích se používá.
Takže přenos dat v DM634 se provádí takto: nastavíme vstup DAI na hodnotu nejstaršího bitu vzdálené LED, zvedneme DCK nahoru a dolů; nastavíme vstup DAI na hodnotu dalšího bitu, zvedneme DCK; a tak dále, dokud nejsou všechny bity předány (zařazeno), po čemž zvedneme LAT. To lze udělat ručně (bit-bang), ale je lepší využít speciálně adaptované rozhraní SPI, které je k dispozici na našem STM32 ve dvou verzích.
Modrá Tabletka STM32F103
Úvod: ovladače STM32 jsou výrazně složitější než Atmega328, než by se mohlo zdát. Přitom z důvodu úspory energie je při spuštění většina periferií vypnuta, a taktovací frekvence je 8 MHz z interního zdroje. Naštěstí programátoři STM napsali kód, který přivádí čip k "výpočtovým" 72 MHz, a autoři všech známých IDE jej zahrnuli do iniciačního procesu, takže my nemusíme taktovat (ale ). Ale zapnout periferii budeme muset.
Dokumentace: na Blue Pill je nainstalován populární čip STM32F103C8T6, k němuž existují dva užitečné dokumenty:
- pro mikrokontrolery STM32F103x8 a STM32F103xB;
- pro celou řadu STM32F103 a nejen pro ni.
V datovém listu nás mohou zajímat:
- Pinouty – rozložení pinů čipů – pro případ, že bychom se rozhodli vyrábět desky sami;
- Paměťová mapa – karta paměti pro konkrétní čip. V Referenčním manuálu je mapa pro celou řadu, ve které jsou zmíněny registry, které na našem nejsou.
- Tabulka Pin Definitions – výčet hlavních a alternativních funkcí pinů; pro "modrou pilulku" lze na internetu najít pohodlnější obrázky se seznamem pinů a jejich funkcemi. Proto okamžitě googlíme Blue Pill pinout a držíme si takový obrázek po ruce:

Poznámka: Na obrázku z internetu byla chyba, která byla zmíněna v komentářích, za což děkujeme. Obrázek byl nahrazen, ale je to lekce – informace, které nejsou v datasheetech, je lepší ověřit.
Datasheet odstraníme, otevřeme Referenční manuál, od nynějška používáme pouze ten.
Postup: vyřešíme standardní vstup/výstup, nastavíme SPI, zapneme potřebné periferie.
Vstup-výstup
Na Atmega328 je vstup-výstup realizován maximálně jednoduše, což může množství možností STM32 zmást. Teď potřebujeme pouze výstupy, ale i ty mají čtyři varianty:

výstup s otevřeným emitorovým bodem, výstup "táhni-tlač", alternativní "táhni-tlač", alternativní otevřený emitorový bod
"Táhni-tlač" (push-pull) – běžný výstup z Arduina, pin může přijímat hodnotu buď HIGH, nebo LOW. A co se týče "otevřeného emitorového bodu", tam vznikají , i když je to tu vlastně všechno jednoduché:


Konfigurace výstupu / když je port přiřazen k výstupu: / zapnutý výstupní buffer: / – režim otevřeného emitorového bodu: "0" v výstupním registru aktivuje N-MOS, "1" v výstupním registru ponechává port v režimu Hi-Z (P-MOS se neaktivuje) / – režim "táhni-tlač": "0" v výstupním registru aktivuje N-MOS, "1" v výstupním registru aktivuje P-MOS.
Všechny rozdíly otevřeného emitorového bodu (open drain) od "táhni-tlač" (push-pull) spočívají v tom, že v prvním případě pin nemůže přijmout stav HIGH: při zápisu jedničky do výstupního registru přechází do režimu vysokého odporu (high impedance, – na pinu není nic, 'vysoký odpor', ve skutečnosti je pro vnější svět neviditelný.). Při zápisu nuly se pin v obou režimech chová stejným způsobem, jak logicky, tak elektricky.
V běžném režimu výstupu pin jednoduše přenáší obsah výstupního registru. V "alternativním" je řízen příslušnou periferií (viz 9.1.4):
![]()
Pokud je bit portu nakonfigurován jako výstup alternativní funkce, výstupní registr se deaktivuje a pin se připojí k výstupnímu signálu periferie.
Alternativní funkčnost každého pinu je popsána v Pin Definitions datum je v obrázku, který byl stažen. Na otázku, co dělat, pokud má pin několik alternativních funkcí, odpovídá poznámka v datovém listu:
![]()
Pokud více periferních bloků používá stejný pin, aby se zabránilo konfliktu mezi alternativními funkcemi, je třeba současně použít jen jeden periferní blok, přepínáním pomocí bitu aktivace hodin periferního zařízení (v příslušném registru RCC).
Nakonec mají piny v režimu výstupu také rychlost taktování. Toto je další funkce úspory energie, v našem případě jednoduše nastavíme na maximum a zapomeneme na to.
Takže: používáme SPI, což znamená, že dva piny (s daty a hodinovým signálem) by měly být „alternativní funkce SDA-SCL“, a ještě jeden (LAT) – „běžný SDA-SCL“. Ale než je přiřadíme, probereme SPI.
SPI
Ještě trochu teorie.
SPI nebo Serial Peripheral Interface (sériový periferní rozhraní) – jednoduché a velmi efektivní rozhraní pro komunikaci MCU s jinými MCU a obecně s vnějším světem. Princip jeho fungování byl již popsán výše, tam, kde je zmíněn čínský LED driver (v referenčním manuálu je to v oddíle 25). SPI může fungovat v režimu master (hlavní) a slave (podřízený). SPI má čtyři základní kanály, z nichž nemusí být využity všechny:
- MOSI, Master Output / Slave Input: tento pin v režimu master posílá, a v režimu slave přijímá data;
- MISO, Master Input / Slave Output: naopak, v masteru přijímá, v slave – posílá;
- SCK, Serial Clock: určuje frekvenci přenosu dat v masteru nebo přijímá hodinový signál ve slave. V podstatě odbíjí bity;
- SS, Slave Select: tímto kanálem se slave dozví, že něco potřebují. Na STM32 se to nazývá NSS, kde N = negative, tj. řadič se stává slaved, pokud je na tomto kanálu země. Dobře se kombinuje s režimem Open Drain Output, ale to je jiný příběh.
Stejně jako všechno ostatní, SPI na STM32 má bohatou funkcionalitu, což trochu komplikuje jeho porozumění. Například umí pracovat nejen jako SPI, ale také jako I2S rozhraní, přičemž je v dokumentaci popisováno spolu, bylo by dobré včas oddělit nepotřebné. Naší úlohou je však velmi jednoduchá: stačí posílat data, přičemž využíváme pouze MOSI a SCK. Jdeme do oddílu 25.3.4 (poloduplexní komunikace), kde nalézáme 1 hodinu a 1 jednosměrný datový vodič (1 hodinový signál a 1 jednosměrný tok dat):

V tomto režimu aplikace používá SPI buď v režimu pouze pro vysílání, nebo pouze pro příjem. / Režim pouze pro vysílání je podobný duplexnímu režimu: data se přenášejí po vysílacím pinu (MOSI v režimu master nebo MISO v režimu slave), zatímco příjmový pin (MISO nebo MOSI podle toho) může být použit jako běžný pin vstupu/výstupu. V tomto případě stačí, aby aplikace ignorovala Rx buffer (pokud jej přečtete, nebude tam žádná přenášená data).
Skvěle, pin MISO máme volný, připojíme na něj signál LAT. Pojďme se podívat na Slave Select, který je na STM32 možné řídit programově, což je velmi pohodlné. Čteme kapitolu 25.3.1 SPI Obecný popis:

Programové řízení NSS (SSM = 1) / Informace o výběru slava je obsažena v bitu SSI registru SPI_CR1. Vnější pin NSS zůstává volný pro jiné potřeby aplikace.
Je čas psát do registrů. Rozhodl jsem se použít SPI2, hledáme v datasheetu jeho základní adresu – v části 3.3 Memory Map (Mapa paměti):
![]()
A začínáme:
#define _SPI2_(mem_offset) (*(volatile uint32_t *)(0x40003800 + (mem_offset)))Otevíráme část 25.3.3 s výmluvným názvem „Nastavení SPI v režimu master“:

1. Nastavte frekvenci hodin sériového rozhraní pomocí bitů BR[2:0] v registru SPI_CR1.
Registry jsou shromážděny v k identickém oddíle reference manual. Posun adresy (Address offset) u CR1 – 0x00, defaultně jsou všechny bity resetovány (Reset value 0x0000):

Bity BR nastavují dělič frekvence hodin řadiče a tím určují frekvenci, na které bude SPI fungovat. Frekvence STM32 bude 72 MHz, LED driver podle jeho datasheetu funguje s frekvencí až 25 MHz, tudíž je potřeba dělit čtyřmi (BR[2:0] = 001).
#define _SPI_CR1 0x00
#define BR_0 0x0008
#define BR_1 0x0010
#define BR_2 0x0020
_SPI2_ (_SPI_CR1) |= BR_0;// pclk/42. Nastavte bity CPOL a CPHA, abyste určili vztahy mezi přenosem dat a taktováním sériového rozhraní (viz schéma na str. 240)
Protože zde čteme datasheet a neprocházíme schémata, pojďme se raději podívat na textový popis bitů CPOL a CPHA na str. 704 (SPI Obecný popis):

Fáze a polarita hodinového signálu
Pomocí bitů CPOL a CPHA registru SPI_CR1 lze programově vybrat čtyři varianty časových vztahů. Bit CPOL (polarita hodinového signálu) ovládá stav hodinového signálu, když se data nepřenášejí. Tento bit řídí režimy master a slave. Pokud je CPOL resetován, pin SCK v klidovém režimu se nachází na nízké úrovni. Pokud je bit CPOL nastaven, pin SCK v klidovém režimu se nachází na vysoké úrovni.
Pokud je bit CPHA (fáze hodinového signálu) nastaven, druhý okraj signálu SCK (klesající, pokud je CPOL resetován, nebo vzrůstající, pokud je CPOL nastaven) slouží jako zarážka pro nejvýznamnější bit. Data jsou uložena při druhé změně hodinového signálu. Pokud je bit CPHA resetován, přední okraj signálu SCK (klesající, pokud je CPOL nastaven, nebo vzrůstající, pokud je CPOL resetován) slouží jako zarážka nejvýznamnějšího bytu. Data jsou uložena při první změně hodinového signálu.
Zjistíme, že oba bity musí zůstat nulové, protože chceme, aby signál SCK zůstal nízký, když se nepoužívá, a data byla přenášena při vzrůstajícím okraji pulzu (viz Rising Edge v datasheetu DM634).
Mimochodem, zde se poprvé setkáváme se zvláštností lexiky v datasheetech ST: ve frázi „resetovat bit na nulu“ se píše to reset a bit, a ne to clear a bit, jako například u Atmega.
3. Nastavte bit DFF pro určení 8bitového nebo 16bitového formátu bloku dat.
Úmyslně jsem vybral 16bitový DM634, abych se nezabýval přenosem 12bitových dat PWM, jako u DM633. DFF má smysl nastavit na jedničku:
#define DFF 0x0800
_SPI2_ (_SPI_CR1) |= DFF; // 16-bit mode4. Nakonfigurujte bit LSBFIRST v registru SPI_CR1 pro určení formátu bloku.
LSBFIRST, jak název napovídá, nastavuje přenos od nejnižšího bitu. Ale DM634 chce přijímat data, počínaje nejvyšším bitem. Proto zůstáváme na resetováno.
5. V hardwarovém režimu, pokud je požadován vstup z pinu NSS, pošlete na pin NSS vysoký signál po celou sekvenci přenosu bajtů. V softwarovém režimu nastavte bity SSM a SSI v registru SPI_CR1. Pokud má pin NSS fungovat jako výstup, mějte nastaven pouze bit SSOE.
Nastavujeme SSM a SSI, abychom zapomněli na hardwarový režim NSS:
#define SSI 0x0100
#define SSM 0x0200
_SPI2_ (_SPI_CR1) |= SSM | SSI; //enable software control of SS, SS high6. Musí být nastaveny bity MSTR a SPE (ty zůstávají nastavené pouze pokud je na NSS podáván vysoký signál).
Těmito bity vlastně přiřazujeme náš SPI jako master a zapínáme ho:
#define MSTR 0x0004
#define SPE 0x0040
_SPI2_ (_SPI_CR1) |= MSTR; //SPI master
//когда все готово, включаем SPI
_SPI2_ (_SPI_CR1) |= SPE;SPI je nastaveno, pojďme nyní napsat funkce, které posílají bajty ovladači. Pokračujeme ve čtení 25.3.3 „Nastavení SPI v režimu master“:

Pořadí přenosu dat
Přenos začíná, když je do bufferu Tx zapsán bajt.
Bajt dat je nahrán do posuvného registru v paralelním režimu (z vnitřní sběrnice) během přenosu prvního bitu, po kterém je přenesen do sekvenčního v režimu pinu MOSI, s prvním nebo posledním bitem vpřed v závislosti na nastavení bitu LSBFIRST v registru CPI_CR1. Příznak TXE se nastaví po odeslání dat z bufferu Tx do posuvného registru, a také se vytvoří přerušení, pokud je nastaven bit TXEIE v registru CPI_CR1.
Vybral jsem několik slov v překladu, abych upozornil na jednu zvláštnost implementace SPI v STM kontrolerech. Na Atmegu se příznak TXE (Tx prázdný, Tx je prázdný a připraven přijímat data) se nastaví až poté, co celý bajt odešel venku. A tady se tento příznak nastaví poté, co byl bajt vložen do interního posuvného registru. Protože je tam vkládán všetkými bity současně (paralelně), a poté jsou data přenášena sériově, TXE se nastaví dříve, než je bajt zcela odeslán. To je důležité, protože v případě našeho LED ovladače potřebujeme stisknout pin LAT po odeslání všechny dat, což znamená, že pouze příznak TXE nám nebude stačit.
A to znamená, že potřebujeme ještě nějaký příznak. Podívejme se na 25.3.7 – „Příznaky stavu“:

<…>

Příznak BUSY
Příznak BSY se nastavuje a resetuje hardwarově (zápis do něj na nic neovlivňuje). Příznak BSY ukazuje stav komunikační vrstvy SPI.
Resetuje se:
když je přenos dokončen (kromě režimu mistra, pokud je přenos kontinuální)
když je SPI vypnuto
když dojde k chybě režimu mistra (MODF=1)
Pokud přenos není kontinuální, příznak BSY se resetuje mezi každým přenosem dat
Dobře, bude se hodit. Zjistíme, kde se nachází buffer Tx. K tomu přečteme „Registr dat SPI“:

Bity 15:0 DR[15:0] Registr dat
Přijatá data nebo data k odeslání.
Registr dat je rozdělen na dva buffery – jeden pro zápis (buffer odesílání) a druhý pro čtení (buffer příjmu). Zápis do registru dat zapisuje do bufferu Tx, a čtení z registru dat vrátí hodnotu obsaženou v bufferu Rx.
A registr stavů, kde se nachází příznaky TXE a BSY:

Píšeme:
#define _SPI_DR 0x0C
#define _SPI_SR 0x08
#define BSY 0x0080
#define TXE 0x0002
void dm_shift16(uint16_t value)
{
_SPI2_(_SPI_DR) = value; //send 2 bytes
while (!(_SPI2_(_SPI_SR) & TXE)); //wait until they're sent
}Ale protože musíme přenést 16krát po dvou bajtech, podle počtu výstupů LED ovladače, tak nějak takto:
void sendLEDdata()
{
LAT_low();
uint8_t k = 16;
do
{ k--;
dm_shift16(leds[k]);
} while (k);
while (_SPI2_(_SPI_SR) & BSY); // dokončit přenos
LAT_pulse();
}Ale zatím neumíme stisknout pin LAT, takže se vrátíme k I/O.
Přiřazujeme piny
U STM32F1 jsou registry, které odpovídají za stav pinů, poměrně neobvyklé. Je jasné, že jich je více než u Atmega, ale také se liší od jiných čipů STM. Oddíl 9.1 Obecný přehled GPIO:

Každý z portů obecného vstupu/výstupu (GPIO) má dva 32bitové konfigurační registry (GPIOx_CRL a GPIOx_CRH), dva 32bitové datové registry (GPIOx_IDR a GPIOx_ODR), 32bitový registr pro nastavení/resetování (GPIOx_BSRR), 16bitový resetovací registr (GPIOx_BRR) a 32bitový blokovací registr (GPIOx_LCKR).
Neobvyklé a také docela nepohodlné jsou první dva registry, protože 16 pinů portu je rozloženo mezi nimi v formátu „čtyři bity na jednoho“. Tedy piny od nultého do sedmého jsou v CRL, zatímco ostatní jsou v CRH. Ostatní registry úspěšně pojmou bity všech pinů portu – často zůstávají napůl "rezervované".
Pro jednodušší pochopení začneme na konci seznamu.
Blokovací registr nepotřebujeme.
Registry pro nastavení a resetování jsou zvláštní tím, že si částečně duplicují funkce: všechno lze zapisovat pouze do BSRR, kde vyšších 16 bitůresetuje pin na nulu a nižších 16 bitů ho nastavuje na 1, nebo také použít BRR, jehož nižších 16 bitů pouze resetuje pin. Já preferuji druhou variantu. Tyto registry jsou důležité, protože zajišťují atomový přístup k pinům:

![]()
Atomové nastavení nebo resetování
Není třeba vypínat přerušení při programování GPIOx_ODR na bitové úrovni: lze měnit jeden nebo více bitů jednou atomovou operací zápisu APB2. Toho je dosaženo zápisem "1" do registru pro nastavení/resetování (GPIOx_BSRR nebo, pouze pro reset, do GPIOx_BRR) bitu, který je třeba změnit. Ostatní bity zůstanou nezměněny.
Datové registry mají docela výmluvné názvy – IDR = Vstup Registr směru, registr pro vstup; ODR = Výstup Registr směru, registr pro výstup. V současném projektu je nepotřebujeme.
A konečně, ovládací registry. Jelikož nás zajímají piny druhého SPI, konkrétně PB13, PB14 a PB15, hned se podíváme na CRH:

A vidíme, že bude třeba něco napsat do bitů od 20. do 31.
Již jsme se výše podívali na to, co chceme od pinů, takže tady se obejdu bez screenshotu, jednoduše řeknu, že MODE určuje směr (vstup, pokud jsou oba bity nastaveny na 0) a rychlost pinu (potřebujeme 50MHz, tj. oba piny na "1"), zatímco CNF určuje režim: běžný "táhni-tlač" – 00, "alternativní" – 10. Jak vidíme výše, je u všech pinů nastaven třetí bit zdola (CNF0), který je uvádí do režimu plovoucí vstup.
Jelikož plánuju udělat s tímto čipem ještě něco dalšího, pro jednoduchost jsem definoval všechny možné hodnoty MODE a CNF pro oba kontrolní registry, jak pro dolní, tak pro horní.
Tak nějak takhle
#define CNF0_0 0x00000004
#define CNF0_1 0x00000008
#define CNF1_0 0x00000040
#define CNF1_1 0x00000080
#define CNF2_0 0x00000400
#define CNF2_1 0x00000800
#define CNF3_0 0x00004000
#define CNF3_1 0x00008000
#define CNF4_0 0x00040000
#define CNF4_1 0x00080000
#define CNF5_0 0x00400000
#define CNF5_1 0x00800000
#define CNF6_0 0x04000000
#define CNF6_1 0x08000000
#define CNF7_0 0x40000000
#define CNF7_1 0x80000000
#define CNF8_0 0x00000004
#define CNF8_1 0x00000008
#define CNF9_0 0x00000040
#define CNF9_1 0x00000080
#define CNF10_0 0x00000400
#define CNF10_1 0x00000800
#define CNF11_0 0x00004000
#define CNF11_1 0x00008000
#define CNF12_0 0x00040000
#define CNF12_1 0x00080000
#define CNF13_0 0x00400000
#define CNF13_1 0x00800000
#define CNF14_0 0x04000000
#define CNF14_1 0x08000000
#define CNF15_0 0x40000000
#define CNF15_1 0x80000000
#define MODE0_0 0x00000001
#define MODE0_1 0x00000002
#define MODE1_0 0x00000010
#define MODE1_1 0x00000020
#define MODE2_0 0x00000100
#define MODE2_1 0x00000200
#define MODE3_0 0x00001000
#define MODE3_1 0x00002000
#define MODE4_0 0x00010000
#define MODE4_1 0x00020000
#define MODE5_0 0x00100000
#define MODE5_1 0x00200000
#define MODE6_0 0x01000000
#define MODE6_1 0x02000000
#define MODE7_0 0x10000000
#define MODE7_1 0x20000000
#define MODE8_0 0x00000001
#define MODE8_1 0x00000002
#define MODE9_0 0x00000010
#define MODE9_1 0x00000020
#define MODE10_0 0x00000100
#define MODE10_1 0x00000200
#define MODE11_0 0x00001000
#define MODE11_1 0x00002000
#define MODE12_0 0x00010000
#define MODE12_1 0x00020000
#define MODE13_0 0x00100000
#define MODE13_1 0x00200000
#define MODE14_0 0x01000000
#define MODE14_1 0x02000000
#define MODE15_0 0x10000000
#define MODE15_1 0x20000000Naše piny se nacházejí na portu B (základní adresa – 0x40010C00), kód:
#define _PORTB_(mem_offset) (*(volatile uint32_t *)(0x40010C00 + (mem_offset)))
#define _BRR 0x14
#define _BSRR 0x10
#define _CRL 0x00
#define _CRH 0x04
//используем стандартный SPI2: MOSI на B15, CLK на B13
//LAT пусть будет на неиспользуемом MISO – B14
//очищаем дефолтный бит, он нам точно не нужен
_PORTB_ (_CRH) &= ~(CNF15_0 | CNF14_0 | CNF13_0 | CNF12_0);
//альтернативные функции для MOSI и SCK
_PORTB_ (_CRH) |= CNF15_1 | CNF13_1;
//50 МГц, MODE = 11
_PORTB_ (_CRH) |= MODE15_1 | MODE15_0 | MODE14_1 | MODE14_0 | MODE13_1 | MODE13_0;A odpovídajícím způsobem můžeme napsat definice pro LAT, které budou manipulovat registry BRR a BSRR:
/*** LAT pulse – high, then low */
#define LAT_pulse() _PORTB_(_BSRR) = (1<<14); _PORTB_(_BRR) = (1<<14)
#define LAT_low() _PORTB_(_BRR) = (1<<14)(LAT_low je prostě zvyklostí, nějak tomu vždycky tak bylo, ať si to zůstane)
Teď už je vše skvělé, jen to nefunguje. Protože je to STM32, šetří se zde elektrická energie, a proto je třeba aktivovat hodinování potřebné periferie.
Aktivujeme hodinování
Za hodinování odpovídají hodiny, také známé jako Clock. A už jsme si mohli povšimnout zkratky RCC. Hledáme ji v dokumentaci: to znamená Reset and Clock Control (Správa resetování a hodinování).
Jak bylo zmíněno výše, naštěstí pro nás to nejtěžší z tématu hodinování udělali lidé z STM, za což jim velmi děkujeme (ještě jednou dávám odkaz na , aby bylo jasné, jak je to komplikované). Potřebujeme pouze registry, které zajišťují aktivaci hodinování periferie (Registry povolení hodinování periferie). Nejprve najdeme základní adresu RCC, ta je na začátku "Paměťové mapy":
![]()
#define _RCC_(mem_offset) (*(volatile uint32_t *)(0x40021000 + (mem_offset)))A pak buď kliknout na odkaz, kde se snažit něco najít v tabulce, nebo, mnohem lépe, prostudovat popisy aktivujících registrů v sekcích o povolení registrování. Kde najdeme RCC_APB1ENR a RCC_APB2ENR:


A v nich, odpovídajícím způsobem, bity aktivující hodinování SPI2, IOPB (I/O Port B) a alternativních funkcí (AFIO).
#define _APB2ENR 0x18
#define _APB1ENR 0x1C
#define IOPBEN 0x0008
#define SPI2EN 0x4000
#define AFIOEN 0x0001
//включаем тактирование порта B и альт. функций
_RCC_(_APB2ENR) |= IOPBEN | AFIOEN;
//включаем тактирование SPI2
_RCC_(_APB1ENR) |= SPI2EN;Finální kód lze nalézt .
Pokud je možnost a chuť si to otestovat, připojujeme DM634 takto: DAI k PB15, DCK k PB13, LAT k PB14. Napájíme ovladač z 5 voltů, nezapomínáme spojit země.

STM8 PWM
PWM na STM8
Když jsem plánoval tento článek, rozhodl jsem se pro příklad vyzkoušet ovládání nějakého funkcionality neznámého čipu pouze pomocí datasheetu, abych se nestal kovářem, který chodí bos. STM8 byl pro tuto roli ideální: za prvé, měl jsem pár čínských desek s STM8S103, a za druhé, není příliš populární, a proto lákání podívat se na internet a najít řešení naráží na nedostatek těchto řešení.
K čipu je také k dispozici a , v prvním jsou pinout a adresy registrů, ve druhém – vše ostatní. Programuje se STM8 v ošklivé IDE .
Kmitání a vstup/výstup
Ve výchozím nastavení STM8 pracuje na frekvenci 2 MHz, to je třeba hned opravit.

Hlavní signál HSI (vysokorychlostní vnitřní)
Hlavní signál HSI je generován z vnitřního 16 MHz RC generátoru s programovatelným děličem (od 1 do 8). Nastavuje se v registru děliče hodinového signálu (CLK_CKDIVR).
Poznámka: při spuštění je jako hlavní zdroj hodinového signálu vybrán HSI RC generátor s děličem 8.
Najdeme adresu registru v datasheetu, popis v refman a vidíme, že registr je třeba vymazat:
#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6
CLK_CKDIVR &= ~(0x18);Protože plánujeme spustit PWM a připojit LED diody, podíváme se na pinout:

Čip je malý, mnoho funkcí je svázáno na stejné pinech. To, co je v hranatých závorkách – „alternativní funkcionalita“, se přepíná „bajty voleb“ (option bytes) – něco jako fúze Atmegy. Jejich hodnoty lze měnit programově, ale není to nutné, protože nový funkcionalita je aktivována až po restartování. Snadnější je použít ST Visual Programmer (stahuje se spolu s Visual Develop), který umí tyto bajty měnit. V pinoutu je vidět, že vývody CH1 a CH2 prvního časovače jsou skryty v hranatých závorkách; musíme v STVP nastavit bity AFR1 a AFR0, přičemž druhý přesune vývod CH1 druhého časovače z PD4 na PC5.
Takže LED diody budou ovládány 6 piny: PC6, PC7 a PC3 pro první časovač, PC5, PD3 a PA3 pro druhý.
Nastavení samotných pinů vstupů a výstupů na STM8 je jednodušší a logičtější než na STM32:
- známý z Atmega registr směru dat DDR (Data Direction Register): 1 = výstup;
- první kontrolní registr CR1 při výstupu nastavuje režim „přítah-push“ (1) nebo otevřený kolektor (0); protože připojuji LED diody k čipu katody, nechávám zde nuly;
- druhý kontrolní registr CR2 při výstupu nastavuje rychlost kmitání: 1 = 10 MHz
#define PA_DDR *(volatile uint8_t *)0x005002
#define PA_CR2 *(volatile uint8_t *)0x005004
#define PD_DDR *(volatile uint8_t *)0x005011
#define PD_CR2 *(volatile uint8_t *)0x005013
#define PC_DDR *(volatile uint8_t *)0x00500C
#define PC_CR2 *(volatile uint8_t *)0x00500E
PA_DDR = (1<<3); //output
PA_CR2 |= (1<<3); //fast
PD_DDR = (1<<3); //output
PD_CR2 |= (1<<3); //fast
PC_DDR = ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //output
PC_CR2 |= ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //fastNastavení PWM
Nejprve si ujasníme pojmy:
- Frekvence PWM – frekvence, s jakou tiká časovač;
- Auto-reload, AR – automaticky načítaná hodnota, do které bude časovač počítat (doba pulzu);
- Update Event, UEV – událost, která nastane, když časovač dosáhne AR;
- PWM Duty Cycle – pracovní cyklus PWM, často nazýván „duty cycle“;
- Capture/Compare Value – hodnota pro zachycení/srovnání, do které časovač něco provede (v případě PWM – invertuje výstupní signál);
- Preload Value – přednačtená hodnota. Srovnávací hodnota se nemůže měnit, dokud časovač tiká, jinak by cyklus PWM selhal. Proto jsou nové přenášené hodnoty uloženy do bufferu a vyzvedávány odtud, když časovač dosáhne konce odpočtu a resetuje se;
- Edge-aligned a Center-aligned režimy – vyrovnání podle okraje a podle středu, to je to, co odpovídá režimům ATMEL. Rychlý PWM a Fázově správný PWM.
- OCiREF, Výstupní srovnávací referenční signál – referenční výstupní signál, vlastně to, co se v režimu PWM objeví na odpovídajícím pinu.
Jak již jasně vyplývá z popisu pinů, PWM mají dva časovače – první a druhý. Oba jsou 16-bitové, první má spoustu dalších funkcí (včetně schopnosti počítat jak nahoru, tak dolů). Potřebujeme, aby oba pracovaly stejně, a proto jsem se rozhodl začít s vědomě chudším druhým, abych náhodou nepoužil něco, co v něm není. Trochu problém spočívá v tom, že popis funkcionality PWM všech časovačů se v referenčním manuálu nachází v kapitole o prvním časovači (17.5.7 PWM Mode), takže musím neustále skákat tam a zpět v dokumentu.
PWM na STM8 má důležitou výhodu oproti PWM Atmega:

PWM s vyrovnáním podle okraje
Konfigurace počítání shora dolů
Počítání shora dolů je aktivní, pokud je bit DIR v registru TIM_CR1 resetován.
Příklad
Příklad používá první režim PWM. Referenční signál PWM OCiREF je udržován na vysoké úrovni, dokud TIM1_CNT < TIM1_CCRi. Jinak přijímá nízkou úroveň. Pokud je hodnota pro srovnání v registru TIM1_CCRi větší než automaticky načítaná hodnota (registr TIM1_ARR), signál OCiREF je udržován v 1. Pokud je hodnota pro srovnání rovna 0, OCiREF je udržován na nule.…
Časovač STM8 během události aktualizace nejprve kontroluje srovnávací hodnotu, a teprve poté vydává referenční signál. U Atmega časovač nejprve akci provádí a poté srovnává, což vede k tomu, že při srovnávací hodnota == 0 Na výstupu se získá jehla, se kterou je třeba nějak bojovat (například programově inverzní logikou).
Takže, co chceme udělat: 8bitový PWM (AR == 255), počítáme zdola nahoru, zarovnání na hranici. Protože jsou lampičky připojeny k čipu katody, PWM by měl vydávat 0 (LED svítí) do srovnávací hodnotu a 1 po.
Už jsme četli o některých PWM režim, proto hledáme potřebný registr druhého časovače pomocí vyhledávání v referenčním manuálu podle této fráze (18.6.8 - TIMx_CCMR1):

110: První režim PWM – při počítání zdola nahoru, první kanál je aktivní, dokud TIMx_CNT < TIMx_CCR1. V opačném případě první kanál není aktivní. [dále v dokumentu chybně zkopírovaný text z časovače 1]
111: Druhý režim PWM – při počítání zdola nahoru, první kanál není aktivní, dokud TIMx_CNT < TIMx_CCR1. V opačném případě první kanál je aktivní.
Protože LED diody jsou připojeny k MCU katodami, vyhovuje nám druhý režim (první také, ale zatím o tom nevíme).

Bit 3 OC1PE: Povolit přednačítání výstupu 1
0: Registr přednačítání na TIMx_CCR1 je vypnut. Do TIMx_CCR1 lze zapisovat kdykoli. Nová hodnota funguje okamžitě.
1: Registr přednačítání na TIMx_CCR1 je zapnut. Operace čtení/zápisu se týkají registru přednačítání. Přednačtená hodnota TIMx_CCR1 se načítá do stínového registru při každé aktualizaci.
*Poznámka: Pro správnou funkci PWM režimu musí být registry přednačítání zapnuty. To není nutné v režimu jednoho signálu (v registru TIMx_CR1 je nastaven bit OPM).
Dobře, zapneme vše potřebné pro tři kanály druhého časovače:
#define TIM2_CCMR1 *(volatile uint8_t *)0x005307
#define TIM2_CCMR2 *(volatile uint8_t *)0x005308
#define TIM2_CCMR3 *(volatile uint8_t *)0x005309
#define PWM_MODE2 0x70 //PWM mode 2, 0b01110000
#define OCxPE 0x08 //preload enable
TIM2_CCMR1 = (PWM_MODE2 | OCxPE);
TIM2_CCMR2 = (PWM_MODE2 | OCxPE);
TIM2_CCMR3 = (PWM_MODE2 | OCxPE);AR se skládá ze dvou osmibitových registrů, tady je to jednoduché:
#define TIM2_ARRH *(volatile uint8_t *)0x00530F
#define TIM2_ARRL *(volatile uint8_t *)0x005310
TIM2_ARRH = 0;
TIM2_ARRL = 255;Druhý časovač dokáže počítat pouze zdola nahoru, zarovnání na hranici, nic neměníme. Nastavíme dělič frekvence, například na 256. Dělič pro druhý časovač se nastavuje v registru TIM2_PSCR a představuje mocninu dvojky:
#define TIM2_PSCR *(volatile uint8_t *)0x00530E
TIM2_PSCR = 8;Nyní je třeba zapnout výstupy a samotný druhý časovač. První úkol se řeší registry Capture/Compare Enable: jsou dva, tři kanály jsou rozmístěny asymetricky. Zde také můžeme zjistit, že lze měnit polaritu signálu, to znamená, že by se v podstatě mohl použít i PWM Mode 1. Zapisujeme:
#define TIM2_CCER1 *(volatile uint8_t *)0x00530A
#define TIM2_CCER2 *(volatile uint8_t *)0x00530B
#define CC1E (1<<0) // CCER1
#define CC2E (1<<4) // CCER1
#define CC3E (1<<0) // CCER2
TIM2_CCER1 = (CC1E | CC2E);
TIM2_CCER2 = CC3E;A nakonec spustíme časovač v registru TIMx_CR1:

#define TIM2_CR1 *(volatile uint8_t *)0x005300
TIM2_CR1 |= 1;Napíšeme jednoduchou analogovou funkci AnalogWrite(), která bude předávat časovači vlastní hodnoty pro porovnání. Registry se očekávaně nazývají Capture/Compare registers, mají po dvou na každý kanál: mladší 8 bitů v TIM2_CCRxL a starší v TIM2_CCRxH. Protože jsme použili 8-bitové PWM, stačí zapisovat pouze mladší bity:
#define TIM2_CCR1L *(volatile uint8_t *)0x005312
#define TIM2_CCR2L *(volatile uint8_t *)0x005314
#define TIM2_CCR3L *(volatile uint8_t *)0x005316
void setRGBled(uint8_t r, uint8_t g, uint8_t b)
{
TIM2_CCR1L = r;
TIM2_CCR2L = g;
TIM2_CCR3L = b;
}Pozorný čtenář si všimne, že máme malou vadu PWM, která není schopna generovat 100% vyplnění (při maximální hodnotě 255 se signál invertuje na jeden cyklus časovače). Pro LED diody to není problém, a pozorný čtenář již ví, jak to opravit.
PWM na druhém časovači funguje, přecházíme k prvnímu.
První časovač má přesně ty samé bity ve stejných registrech (jenom ty bity, které zůstaly 'rezervovány' ve druhém časovači, se v prvním aktivně používají pro různé pokročilé funkce). Stačí najít adresy těchto registrů v datovém listu a zkopírovat kód. A také změnit hodnotu děliče frekvence, protože první časovač chce získat nikoli mocninu dvou, ale přesnou 16-bitovou hodnotu ve dvou registrech. Prescaler High a Low. Děláme to vše a… první časovač nefunguje. Co je špatně?
Problém lze vyřešit pouze prozkoumáním celé sekce o řídicích registrech časovače 1, kde hledáme ten, který druhý časovač nemá. Najde se 17.7.30 Break register (TIM1_BKR), kde je takový bit:
![]()
Povolte hlavní výstup
#define TIM1_BKR *(volatile uint8_t *)0x00526D
TIM1_BKR = (1<<7);Teď už jistě vše, kód .

STM8 Multiplex
Multiplexování na STM8
Třetí mini-projekt spočívá v připojení osmi RGB LED diod k druhému časovači v režimu PWM a v zajištění, aby ukazovaly různé barvy. Základem je koncept LED multiplexování, který spočívá v tom, že pokud velmi rychle zapínáme a vypínáme LED diody, bude nám připadat, že svítí stále (persistence of vision, setrvačnost zrakového vnímání). Kdysi jsem dělal .
Algoritmus funkce vypadá takto:
- připojte anodovou nohu první RGB LED;
- zapněte ji, podáním potřebných signálů na katody;
- čekejte na konec PWM cyklu;
- připojte anodovou nohu druhé RGB LED;
- zapněte ji…
No a tak dále. Samozřejmě, pro hezkou práci je potřeba, aby připojení anodové nohy a 'zapnutí' LED diody probíhaly současně. Nebo téměř. V každém případě musíme napsat kód, který ve třech kanálech druhého časovače bude vydávat hodnoty, při dosažení UEV je změní a zároveň změní aktivní RGB LED diodu.
Jelikož se přepínání LED provádí automaticky, je třeba vytvořit "video paměť", odkud handler přerušení bude získávat data. Je to jednoduché pole:
uint8_t colors[8][3];Abychom změnili barvu konkrétní LED diody, stačí do tohoto pole zapsat potřebné hodnoty. A za číslo aktivní LED diody bude odpovídat proměnná
uint8_t cnt;Demux
Pro správný multiplexing budeme potřebovat, jak překvapivé, demultiplexer CD74HC238. Demultiplexer je čip, který hardwarově realizuje operátor <<. Přes tři vstupní piny (bity 0, 1 a 2) mu poskytneme trojbitové číslo X, a on na oplátku aktivuje výstup číslo (1<<X). Ostatní vstupy čipu se používají pro škálování celé konstrukce. Tento čip potřebujeme nejen pro zmenšení počtu obsazených pinů mikrokontroléru, ale také pro bezpečnost – abychom náhodou nezapnuli více LED diod, než je možné, a nespálili MCU. Čip stojí pár korun, měl by být vždy po ruce.
CD74HC238 u nás bude zodpovídat za dodání napětí k anodě potřebné LED diody. V plném multiplexu by dodával napětí na sloupec přes P-MOSFET, ale v této ukázce to lze i přímo, protože spotřebovává 20 mA, podle absolutní maximální zatížení datového listu. Z budeme potřebovat pinout a tuto příručku:

H = vysoká úroveň napětí, L = nízká úroveň napětí, X – nezáleží
Připojíme E2 a E1 k zemi, E3, A0, A1 a A3 k pinům PD5, PC3, PC4 a PC5 STM8. Vzhledem k tomu, že tabulka výše obsahuje jak nízkou, tak vysokou úroveň, nastavíme tyto piny jako výstupy typu push-pull.
PWM
PWM na druhém časovači se nastavuje stejně jako v předchozím příběhu, s dvěma rozdíly:
Za prvé, musíme povolit přerušení na Update Event (UEV), které vyvolá funkci přepínající aktivní LED. To se provádí změnou bitu Update Interrupt Enable v registru, který má výmluvný název

Registr povolení přerušení
#define TIM2_IER *(volatile uint8_t *)0x005303
//enable interrupt
TIM2_IER = 1;Druhý rozdíl se týká takového jevu multiplexování, jako je ghosting – parazitní svícení diod. V našem případě může vzniknout z důvodu, že časovač, který způsobil přerušení na UEV, pokračuje v tikání a obsluha přerušení nestihne přepnout LED, než časovač začne něco zapisovat na výstupy. Proti tomu bude třeba invertovat logiku (0 = maximální jas, 255 = nic nesvítí) a nedovolit extrémní hodnoty pracovní cyklu. Tedy dosáhnout toho, aby po UEV LED diody úplně zhasly na jeden takt PWM.
Měníme polaritu:
//set polarity
TIM2_CCER1 |= (CC1P | CC2P);
TIM2_CCER2 |= CC3P;Vyvarujeme se nastavování r, g a b na 255 a nezapomínáme je inverzovat při použití.
Přerušení
Podstatou přerušení je, že za určitých okolností čip přestane vykonávat hlavní program a zavolá nějakou externí funkci. Přerušení vznikají v důsledku vnějších nebo vnitřních vlivů, včetně časovače.
Když jsme poprvé vytvořili projekt ve ST Visual Develop, kromě main.c jsme získali okno s tajemným souborem stm8_interrupt_vector.c, automaticky zařazeným do projektu. V tomto souboru je pro každé přerušení přiřazena funkce NonHandledInterrupt. Musíme přiřadit svou funkci k potřebnému přerušení.
V datasheetu je tabulka vektorů přerušení, kde najdeme to potřebné:

13 TIM2 aktualizace/přetečení
14 TIM2 zachytávání/srovnávání
Musíme měnit LED při UEV, takže potřebujeme přerušení č. 13.
Proto nejprve ve souboru stm8_interrupt_vector.c změníme jméno funkce pro přerušení č. 13 (IRQ13) na vlastní:
{0x82, TIM2_Overflow}, /* irq13 */Za druhé, budeme muset vytvořit soubor main.h s tímto obsahem:
#ifndef __MAIN_H
#define __MAIN_H
@far @interrupt void TIM2_Overflow (void);
#endifA nakonec zapsat tuto funkci do svého main.c:
@far @interrupt void TIM2_Overflow (void)
{
PD_ODR &= ~(1<<5); // vypneme demultiplexor
PC_ODR = (cnt<<3); // zapíšeme do demultiplexoru novou hodnotu
PD_ODR |= (1<<5); // zapneme demultiplexor
TIM2_SR1 = 0; // resetujeme flag Update Interrupt Pending
cnt++;
cnt &= 7; // posuneme čítač LED
TIM2_CCR1L = ~colors[cnt][0]; // předáme do bufferu inverzní hodnoty
TIM2_CCR2L = ~colors[cnt][1]; // pro další cyklus PWM
TIM2_CCR3L = ~colors[cnt][2]; //
return;
}Zbývá zapnout přerušení. To se provádí pomocí assemblerového příkazu rim – hledat ji budeme muset v :
//enable interrupts
_asm("rim");Další assemblerový příkaz – sim – vypíná přerušení. Ta je třeba vypnout během zápisu nových hodnot do 'videopaměti', aby vyvolané v nevhodném momentu přerušení nezkazilo pole.
Celý kód – .

Pokud tato článek alespoň někomu pomůže, znamená to, že jsem ho psal správně. Budu rád za komentáře a poznámky, pokusím se odpovědět na vše.
Zdroj: habr.com
