Skip to main content

7.6 Morseov kod

Svaki projekt do sada bio je jednosmjeran razgovor: pločica govori, ti gledaš. Ovaj sluša. Upišeš poruku u konzolu, 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 konzolu pomoću input()
  • Što je rječnik (dictionary), i zašto pobjeđuje dugi lanac if izjava
  • Kako get() nešto potraži bez rušenja kada toga nema
  • 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
ℹ️
LED-ici treba njezin otpornik od 330 Ω. LED-ica gotovo da nema vlastitog otpora, pa bez otpornika u seriji vuče više struje nego što su ona ili pin građeni izdržati, i oboje se može oštetiti. Jedan otpornik po LED-ici, svaki put.

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.

⚠️
Brojeve redova dijele obje strane breadboarda, ali pinove ne. Red 25 je 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

ℹ️
Ovaj korak pretpostavlja da znaš kako je breadboard spojen iznutra i što su njegove naponske vodilice (power rails). Za uvod pogledaj stranicu dokumentacije Osnove breadboarda.

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.

NULA MINI pločica postavljena na breadboard, zauzima redove 25 do 30
Korak 1: pločica postavljena na breadboard, s IO2 u redu 25 i GND u redu 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.

Dvije jumper žice na strani f do j: kratka plava iz j30 na negativnu vodilicu, i duga narančasta koja ide iz j25 u j15
Korak 2: GND vezan na plavu − vodilicu, i IO2 izveden iz j25 u j15

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.

Otpornik od 330 ohma premošćuje redove 15 i 13 na strani f do j
Korak 3: otpornik od 330 Ω premošćuje redove 15 i 13
⚠️
Dvije nožice moraju sletjeti u različite redove. Otpornik s obje nožice u jednom redu je kratko spojen. Struja teče pravo pored njega i LED-ica radi bez ičega što je ograničava.

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.

LED-ica dodana, s dugom nožicom u redu 13 uz otpornik i kratkom nožicom u redu 11
Korak 4: LED-ica na mjestu, duga nožica prema otporniku

5. Spoji LED-icu na masu

Još jedna jumper žica, iz j11 do plave vodilice. To zatvara krug: IO2 → otpornik → LED-ica → masa.

Druga plava jumper žica iz j11 na negativnu vodilicu, zatvara krug LED-ice
Korak 5: kratka nožica LED-ice spojena na plavu − vodilicu, čime se krug zatvara
ℹ️
Obje žice vodilice idu u red rupica uz plavu liniju. Dva reda u pojasu vodilice su odvojene sabirnice, a onaj uz crvenu + liniju u ovom se projektu ne koristi uopće.

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.

Gotov predajnik Morseovog koda napajan preko USB-C, s tamnom zelenom LED-icom
Korak 6: gotov sklop, napajan preko USB-C. LED-ica ostaje tamna dok nešto ne upišeš

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
15jumper žica s IO2, i jedna nožica otpornika
13druga nožica otpornika, i duga nožica LED-ice
11kratka 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 skripta pozove led.value(1), IO2 se dovede na 3.3 V, struja teče tim lancem i LED-ica se pali. led.value(0) 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čka1 jedinica svjetla
Crtica3 jedinice svjetla
Razmak između točaka i crtica unutar slova1 jedinica tišine
Razmak između slova3 jedinice tišine
Razmak između riječi7 jedinica tišine

Zato skripta sadrži samo jedan pravi broj. DOT_DURATION je 300 ms a sve ostalo se iz njega izračunava:

DOT_DURATION = 300
DASH_DURATION = DOT_DURATION * 3
SYMBOL_GAP = DOT_DURATION
LETTER_GAP = DOT_DURATION * 3
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, kao rječnik

Skripta znak mora pretvoriti u niz točaka i crtica. Mogla bi to raditi s dvadeset i šest if izjava, ali umjesto toga koristi rječnik: tablicu parova gdje nešto potražiš imenujući to.

MORSE_TABLE = {
"A": ".-", "B": "-...", "C": "-.-.", "D": "-..", "E": ".",
...
" ": " ",
}

Svaki unos sparuje ključ, znak, s vrijednošću, kodom koji ga predstavlja. Traženje jednog svodi se na imenovanje ključa, a dodati vlastiti znak znači dodati jednu liniju ovdje i ne promijeniti ništa drugo. To je cijela prednost pred lancem if izjava.

ℹ️
Arduino skica nema rječnik, pa istoj tablici tamo treba struct da drži svaki par slova i koda, polje tih structova, prebrojavanje izračunato pomoću sizeof, i petlja da se kroz to traži. Python sve to ima ugrađeno, i zato je MicroPython verzija ovog projekta petnaest linija koda kraća od Arduino verzije.

Posljednji unos sparuje razmak s razmakom, a tako skripta prepoznaje razmak između dvije riječi.

Sigurno traženje pomoću get()

def get_morse_code(character):
return MORSE_TABLE.get(character.upper(), "")

U toj jednoj liniji događaju se dvije stvari.

upper() malo slovo pretvara u veliko, pa upisivanje sos radi jednako dobro kao SOS, jer tablica drži samo velika slova.

get() je siguran način čitanja iz rječnika. Pisanje MORSE_TABLE["#"] za znak koji nije u tablici diže KeyError i zaustavlja program. get() prima drugi argument upravo za taj slučaj, a ovdje je to prazan tekst, pa nepoznat znak tiho proizvede ništa, a funkcija za prijenos ga preskoči.

⚠️
To postaje važno onog trenutka kada itko upiše pravi tekst. Točka, zarez ili uskličnik nisu u tablici, a s uglatim zagradama umjesto get() cijela bi skripta pala na prvom takvom znaku.

Dvije petlje kroz isti tekst

transmit_text() kroz tvoju poruku prolazi dvaput. Prvi prolaz skuplja prijevod i ispisuje ga da možeš pratiti; 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.

codes = []
for character in text:
codes.append(get_morse_code(character))
print("Morse: ", " ".join(codes))
ℹ️
" ".join(codes) lijepi listu dijelova u jedan string s razmakom između svakog para. To je Python način gradnje linije iz dijelova, i čita se bolje od sabiranja stringova jednog po jednog.

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.

ℹ️
for symbol in code: kroz string prolazi znak po znak, pa code od "..." daje tri prolaza s symbol koji je svaki put .. Stringovi se u Pythonu ponašaju kao liste znakova, što te poštedi ručnog indeksiranja.

Razmaci su malo darežljivi

Prije nego počneš brojati blinkove, vrijedi znati jednu stvar. blink_symbol() svaki simbol završava jednom jedinicom tišine, a onda transmit_text() 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:

from machine import Pin
import time

# 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.
#
# 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.
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.
DOT_DURATION = 300
DASH_DURATION = DOT_DURATION * 3
SYMBOL_GAP = DOT_DURATION
LETTER_GAP = DOT_DURATION * 3
WORD_GAP = DOT_DURATION * 7

# Ovo je naša tablica za traženje: sparuje svaki znak koji možemo poslati s točkama i crticama koje ga predstavljaju. U
# Pythonu se ovakva tablica zove rječnik, a potražiti nešto u njoj jednostavno je kao imenovati
# znak koji želiš.
# Posljednji unos sparuje razmak s razmakom, a tako prepoznajemo razmak između dvije riječi.
MORSE_TABLE = {
"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 stvaramo naš Pin objekt za LED-icu. Pin.OUT govori pločici da ovaj pin treba pisati vrijednost umjesto da je
# čita. Odmah nakon toga u njega pišemo 0, pa LED-ica počinje ugašena.
led = Pin(LED_PIN, Pin.OUT)
led.value(0)


def get_morse_code(character):
# Ovo je funkcija koju smo napisali sami. Prima znak i vraća točke i crtice koje ga predstavljaju.
# upper() malo slovo pretvara u veliko, tako da upisivanje "sos" radi jednako dobro kao "SOS".
# get() znak traži u našoj tablici, a druga vrijednost koju mu damo je ono što se vrati kada
# znaka uopće nema u tablici. Ovdje je to prazan tekst, koji funkcija za prijenos jednostavno preskoči.
return MORSE_TABLE.get(character.upper(), "")


def blink_symbol(symbol):
# 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.
if symbol == ".":
duration = DOT_DURATION
else:
duration = DASH_DURATION

led.value(1)
time.sleep_ms(duration)
led.value(0)
time.sleep_ms(SYMBOL_GAP)


def transmit_text(text):
# Ova funkcija prenosi cijeli komad teksta. Posao radi dvaput: prvo ispisuje prijevod u
# konzolu da možeš pratiti, a zatim istu stvar izblinka na LED-ici.
print()
print("--- TRANSMITTING ---")
print("Text: ", text)

# Ova prva petlja prolazi kroz tekst znak po znak. Za svaki od njih potražimo kod i
# skupimo ga, a join() zatim skupljene dijelove slijepi s razmakom između njih.
codes = []
for character in text:
codes.append(get_morse_code(character))
print("Morse: ", " ".join(codes))
print("--------------------")

# 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 character in text:
code = get_morse_code(character)
if code == " ":
time.sleep_ms(WORD_GAP)
continue
for symbol in code:
blink_symbol(symbol)
time.sleep_ms(LETTER_GAP)

print()
print("Transmission complete!")
print()


# Pozovi korisnika da nešto upiše. Piši u konzolu svog editora i pritisni Enter.
print("Enter text to send via Morse code:")

while True:

# input() čeka dok ne upišeš liniju i pritisneš Enter, pa nam preda ono što si upisao. To je najjednostavniji način
# dobivanja informacije s računala u pločicu, i protuteža je print()-u koji smo cijelo vrijeme
# koristili u drugom smjeru.
text = input()

# strip() 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.
text = text.strip()

# 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 len(text) > 0:
transmit_text(text)

Što bi trebao vidjeti

Pritisni Run. Pločica te pozdravi i onda čeka:

Enter text to send via Morse code:

Klikni u Shell, upiši poruku 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 upper() unutar get_morse_code() 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.

⚠️
Moraš kliknuti u Shell prije nego upišeš. input() čeka liniju iz konzole, a pritisci tipki upisani u polje editora idu u tvoju datoteku. Ako se ništa ne dogodi kada tipkaš, to je gotovo uvijek razlog.

Linija Transmission complete! ne pojavljuje se dok LED-ica ne završi. To vrijedi primijetiti: cijeli transmit_text() izgrađen je iz time.sleep_ms(), 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:

SlovoKodŠ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.

ℹ️
Probaj upisati nešto s točkom, kao hello. Točke nema u tablici, pa get() vraća prazan tekst i ona se jednostavno preskoči. Nema greške i nema rušenja, a ostatak poruke normalno se prenese.

Ako se LED-ica nikada ne upali, skripta 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 konzola ispisuje prijevod a LED-ica ostaje tamna, program se ispravno izvršava i greška je definitivno u spajanju, što ju lijepo sužava.


Cijeli primjer

Pogledaj cijeli kod primjera na linku ispod:

7.6_Morse_code_transmitter.py

Projekt koji tekst koji upišeš u konzolu pretvara u Morseov kod i izblinka ga na jednoj LED-ici, koristeći rječnik kao tablicu za traženje i vlastite funkcije.