Zápisník experimentátora
Hierarchy: Časovač (timer)
Pomocí timeru si můžete zjednodušit několik úkolů. Můžete ho využít i ke kontrole stavu tlačítka. V tomto článku si ukážeme, jak můžeme kontrolovat dvě tlačítka, které mění hodnotu proměnné. Jedno tlačítko hodnotu snižuje a druhé ji zvyšuje. Během stisknutí tlačítka se vše děje s minimem kódu a v tomto speciálním případě není ani nutné ošetřovat zákmity na tlačítkách.
Na tento experiment stačí pouze pár součástek:
Mikrospínače jsou připojeny na piny 8 a 6 Arduina. Na druhé straně jsou propojeny na GND, proto nezapomeňte v kódu vždy nastavit pin jako vstupní pull-up.
V prvním příkladu bude timer1
nastaven na frekvenci 10 Hz. Využil jsem na to mou online kalkulačku na výpočet časovačů. Přerušení nastane 10x za sekundu. Pokud držíme stisknuté levé tlačítko, 10x za sekundu se zmenší hodnota proměnné a výsledek se vypíše na sériový port. Pokud držíme stisknuté pravé tlačítko, 10x za sekundu se zvětší hodnota proměnné a výsledek se vypíše na sériový port.
Definice proměnných. Modifikátor volatile
musíme použít tehdy, když proměnnou modifikujeme z hlavního programu a z přerušení. Jinak by mohl překladač optimalizovat proměnnou do registru a dostali bychom podivné výsledky při čtení jejího obsahu.
#define ledPin 13
#define buttonLeft 8
#define buttonRight 6
volatile int value = 0;
int oldvalue = -999;
Toto je kompletně generován kód. V kalkulátoru jsem pouze nastavil požadovaný timer a jeho frekvenci.
void setupTimer1() {
noInterrupts();
// Clear registers
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
// 10 Hz (16000000/((6249+1)*256))
OCR1A = 6249;
// CTC
TCCR1B |= (1 << WGM12);
// Prescaler 256
TCCR1B |= (1 << CS12);
// Output Compare Match A Interrupt Enable
TIMSK1 |= (1 << OCIE1A);
interrupts();
}
Nastavení pinů. Všimněte si, že piny 8 a 6 jsou nastaveny jako INPUT_PULLUP
, abych nemusel použít externí rezistor. Takto je hodnota pinu HIGH
v nestlačeném stavu a LOW
ve stlačeném stavu.
void setup() {
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
pinMode(buttonLeft,INPUT_PULLUP); // left button
pinMode(buttonRight,INPUT_PULLUP); // right button
setupTimer1();
}
Vypisování obsahu proměnné na sériový port. Protože proměnná má typ int
, musím zakázat během jejího čtení přerušení, abych zaručil atomické přečtení obsahu. Zbytek kódu se stará o to, aby se vypisovalo pouze při změně její hodnoty.
void loop() {
int v;
noInterrupts();
v = value;
interrupts();
if(v!=oldvalue) {
Serial.println(v);
oldvalue = v;
}
}
Obsluha přerušení. Tento kód je snadno pochopitelný. Přerušení se vyvolá 10x za sekundu, takže při stlačení se poměrně rychle vypisují změněné hodnoty. Při taktomto nastavení je ale komplikované nastavovat změny například o 1. To zajistí příklad číslo 2.
ISR(TIMER1_COMPA_vect) {
digitalWrite(ledPin, digitalRead(ledPin) ^ 1);
// left button
if(digitalRead(buttonLeft)==false) {
value--;
}
// right button
if(digitalRead(buttonRight)==false) {
value++;
}
}
Ve druhém příkladu bude timer1
nastaven na frekvenci 100 Hz. Využil jsem na to mou online kalkulačku na výpočet časovačů. Pokud držíme stisknuté levé tlačítko, 2-10-50x za sekundu se zmenší hodnota proměnné a výsledek se vypíše na sériový port. Pokud držíme stisknuté pravé tlačítko, 2-10-50x za sekundu se zvětší hodnota proměnné a výsledek se vypíše na sériový port. V obou případech se rychlost změny obsahu proměnné postupně zvyšuje.
Definice proměnných. Modifikátor volatile
musíme použít tehdy, když proměnnou modifikujeme z hlavního programu a z přerušení. Jinak by mohl překladač optimalizovat proměnnou do registru a dostali bychom podivné výsledky při čtení jejího obsahu.
#define ledPin 13
#define buttonLeft 8
#define buttonRight 6
volatile int value = 0;
int oldvalue = -999;
int int_counter_left = 0;
int int_counter_right = 0;
Toto je kompletně generován kód. V kalkulátoru jsem pouze nastavil požadovaný timer a jeho frekvenci.
void setupTimer1() {
noInterrupts();
// Clear registers
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
// 100 Hz (16000000/((624+1)*256))
OCR1A = 624;
// CTC
TCCR1B |= (1 << WGM12);
// Prescaler 256
TCCR1B |= (1 << CS12);
// Output Compare Match A Interrupt Enable
TIMSK1 |= (1 << OCIE1A);
interrupts();
}
Nastavení pinů. Všimněte si, že piny 8 a 6 jsou nastaveny jako INPUT_PULLUP
, abych nemusel použít externí rezistor. Takto je hodnota pinu HIGH
v nestlačeném stavu a LOW
ve stlačeném stavu.
void setup() {
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
pinMode(buttonLeft,INPUT_PULLUP); // left button
pinMode(buttonRight,INPUT_PULLUP); // right button
setupTimer1();
}
Vypisování obsahu proměnné na sériový port. Protože proměnná má typ int
, musím zakázat během jejího čtení přerušení, abych zaručil atomické přečtení obsahu. Zbytek kódu se stará o to, aby se vypisovalo pouze při změně její hodnoty.
void loop() {
int v;
noInterrupts();
v = value;
interrupts();
if(v!=oldvalue) {
Serial.println(v);
oldvalue = v;
}
}
Obsluha přerušení. Přerušení se zavolá 100x za sekundu. To je již poměrně rychlé a proto musíme změny proměnné value vhodně zpomalit. Takovou frekvenci jsem zvolil proto, aby byla obsluha tlačítka svižná a reagovala i na nejmenší stisknutí tlačítka.
int_counter_xxx
a práce s operátorem modulo.
ISR(TIMER1_COMPA_vect) {
digitalWrite(ledPin, digitalRead(ledPin) ^ 1);
// left button
if(digitalRead(buttonLeft)==false) {
if(int_counter_left<100)
value-=(int_counter_left%50) ? 0 : 1;
else if(int_counter_left<200)
value-=(int_counter_left%10) ? 0 : 1;
else
value-=(int_counter_left%2) ? 0 : 1;
int_counter_left++;
}
else
int_counter_left = 0;
// right button
if(digitalRead(buttonRight)==false) {
if(int_counter_right<100)
value+=(int_counter_right%50) ? 0 : 1;
else if(int_counter_right<200)
value+=(int_counter_right%10) ? 0 : 1;
else
value+=(int_counter_right%2) ? 0 : 1;
int_counter_right++;
}
else
int_counter_right = 0;
}
Zdrojový kód se nachází na GitHub.
17.07.2018