Zápisník experimentátora
Pro dnešní téma jsem vybral zajímavou oblast. Budeme pomocí Arduina vytvářet signál SOS. Signál se bude zobrazovat pomocí LED diody. Prostudujeme si spolu několik způsobů vytvoření signálu. Nakonec skončíme při používání sekvenceru a timeru. To bude náš vrchol programátorského umění. Vytvořený sekvencer nám poslouží jako základ pro připravovaný článek o řízení semaforů a signalizaci pro ESP8266.
Na fotografiích je použito Arduino Pro Mini. To vás nelimituje, můžete použít v podstatě libovolné Arduino.
Zapojení je jednoduché. LED diodu připojíme na pin 9. Připojíme ji pomocí rezistoru na GND výstup z Arduina.
Signál SOS v Morseovej abecedě vypadá takto . . . - - - . . .
. Ukážeme si jeho vytvoření pomocí těchto algoritmů. Algoritmy jsou seřazeny od nejprimitivnějších směrem k těm šikovnějším. Protože směřujeme k vytvoření sekvenceru, naším cílem bude poslední algoritmus.
Když jsem si připravoval podklady pro článek, narazil jsem na množství různých implementací. Proto je třeba zmínit i generování signálu pomocí brutální síly. Každou tečku, čárku a mezeru v SOS poskládáte pomocí funkcí digitalWrite
a delay
. Je to sice nejhorší možný způsob programování, ale fungovat to bude. Čili ho nemůžeme zcela zavrhnout jen proto, že se při něm upíšete k smrti. Nebudu zde uvádět příklad, protože pak by byla tato stránka dlouhá tři kilometry. Ale když napíšete do Google výraz Arduino SOS
, narazíte na dostatečné množství příkladů.
Pokud se podíváte na to, jak signál vypadá, hned vás napadne, že ho lze vytvořit pomocí tří krátkých úseků, které reprezentují jednotlivé písmena. A protože v signálu SOS je každé písmeno jako trojice stejných znaků, dá se i toto docela šikovně udělat pomocí funkcí. Na Internetu jsem našel pěkný příklad, který jsem pouze mírně upravil. Původní zdroj najdete jako odkaz ve zdrojovém textu na GitHub.
Příklad sice negeneruje SOS přesně podle požadavků Morseovy abecedy, ale jak ukázka je to dobré. Jednotlivé znaky jsou jasně odlišitelné a trvají dost dlouho, aby SOS rozeznalo i netrénované oko. Příklad je v souboru sequencer_sos_01.ino
.
int LED = 9;
int s = 300;
int o = 800;
void setup() {
// put your setup code here, to run once:
pinMode(LED, OUTPUT);
}
void character(int speed) {
digitalWrite(LED, HIGH);
delay(speed);
digitalWrite(LED, LOW);
delay(300);
}
void loop() {
// put your main code here, to run repeatedly:
for (int x = 1; x <= 3; x++) {
character(s);
}
delay(100);
for (int x = 1; x <= 3; x++) {
character(o);
}
delay(100);
for (int x = 1; x <= 3; x++) {
character(s);
}
delay(2000);
}
Sekvencery jsou velmi jednoduché nástroje. V pravidelných intervalech posílají na výstup požadovanou hodnotu. V našem případě vytvoříme sekvencer, který bude simulovat předchozí příklad. V intervalech 100 ms zapne nebo vypne LED diodu. Budete vidět, že je výsledek k nerozeznání od programu, který vytvářel znaky SOS pomocí funkcí. Sekvencer má tu výhodu, že pokud by došlo ke změně požadavků na program, samotný program se nemusí změnit a změní se pouze sekvence.
Program se může zdát být delším, ale to je jen proto, že jsem do něj doplnil výpisy na sériový port, abyste viděli, co sekvencer dělá. V programu je podstatná sekvence, která je uložena v proměnné sequence
. Je to zaznamenán průběh blikání LED diodou v intervalech 100 ms. Tento interval jsem zvolil proto, že i původní program s funkcemi používal takové dělení. Kvůli šetření RAM jsem proměnnou uložil do Flash paměti, což způsobuje modifikátor PROGMEM
.
Sekvencer je vytvořen ve třídě Sequencer
. Má jen dvě funkce. V konstruktoru
si poznačit údaje o sekvenci a ve funkci next
se pokaždé posuneme vpřed v sekvenci a podle toho nastavíme LED diodu na HIGH nebo LOW. Pusťte si monitor sériového portu a budete vidět celou činnost sekvenceru. Příklad je v souboru sequencer_sos_02.ino
.
const int LED = 9;
// 1 char = 100 ms
const char PROGMEM sequence[] =
"111000111000111000"
"0"
"111111110001111111100011111111000"
"0"
"111000111000111000"
"00000000000000000000";
const int sequence_length = sizeof(sequence)/sizeof(char);
class Sequencer {
char *data;
int len;
int pos;
public:
Sequencer(char *_data, int _len)
: data(_data), len(_len), pos(0)
{}
void next() {
char b = pgm_read_byte(&data[pos]);
Serial.print(b);
digitalWrite(LED,b=='1');
pos++;
if(pos%10==0)
Serial.println("");
if(pos==len) {
pos=0;
Serial.println("");
Serial.println("-- restart --");
}
}
};
Sequencer seq(sequence,sequence_length);
void setup() {
pinMode(LED, OUTPUT);
Serial.begin(115200);
Serial.print("sequence_length: ");
Serial.println(sequence_length);
}
void loop() {
seq.next();
delay(100);
}
Předchozí příklad byl kvůli přehlednosti časovaný pomocí funkce delay
. Nyní už můžeme náš program udělat maximálně efektivně a na časování využijeme timer1. Pomocí kalkulátoru si nastavíme timer na frekvenci 10 Hz (každých 100 ms) a funkci next
z třídy Sequencer
budeme volat z přerušení TIMER1_COMPA_vect
. Z příkladu jsem odstranil všechny kontrolní výpisy na sériový port. V takové podobě je program krátký a dobře pochopitelný. Příklad je v souboru sequencer_sos_03.ino
.
#define ledPin 9
// 1 char = 100 ms
const char PROGMEM sequence[] =
"111000111000111000"
"0"
"111111110001111111100011111111000"
"0"
"111000111000111000"
"00000000000000000000";
const int sequence_length = sizeof(sequence)/sizeof(char);
class Sequencer {
char *data;
int len;
int pos;
public:
Sequencer(char *_data, int _len)
: data(_data), len(_len), pos(0)
{}
void next() {
char b = pgm_read_byte(&data[pos]);
digitalWrite(ledPin,b=='1');
pos++;
if(pos==len)
pos=0;
}
};
Sequencer seq(sequence,sequence_length);
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();
}
void setup() {
pinMode(ledPin, OUTPUT);
setupTimer1();
}
void loop() {
}
ISR(TIMER1_COMPA_vect) {
seq.next();
}
Schválně jsem vybral extrémně jednoduchý příklad signálu SOS. Na něm jsme si ukázali princip tvorby sekvenceru. V pokračování článku budeme řešit problematiku křižovatky. Budeme používat dva semafory s třemi světly a budete vidět, že pomocí sekvenceru je kód triviální jednoduchý. Kdo by chtěl vědět, jak budeme řídit 6 pinů najednou a jak je budeme ukládat do sekvenceru, vodítko najde v článku 8x PWM na jednom Arduinu.
Zdrojový kód se nachází na serveru GitHub.
07.09.2017