Tady najdete směs různých krátkých ukázek programů pro STM8, které slouží jako podpora k výuce na SPSE.
Seznam ukázek:
// Jednoduchý příklad pro generování jednoduchého PWM Timerem 2 (ukázka dvou ze tří kanálů) // Timer generuje "vlevo zarovnané" PWM se střídou 0.25 a frekvencí 1kHz na výstupu PD4 // Timer generuje "vpravo zarovnané" PWM se střídou 0.25 a frekvencí 1kHz na výstupu PD3 (což je totéž jako invertované vlevo zarovnané PWM se střídou 0.75) #include "stm8s.h" //#include "milis.h" //#include "spse_stm8.h" void main(void){ CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // taktovat MCU na 16MHz // inicializuji výstupy PD3 a PD4 (výstupy timeru) GPIO_Init(GPIOD,GPIO_PIN_4,GPIO_MODE_OUT_PP_LOW_SLOW); // PD4 - TIM2_CH1 GPIO_Init(GPIOD,GPIO_PIN_3,GPIO_MODE_OUT_PP_LOW_SLOW); // PD3 - TIM2_CH2 // inicializace Timeru 2 pro dvoukanálové PWM TIM2_TimeBaseInit(TIM2_PRESCALER_16,999); // Clock pro Timer = 16MHz / 16 = 1MHz, strop timeru 1000, perioda timeru 1MHz/1000 = 1kHz // Na kanále 1 střídu 250/1000 s pozitivní polaritou (v 0 úroveň H, v 250 přejde do L) TIM2_OC1Init(TIM2_OCMODE_PWM1,TIM2_OUTPUTSTATE_ENABLE,249,TIM2_OCPOLARITY_HIGH); // Na kanále 2 střídu 750/1000 s invertovanou polaritou (v 0 úroveň L, v 750 přejde do H) TIM2_OC2Init(TIM2_OCMODE_PWM1,TIM2_OUTPUTSTATE_ENABLE,749,TIM2_OCPOLARITY_LOW); // Povolit "preload" na kanálech kde plánujeme přepisovat střídu (zabraňuje "glitchům" TIM2_OC1PreloadConfig(ENABLE); TIM2_OC2PreloadConfig(ENABLE); // spustit Timer TIM2_Cmd(ENABLE); //TIM2_SetCompare1(499); // takhle měním hodnotu střídy na kanále 1 //TIM2_SetCompare2(499); // takhle měním hodnotu střídy na kanále 2 while (1){ // není co dělat, PWM se generuje autonomně } }
// Jednoduchý příklad pro generování jednoduchého PWM Timerem 1 // Timer generuje kladný pulz šířky 1.5ms s periodou 20ms (50Hz) - např. "signál pro servo" // (Timer 1 je "komplikovaný" kvůli komplementárním výstupům a pokročilým funkcím) #include "stm8s.h" //#include "milis.h" //#include "spse_stm8.h" void main(void){ CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // taktovat MCU na 16MHz // konfigurace PD1 (TIM1_CH1) GPIO_Init(GPIOC,GPIO_PIN_1,GPIO_MODE_OUT_PP_LOW_SLOW); // inicializace časové základny Timeru 1 TIM1_TimeBaseInit(15,TIM1_COUNTERMODE_UP,19999,0); // konfigurace kanálu 1 TIM1_OC1Init( TIM1_OCMODE_PWM1, // režim "vlevo zarovnané" PWM TIM1_OUTPUTSTATE_ENABLE, // povolit timeru výstup TIM1_CH1 TIM1_OUTPUTNSTATE_DISABLE, // nepovolit timeru výstup TIM1_CH1N 1499, // šířka pulzu 1.5ms (střída 1.5/20) TIM1_OCPOLARITY_HIGH, // polarita výstupu kladná TIM1_OCNPOLARITY_HIGH, // polarita komplementárního výstupu (nehraje roli, není povolen) TIM1_OCIDLESTATE_RESET, // neutrální stav výstupu (nehraje roli, nepoužíváme "break" funkci) TIM1_OCNIDLESTATE_RESET // neutrální stav komplementárního výstupu (nehraje roli, nepoužíváme "break" funkci) ); // Povolit "preload" na kanálech kde plánujeme přepisovat střídu (zabraňuje "glitchům" TIM2_OC1PreloadConfig(ENABLE); // globální povolení výstupů TIM1_CtrlPWMOutputs(ENABLE); // spustit Timer TIM1_Cmd(ENABLE); //TIM2_SetCompare1(499); // takhle měním hodnotu střídy na kanále 1 while (1){ // není co dělat, PWM se generuje autonomně } }
// Ukázka posílání textových (ASCII) zpráv pomocí UARTu a snprintf z knihovny stdio.h // UART2 má výstup na pinu PD5 (UART2_TX) // Aby funkce UART2_Init správně fungovala, musí MCU znát svou vlastní taktovací frekvenci, což je // důležité pokud MCU taktujete externím clockem nebo krystalem protože musíte překladači // sdělit frekvenci krytalu/clocku) - nijak nesouvisí s naším makrem F_CPU (!) #include "stm8s.h" #include "milis.h" #include "stdio.h" void usart_puts(char *Buffer); void process_message_out(void); uint8_t txt[32]; // pole pro řetězec uint16_t counter=0; // proměnná jejíž hodnotu chci posílat void main(void){ CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // taktovat MCU na 16MHz init_milis(); // naství UART na 115200b/s, formát 8+N+1, povolí puze vysílač UART2_Init( 115200, // komunikační rychlost (bit/s) UART2_WORDLENGTH_8D, // délka zprávy (8bit) UART2_STOPBITS_1, // počet stopbitů (1) UART2_PARITY_NO, // parita žádná UART2_SYNCMODE_CLOCK_DISABLE, // nepoužíváme synchronní režim UART2_MODE_TX_ENABLE // zapnout pouze vysílač (nechci přijímat) ); // spustí UART (převezme kontrolu nad PD5) UART2_Cmd(ENABLE); while (1){ process_message_out(); } } // každou sekundu pošle zprávu void process_message_out(void){ static uint16_t lasttime=0; if(milis() - lasttime > 500){ lasttime = milis(); // sestaví řetězec podle "předpisu" (viz tutoriál o znakovém LCD) sprintf(txt,"Counter= %5u\n\r",counter); usart_puts(txt); counter++; // inkrementuje proměnnou kterou posíláme (aby nebyla nuda) } } // Odvysílá řetězec po UARTu void usart_puts(char *Buffer){ while(*Buffer){ // než narazíš na konec řetězce (znak /0) while(!UART2_GetFlagStatus(UART2_FLAG_TXE)){}; // čekej než bude volno v Tx Bufferu UART2_SendData8(*Buffer++); // předej znak k odeslání } }
// Ukázka příjmu po UARTu s pomocí přerušení // program každý přijatý byte odvysílá zpět #include "stm8s.h" void process_response(void); // volatile nutné pro proměnné které jsou sdílené mezi hlavní smyčkou a rutinou přerušení volatile uint8_t znak; // přijatý byte volatile uint8_t flag=0; // informace o tom že byl přijat byte void main(void){ CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // taktovat MCU na 16MHz // naství UART na 115200b/s, formát 8+N+1, povolí puze vysílač UART2_Init( 115200, // komunikační rychlost (bit/s) UART2_WORDLENGTH_8D, // délka zprávy (8bit) UART2_STOPBITS_1, // počet stopbitů (1) UART2_PARITY_NO, // parita žádná UART2_SYNCMODE_CLOCK_DISABLE, // nepoužíváme synchronní režim UART2_MODE_TXRX_ENABLE // zapnout pouze vysílač (nechci přijímat) ); // spustí UART (převezme kontrolu nad PD5 a PD6) UART2_Cmd(ENABLE); // povolí přerušení od přijímače UART2_ITConfig(UART2_IT_RXNE_OR, ENABLE); // globální povolení přeušení enableInterrupts(); while (1){ process_response(); } } // Rutina přerušení přijímače UARTu (přemístěná ze souboru stm8s_it.c) INTERRUPT_HANDLER(UART2_RX_IRQHandler, 21){ // pokud je to potřeba "odchytím" Overrun událost // if(UART2_GetFlagStatus()UART2_FLAG_OR_LHE){} // nastal overrun znak = UART2_ReceiveData8(); // přečtu přijatý byte flag=1; // dám vědět zbytku programu že jsem přijal byte } // kdykoli cokoli přijde tak to pošle zpět void process_response(void){ if(flag){ // pokud mám nový přijatý byte flag=0; // počkám až bude ve vysílacám bufferu místo... while(!UART2_GetFlagStatus(UART2_FLAG_TXE)){}; UART2_SendData8(znak); // .. a zapíšu data k odeslání } }
Mechanická tlačítka mohou trpět "zákmity" - tedy jevem kdy při stisku nebo uvolňování dojde k několikanásobnému krátkému doteku a uvolnění kontaktů. Z hlediska mikropočítače se tato situace jeví jako serie krátkých stisků a uvolnění tlačítka. Pokud tento jev není ošetřen (softwarově nebo hardwarově) může to mít nepříjemné následky. Hardwarové ošetření je pracné (poctivé ošetření vyžaduje dvě součástky navíc). Nejjednodušší softwarové ošetření spočívá v tom, že program sleduje stav tlačítek jen několikrát za sekundu (např 30-100x). Perioda sledování musí být delší jako trvání zákmitů (typicky do 1-2ms). Rychlejší skenování nemá opodstatnění, protože typický nejkratší stisk tlačítka trvá mezi 50-100ms.
Jedna z variant jak toho docílit je volat v hlavní smyčce stále dokola rutinu skenování tlačítek. Ta pomocí milis odpočítává potřebný čas a stav tlačítek přečte jen po uplynutí zvoleného času (periody). Pokud je v hlavní smyčce nějaký blokující dlouho trvající proces, mohlo by se stát, že se rutina sledování tlačítek nezavolá včas a programu tak nějaký stisk unikne. V takovém případě je možné provádět sledování v rutině přerušení od časovače (bez využití milis). Skenování lze snadno přepsat na detekci stisku i detekci uvolnění, případně lze detekovat oba přechody - což lze následně využít ke měření doby stisku a rozlišovat "krátký" a "dlouhý" stisk.
Timer má funkci zvanou "input capture". Ta funguje jako přesné stopky. V okamžiku kdy tato událost nastane se obsah čítače zkopíruje do "capture" registru, kde si ho váš program může vyzvednout. Tato operace proběhne v jednom strojovém cyklu, takže s ní lze měřit čas s přesností až na jeden cyklus (při 16MHz až na 62.5ns). Událostí může být příchod vzestupné nebo sestupné hrany. S touto funkcí lze různě kouzlit a měřit třeba frekvence a periody nebo taky střídu PWM signálu. V této ukázce demonstrujeme jak "capture" využít ke čtení signálu z RC přijímače. To jsou kladné (5V) pulzy široké 1 až 2ms a přichází s frekvencí přibližně 50Hz a běžně se jimi řídí servomotory. Vy je ale můžete číst mikropočítačem a zpracovat podle potřeby (například pro buzení motorů vaše dálkově ovládaného robota atp.). V následujícím programu přijatý signál využijeme k regulaci jasu LEDky (1ms bude odpovídá zhasnuté LED a 2ms plnému svitu). K měření pulzu použijeme kanál1 TIMeru 3 (TIM3_CH1), kdo bude chtít může přidat i druhý kanál. Ke generování PWM pro LED poslouží TIM2. Princip aplikace je jednoduchý. Spustíme timer3 s funkcí "capture" vzestupné hrany a povolíme přerušení od "capture" události. Jakmile přijde na vstup vzestupná hrana, timer zaznamená přesný čas do capture registru a vyvolá přerušení. V rutině přerušení si vyčteme zachycená data a přepneme timer na detekci sestupné hrany (na to není knihovní funkce a tak to uděláme přímým přístupem do registru). Po příchodu sestupné hrany od sebe oba časy odečteme a tím získáme šířku pulzu, kterou hledáme. Poté přepneme timer opět na detekci vzestupné hrany a dáme vědět hlavní smyčce programu, že jsme změřili pulz ať na to zareaguje. Drobný čertík je schovaný v přetečení timeru (co se stane když je čas vzestupné hrany menší jak sestupné ?!), ale správná 16bitová aritmetika se s tím vypořádá. Jedinou limitací tohoto řešení je že pulzy musí být nutně kratší jak perioda timeru (zde 65ms) a delší jak nějaké minimum (potřebujeme trochu času abychom se přepnuli z detekce vzestupné na detekci sestupné hrany). Druhou z podmínek lze eliminovat použitím módu kdy vzestupnou hranu čteme jedním kanálem a sestupnou hranu druhým kanálem - čehož lze docílit interním přesměrováním, takže je k tomu potřeba fyzicky jen jeden pin, ale o tom až někdy jindy...
/* Aplikace demonstrující "input capture" využitý pro čtení signálů z RC přijímače Jednokanálová varianta. Přijímaný signál je pulz šířky 1-2ms s periodou 20ms Signál z přijímače je přiveden na TIM3_CH1 (PD2) TIM3 na vstupu měří pomocí input capture čas příchodu vzestupné a sestupné hrany a z nich pak dopočítává šířku pulzu Přijatý signál je pak upraven pro demonstrační účely na PWM výstupy TIM2 a to na pin PD4 (TIM2_CH1) vstupní pulz o šířce 2ms odpovídá plnému jasu LED a 1ms minimálnímu jasu (jako by to byl servomotorek) - před testováním programu si v STVP zkontrolujte option bytes kam jsou namapovány použité vstupy a výstupy timerů */ #include "stm8s.h" #include "milis.h" // PD2 - TIM3_CH1 // PD4 - TIM2_CH1 volatile uint8_t flag1=0; // informuje že byl změřen nový pulz volatile uint16_t pulse1=0; // šířky změřených pulzů uint16_t chn1=0; // "PWM" pro LEDky void main(void){ CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // 16MHz z interního RC oscilátoru init_milis(); // nevyužíváme, ale nevadí // TIM3 poběží s taktem 1MHz (1us) a maximálním stropem "free running" TIM3_TimeBaseInit(TIM3_PRESCALER_16,0xFFFF); // na TIM3_CH1 chytáme vzestupnou hranu, bez filtrů a přesměrování TIM3_ICInit(TIM3_CHANNEL_1,TIM3_ICPOLARITY_RISING,TIM3_ICSELECTION_DIRECTTI,TIM3_ICPSC_DIV1,0); // povolíme přerušení od "capture" události TIM3_ITConfig(TIM3_IT_CC1,ENABLE); TIM3_Cmd(ENABLE); // spustíme timer // nastavíme TIM2 pro generování PWM (invertované, LED anodou na +5V) GPIO_Init(GPIOD,GPIO_PIN_4,GPIO_MODE_OUT_PP_LOW_SLOW); // PD4 - TIM2_CH1 TIM2_TimeBaseInit(TIM2_PRESCALER_32,999); // 16M/32/500=1kHz, rozsah 0-1000 TIM2_OC1Init(TIM2_OCMODE_PWM1,TIM2_OUTPUTSTATE_ENABLE,0,TIM2_OCPOLARITY_LOW); TIM2_OC1PreloadConfig(ENABLE); // nechceme při změně střídy vidět glitche TIM2_Cmd(ENABLE); // spustíme TIM2 - začíná generovat PWM (0%) while (1){ if(flag1){ // pokud jsme na kanále 1 změřili nový pulz flag1=0; // vyčistíme si "vlajku" a upravíme PWM pro příslušnou LED if(pulse1<1000){pulse1=1000;}else if(pulse1>2000){pulse1=2000;} TIM2_SetCompare1(pulse1-1000); // zapsat hodnotu "střídy" do timeru } // dělej něco jiného, užitečného } } // rutina přerušení CAPture/COMpare TIMeru 3 (vytažená z ***_it.c) INTERRUPT_HANDLER(TIM3_CAP_COM_IRQHandler, 16){ // ukládáme si časy příletu poslední vzestupné a sestupné hrany static uint16_t capt1_rising, capt1_falling, capt2_rising, capt2_falling; // zjistit jestli IRQ rutinu volá událost na prvním kanále if(TIM3_GetFlagStatus(TIM3_FLAG_CC1) != RESET){ // zjistit kterou hranu máme zachycenou if(TIM3->CCER1 & TIM1_CCER1_CC1P){ // sestupná hrana ? TIM3->CCER1 &=~TIM1_CCER1_CC1P; // přepneme na detekci vzestupné hrany capt1_falling = TIM3_GetCapture1(); // uložíme si zaznamenaný čas // rozdíl času pro vzestupnou a sestupnou hranu dává šířku pulzu (FINTA !) pulse1 = capt1_falling - capt1_rising; flag1=1; // oznámíme hlavní smyčce že jsme změřili nový pulz }else{ // vzestupná hrana TIM3->CCER1 |= TIM1_CCER1_CC1P; // přepneme na detekci sestupné hrany capt1_rising = TIM3_GetCapture1(); // uložíme si zaznamenaný čas } } // pokud bych používal i druhý kanál... //if(TIM3_GetFlagStatus(TIM3_FLAG_CC2) != RESET){} }
Home
| V1.00 24.10.2023 /
| By Michal Dudka (m.dudka@seznam.cz) /