logo_elektromys.eu

/ STM8 Výukové materiály |

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:

/ TIM2 PWM varianta I |

// 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ě
 }
}

PWM na výstupech PD4 a PD3.

/ TIM1 PWM varianta I |

// 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ě
 }
}

PWM na výstupu PC1

/ UART blokující vysílání |

// 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í
 }
}

Zpráva odeslaná UARTem s rychlostí 115200b/s (1bit trvá 1/115200 = 8.7us). Zoomováno na první znak. Graficky je vyznačen Start bit i Stop bit, datové bity znázorněny modře. Data se posílají "LSB first" - jako první se vysílá nejnižší bit. První odeslaný bit má tedy hodnotu 0b01000011 = 67 což odpovídá v ASCII tabulce znaku "C".

/ UART příjem pomocí přerušení |

// 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í
 }
}

Modrá stopa je RX MCU (signál přichází z FTDI modulu), žlutá stopa je odpověď z MCU (na TX pinu).

/ Detekce stisku tlačítka |

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.

/ Využití "Capture" události v časovači k měření šířky pulzu |

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){}
 }

Žlutá stopa představuje sginál z RC přijímače. S příchodem vzestupné hrany se vyvolá rutina přerušení a zaznamená čas jejícho příchodu a timer se přepne na detekci sestupné hrany.

Home
| V1.00 24.10.2023 /
| By Michal Dudka (m.dudka@seznam.cz) /