STM32 Timery II

O tom že timery jsou na STM32 velké sousto jsem psal už v předchozím díle. A právě proto bude tutoriál o timerech pokračovat ještě několika dalšími díly. Jejichž obsah je mi v tuto chvíli ještě neznámý. Seznam příkladů v tomto díle najdete níže.

7B - dynamická změna PWM pomocí DMA

V minulém díle jsme v příkladu 6e zkoušeli dynamicky ladit jas LED diody pomocí PWM. PWM jsme měnili v rutině přerušení podle tabulky jasů uložených v paměti. Pokud mě paměť neklame, zmínil jsem že je to úloha jako stvořená pro DMA a že by celý proces mohl běžet nezávisle na jádře. Nezůstal jsem u planých řečí a ukázku jak to pomocí DMA zvládnout jsem připravil. Na tomto místě předpokládám, že už povědomí o DMA máte a pokud ne pročtěte si nejprve tutoriál o DMA. Než se pustíme do technikálií řekněme si jen ve stručnosti co ukázka bude dělat. LED připojenou na PC8 budeme budit PWM signálem a modulovat tak její jas. Jas budeme v čase měnit tak aby měl průběh podobný lidskému tepu. Což nemá žádný zvláštní důvod, jen jsem měl vhodná data zrovna po ruce. Na obrázku níže můžete vidět jak by se měl jas LED měnit v čase. Celý proces necháme probíhat stále dokola.

Průběh se skládá ze 143 vzorků. Což není nijak speciálně zvolené číslo, je to prostě množství dat které jsem měl k dispozici a musel jsem se jim přizpůsobit. Přirozeně bych byl raději za nějaké "kulaté" číslo jako třeba 256 nebo 128. Průběh (tedy 143 vzorků typu uint8) jsou uloženy ve flash paměti v poli intenzita. PWM o frekvenci přibližně 2kHz generujeme pomocí TIM3 a LED je připojena na jeho kanále 3 (PC8). K přepisování CCR3 registru (hodnota PWM na kanále 3) využijeme DMA. K časování "animace" se bude hodit přetečení od TIM6 (Update událost). Frekvence s jakou TIM6 přetéká tedy rozhoduje o rychlosti s jakou se "animace" generuje. Například pro tep o frekvenci se 82 údery za minutu je potřeba aby TIM6 přetékal každých t=60/82/143=5.117ms. Protože používáme jako zdroj requestů TIM6 je jednoznačně dané že musíme použít DMA kanál 3. A teď se stručně podívejme na konfiguraci DMA.

Adresa v periferii je zmíněný TIM3->CCR3 registr. Ačkoli do něj zapisujeme 8bitová data, je sám o sobě 16bitový, takže datový typ v periferii je Half-Word. Inkrementovat ho přirozeně nechceme, každá další data mají dorazit stále do tohoto registru. Naopak na straně paměti specifikujeme v položce adresy ukazatel na pole ze kterého chceme data odebírat, nastavíme datový typ na Byte a zapneme inkrementaci. Celkový počet přenášených dat je 143 (počet vzorků jasu v animaci). Aby proces běžel stále dokola, zapneme kruhový (circular) mód. A přirozeně specifikujeme že jde o přenos Paměť->Periferie. K tomu aby se DMA přenos rozběhl je potřeba příslušný DMA kanál zapnout. To ale samo o sobě nestačí, ještě je potřeba TIM6 vysvětlit, že má DMA requesty generovat (a typicky je potřeba mu k tomu říct i při jaké příležitosti). Aby to všechno dávalo smysl je vhodné TIM6 konfigurovat a spouštět až po nastavení TIM3 a DMA. Tím si zajistíme, že nepřijde žádný request dříve než bude vše připraveno. No a to je vše. Jakmile to jednou spustíte, LED bude animovat lidský tep naprosto bez jakékoli intervence jádra. Žádné přerušení, žádná obsluha ... to je na tom to krásné :)

Celý zdrojový kód

void init_DMA(void){
LL_DMA_InitTypeDef dma;
// clock pro DMA
LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);
// přenos mezi pamětí a periferií (TIM3 PWM)
dma.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
dma.MemoryOrM2MDstAddress = (uint32_t)intenzita; // zdrojová adresa (hodnoty PWM)
dma.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE; // zdrojový datový typ (uint8)
dma.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; // zdrojovou adresu inkrementovat
dma.Mode = LL_DMA_MODE_CIRCULAR; // kruhový mód (odesílat pole stále dokola)
dma.NbData = sizeof(intenzita); // počet přenášených dat
dma.PeriphOrM2MSrcAddress = (uint32_t)&(TIM3->CCR3); // cílová adresa (Compare registr TIM3)
dma.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD; // cílový datový typ (uint16)
dma.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; // cílová adres je fixní
dma.Priority = LL_DMA_PRIORITY_MEDIUM; // priorita přenosu střední
// Channel3 - requesty od TIM6
LL_DMA_Init(DMA1,LL_DMA_CHANNEL_3,&dma); // aplikovat konfiguraci na Channel3
LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_3); // povolíme Channel
}

// TIM6 časování jasu (DMA requestů)
void init_tim6(void){
LL_TIM_InitTypeDef tim;
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM6);
LL_TIM_StructInit(&tim);
tim.Prescaler = 47; // 1us do timeru
tim.Autoreload = 5116; // 5.117ms perioda
LL_TIM_Init(TIM6,&tim);

// povolíme generovat DMA requesty s přetečením timeru (Update)
LL_TIM_EnableDMAReq_UPDATE(TIM6); 
LL_TIM_EnableCounter(TIM6); // spustíme Timer
}

Malá poznámka na závěr (pro rýpaly). TIM6 generuje DMA Requesty v okamžiku kdy se mu zlíbí a nebere žádný ohled na to co zrovna dělá TIM3. Stane se tedy, že čas od času některý ze 143 vzorků má to štěstí, že jeho PWM hodnota na výstupu TIM3 setrvá o něco déle než ostatní vzroky. Někdy ale zase některý ze vzorků setrvá na výstupu kratší dobu. V dlouhodobém průměru ale každý vzorek stráví na výstupu stejný čas.

Home
V0.2b 30.8.2017
By Michal Dudka (m.dudka@seznam.cz)