Zápisník experimentátora
Hierarchy: Časovač (timer)
V tomto článku navážeme na předchozí článek, který popisoval timer2. Pokusíme se o stejnou vizualizaci činnosti registru TCNT0, který se nachází v časovači 0. Timer0 v Arduinu obsluhuje výpočet času a proto zásahy do něj ovlivňují i funkce millis a delay. Ukážeme si jednoduché řešení, jak se vypořádat i s takovým problémem.
K vytvoření animace můžeme použít:
Vše je zapojené podle následujícího obrázku. LED diody jsou připojeny na piny RX (0), TX (1), 2-7 a na obrázku je ještě vidět připojení GND na mé LED desce pomocí dvou drátěných propojek k Arduinu. Arduino Pro Mini má vyvedeno GND i na druhé straně, takže jsem mohl použít i jedinou propojku, ale pak by se to špatně fotografovalo.
RX a TX jsou navzájem přehozené, protože se bude zobrazovat bitová podoba 8bitová čísla pomocí přímého zápisu do rejstříku PORTD a potřebujeme mít piny seřazeny stejně, jako jsou bity v registru. Budeme zobrazovat obsah registru TCNT0, což je registr pro timer0. V něm se postupně zvyšuje hodnota až po nastavenou hodnotu registru OCR0A. To se děje v případě režimu CTC. Mělo by platit, že maximální hodnota TCNT0 = OCR0A. Na diodách je to snadno kontrolovatelné, protože pokud do OCR0A zapíšete například hodnotu 63 (binární 0b00111111), neměly by svítit poslední dvě LED. Pokud se rozsvítí, pak jste něco naprogramovali špatně.
Tento program ovlivňuje timer0 a proto se v něm nelze používat funkce millis a delay.
Základ programu jsem vygeneroval v mé aplikaci. Protože hodiny Arduina jsou nastaveny na 16 MHz a používáme 8bitový timer, nemůžeme ho nastavit na příliš nízkou hodnotu. Minimum je přibližně 70 Hz. Proto jsem to raději nastavil na 100 Hz.
Abychom viděli, že program funguje správně, nechal jsem v programu blikat i LED diodu na pinu 13. Blikání se odehrává v obsluze přerušení ISR (TIMER0_COMPA_vect)
a protože přerušení se vyvolá 100x za sekundu, zpomalil jsem ho pomocí proměnné divider
na frekvenci 1 Hz.
Vizualizace přerušení se nachází ve funkci loop. Nemůžeme ji mít v přerušení, protože v okamžiku, jak se přerušení vyvolá, má rejstřík TCNT0 stále stejnou hodnotu. V předchozím příkladu jsem na tomto místě zápis mírně pozdržel pomocí funkce delay
. Tu ale nyní použít nemůžeme, protože jsem přeprogramoval časování timeru 0 a tím jsem změnil počítání času pro Arduino. Například funkce delay je v jádru Arduina definována takto.
void delay(unsigned long ms)
{
uint32_t start = micros();
while (ms > 0) {
yield();
while ( ms > 0 && (micros() - start) >= 1000) {
ms--;
start += 1000;
}
}
}
Protože v tomto případě potřebuji jen nějaké zpomalení, stačí mi v dostatečném množství použít instrukci nop
. Použil jsem cyklus, který tuto instrukce použije v dostatečném množství, abych na pár milisekund zpomalil vykreslování a tím dosáhl kýžený efekt. Text v uvozovkách musí být zapsán is tím \
n na konci, protože instrukce assembleru se zapisují každá do zvláštního řádku a toto nám to zajistí. Když máme jen jednu instrukci, tak to není tak viditelné, jako kdybychom je v kódu použili více najednou.
for(long i=0;i<100000;i++)
__asm__("nop\n");
Takto vznikl zajímavý animační efekt. Trochu to připomíná staré sci-fi filmy, kde kosmické lodě, nebo jakákoliv pokročilá technologie bývala ztvárněná jako určitým způsobem blikající skupina světel.
Když se zadíváte do blikání, všimnete si, že binární číslo nejde až do 255. Kdyby byla v rejstříku OCR0A
například hodnota 31, 63 nebo 127, byli by zhasnuté diody z horních bitů ještě více viditelné. To je vlastně i důkazem toho, že to v režimu CTC opravdu počítá správně.
#define ledPin 13
int divider=0;
void setupTimer() {
noInterrupts();
TCCR0A = 0;
TCCR0B = 0;
TCNT0 = 0;
OCR0A = 155; // 100.16025641025641 Hz
TCCR0A |= (1 << WGM01);
TCCR0B |= 0;
TCCR0B |= (1 << CS02) | (0 << CS01) | (1 << CS00);
TIMSK0 |= (1 << OCIE0A);
interrupts();
}
void setup() {
pinMode(ledPin, OUTPUT);
for (int i = 0; i < 8; i++)
pinMode(i, OUTPUT);
setupTimer();
}
void loop() {
PORTD = TCNT0;
// delay replacement, because we changed timer0
for(long i=0;i<100000;i++)
__asm__("nop\n");
}
ISR(TIMER0_COMPA_vect) {
if(divider==0)
digitalWrite(ledPin, digitalRead(ledPin) ^ 1);
divider++;
divider%=100;
}
Zdrojový kód se nachází na serveru GitHub.
31.05.2020