Сборка морзянки на Arduino UNO — практичный проект связи
Компоненты и материалы
| Arduino UNO | × | 2 |
| Светодиод (универсальный) | × | 2 |
| Фоторезистор (LDR) | × | 1 |
| Пьезо-звукопискатель (Buzzer) | × | 1 |
О проекте

Сегодня я проводил занятие по системам связи и хотел, чтобы студенты научились через визуализацию и опыт. Мы сделали несколько экспериментов, начиная с каменного века, чтобы понять потребность в коммуникации и то, что означают язык и грамматика, что такое надежная связь, сигналы, сообщения и т.д.
Обсуждая всё это, мы пришли к точке, где захотели определить свой язык для визуальной связи, и так мы придумали азбуку Морзе!
Очевидно, что изучение азбуки Морзе было бы трудоемкой задачей для каждого из нас, поэтому нам нужна была машина. Мы решили написать небольшую программу, которая может кодировать заданную строку в эквивалентный код Морзе и другой декодер, который возвращает строку для заданного потока кода Морзе.
Для поиска кода Морзе для символа я использовал следующую таблицу:

Для перевода кода Морзе в соответствующий символ его можно организовать в бинарное дерево, как показано ниже. С каждой «точкой» мы переходим в левое поддерево, а с каждой «тире» — в правое.

Это позволило мне быстро кодировать строку в соответствующий код Морзе и наоборот.
Я написал программу на C# как доказательство концепции (PoC):

Выходные данные
Hello World .... . .-.. .-.. --- .-- --- .-. .-.. -..
Однако, поскольку связь должна происходить между двумя сторонами, я хотел передать этот закодированный код Морзе на другое устройство и декодировать его там.
Я использовал две платы Arduino для построения передатчика и приемника.
На плате передатчика я подключил лазерный диод от Keyes. Легко доступное и дешевое устройство. Для моей дистанции 3 В было более чем достаточно, однако с 5 В лазер достигает как минимум 200 метров.

Мой передатчик подключен к компьютеру через последовательный интерфейс (через USB). Он считывает данные из последовательного порта посимвольно, находит эквивалентный код Морзе для символа и генерирует световой поток «точек» и «тире».
Я создал импульс 50 мс, где каждая «точка» — это луч света длительностью 50 мс, а «тире» — 150 мс. Каждая «точка» и «тире» разделены 50 мс тишины. Каждый символ сопровождается еще 100 мс тишины.
Код передатчика
const char* MorseTable[] = {
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
// space, !, ", #, $, %, &, '
NULL, "-.-.--", ".-..-.", NULL, NULL, NULL, NULL, ".----.", // ( ) * + , - . /
"-.--.", "-.--.-", NULL, ".-.-.", "--..--", "-....-", ".-.-.-", "-..-.",
// 0 1 2 3 4 5 6 7
"-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...",
// 8 9 : ; < = > ?
"---..", "----.", "---...", "-.-.-.", NULL, "-...-", NULL, "..--..",
// @ 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 [ \ ] ^ _
"-..-", "-.--", "--..", NULL, NULL, NULL, NULL, "..--.-",
// ' a b c d e f g
NULL, ".-", "-...", "-.-.", "-..", ".", "..-.", "--.",
// h i j k l m n o
"....", "..", ".---", "-.-", ".-..", "--", "-.", "---",
// p q r s t u v w
".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--",
// x y z { | } ~ DEL
"-..-", "-.--", "--..", NULL, NULL, NULL, NULL, NULL,
};
int dotLength = 50;
int dashLength = dotLength*3;
void setup() {
pinMode(13, OUTPUT);
Serial.begin(9600);
}
void loop() {
char ch;
if(Serial.available()){
ch = Serial.read();
flashDashDot(MorseTable[ch]);
delay(dotLength*2);
}
}
void flashDashDot(const char * morseCode) {
int i = 0;
while(morseCode[i] != 0) {
if(morseCode[i] == '.'){
dot();
} else if (morseCode[i] == '-'){
dash();
}
i++;
}
}
void dot() {
digitalWrite(13, HIGH);
delay(dotLength);
digitalWrite(13, LOW);
delay(dotLength);
}
void dash() {
digitalWrite(13, HIGH);
delay(dashLength);
digitalWrite(13, LOW);
delay(dotLength);
}
На плате приемника Arduino я подключил фоторезистор (LDR), пьезо-звукопискатель и светодиод. Светодиод и звукопискатель избыточны, они просто дают аудио- и визуальную обратную связь, и приятно видеть, как всё работает ;)

Мой приемник также подключен к компьютеру для чтения полученной строки символов.
На устройстве в цикле я считываю любой свет, падающий на LDR, подключенный к аналоговому порту 4. Я экранировал его от окружающего света, поместив в длинную трубку. Когда свет от моего лазерного диода попадает на этот LDR, полученное значение становится высоким, и я включаю звукопискатель и светодиод. Когда оно падает — выключаю.
Я вычисляю длину светового луча, чтобы определить, была ли это «точка» или «тире». Учитывая все задержки, это составило ~400 циклов для импульса на моем 16 МГц процессоре Atmel.
Код приемника
// Morse Code Binary Tree (Бинарное дерево кода Морзе)
// Left child at 2n+1 (левый потомок)
// Right child at 2n+2 (правый потомок)
// <.|->
// .E -T
// .I -A .N -M
const char MorseTree[] = {
'\0','E', 'T', 'I', 'A', 'N', 'M', 'S', 'U', 'R', 'W', 'D', 'K', 'G', 'O', 'H', 'V', 'F', 'U', 'L', 'A', 'P', 'J', 'B', 'X', 'C', 'Y', 'Z', 'Q', '\0','\0','5', '4', '\0','3', '\0','\0','\0','2', '\0', '\0','+', '\0','\0','\0','\0','1', '6', '=', '/', '\0','\0','\0','(', '\0','7', '\0','\0','\0','8', '\0','9', '0', '\0', '\0','\0','\0','\0','\0','\0','\0','\0', '\0','\0','\0','?', '_', '\0','\0','\0', '\0','"', '\0','\0','.', '\0','\0','\0', '\0','@', '\0','\0','\0','\0','\0','\0', '-', '\0','\0','\0','\0','\0','\0','\0', '\0',';', '!', '\0',')', '\0','\0','\0', '\0','\0',',', '\0','\0','\0','\0',':', '\0','\0','\0','\0','\0','\0','\0'
};
int val = 0; // Переменная для хранения значения света от LDR
int ctrHigh = 0;
int ctrLow = 0;
int codePtr = 0;
int dotLen = 400;
void setup() {
Serial.begin(9600); // Запуск последовательного соединения
Serial.print(codePtr);
}
void loop() {
val = analogRead(4);
if (val >= 10) {
ctrHigh++;
ctrLow = 0;
digitalWrite(13, HIGH);
tone(9, 1000);
}
else {
ctrLow++;
if ((ctrHigh >= dotLen) && (ctrHigh < dotLen*2)) {
Serial.print(".");
codePtr = (2*codePtr) + 1;
}
else if (ctrHigh >= dotLen * 2) {
Serial.print("-");
codePtr = (2*codePtr) + 2;
}
else {
if(ctrLow == dotLen*2){
Serial.print(MorseTree[codePtr]);
codePtr = 0;
}
}
ctrHigh = 0;
digitalWrite(13, LOW);
noTone(9);
}
}
Выходные данные на приемнике
....H.E.-..L.-..L---O.--W---O.-.R.-..L-..D-.-.--!
Код
Передатчик (Transmitter)
const char* MorseTable[] = {
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
// space, !, ", #, $, %, &, '
NULL, "-.-.--", ".-..-.", NULL, NULL, NULL, NULL, ".----.", // ( ) * + , - . /
"-.--.", "-.--.-", NULL, ".-.-.", "--..--", "-....-", ".-.-.-", "-..-.",
// 0 1 2 3 4 5 6 7
"-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...",
// 8 9 : ; < = > ?
"---..", "----.", "---...", "-.-.-.", NULL, "-...-", NULL, "..--..",
// @ 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 [ \ ] ^ _
"-..-", "-.--", "--..", NULL, NULL, NULL, NULL, "..--.-",
// ' a b c d e f g
NULL, ".-", "-...", "-.-.", "-..", ".", "..-.", "--.",
// h i j k l m n o
"....", "..", ".---", "-.-", ".-..", "--", "-.", "---",
// p q r s t u v w
".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--",
// x y z { | } ~ DEL
"-..-", "-.--", "--..", NULL, NULL, NULL, NULL, NULL,
};
int dotLength = 50;
int dashLength = dotLength*3;
void setup() {
pinMode(13, OUTPUT);
Serial.begin(9600);
}
void loop() {
char ch;
if(Serial.available()){
ch = Serial.read();
flashDashDot(MorseTable[ch]);
delay(dotLength*2);
}
}
void flashDashDot(const char * morseCode) {
int i = 0;
while(morseCode[i] != 0) {
if(morseCode[i] == '.'){ dot(); }
else if (morseCode[i] == '-'){ dash(); }
i++;
}
}
void dot() {
digitalWrite(13, HIGH);
delay(dotLength);
digitalWrite(13, LOW);
delay(dotLength);
}
void dash() {
digitalWrite(13, HIGH);
delay(dashLength);
digitalWrite(13, LOW);
delay(dotLength);
}
Приемник (Receiver)
// Morse Code Binary Tree
// Left child at 2n+1
// Right child at 2n+2
// <.|->
// .E -T
// .I -A .N -M
const char MorseTree[] = {'\0','E', 'T', 'I', 'A', 'N', 'M', 'S', 'U', 'R', 'W', 'D', 'K', 'G', 'O', 'H', 'V', 'F', 'U', 'L', 'A', 'P', 'J', 'B', 'X', 'C', 'Y', 'Z', 'Q', '\0','\0','5', '4', '\0','3', '\0','\0','\0','2', '\0', '\0','+', '\0','\0','\0','\0','1', '6', '=', '/', '\0','\0','\0','(', '\0','7', '\0','\0','\0','8', '\0','9', '0', '\0', '\0','\0','\0','\0','\0','\0','\0','\0', '\0','\0','\0','?', '_', '\0','\0','\0', '\0','"', '\0','\0','.', '\0','\0','\0', '\0','@', '\0','\0','\0','\0','\0','\0', '-', '\0','\0','\0','\0','\0','\0','\0', '\0',';', '!', '\0',')', '\0','\0','\0', '\0','\0',',', '\0','\0','\0','\0',':', '\0','\0','\0','\0','\0','\0','\0' };
int val = 0;
int ctrHigh = 0;
int ctrLow = 0;
int codePtr = 0;
int dotLen = 400;
void setup() {
Serial.begin(9600);
Serial.print(codePtr);
}
void loop() {
val = analogRead(4);
if (val >= 10) {
ctrHigh++;
ctrLow = 0;
digitalWrite(13, HIGH);
tone(9, 1000);
}
else {
ctrLow++;
if ((ctrHigh >= dotLen) && (ctrHigh < dotLen*2)) {
Serial.print(".");
codePtr = (2*codePtr) + 1;
}
else if (ctrHigh >= dotLen * 2) {
Serial.print("-");
codePtr = (2*codePtr) + 2;
}
else {
if(ctrLow == dotLen*2){
Serial.print(MorseTree[codePtr]);
codePtr = 0;
}
}
ctrHigh = 0;
digitalWrite(13, LOW);
noTone(9);
}
}
Схемы
Схема приемника морзянки
Схема передатчика морзянки
Производственный процесс
- Сканеры штрих-кода: технологии, дизайн и будущие тренды
- Комментарии в C++: лучшие практики для читаемого и поддерживаемого кода
- Мастерство частоты ошибок кода ADC: Что нужно знать
- Код Морзе и двойное вымогательство: ландшафт передовых вредоносных угроз
- Создание DTMF-декодера на Arduino без внешних микросхем
- SERENA: Пользовательская система сигнализации на Arduino Mega 2560 с TFT Touchscreen
- Выбор между Data Matrix и QR-кодами: что лучше для вашего бизнеса
- G-коды ЧПУ объяснены: основное руководство для программистов ЧПУ
- Полный справочник Fanuc G-кодов: Все команды с пояснениями
- Полное руководство по CNC G-кодам для систем управления Fanuc