В начале февраля я попробовала собрать морзе-клавиатуру на базе «радиоконструктора» Arduino. Получился вполне работоспособный прототип с единственной кнопкой, нажимая которую, можно «генерировать» точки и тире — из которых микроконтроллер будет собирать буквы и отправлять их на компьютер. Девайс (если можно назвать девайсом с полдюжины деталек на макетной плате) получился вполне работоспособный. Но для практического применения малопригодный, так что я собиралась усовершенствовать конструкцию. И вот, что у меня получилось.

Прежде всего, в новой версии морзе-клавиатуры стоило бы отказаться от ввода точек и тире одной кнопкой. Однокнопочный ввод — это необходимость подстраивать свой темп печати под временные интервалы, прописанные в программе, чтобы точки и тире пользователя «попадали» в точки и тире клавиатуры. Да и вводить точки-тире куда удобнее, не задумываясь над длительностью нажатия. К такому варианту давным-давно пришли радисты и телеграфисты, перейдя на подобные ключи:

Итак, мы будем использовать две кнопки — одну для точек, вторую для тире. Заодно заменим малоинформативный одноцветный светодиод на RGB-диод. Цветом можно будет информировать пользователя о текущей раскладке. Кроме того, стоило бы дополнить девайс обратной тактильной связью — например, через вибромотор. Правда, как выяснилось в процессе работы, вибромотор нужно подключать через специальную микросхему — драйвер управления моторами. Поэтому пока я заменила вибромотор пьезодинамиком-пищалкой, который можно подключить к Arduino напрямую, без дополнительных деталей.
Что мне потребовалось для работы:

1) Freeduino 2009 — полный аналог Arduino Duemilanove (впрочем, можно было обойтись куда более слабыми вариациями микроконтроллера)
2) Макетная плата
3) 2 кнопки
4) RGB-светодиод с общим анодом
5) 2 резистора 10 кОм
6) 3 резистора 330 Ом
7) Пьезо-динамик (на картинко по ссылке это Piezo Buzzer)
8) дюжина проводков для соединения деталей на макетной плате
Для создания альфа-версии мне дополнительно понадобились:
9) Полметра кабеля «витая пара». Как оказалось, проводки из «витой пары» вполне годятсяв качестве соединительных проводов для макетки.
10) Старый непишущий фломастер для надписей на компакт-дисках.
11) Паяльник, припой, изолента.
Как подключить RGB-светодиод.
В прошлой версии морзе-клавиатуры мы использовали простой одноцветный светодиод, с одним анодом и одним катодом. Его подключить было несложно: анод подключаем к выводу микроконтроллера, катод — через резистор к земле. Возможно, если бы у меня был RGB-диод с общим катодом и тремя анодами, я бы поступила аналогично. Но в набор Seeeduino Catalyst Pack входили RGB-диоды с общим анодом, так что пришлось немножко подумать над подключением (спасибо гуру Arduino, давшим мне правильные советы!). Суть решения оказалась в использовании инвертированной логики (возможно, термин не совсем правилен — буду благодарна за поправку). В прошлой версии светодиод горел, когда на вывод микроконтроллера (МК) подавалось напряжение, и гас, когда напряжение отсутствовало. В новой версии все будет наоборот. При отсутствии напряжения на выводе диод будет гореть, а при наличии — гаснуть. Для реализации этой хитроумной схемы подключим катоды диода через сопротивления к выводам процессора. А анод — подключим к +5В. Теперь при наличии +5В на выводе МК тока через диод не будет, а при отсутствии напряжения — ток потечет и зажжет светодиод. Естественно, при написании программы нам нужно помнить об инвертированной логике.
Мне посоветовали применить инвертированную же логику и при подключении кнопок — потому, что с учетом возможности короткого замыкания безопаснее тянуть провод с землей, чем с напряжением. Честно говоря, логика мне не совсем понятна — ведь электроны-то «текут» не с положительного, а с отрицательного вывода. Решив разобраться с этим позже, я подключила кнопки немного иначе, чем в предыдущей версии. Теперь при отпущенной кнопке с подключенного к ней вывода МК будет считывать HIGH, а при нажатой кнопке — LOW.
Зачем нам нужна пьезо-пищалка. При наборе точек и тире хотелось бы, не глядя на экран, получать подтверждение, что набранная буква принята и отправлена на компьтер. Иначе при быстром наборе, сделав недостаточную паузу между буквами, мы можем, например, вместо символов АЕ (*- *) отправить на компьютер бкуву Р (*-*). Если пользователь будет получать сигнал при отправке каждой буквы, он наделает меньше ошибок. Конечно, вместо «пищалки» стоило бы использовать вибро-моторчик, но пищалку подключить проще. А с виброй я разберусь в следующей части морзе-эпопеи. Так что пока вместо вибрации каждый отправленный клавиатурой символ будет сопровождаться коротким звуком.
Соберем схему:


Схему я рисовала в программе Fritzing, но мне нигде не удалось найти для нее RGB-светодиода. Поэтому я самостоятельно собрала эту «детальку», склеив красный, зеленый и синий светодиоды. При подключении диода стоит посмотреть на его спецификацию, чтобы понять, где какая ножка. Нелишним будет и протестировать диод, подключив общий анод к плюсу батарейки, а каждый из катодов — поочередно к его минусу. Для тестирования своего светодиода я использовала два подключенных последовательно полуторавольтовых (итого 3 вольта) AA-аккумулятора от фотоаппарата.
Откомпилируем и загрузим код (он довльно подробно откомментирован, так что просто приведу листинг):
Voila! Что умеет наш девайс? Левая кнопка выдает точку. Правая — тире. При этом диод вспыхивает в такт нажимаемым кнопкам (для тире — ярче, чем для точки). Если нажать левую и, не отпуская ее, правую — переключимся на кириллицу (зеленый цвет диода). А нажав правую и, не отпуская ее, левую — на латынь (желтый цвет). Если в течение минуты ни одна кнопка не нажималась, девайс передет в спящий режим, а диод начнет мигать фиолетовым.
Теоретически клавиатура готова. Но на практике не слишком-то удобно щелкать кнопками, расположенными на макетной плате. Попробуем придать нашей конструкции более функциональный вид.
В качестве корпуса я взяла старый высохший фломастер для надписей на компакт-дисках. В следующий раз я выберу более вместительный корпус — очень уж неудобно в такой узкой трубочке протягивать кучу проводов. А у нас их получится целых 10: 4 от светодиода и по 3 от каждой кнопки. Припаяем к каждой кнопке и диоду необходимые провода и резисторы:

(кажется, на этой фотографии я перепутала провода для напря��ения и провода для земли — к счастью, ничего не сгорело, но пришлось все перепаять)
Можно, конечно, было соптимизировать, спаяв внутри вместе 3 «напряжения» и 2 «земли», но тогда смонтировать все это внутри фломастера стало бы сложнее. Кроме того, спаяв вместе кнопки и диод, я бы не смогла экспериментировать с различными вариантами корпусов и размещением кнопок и диода. Поэтому я припала к катодам светодиода по 330-омному резистору и проводок соответствующего цвета (R — оранжевый, G — зеленый, B — синий), а к аноду — оранжево-белый. Так же поступила и с кнопками, припаяв к каждой по 10-килоомному сопротивлению и по 3 провода — для земли (коричневый), питания (рыже-белый) и для подключения к выводам Arduino. Припаивая провода, стоит уделить внимание, какой цвет куда должен быть подсоединен, чтобы, глядя на выходящий из корпуса десяток проводов, не гадать, какой — к чему. Оголенные места проводов я обмотала скотчем. Хотя, видимо, грамотнее было бы изолировать их с помощью термоусадочной трубочки, входящей в набор «Seeeduino Catalyst Pack». Нужно будет расспросить об этом знающих людей :)
Перед тем, как поместить кнопки и диод в корпус, я убедилась, что спаянные детали работают:

Все в сборе:

Пожалуй, получившийся девайс можно назвать бета-версией. Функционал практически полностью реализован, осталось превратить поделку в законченное устройство.
Для следующей версии морзе-клавиатуры я собираюсь подобрать более удобный корпус, в котором уместится вся электроника (нужно будет перейти на более компактную версию Arduino), заменить пищалку вибро-моторчиком и, возможно, дополнить клавиатуру жк-экранчиком, на который можно будет выводить подсказки.

Прежде всего, в новой версии морзе-клавиатуры стоило бы отказаться от ввода точек и тире одной кнопкой. Однокнопочный ввод — это необходимость подстраивать свой темп печати под временные интервалы, прописанные в программе, чтобы точки и тире пользователя «попадали» в точки и тире клавиатуры. Да и вводить точки-тире куда удобнее, не задумываясь над длительностью нажатия. К такому варианту давным-давно пришли радисты и телеграфисты, перейдя на подобные ключи:

Итак, мы будем использовать две кнопки — одну для точек, вторую для тире. Заодно заменим малоинформативный одноцветный светодиод на RGB-диод. Цветом можно будет информировать пользователя о текущей раскладке. Кроме того, стоило бы дополнить девайс обратной тактильной связью — например, через вибромотор. Правда, как выяснилось в процессе работы, вибромотор нужно подключать через специальную микросхему — драйвер управления моторами. Поэтому пока я заменила вибромотор пьезодинамиком-пищалкой, который можно подключить к Arduino напрямую, без дополнительных деталей.
Что мне потребовалось для работы:

1) Freeduino 2009 — полный аналог Arduino Duemilanove (впрочем, можно было обойтись куда более слабыми вариациями микроконтроллера)
2) Макетная плата
3) 2 кнопки
4) RGB-светодиод с общим анодом
5) 2 резистора 10 кОм
6) 3 резистора 330 Ом
7) Пьезо-динамик (на картинко по ссылке это Piezo Buzzer)
8) дюжина проводков для соединения деталей на макетной плате
Для создания альфа-версии мне дополнительно понадобились:
9) Полметра кабеля «витая пара». Как оказалось, проводки из «витой пары» вполне годятсяв качестве соединительных проводов для макетки.
10) Старый непишущий фломастер для надписей на компакт-дисках.
11) Паяльник, припой, изолента.
Как подключить RGB-светодиод.
В прошлой версии морзе-клавиатуры мы использовали простой одноцветный светодиод, с одним анодом и одним катодом. Его подключить было несложно: анод подключаем к выводу микроконтроллера, катод — через резистор к земле. Возможно, если бы у меня был RGB-диод с общим катодом и тремя анодами, я бы поступила аналогично. Но в набор Seeeduino Catalyst Pack входили RGB-диоды с общим анодом, так что пришлось немножко подумать над подключением (спасибо гуру Arduino, давшим мне правильные советы!). Суть решения оказалась в использовании инвертированной логики (возможно, термин не совсем правилен — буду благодарна за поправку). В прошлой версии светодиод горел, когда на вывод микроконтроллера (МК) подавалось напряжение, и гас, когда напряжение отсутствовало. В новой версии все будет наоборот. При отсутствии напряжения на выводе диод будет гореть, а при наличии — гаснуть. Для реализации этой хитроумной схемы подключим катоды диода через сопротивления к выводам процессора. А анод — подключим к +5В. Теперь при наличии +5В на выводе МК тока через диод не будет, а при отсутствии напряжения — ток потечет и зажжет светодиод. Естественно, при написании программы нам нужно помнить об инвертированной логике.
Мне посоветовали применить инвертированную же логику и при подключении кнопок — потому, что с учетом возможности короткого замыкания безопаснее тянуть провод с землей, чем с напряжением. Честно говоря, логика мне не совсем понятна — ведь электроны-то «текут» не с положительного, а с отрицательного вывода. Решив разобраться с этим позже, я подключила кнопки немного иначе, чем в предыдущей версии. Теперь при отпущенной кнопке с подключенного к ней вывода МК будет считывать HIGH, а при нажатой кнопке — LOW.
Зачем нам нужна пьезо-пищалка. При наборе точек и тире хотелось бы, не глядя на экран, получать подтверждение, что набранная буква принята и отправлена на компьтер. Иначе при быстром наборе, сделав недостаточную паузу между буквами, мы можем, например, вместо символов АЕ (*- *) отправить на компьютер бкуву Р (*-*). Если пользователь будет получать сигнал при отправке каждой буквы, он наделает меньше ошибок. Конечно, вместо «пищалки» стоило бы использовать вибро-моторчик, но пищалку подключить проще. А с виброй я разберусь в следующей части морзе-эпопеи. Так что пока вместо вибрации каждый отправленный клавиатурой символ будет сопровождаться коротким звуком.
Соберем схему:


Схему я рисовала в программе Fritzing, но мне нигде не удалось найти для нее RGB-светодиода. Поэтому я самостоятельно собрала эту «детальку», склеив красный, зеленый и синий светодиоды. При подключении диода стоит посмотреть на его спецификацию, чтобы понять, где какая ножка. Нелишним будет и протестировать диод, подключив общий анод к плюсу батарейки, а каждый из катодов — поочередно к его минусу. Для тестирования своего светодиода я использовала два подключенных последовательно полуторавольтовых (итого 3 вольта) AA-аккумулятора от фотоаппарата.
Откомпилируем и загрузим код (он довльно подробно откомментирован, так что просто приведу листинг):
//================================================================================================================
// Настройки кнопок.
//================================================================================================================
// Кнопка-точка будет слева.
#define BUTTON_DOT 6
// Кнопка-тире - справа.
#define BUTTON_TIRE 7
int buttons, buttonsPrev; // Здесь будем в виде битовых масок хранить нынешнее и предыдущее состояние клавиш.
#define BUTTON_DOT_MASK 1
#define BUTTON_TIRE_MASK 2
// С помощью этих переменных мы засекаем, когда в последний раз была нажата и отпущена кнопка. Любая. По этому
// времени мы определяем, завершен ли ввод текущего символа и не пора ли переходить в спящий режим.
unsigned long int timeRelease, timePress;
// Смену состояния кнопки на время менее 0.03с будем считать дребезгом и игнорировать.
#define DEBOUNCING_TIME 30
// В эти переменные будем сохранять время для отфильтровывания дребезга контактов.
unsigned long int timeDotDebouncing;
unsigned long int timeTireDebouncing;
// Максимальная пауза между точками и тире в букве - 0.4 секунды.
// Если пауза больше - считаем ввод буквы завершенным и переходим к вводу следующей буквы.
#define DELIMITER_TIME 500
// Если кнопка не нажималась более минуты - переходим в спящий режим.
#define SLEEP_TIME 60000
// Выйти из спящего режима можно, удерживая нажатой любую кнопку в течение 1 секунды.
#define WAKEUP_TIME 2000
// Для переключения раскладки на кириллицу нажимаем кнопку-точку и, не отпуская ее, нажимаем кнопку-тире.
// Для переключения раскладки на латынь нажимаем правую кнопку-тире и, не отпуская ее, нажимаем кнопку-точку.
//================================================================================================================
// Настройки RGB-светодиода.
//================================================================================================================
// Для обратной связи будем использовать RGB-светодиод:
#define LED_R 11
#define LED_G 10
#define LED_B 9
// Цвета диода будем задавать в виде числа-битовой маски: 00000RGB, вспомним цвета в старых добрых EGA и Yamaha MSX.
// Семи цветов (черный не в счет) нам более, чем хватит.
#define COLOR_BLACK 0
#define COLOR_BLUE 1
#define COLOR_GREEN 2
#define COLOR_CYAN 3
#define COLOR_RED 4
#define COLOR_MAGENTA 5
#define COLOR_YELLOW 6
#define COLOR_WHITE 7
// Кириллица - зеленый, латынь - желтый, спящий режм - мигание фиолетовым.
#define COLOR_CYRILLIC_LAYOUT COLOR_GREEN
#define COLOR_LATIN_LAYOUT COLOR_YELLOW
#define COLOR_SLEEP_MODE COLOR_MAGENTA
// Яркость мигания для режима печати и спящего режима. Не забывваем, что у нас логика инвертирована
// и 0 означает максимальную яркость, а 255 - погашенный светодиод.
#define BRIGHTNESS_TYPING_LOW (255-1)
#define BRIGHTNESS_TYPING_DOT (255-7)
#define BRIGHTNESS_TYPING_TIRE (255-15)
#define BRIGHTNESS_SLEEP_LOW (255-0)
#define BRIGHTNESS_SLEEP_HIGH (255-1)
/*
#define BRIGHTNESS_TYPING_LOW (255-7)
#define BRIGHTNESS_TYPING_DOT (255-128)
#define BRIGHTNESS_TYPING_TIRE (255-255)
#define BRIGHTNESS_SLEEP_LOW (255-8)
#define BRIGHTNESS_SLEEP_HIGH (255-128)
*/
//================================================================================================================
// Настройки пьезо-динамика.
//================================================================================================================
#define PIEZO 12
byte piezoData;
unsigned long int piezoShutUpTime;
//================================================================================================================
// Азбука Морзе.
//================================================================================================================
// Этими символами мы будем обозначать точки и тире.
#define MORSE_DOT '*'
#define MORSE_TIRE '-'
// Точка или тире пока не введены.
#define MORSE_EMPTY 0
// Это - для блокировки ввода точек/тире при смене раскладки или выходе из спящего режима.
#define MORSE_LOCKED '!'
// Максимальная длина символа азбуки Морзе (в точках и тире)
#define MAX_MORSE_SYMBOL_LENGTH 8
// Буфер для записи морзе-символа.
byte morseSymbol[MAX_MORSE_SYMBOL_LENGTH];
unsigned int morseSymbolLen;
byte newMorseSignal; // Новый введенный сигнал - точка или тире.
// Таблица кодов Морзе. N-ный элемент кода соответствует n-ному символу раскладки.
char* code[] = {
"*-","-***","*--","--*","-**","*","***-","--**","**","*---",
"-*-","*-**","--","-*","---","*--*","*-*","***","-","**-",
"**-*","****","-*-*","---*","----","--*-","-*--","-**-","**-**","**--",
"*-*-",
"*----","**---","***--","****-","*****","-****","--***","---**","----*","-----",
"......","*-*-*-","---***","-*-*-","-*--*-","*----*","*-**-*","-****-","-**-*","**--**","--**--",
"-***-","********","*--*-*","**-*-",
""
};
// Кириллическая раскладка.
char* layoutCyrillic[] = {
"а","б","в","г","д","е","ж","з","и","й",
"к","л","м","н","о","п","р","с","т","у",
"ф","х","ц","ч","ш","щ","ы","ь","э","ю",
"я",
"1","2","3","4","5","6","7","8","9","0",
".",",",":",";","(","\'","\"","-","/","?","!",
" *DELIMITER* "," *ERR* ","@"," *END* ",
""
};
// Латинская раскладка.
char* layoutLatin[] = {
"a","b","w","g","d","e","v","z","i","j",
"k","l","m","n","o","p","r","s","t","u",
"f","h","c","ö","ch","q","y","x","é","ü",
"ä",
"1","2","3","4","5","6","7","8","9","0",
".",",",":",";","(","\'","\"","-","/","?","!",
" *DELIMITER* "," *ERR* ","@"," *END* ",
""
};
char** currentLayout;
char** newLayout;
//================================================================================================================
// Режимы работы.
//================================================================================================================
#define TYPING_MODE 0
#define SLEEP_MODE 1
int mode;
boolean flagWakeUp; // Этот флаг будем использовать для выхода из спящего режима.
byte ledLevelSleepCounter; // Переключатель яркости для мигания диода в спящеь режиме.
//================================================================================================================
void setup() {
Serial.begin(9600);
pinMode(LED_R, OUTPUT);
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);
// И кнопки, и светодиод у нас работают с инвертированной логикой: кнопка нажата = LOW, отпущена = HIGH,
// светодиод горит на полную яркость = LOW, погашен = HIGH. Погасим светодиоды:
analogWrite(LED_R, 255);
analogWrite(LED_G, 255);
analogWrite(LED_B, 255);
pinMode(PIEZO, OUTPUT);
digitalWrite(PIEZO, LOW);
pinMode(BUTTON_DOT, INPUT);
pinMode(BUTTON_TIRE, INPUT);
buttons = 0;
buttonsPrev = 0;
mode = TYPING_MODE;
flagWakeUp = false;
morseSymbolLen = 0;
currentLayout = layoutLatin;
newLayout = 0;
newMorseSignal = MORSE_EMPTY;
ledLevelSleepCounter = 0;
}
//================================================================================================================
// Зажжем светодиод нужными цветом и яркостью. Не забываем, что у нас инвертирована логика и 0 - это самый яркий
// свет, а 255 - погашенный светодиод.
void setLed(int ledColor, int ledBrightness) {
if (ledColor & COLOR_RED) {
analogWrite(LED_R, ledBrightness);
} else {
analogWrite(LED_R, 255);
}
if (ledColor & COLOR_GREEN) {
analogWrite(LED_G, ledBrightness);
} else {
analogWrite(LED_G, 255);
}
if (ledColor & COLOR_BLUE) {
analogWrite(LED_B, ledBrightness);
} else {
analogWrite(LED_B, 255);
}
}
//================================================================================================================
// Работа с пьезо-динамиком
void doPiezo(unsigned long int currentTime) {
if (currentTime >= piezoShutUpTime) {
if (piezoShutUpTime > 0) {
piezoShutUpTime = 0;
digitalWrite(PIEZO, LOW);
}
return;
}
piezoData = (piezoData == LOW) ? HIGH : LOW;
digitalWrite(PIEZO, piezoData);
}
void playPiezo(unsigned long int t, unsigned long int currentTime) {
piezoShutUpTime = currentTime + t;
}
//================================================================================================================
// Считывание состояния кнопки с учетом возможного дребезга контактов.
int getButtonState(int btnPrevState, int BUTTON_PIN, unsigned long int* timeDebouncing, unsigned long int currentTime) {
int btnState = digitalRead(BUTTON_PIN);
if (btnState == HIGH) {
if (btnPrevState == LOW) {
if (*timeDebouncing == 0) {
// Засечем время, которое кнопка будет нажата - чтобы не спутать дребезг контактов с нажатием.
*timeDebouncing = currentTime;
// Пока не воспринимаем нажатие, считая его дребезгом контактов.
btnState = LOW;
} else {
if ((currentTime - *timeDebouncing) < DEBOUNCING_TIME) {
// Пока не воспринимаем нажатие, считая его дребезгом контактов.
btnState = LOW;
} else {
// Это не дребезг контактов, это реальное нажатие кнопки.
btnState = HIGH;
*timeDebouncing = 0;
}
}
} else {
*timeDebouncing = 0;
}
} else {
if (btnPrevState == HIGH) {
if (*timeDebouncing == 0) {
// Засечем время, которое кнопка будет нажата - чтобы не спутать дребезг контактов с нажатием.
*timeDebouncing = currentTime;
// Пока не воспринимаем отпускание, считая его дребезгом контактов.
btnState = HIGH;
} else {
if ((currentTime - *timeDebouncing) < DEBOUNCING_TIME) {
// Пока не воспринимаем отпускание, считая его дребезгом контактов.
btnState = HIGH;
} else {
// Это не дребезг контактов, это реальное отпукание кнопки.
btnState = LOW;
*timeDebouncing = 0;
}
}
} else {
*timeDebouncing = 0;
}
}
return btnState;
}
//================================================================================================================
// Отправим на компьютер введенный символ.
void sendMorseSymbol() {
int i, j;
if (morseSymbolLen < 1) {
return;
}
playPiezo(50, millis());
for (i = 0; code[i][0] != '\0'; i++) {
// Сравним введенный символ с символами из таблицы кодов Морзе.
for (j = 0; (j < morseSymbolLen) && (code[i][j] != '\0'); j++) {
if (code[i][j] != morseSymbol[j]) {
j = -1;
break;
}
}
if ((j != -1) && (j == morseSymbolLen) && (code[i][j]=='\0')) {
// Символ из таблицы кодов Морзе соответствует введенному символу.
// Отправим символ на компьютер.
Serial.print(currentLayout[i]);
morseSymbolLen = 0;
return;
}
}
// Символ в таблице не найден. Напечатаем нераспознанный символ.
Serial.print(" [");
for (i = 0; i < morseSymbolLen; i++) {
Serial.print(morseSymbol[i]);
}
Serial.print("] ");
morseSymbolLen = 0;
}
//================================================================================================================
// Режим печати
void typingLoop() {
unsigned long int t, dt; // Эти пременные будем использовать для замеров времени.
int btnDotState, btnTireState; // В эти переменные считаем состояния кнопок. В принципе, их можно было бы сразу
// занести в переменную buttons, но так код будет понятнее.
int ledLevel; // Яркость диода
int ledColor; // Цвет диода, битовая маска - 00000RGB.
// analogWrite(PIEZO, 0);
t = millis();
// Не забываем, что у нас логика инвертирована, и нажатая кнопка - это LOW.
btnDotState = getButtonState((buttonsPrev & BUTTON_DOT_MASK) ? LOW : HIGH, BUTTON_DOT, &timeDotDebouncing, t);
btnTireState = getButtonState((buttonsPrev & BUTTON_TIRE_MASK) ? LOW : HIGH, BUTTON_TIRE, &timeTireDebouncing, t);
buttons = ((btnDotState == LOW) ? BUTTON_DOT_MASK : 0) | ((btnTireState == LOW) ? BUTTON_TIRE_MASK : 0);
if (buttons == 0) {
// Обе кнопки отпущены, можно добавить введенную точку, тире или переключить раскладку.
// Если пауза дольше SLEEP_TIME - перейдем в спящий режим.
// Если пауза дольше DELIMITER_TIME - отправим символ.
if (buttonsPrev != 0) {
timeRelease = t;
}
if (newLayout) {
currentLayout = newLayout;
newLayout = 0;
} else switch (newMorseSignal) {
case MORSE_DOT:
case MORSE_TIRE:
morseSymbol[morseSymbolLen++] = newMorseSignal;
break; // MORSE_DOT, MORSE_TIRE
}
newMorseSignal = MORSE_EMPTY;
dt = t - timeRelease;
if ((morseSymbolLen > 0) && (dt > DELIMITER_TIME)) {
sendMorseSymbol();
} else if (dt > SLEEP_TIME) {
mode = SLEEP_MODE;
Serial.println("\nSleep mode\n");
}
} else if (newMorseSignal != MORSE_LOCKED) {
switch (buttons) {
case BUTTON_DOT_MASK:
if (newMorseSignal == MORSE_EMPTY) {
// Нажата "точка".
newMorseSignal = MORSE_DOT;
timePress = t;
}
break; // BUTTON_DOT_MASK
case BUTTON_TIRE_MASK:
if (newMorseSignal == MORSE_EMPTY) {
// Нажато "тире".
newMorseSignal = MORSE_TIRE;
timePress = t;
}
break; // BUTTON_DOT_MASK
case BUTTON_DOT_MASK | BUTTON_TIRE_MASK:
// Нажаты обе кнопки. Сменим раскладку.
switch (buttonsPrev ) {
case 0: // Маловероятно, что обе кнопки нажаты одновременно, но в этом случае переключимся на кириллицу.
case BUTTON_DOT_MASK:
if (newLayout == 0) {
sendMorseSymbol();
newLayout = layoutCyrillic;
Serial.println("\nLayout: cyrillic\n");
}
break; // 0, BUTTON_DOT_MASK
case BUTTON_TIRE_MASK:
if (newLayout == 0) {
sendMorseSymbol();
newLayout = layoutLatin;
Serial.println("\nLayout: latin\n");
}
break; // BUTTON_TIRE_MASK
}
timePress = t;
newMorseSignal = MORSE_LOCKED;
break; // BUTTON_DOT_MASK | BUTTON_TIRE_MASK
}
}
// Займемся светодиодом.
if (currentLayout == layoutCyrillic) {
ledColor = COLOR_CYRILLIC_LAYOUT;
} else {
ledColor = COLOR_LATIN_LAYOUT;
}
setLed(ledColor, (buttons == 0) ? BRIGHTNESS_TYPING_LOW : ((buttons == BUTTON_DOT_MASK) ? BRIGHTNESS_TYPING_DOT : BRIGHTNESS_TYPING_TIRE));
doPiezo(t);
buttonsPrev = buttons;
delay(10);
}
//================================================================================================================
// Спящий режим
void sleepLoop() {
unsigned long int t, dt; // Эти пременные будем использовать для замеров времени.
int btnDotState, btnTireState; // В эти переменные считаем состояния кнопок. В принципе, их можно было бы сразу
// занести в переменную buttons, но так код будет понятнее.
int ledLevel; // Яркость диода
int ledColor; // Цвет диода, битовая маска - 00000RGB.
// Мы же спим - поэтому будем проверять статус кнопок редко - раз в 0.3 с.
delay(300);
t = millis();
// Не забываем, что у нас логика инвертирована, и нажатая кнопка - это LOW.
btnDotState = getButtonState((buttonsPrev & BUTTON_DOT_MASK) ? LOW : HIGH, BUTTON_DOT, &timeDotDebouncing, t);
btnTireState = getButtonState((buttonsPrev & BUTTON_TIRE_MASK) ? LOW : HIGH, BUTTON_TIRE, &timeTireDebouncing, t);
buttons = ((btnDotState == LOW) ? BUTTON_DOT_MASK : 0) | ((btnTireState == LOW) ? BUTTON_TIRE_MASK : 0);
if (buttons != 0) {
if (buttonsPrev == 0) {
timePress = t;
}
// Определим, достаточно ли долго была нажата кнопка для выхода из спячки.
if (!flagWakeUp && ((t - timePress) >= WAKEUP_TIME)) {
flagWakeUp = true;
}
} else {
if (buttonsPrev != 0) {
timeRelease = t;
}
if (flagWakeUp) {
// Просыпаемся.
flagWakeUp = false;
mode = TYPING_MODE;
Serial.println("\nTYPING_MODE\n");
return;
}
}
// Помигаем светодиодом.
if (flagWakeUp) {
// Зажжем цвет, соответствующий текущей раскладке.
if (currentLayout == layoutCyrillic) {
ledColor = COLOR_CYRILLIC_LAYOUT;
} else {
ledColor = COLOR_LATIN_LAYOUT;
}
ledLevel = BRIGHTNESS_TYPING_TIRE;
} else {
ledColor = COLOR_SLEEP_MODE;
ledLevel = (ledLevelSleepCounter == 0) ? BRIGHTNESS_SLEEP_LOW : BRIGHTNESS_SLEEP_HIGH;
ledLevelSleepCounter = 1-ledLevelSleepCounter;
}
setLed(ledColor, ledLevel);
buttonsPrev = buttons;
}
//================================================================================================================
// Главный цикл.
void loop() {
switch(mode) {
case TYPING_MODE:
typingLoop();
break;
case SLEEP_MODE:
sleepLoop();
break;
}
}
Voila! Что умеет наш девайс? Левая кнопка выдает точку. Правая — тире. При этом диод вспыхивает в такт нажимаемым кнопкам (для тире — ярче, чем для точки). Если нажать левую и, не отпуская ее, правую — переключимся на кириллицу (зеленый цвет диода). А нажав правую и, не отпуская ее, левую — на латынь (желтый цвет). Если в течение минуты ни одна кнопка не нажималась, девайс передет в спящий режим, а диод начнет мигать фиолетовым.
Теоретически клавиатура готова. Но на практике не слишком-то удобно щелкать кнопками, расположенными на макетной плате. Попробуем придать нашей конструкции более функциональный вид.
В качестве корпуса я взяла старый высохший фломастер для надписей на компакт-дисках. В следующий раз я выберу более вместительный корпус — очень уж неудобно в такой узкой трубочке протягивать кучу проводов. А у нас их получится целых 10: 4 от светодиода и по 3 от каждой кнопки. Припаяем к каждой кнопке и диоду необходимые провода и резисторы:

(кажется, на этой фотографии я перепутала провода для напря��ения и провода для земли — к счастью, ничего не сгорело, но пришлось все перепаять)
Можно, конечно, было соптимизировать, спаяв внутри вместе 3 «напряжения» и 2 «земли», но тогда смонтировать все это внутри фломастера стало бы сложнее. Кроме того, спаяв вместе кнопки и диод, я бы не смогла экспериментировать с различными вариантами корпусов и размещением кнопок и диода. Поэтому я припала к катодам светодиода по 330-омному резистору и проводок соответствующего цвета (R — оранжевый, G — зеленый, B — синий), а к аноду — оранжево-белый. Так же поступила и с кнопками, припаяв к каждой по 10-килоомному сопротивлению и по 3 провода — для земли (коричневый), питания (рыже-белый) и для подключения к выводам Arduino. Припаивая провода, стоит уделить внимание, какой цвет куда должен быть подсоединен, чтобы, глядя на выходящий из корпуса десяток проводов, не гадать, какой — к чему. Оголенные места проводов я обмотала скотчем. Хотя, видимо, грамотнее было бы изолировать их с помощью термоусадочной трубочки, входящей в набор «Seeeduino Catalyst Pack». Нужно будет расспросить об этом знающих людей :)
Перед тем, как поместить кнопки и диод в корпус, я убедилась, что спаянные детали работают:

Все в сборе:

Пожалуй, получившийся девайс можно назвать бета-версией. Функционал практически полностью реализован, осталось превратить поделку в законченное устройство.
Для следующей версии морзе-клавиатуры я собираюсь подобрать более удобный корпус, в котором уместится вся электроника (нужно будет перейти на более компактную версию Arduino), заменить пищалку вибро-моторчиком и, возможно, дополнить клавиатуру жк-экранчиком, на который можно будет выводить подсказки.
