7.6 Morseov kod
Svaki projekt do sada bio je jednosmjeran razgovor: pločica govori, ti gledaš. Ovaj sluša. Upišeš poruku u Serial Monitor, pločica svako slovo potraži u tablici točaka i crtica, i izblinka ga na jednoj jedinoj LED-ici, koristeći isti kod koji je poruke nosio telegrafskim žicama stoljeće prije nego je itko imao računalo.
Krug je najjednostavniji u kitu: jedna LED-ica, jedan otpornik, tri žice. Sve zanimljivo je u programu.
U ovoj dokumentaciji naučit ćeš:
- Kako pročitati tekst koji upišeš u Serial Monitor pomoću
Serial.readStringUntil() - Što je tablica za traženje, i zašto pobjeđuje dugi lanac
ifizjava - Kako pomoću
structdvije vrijednosti spojiti u jednu vlastitu stvar - Kako pisati vlastite funkcije da dulji program ostane čitljiv
- Kako Morseov kod sve mjeri u jedinicama umjesto u milisekundama
Potreban hardver:
- 1x Soldered NULA MINI pločica
- 1x Breadboard
- 1x LED-ica (bilo koje boje)
- 1x otpornik od 330 Ω (narančasti-narančasti-smeđi)
- 3x Jumper žice
- 1x USB-C kabel
Spajanje komponenti
Ovaj projekt koristi jedan jedini pin: IO2, koji sjedi u redu 25 na f–j strani. Tijelo pločice prekriva stupce f–i, pa su j25 i j30 jedine rupice u koje na tom kraju stvarno možeš uvesti žicu. Sve ostalo odvija se dalje duž iste f–j strane, dobro odmaknuto od pločice.
IO2 na f–j strani ali TX na a–e strani. Broji redove na istoj strani na koju utiskuješ.1. Umetni NULA MINI pločicu na breadboard
Utisni pločicu u jedan kraj breadboarda tako da njezina dva reda pinova sjednu s obje strane središnjeg kanala, s čipom okrenutim prema dolje. Trebala bi zauzeti redove 25 do 30.

2. Napravi liniju mase i izvedi IO2
Ovdje ulaze dvije žice, jer su to dva kraja kruga koji ćeš između njih sagraditi.
Prvo jumper žica iz j30 (GND) do plave − vodilice. Ta vodilica postaje dijeljena linija mase, točno kao u svakom drugom projektu u ovom kitu.
Zatim jumper žica iz j25 (IO2) duž do j15. Ona nosi Morseov signal ispod pločice do reda do kojeg stvarno možeš doći.

3. Dodaj otpornik od 330 Ω
Utisni otpornik tako da premosti red 15 i red 13. Prstenovi mu se čitaju narančasti-narančasti-smeđi. Otpornik nema polaritet, pa je bilo koja strana ispravna.

4. Dodaj LED-icu
LED-ica ima polaritet. Njezina duža nožica je anoda, i ta ide u red 13, uz otpornik. Kraća nožica ide u red 11.

5. Spoji LED-icu na masu
Još jedna jumper žica, iz j11 do plave − vodilice. To zatvara krug: IO2 → otpornik → LED-ica → masa.

6. Spoji pločicu na računalo
Utisni USB-C kabel u pločicu i u računalo. Ljubičasta PWR LED-ica se upali; zelena LED-ica ostaje tamna, jer još ništa nije upisano.

Kako krug radi
Važna su samo tri reda, i sjede serijski, pa struja ima točno jedan put koji može slijediti:
| Red | Što je u njemu |
|---|---|
| 15 | jumper žica s IO2, i jedna nožica otpornika |
| 13 | druga nožica otpornika, i duga nožica LED-ice |
| 11 | kratka nožica LED-ice, i jumper žica do − vodilice |
Svih pet rupica u redu spojeno je unutar breadboarda, a to je ono što dopušta da se dvije različite komponente sretnu u jednom redu a da se ne dotiču. Zato i točni brojevi redova nisu važni: bilo koja tri slobodna reda rade, dokle god je redoslijed IO2 → otpornik → LED-ica → masa sačuvan.
Kada skica pozove digitalWrite(LED_PIN, HIGH), IO2 se dovede na 3.3 V, struja teče tim lancem i LED-ica se pali. LOW vraća pin na 0 V i LED-ica se gasi. To je cijeli hardver. Sve ostalo na ovoj stranici je program.
Kako Morseov kod radi
Morseov kod svoje signale ne mjeri u milisekundama. Sve mjeri u jedinicama, gdje je jedna jedinica dužina točke, a svako drugo trajanje je njezin višekratnik:
| Dužina | |
|---|---|
| Točka | 1 jedinica svjetla |
| Crtica | 3 jedinice svjetla |
| Razmak između točaka i crtica unutar slova | 1 jedinica tišine |
| Razmak između slova | 3 jedinice tišine |
| Razmak između riječi | 7 jedinica tišine |
Zato skica sadrži samo jedan pravi broj. DOT_DURATION je 300 ms a sve ostalo se iz njega izračunava:
const int DOT_DURATION = 300;
const int DASH_DURATION = DOT_DURATION * 3;
const int SYMBOL_GAP = DOT_DURATION;
const int LETTER_GAP = DOT_DURATION * 3;
const int WORD_GAP = DOT_DURATION * 7;
Promijeni sam DOT_DURATION i cijeli se prijenos ubrza ili uspori a ostaje ispravan Morseov kod. Postavi ga na 100 i dobiješ brzog operatera; postavi ga na 600 i dobiješ sporog.
Tablica slova
Skica znak mora pretvoriti u niz točaka i crtica. Mogla bi to raditi s dvadeset i šest if izjava, ali umjesto toga koristi tablicu za traženje: listu kroz koju traži.
Da bi tu listu sagradila, prvo joj treba način da slovo i njegov kod drži zajedno kao jednu stvar, a za to služi struct:
struct MorseEntry { char letter; const char *code; };
Sada morseTable može biti polje tih struktura, po jedan unos za svaki znak. Dodati vlastiti znak znači dodati jednu liniju u tablicu i ne promijeniti ništa drugo. To je cijela prednost pred lancem if izjava.
getMorseCode() je funkcija koja obavlja traženje. Prolazi tablicu od početka i vraća se odmah kada nađe poklapanje, a toupper() na vrhu znači da upisivanje sos radi jednako dobro kao SOS. Ako znaka uopće nema u tablici, vraća prazan tekst, koji funkcija za prijenos jednostavno preskoči.
const int MORSE_COUNT = sizeof(morseTable) / sizeof(MorseEntry); računa koliko unosa tablica ima umjesto da ih brojimo napamet: veličina cijele tablice podijeljena s veličinom jednog unosa. Napisano ovako, broj ostaje ispravan nakon što dodaš vlastiti znak.Dvije petlje kroz isti tekst
transmitText() kroz tvoju poruku prolazi dvaput. Prvi prolaz ispisuje prijevod u Serial Monitor da možeš pratiti, a drugi prolaz izblinka istu stvar na LED-ici. Razdvajanje na dva je ono što dopušta da se cijeli prijevod pojavi na ekranu odmah, prije nego je LED-ica dovršila sricanje čak i prvog slova.
U prolazu s blinkanjem razmak se ne blinka uopće. On je pauza. Kada petlja nađe jedan, čeka razmak riječi i pomoću continue preskače ostatak tog prolaza. Svakom drugom znaku simboli se izblinkaju jedan po jedan, a onda petlja čeka razmak slova prije nego nastavi.
Razmaci su malo darežljivi
Prije nego počneš brojati blinkove, vrijedi znati jednu stvar. blinkSymbol() svaki simbol završava jednom jedinicom tišine, a onda transmitText() na to dodaje LETTER_GAP. Zato je razmak koji stvarno vidiš između dva slova 1 + 3 = 4 jedinice, a ne 3, a između dvije riječi 1 + 3 + 7 = 11 jedinica, a ne 7.
Pravi Morseov kod razmak simbola zamjenjuje, a ne dodaje na njega. Ta razlika poruku ne čini pogrešnom. Svaka točka i crtica su točno prave dužine, a razmaci se i dalje jasno razlikuju jedan od drugoga; prijenos je samo malo sporiji od standarda. Vrijedi to znati da se ono što brojiš poklapa s onim što očekuješ.
Kod
Ispod je cijeli kod primjera za ovaj projekt:
/**
**************************************************
*
* @file 7.6_Morse_code_transmitter.ino
* @brief Projekt koji tekst koji upišeš u Serial Monitor pretvara u Morseov kod i izblinka ga na LED-ici.
* Morseov kod svako slovo predstavlja kao uzorak kratkih i dugih signala, zvanih točke i crtice.
* Uz put primjer uvodi tri nove ideje: čitanje teksta iz Serial Monitora, spremanje
* tablice za traženje, i pisanje vlastitih funkcija da dulji program ostane čitljiv.
* Za detalje, shemu spajanja i više, pogledaj dokumentaciju primjera na: <link placeholder>
* @author Soldered
***************************************************
*/
/*
Ovo je varijabla kojoj predajemo broj pina na koji smo spojili LED-icu.
NULA pločica ima logiku imenovanja pinova ovako: IO2, gdje je 2 broj koji dajemo varijabli.
Ako želiš koristiti drugi pin, pobrini se da koristiš pin označen s IO__.
Zapamti da LED-ici treba otpornik od 330 ohma u seriji s njom. Taj otpornik ograničava koliko struje teče, a
bez njega LED-ica vuče više nego što su ona ili pin građeni izdržati, pa se oboje može oštetiti.
*/
const int LED_PIN = 2;
/*
Morseov kod svoje signale ne mjeri u milisekundama nego u jedinicama, a svako drugo trajanje je višekratnik jedne
jedinice. Zato je samo prva vrijednost ispod pravi broj a ostale se iz nje izračunavaju: promijeni sam DOT_DURATION
i cijeli se prijenos ubrza ili uspori a ostaje ispravan Morseov kod.
Standardni razmjeri su crtica dugačka tri jedinice, jedna jedinica tišine između točaka i crtica slova,
tri jedinice između slova, i sedam jedinica između riječi.
*/
const int DOT_DURATION = 300;
const int DASH_DURATION = DOT_DURATION * 3;
const int SYMBOL_GAP = DOT_DURATION;
const int LETTER_GAP = DOT_DURATION * 3;
const int WORD_GAP = DOT_DURATION * 7;
/*
Struct nam dopušta da nekoliko vrijednosti spojimo u jednu stvar koju sami pravimo. Ovdje svaki MorseEntry drži znak i
točke i crtice koje ga predstavljaju, pa ta dva uvijek putuju zajedno.
*/
struct MorseEntry { char letter; const char *code; };
/*
Ovo je naša tablica za traženje: polje struktura iznad, po jedna za svaki znak koji možemo poslati. Tablica za traženje je jednostavno
lista kroz koju tražimo umjesto da ispisujemo desetine if izjava, i ima ono lijepo svojstvo da dodavanje novog
znaka znači dodavanje jedne linije ovdje i nikakvu drugu promjenu.
Posljednji unos mapira razmak na razmak, a tako prepoznajemo razmak između dvije riječi.
*/
const MorseEntry morseTable[] = {
{'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', "-----"},
{' ', " "}
};
/*
Ovdje računamo koliko unosa tablica iznad stvarno ima, umjesto da ih brojimo napamet. sizeof() nam kaže koliko
memorije nešto zauzima, pa nam veličina cijele tablice podijeljena s veličinom jednog unosa daje broj
unosa. Napisano ovako, broj ostaje ispravan i nakon što dodaš vlastiti znak.
*/
const int MORSE_COUNT = sizeof(morseTable) / sizeof(MorseEntry);
/*
Ovo je funkcija koju smo napisali sami. Do sada su naše skice koristile samo setup() i loop() plus funkcije iz
biblioteka, ali kako program raste pomaže dati poslu vlastito ime.
Ova prima znak i vraća točke i crtice koje ga predstavljaju. toupper() malo slovo pretvara
u veliko, tako da upisivanje "sos" radi jednako dobro kao "SOS". Zatim prolazimo kroz tablicu od početka
i vraćamo se odmah kada nađemo poklapanje. Ako znaka uopće nema u tablici, vraćamo prazan tekst, koji funkcija
za prijenos ispod jednostavno preskoči.
*/
const char* getMorseCode(char c) {
c = toupper(c);
for (int i = 0; i < MORSE_COUNT; i++)
if (morseTable[i].letter == c) return morseTable[i].code;
return "";
}
/*
Ova funkcija izblinka jednu točku ili crticu. Odabire pravo trajanje za simbol koji je dobila, pali
LED-icu točno toliko dugo, a onda je gasi i čeka još jednu jedinicu, a to je tišina koja jedan
simbol odvaja od sljedećeg.
*/
void blinkSymbol(char symbol) {
int duration;
if(symbol=='.'){
duration=DOT_DURATION;
}
else{
duration=DASH_DURATION;
}
digitalWrite(LED_PIN, HIGH);
delay(duration);
digitalWrite(LED_PIN, LOW);
delay(SYMBOL_GAP);
}
/*
Ova funkcija prenosi cijeli komad teksta. Posao radi dvaput: prvo ispisuje prijevod u
Serial Monitor da možeš pratiti, a zatim istu stvar izblinka na LED-ici.
"&" u parametru znači da se tekst predaje bez kopiranja, što na duljim porukama štedi memoriju.
*/
void transmitText(const String &text) {
Serial.println("\n--- TRANSMITTING ---");
Serial.print("Text: "); Serial.println(text);
Serial.print("Morse: ");
/*
Ova prva petlja prolazi kroz tekst znak po znak. length() nam kaže koliko znakova ima, a
text[i] nam daje onaj na poziciji i, brojeći od nule. Za svaki od njih potražimo kod i ispišemo ga.
*/
for (unsigned int i = 0; i < text.length(); i++) {
const char *code = getMorseCode(text[i]);
Serial.print(code);
Serial.print(" ");
}
Serial.println("\n--------------------");
/*
Ova druga petlja prolazi kroz taj isti tekst ponovno, ali ovaj put blinka umjesto da ispisuje.
Razmak se ne blinka uopće, on je pauza, pa kada ga nađemo čekamo razmak riječi i preskačemo ostatak ovog prolaza
pomoću continue. Inače unutarnja petlja izblinka simbole slova jedan po jedan, a kada su gotovi čekamo
razmak slova prije nego nastavimo na sljedeće slovo.
*/
for (unsigned int i = 0; i < text.length(); i++) {
const char *code = getMorseCode(text[i]);
if (*code == ' ') {
delay(WORD_GAP);
continue;
}
for (int j = 0; code[j]; j++){
blinkSymbol(code[j]);
}
delay(LETTER_GAP);
}
Serial.println("\nTransmission complete!\n");
}
void setup() {
/*
Serial.begin() uspostavlja serijsku komunikaciju između tvoje pločice i tvog računala preko USB kabela. U ovom primjeru
ga prvi put koristimo u oba smjera: za ispis poruka van, i za čitanje teksta koji upišeš unutra.
*/
Serial.begin(115200);
/*
pinMode() je funkcija koja konfigurira zadani pin da se ponaša ili kao ulaz ili u ovom slučaju kao izlaz.
Budući da naš pin mora upaliti LED-icu, pin ćemo staviti u OUTPUT mod.
*/
pinMode(LED_PIN, OUTPUT);
//Pozovi korisnika da nešto upiše. Piši u polje na vrhu Serial Monitora i pritisni Enter.
Serial.println("Enter text to send via Morse code:");
}
void loop() {
/*
Serial.available() nam kaže koliko je znakova došlo s računala i čeka da bude pročitano. Vraća
nulu kada ništa nije upisano, pa je ova if izjava način na koji ne radimo ništa dok nam korisnik nešto ne pošalje.
*/
if (Serial.available()) {
/*
readStringUntil() skuplja znakove koji dolaze u tekst i staje na znaku koji imenujemo, u ovom slučaju na
novom redu koji Serial Monitor pošalje kada pritisneš Enter.
trim() zatim odstranjuje zalutale razmake ili zaostale znakove kraja linije s oba kraja, što vrijedi napraviti jer
različiti sustavi svoje linije završavaju malo drugačije.
*/
String input = Serial.readStringUntil('\n');
input.trim();
/*
Na kraju provjeravamo da je nakon odstranjivanja stvarno nešto ostalo, tako da pritisak na Enter u praznoj liniji ne
pokrene prijenos, i tekst predajemo našoj funkciji iznad.
*/
if (input.length() > 0) transmitText(input);
}
}
Što bi trebao vidjeti
Prenesi skicu, pa otvori Serial Monitor i postavi baud rate na 115200. Pločica te pozdravlja:
Enter text to send via Morse code:
Upiši poruku u polje za unos na vrhu Serial Monitora i pritisni Enter. Upiši je malim slovima, kao sos, da vidiš nešto korisno:
--- TRANSMITTING ---
Text: sos
Morse: ... --- ...
--------------------
Transmission complete!
Linija Text: vraća točno ono što si upisao, mala slova i sve, ali je linija Morse: ipak ispravna. To je toupper() unutar getMorseCode() koji radi svoj posao: tablica drži samo velika slova, a svaki znak se pretvori prije nego ga se potraži. Prijevod se pojavljuje odjednom, sav, i tek onda LED-ica počinje blinkati.
Tri stvari u tom prozoru važne su koliko i sam tekst. Odabirač 115200 bauda s desne strane mora se poklapati s Serial.begin(115200) u skici, ili ispis dolazi kao nečitljivo smeće. Padajući izbornik New Line uz njega odlučuje što se šalje kada pritisneš Enter. A traka preko vrha imenuje pločicu i port na koji će poslati, ovdje Soldered NULA Mini ESP32C6 na COM6, iako će tvoj vlastiti port gotovo sigurno imati drugo ime.
readStringUntil('\n') čeka znak novog reda koji on pošalje, a na No Line Ending mora umjesto toga počekati da istekne njegova jedna sekunda. Poruka se i dalje prenese, samo počne sekundu kasnije svaki put.Linija Transmission complete! ne pojavljuje se dok LED-ica ne završi. To vrijedi primijetiti: cijeli transmitText() izgrađen je iz delay(), pa dok poruka izlazi pločica ne radi ništa drugo uopće. Možeš tipkati tijekom prijenosa, ali se ništa neće dogoditi dok trenutni ne završi.
Pogledaj to jednom i tri se slova sama razdvajaju:
| Slovo | Kod | Što LED-ica radi |
|---|---|---|
| S | ... | tri kratka bljeska, blizu jedan drugom |
| O | --- | tri duga bljeska, blizu jedan drugom |
| S | ... | ponovno tri kratka bljeska |
Same bljeskove lako je razlikovati kada znaš što usporediti: crtica je upaljena tri puta dulje od točke. Razmaci rade ostatak posla: jedna jedinica tame između simbola unutar slova, i četiri jedinice između jednog slova i sljedećeg, što je dovoljno dugo da vidiš prekid ritma a da ništa ne brojiš.
SOS traje 9.9 sekundi od kraja do kraja, i to možeš izračunati na papiru. Tri točke, tri crtice i tri točke je 15 jedinica svjetla. Nakon svakog od devet simbola slijedi jedna jedinica tišine, a nakon svakog od tri slova još tri, što je 18 jedinica tišine. To je 33 jedinice ukupno, a 33 × 300 ms je 9.9 sekundi.
Kada se pojavi Transmission complete!, pločica je natrag u čekanju i možeš upisati nešto drugo. Probaj riječ s razmakom, kao HELLO WORLD, i pazi na dugu pauzu u sredini. To je razmak riječi, i daleko je najdulja tišina u bilo kojoj poruci.
setup(), pa se pojavljuje jednom, kada se pločica pokrene. Pritisni tipku RST i ispisuje se ponovno. Ako Serial Monitor iznad njega prikazuje zid nečitljivog teksta, to je ESP32-ov vlastiti ROM bootloader koji se javlja prije nego tvoja skica proradi. To nije greška, i nije nešto što je skica ispisala.Ako se LED-ica nikada ne upali, skica je gotovo sigurno u redu. Provjeri prvo polaritet LED-ice. Okreni je: njezina duga nožica pripada u red 13, s otpornikom, a LED-ica postavljena obrnuto ne provodi ništa, što izgleda točno kao mrtav krug. Ako Serial Monitor ispisuje prijevod a LED-ica ostaje tamna, program se ispravno izvršava i greška je definitivno u spajanju, što ju lijepo sužava. Ako se u Serial Monitoru ne pojavi ništa uopće, provjeri je li baud rate 115200 i imaš li odabran pravi port.
Cijeli primjer
Pogledaj cijeli kod primjera na linku ispod:
7.6_Morse_code_transmitter.ino
Projekt koji tekst koji upišeš u Serial Monitor pretvara u Morseov kod i izblinka ga na jednoj LED-ici, koristeći struct, tablicu za traženje i vlastite funkcije.