Zapisovátko morseovky s arduinem

Zapisovátko morseovky, které jsem na Arduinu vytvořil, využívá tři tlačítka (čárka, tečka a oddělovač) a vypisuje text na dvouřádkový display.

Na prvním řádku je zapisovaný text, na druhém buffer tónů, tedy aktuálně naťukané čárky a tečky. Tlačítkem oddělovače se buffer ukončí a napíše se odpovídající písmeno. Pokud je zmačknutý oddělovač 2x po sobě, do textu se zapíše mezera. Kromě čísel a písmen anglické abecedy jsou definovány speciální znaky pro smazání písmena (6x tečka) a slova (6x čárka).

Morseova abeceda je určena pro přenos informací s pomocí několika symbolů. Mohlo by se zdát, že symboly jsou dva- tečka a čárka. Nicméně informaci přenáší ještě různě dlouhá mezera, nejkratší pro oddělení písmen, delší pro slova a nejdelší pro věty. Symbolů tak máme pět a všechny je možné vytvořit pomocí jednoho tlačítka. Já jsem pro zjednodušení použil tři. Jak wikipedie praví, morseovkou můžeme přenášet rychlostí 60 – 250 znaků za minutu, rychlost řeči je přibližně pětinásobná.

Hardware není nikterak složitý, postačí Arduino (testováno na UNO R3), LCD display (já jsem použil celkem běžný dvou řádkový display s řadičem hd44780 a I2C sběrnicí), 3 tlačítka, k nim 3 odpory a pár drátů.

morsesketch_bb

Sketch, který to celé řídí je na 170 řádků a to včetně definice 36 znakové abecedy.

Kód popíšu po jednotlivých logických blocích, celistvý sketch je ke stažení níže.

Nejprve je třeba sepsat pár počátečních řádků:

Importovat nezbytné knihovny.

#include // pro práci s I2C
#include // pro práci s displayem

 

Vytvořit proměnné pro ukládání aktuálního a předchozího stavu tlačítek, tak aby se zajistilo, že se tón při stisku tlačítka zapíše jen jednou.

int carkaButtonState = 0;
int carkaButtonLastState = 0;
int teckaButtonState = 0;
int teckaButtonLastState = 0;
int oddelButtonState = 0;
int oddelButtonLastState = 0;

 

Vytvořit proměnné pro ukládání aktuální sekvence tónů a aktuálního textu. Tónem mám na mysli čárku, tečku, nebo oddělovač. Symbol je v tomto programu číslo nebo písmeno. Ze symbolů se skládá zobrazovaný text.

String tonesBuffer;
String text;

A poslední inicializovaná proměnná je samotná abeceda symbolů, kterou jsem uložil do dvourozměrného pole.

String symbolsAlphabet[][2] =
{
  { ".-","A" },
  { "-...","B" },
  { "-.-.","C" },
  { "-..","D" },
  { ".","E" },
  { "..-.","F" },
  { "--.","G" },
  { "....","H" },
  { "..","I" },
  { ".---","J" },
  { "-.-","K" },
  { ".-..","L" },
  { "--","M" },
  { "-.","N" },
  { "---","O" },
  { ".--.","P" },
  { "--.-","Q" },
  { ".-.","R" },
  { "...","S" },
  { "-","T" },
  { "..-","U" },
  { "...-","V" },
  { ".--","W" },
  { "-..-","X" },
  { "-.--","Y" },
  { "--..","Z" },
  { ".----","1" },
  { "..---","2" },
  { "...--","3" },
  { "....-","4" },
  { ".....","5" },
  { "-....","6" },
  { "--...","7" },
  { "---..","8" },
  { "----.","9" },
  { "-----","0"}
};

Ve funkci setup(), která se spouští při startu, se inicializuje display, zapne se podsvícení, zobrazí se nápis a jednoduchá nápověda. Také zde musíme nastavit pinMode na INPUT pro digitální komunikaci s tlačítky.

void setup() {

  lcd.init();
  lcd.backlight();
  lcd.print("Morseovkovnitko");
  lcd.setCursor(0, 1);
  lcd.print("6x.Smaze1 6x-Vse");

  pinMode(CARKABUTTONPIN, INPUT);
  pinMode(TECKABUTTONPIN, INPUT);
  pinMode(ODDELBUTTONPIN, INPUT);
}

 

Nekonečná smyčka, tedy funkce loop() začíná čtením stavů tlačítek a voláním funkce getToneFromButtonStates().

void loop() {

  //načtení stavů tlačítek
  carkaButtonState = digitalRead(CARKABUTTONPIN);
  teckaButtonState = digitalRead(TECKABUTTONPIN);
  oddelButtonState = digitalRead(ODDELBUTTONPIN);

  char tone = getToneFromButtonStates();//zjistí jestli a jaké tlačítko je stisklé

 

Funkce getToneFromButtonStates() z různého stavu tlačítek vyčte, které (a jestli nějaké) bylo stisknuto a vrátí daný tón. Za stisknutí se ve skutečnosti považuje uvolnění tlačítka, tedy chvíle, kdy tlačítko v předchozím okamžiku bylo stisknuté a nyní není. Pokud taková takový okamžik nenastane, vrátí se prázdný char.

char getToneFromButtonStates()
{
  //vrátí v případě uvolnění tlačítka
  //tedy když nynější stav je 0, předchozí 1

  if (!carkaButtonState&& carkaButtonLastState)
    return '-';
  if (!teckaButtonState && teckaButtonLastState)
    return '.';
  if (!oddelButtonState && oddelButtonLastState)
    return ' ';

  return (char)0;
}

Ve funkci loop() následuje kontrola tónu, pokud je prázdný (uživatel nezmáčknul tlačítko), nic se neděje, jen se na konci přepíše přechozí stav stisknutí tlačítek tím aktuálním. Pokud tón není prázdný, přichází další kontrola, program se větví na část „stisknutý oddělovač“ a „stisknutý jiný tón (tečka nebo čárka)“. Obsahy těchto bloků jsou popsány v dalších částech.

if (tone != (char)0)
  {
    if (tone == ' ')//ukončuji sekvenci tónů, hledám symbol
    {
    //Zdejší kód je níže	
    }
    else//čárka nebo tečka
    {
                //Zdejší kód je ještě nížeji	
    }

    //psaní na display se provádí pouze v případě, že bylo zmačknuté nějaké tlačítko

    lcd.clear();//vyčistí se display
    lcd.print(text);//napíše se text
    lcd.setCursor(0, 1);
    lcd.print(tonesBuffer);//napíše se sled tónů

  }

  //aktualizuje se předchozí stav
  carkaButtonLastState = carkaButtonState;
  teckaButtonLastState = teckaButtonState;
  oddelButtonLastState = oddelButtonState;

Pokud je stisknutým symbolem mezera, znamená to, že ukončuji sekvenci naťukaných tónů a chci z nich vytvořit symbol, na konci bloku se vymaže tonesBuffer.

if (tone == ' ')//ukončuji sekvenci tónů, hledám symbol
    {
      char symbol = getSymbolFromBuffer();

      if (symbol != (char)0)//Sled tónů nalezl nějaký symbol, zapíše se do textu
      {
        text += symbol;
        if (text.length() > DISPLAY_NUMOFCOLUMNS)//Pokud přesáhne počet znaků velikost displaye,
          //napíše se nový znak na první místo. Ostatní se vymažou.
        {
          text = (String)symbol;
        }
      }
      else//Sled tónů nedává žádný symbol, ale možná nějakou akci (například vymazání znaku)
      {
        extractActionFromTonesBuffer();
      }
      tonesBuffer = "";//vymaže se buffer (čárky a tečky)
    }

Funkce getSymbolFromBuffer() vrací jedu ze tří možností. Pokud uživatel stiskne jednou tlačítko oddělovače, pouze se ukončí buffer. Pokud je buffer ukončený (prázdný) a je zmačknutý oddělovač, funkce getSymbolFromBuffer() vrátí mezeru,  pokud buffer prázdný není, prohledá se abeceda a vrátí se nalezený symbol. V případě nenalezení symbolu se vrátí prázdný char.

char getSymbolFromBuffer()
{
  if (tonesBuffer == "")
    return ' '; //udělá mezeru, pokud předím nejsou žádné znaky

  for (int i = 0; i < sizeof symbolsAlphabet / sizeof symbolsAlphabet[0]; i++)
    //Projdu všechny symboly a porovnávám buffer s abecedou 
    if (tonesBuffer == symbolsAlphabet[i][0])
      return symbolsAlphabet[i][1][0];//pokud se rovna vrátím daný symbol

  //Buffer neodpovídá žádnému symbolu, pošlu tedy nic
  return (char)0;
}

Vrácení prázdného charu zapříčiní volání funkce extractActionFromTonesBuffer(). Tato funkce znovu porovnává buffer a ptá se, zdalipak jeho obsah neodpovídá nějaké akci, kterouž je v tomto případě mazání symbolu, či celého textu.

void extractActionFromTonesBuffer()
{
  if (tonesBuffer == "......")//6x tečka
    text.remove(text.length() - 1, 1);//umaže jedno písmeno
  if (tonesBuffer == "------")//6x čárka
    text = "";//smaže celý text
}

Výše popsané bloky kódu se vykonávají, pokud uživatel stisknul oddělovač, pokud uživatel stisknul jiné tlačítko (tečku nebo čárku), tak se tón připíše k bufferu a vykoná se ověření, jestli velikost bufferu nepřesahuje velikost display, v tom případě se buffer pouze přepíše na stisknutý tón.

else//čárka nebo tečka
    {
      tonesBuffer += tone;
      if (tonesBuffer.length() > DISPLAY_NUMOFCOLUMNS)//pokud je tónů víc než velikost displaye, vymže se buffer
      {
        tonesBuffer = (String)tone;
      }
    }

Toť vše!

Kompletní kód:

/*ZAPISOVÁTKO MORSEOVY ABECEDY
 Využívá 3 tlačíka (tečka, čárka, oddělovač)
 Zapisuje na LCD 
 http://5vet.cz/
*/
#include <Wire.h> // pro práci s I2C
#include <LiquidCrystal_I2C.h> // pro práci s displayem


#define CARKABUTTONPIN 12 
#define TECKABUTTONPIN 8
#define ODDELBUTTONPIN 10
#define DISPLAY_NUMOFCOLUMNS 16 //Pracuji s displayem 16x2 




int carkaButtonState = 0;
int carkaButtonLastState = 0;
int teckaButtonState = 0;
int teckaButtonLastState = 0;
int oddelButtonState = 0;
int oddelButtonLastState = 0;
String tonesBuffer;
String text;
String expectedText;

String symbolsAlphabet[][2] =
{
  { ".-","A" },
  { "-...","B" },
  { "-.-.","C" },
  { "-..","D" },
  { ".","E" },
  { "..-.","F" },
  { "--.","G" },
  { "....","H" },
  { "..","I" },
  { ".---","J" },
  { "-.-","K" },
  { ".-..","L" },
  { "--","M" },
  { "-.","N" },
  { "---","O" },
  { ".--.","P" },
  { "--.-","Q" },
  { ".-.","R" },
  { "...","S" },
  { "-","T" },
  { "..-","U" },
  { "...-","V" },
  { ".--","W" },
  { "-..-","X" },
  { "-.--","Y" },
  { "--..","Z" },
  { ".----","1" },
  { "..---","2" },
  { "...--","3" },
  { "....-","4" },
  { ".....","5" },
  { "-....","6" },
  { "--...","7" },
  { "---..","8" },
  { "----.","9" },
  { "-----","0"}
};

LiquidCrystal_I2C lcd(0x27, DISPLAY_NUMOFCOLUMNS, 2);
char getToneFromButtonStates()
{
  //vrátí v případě uvolnění tlačítka
  //tedy když nynější stav je 0, předchozí 1

  if (!carkaButtonState&& carkaButtonLastState)
    return '-';
  if (!teckaButtonState && teckaButtonLastState)
    return '.';
  if (!oddelButtonState && oddelButtonLastState)
    return ' ';

  return (char)0;

}


char getSymbolFromBuffer()
{
  if (tonesBuffer == "")
    return ' '; //udělá mezeru, pokud předím nejsou žádné znaky

  for (int i = 0; i < sizeof symbolsAlphabet / sizeof symbolsAlphabet[0]; i++)
    //Projdu všechny symboly a porovnávám buffer s abecedou 
    if (tonesBuffer == symbolsAlphabet[i][0])
      return symbolsAlphabet[i][1][0];//pokud se rovna vrátím daný symbol

  //Buffer neodpovídá žádnému symbolu, pošlu tedy nic
  return (char)0;
}

void extractActionFromTonesBuffer()
{
  if (tonesBuffer == "......")//6x tečka
    text.remove(text.length() - 1, 1);//umaže jedno písmeno
  if (tonesBuffer == "------")//6x čárka
    text = "";//smaže celý text
}


void setup() {

  lcd.init();
  lcd.backlight();
  lcd.print("Morseovkovnitko");
  lcd.setCursor(0, 1);
  lcd.print("6x.Smaze1 6x-Vse");

  pinMode(CARKABUTTONPIN, INPUT);
  pinMode(TECKABUTTONPIN, INPUT);
  pinMode(ODDELBUTTONPIN, INPUT);
}

void loop() {

  //načtení stavů tlačítek
  carkaButtonState = digitalRead(CARKABUTTONPIN);
  teckaButtonState = digitalRead(TECKABUTTONPIN);
  oddelButtonState = digitalRead(ODDELBUTTONPIN);

  char tone = getToneFromButtonStates();//zjistí jestli a jaké tlačítko je stisklé

  if (tone != (char)0)
  {
    if (tone == ' ')//ukončuji sekvenci tónů, hledám symbol
    {
      char symbol = getSymbolFromBuffer();

      if (symbol != (char)0)//Sled tónů nalezl nějaký symbol, zapíše se do textu
      {
        text += symbol;
        if (text.length() > DISPLAY_NUMOFCOLUMNS)//Pokud přesáhne počet znaků velikost displaye,
          //napíše se nový znak na první místo. Ostatní se vymažou.
        {
          text = (String)symbol;
        }
      }
      else//Sled tónů nedává žádný symbol, ale možná nějakou akci (například vymazání znaku)
      {
        extractActionFromTonesBuffer();
      }
      tonesBuffer = "";//vymaže se buffer (čárky a tečky)
    }
    else//čárka nebo tečka
    {
      tonesBuffer += tone;
      if (tonesBuffer.length() > DISPLAY_NUMOFCOLUMNS)//pokud je tónů víc než velikost displaye, vymže se buffer
      {
        tonesBuffer = (String)tone;
      }
    }

    //psaní na display se provádí pouze v případě, že bylo zmačknuté nějaké tlačítko

    lcd.clear();//vyčistí se display
    lcd.print(text);//napíše se text
    lcd.setCursor(0, 1);
    lcd.print(tonesBuffer);//napíše se sled tónů

  }

  //aktualizuje se předchozí stav
  carkaButtonLastState = carkaButtonState;
  teckaButtonLastState = teckaButtonState;
  oddelButtonLastState = oddelButtonState;

}

 

Leave a Reply

Your email address will not be published. Required fields are marked *