Používame sekvencer - signál SOS

Zápisník experimentátora

Pre dnešnú tému som vybral zaujímavú oblasť. Budeme pomocou Arduina vytvárať signál SOS. Signál sa bude zobrazovať pomocou LED diódy. Preštudujeme si spolu niekoľko spôsobov vytvorenia signálu. Nakoniec skončíme pri používaní sekvencera a timera. To bude náš vrchol programátorského umenia. Vytvorený sekvencer nám poslúži ako základ pre pripravovaný článok o riadení semafórov a signalizáciu pre ESP8266.

Nákup súčiastok

Na fotografiách je použité Arduino Pro Mini. To vás nelimituje, môžete použiť v podstate ľubovoľné Arduino.

  • Arduino Pro Mini (link) - Toto Arduino som použil.
  • Breadboard (link) - Sem som zastrčil všetky súčiastky.
  • Modrá LED dióda - Modrá farba je vhodná preto, lebo LED dióda má vysokú svietivosť a dobre vidíte aj jemné zmeny jasu.
  • Rezistor 1k - Rezistor obmedzuje veľkosť prúdu, ktorý tečie cez LED diódu. Pretože používame vysokosvietivú LED diódu, stačí nám prúd pár miliampérov.

Zapojenie je jednoduché. LED diódu pripojíme na pin 9. Pripojíme ju pomocou rezistora na GND výstup z Arduina.

SOS

Signál SOS v Morseovej abecede vyzerá takto . . . - - - . . .. Ukážeme si jeho vytvorenie pomocou týchto algoritmov. Algoritmy sú zoradené od najprimitívnejších smerom k tým šikovnejším. Pretože smerujeme k vytvoreniu sekvencera, našim cieľom bude posledný algoritmus.

  • Brutálna sila
  • Funkcie
  • Sekvencer
  • Sekvencer a timer

Generovanie pomocou brutálnej sily

Keď som si pripravoval podklady pre článok, narazil som na množstvo rôznych implementácií. Preto treba spomenúť aj generovanie signálu pomocou brutálnej sily. Každú bodku, čiarku a medzeru v SOS vyskladáte pomocou funkcii digitalWrite a delay. Je to síce najhorší možný spôsob programovania, ale fungovať to bude. Čiže ho nemôžeme úplne zavrhnúť len preto, že sa pri ňom upíšete k smrti. Nebudem tu uvádzať príklad, pretože potom by bola táto stránka dlhá tri kilometre. Ale keď napíšete do Google výraz Arduino SOS, narazíte na dostatočné množstvo príkladov.

Generovanie pomocou funkcií

Ak sa pozriete na to, ako signál vyzerá, hneď vás napadne, že sa dá vytvoriť pomocou troch krátkych úsekov, ktoré reprezentujú jednotlivé písmená. A pretože v signáli SOS je každé písmeno ako trojica rovnakých znakov, dá sa aj toto celkom šikovne urobiť pomocou funkcií. Na Internete som našiel pekný príklad, ktorý som iba mierne upravil. Pôvodný zdroj nájdete ako odkaz v zdrojovom texte na GitHub.

Príklad síce negeneruje SOS presne podľa požiadaviek Morseovej abecedy, ale ako ukážka je to dobré. Jednotlivé znaky sú jasne odlíšiteľné a trvajú dosť dlho, aby SOS rozoznalo aj netrénované oko. Príklad je v súbore 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 sú veľmi jednoduché nástroje. V pravidelných intervaloch posielajú na výstup požadovanú hodnotu. V našom prípade vytvoríme sekvencer, ktorý bude simulovať predchádzajúci príklad. V intervaloch 100 ms zapne alebo vypne LED diódu. Budete vidieť, že je výsledok na nerozoznanie od programu, ktorý vytváral znaky SOS pomocou funkcií. Sekvencér má tu výhodu, že pokiaľ by došlo ku zmene požiadaviek na program, samotný program sa nemusí zmeniť a zmení sa iba sekvencia.

Program sa môže zdať byť dlhším, ale to je len preto, že som do neho doplnil výpisy na sériový port, aby ste videli, čo sekvencer robí. V programe je podstatná sekvencia, ktorá je uložená v premennej sequence. Je to zaznamenaný priebeh blikania LED diódou v intervaloch 100 ms. Tento interval som zvolil preto, lebo aj pôvodný program s funkciami používal takéto delenie. Kvôli šetreniu RAM som premennú uložil do Flash pamäte, čo spôsobuje modifikátor PROGMEM.

Sekvencer je vytvorený v triede Sequencer. Má len dve funkcie. V konštruktore si poznačíme údaje o sekvencii a vo funkcii next sa zakaždým posunieme dopredu v sekvencii a podľa toho nastavíme LED diódu na HIGH alebo LOW. Pustite si monitor sériového portu a budete vidieť celú činnosť sekvenceru. Príklad je v súbore 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

Predchádzajúci príklad bol kvôli prehľadnosti časovaný pomocou funkcie delay. Teraz už môžeme náš program urobiť maximálne efektívne a na časovanie využijeme timer1. Pomocou kalkulátora si nastavíme timer na frekvenciu 10 Hz (každých 100 ms) a funkciu next z triedy Sequencer budeme volať z prerušenia TIMER1_COMPA_vect. Z príkladu som odstránil všetky kontrolné výpisy na sériový port. V takejto podobe je program krátky a dobre pochopiteľný. Príklad je v súbore 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();
}

Čo ďalej?

Schválne som vybral extrémne jednoduchý príklad signálu SOS. Na ňom sme si ukázali princíp tvorby sekvencera. V pokračovaní článku budeme riešiť problematiku križovatky. Budeme používať dva semafóry s tromi svetlami a budete vidieť, že pomocou sekvencera je kód triviálne jednoduchý. Kto by chcel vedieť, ako budeme riadiť 6 pinov naraz a ako ich budeme ukladať do sekvencera, vodítko nájde v článku 8x PWM na jednom Arduine.

Zdrojový kód

Zdrojový kód sa nachádza na serveri GitHub.


06.08.2017


Menu