Používáme sekvencer - signál SOS

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.

Nákup součástek

Na fotografiích je použito Arduino Pro Mini. To vás nelimituje, můžete použít v podstatě libovolné Arduino.

  • Arduino Pro Mini (link) - Toto Arduino jsem použil.
  • Breadboard (link) - Sem jsem zastrčil všechny součástky.
  • Modrá LED dioda - Modrá barva je vhodná proto, že LED dioda má vysokou svítivost a dobře vidíte i jemné změny jasu.
  • Rezistor 1k - rezistor omezuje velikost proudu, který teče přes LED diodu. Protože používáme vysocesvítivou LED diodu, stačí nám proud pár miliampérů.

Zapojení je jednoduché. LED diodu připojíme na pin 9. Připojíme ji pomocí rezistoru na GND výstup z Arduina.

SOS

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.

  • Brutální síla
  • Funkce
  • Sekvencer
  • Sekvencer a timer

Generování pomocí brutální síly

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ů.

Generování pomocí funkcí

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

Sekvencer

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

Sekvencer a timer

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();
}

Co dál?

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

Zdrojový kód se nachází na serveru GitHub.


07.09.2017


Menu