📜 ⬆️ ⬇️

Getting to know the Arduino, part 3. Morse keyboard: a beta version

In early February, I tried to assemble a Morse keyboard based on the Arduino “radio constructor”. It turned out quite workable prototype with a single button, pressing which you can "generate" points and dashes - from which the microcontroller will collect the letters and send them to the computer. The device (if you can call a device with half a dozen parts on a breadboard) turned out to be quite workable. But for practical use of little use, so I was going to improve the design. And that's what I did.



First of all, in the new version of the Morse keyboard, it would be worth refusing to enter points and a dash with one button. One-touch input is the need to adjust your typing speed to the time intervals prescribed in the program so that the user's points and dashes “hit” the dots and dashes of the keyboard. Yes, and enter the point-dash where it is more convenient, without thinking about the duration of pressing. Radio operators and telegraph operators came to this version a long time ago, going to these keys:


')
So, we will use two buttons - one for points, the second for a dash. At the same time replace the uninformative single-color LED on the RGB-diode. Color can inform the user about the current layout. In addition, it would be worth complementing the device with a tactile feedback - for example, through a vibration motor. However, as it turned out in the process of work, the vibration motor needs to be connected through a special chip - the motor control driver . Therefore, while I replaced the vibration motor with a piezo dynamics speaker that can be connected to the Arduino directly, without additional details.

What I needed to work:



1) Freeduino 2009 - a complete analogue of the Arduino Duemilanove (however, it was possible to get along with much weaker variations of the microcontroller)
2) Breadboard
3) 2 buttons
4) RGB LED with common anode
5) 2 10 kΩ resistors
6) 3 330 ohm resistors
7) Piezo speaker ( pictured by link is Piezo Buzzer)
8) a dozen wiring for connecting parts on the breadboard

To create an alpha version I additionally needed:
9) Half a meter of cable "twisted pair". As it turned out, the wiring from the "twisted pair" is quite suitable as connecting wires for the layout.
10) Old non-writing marker for CD writing.
11) Soldering iron, solder, electrical tape.

How to connect RGB-LED.

In the previous version of the morse keyboard, we used a simple monochrome LED, with one anode and one cathode. It was easy to connect it: the anode is connected to the output of the microcontroller, the cathode - through a resistor to the ground. Perhaps if I had an RGB diode with a common cathode and three anodes, I would do the same. But the Seeeduino Catalyst Pack included RGB diodes with a common anode, so I had to think a bit about connecting (thanks to the Arduino gurus who gave me the right advice !). The essence of the solution was to use inverted logic (perhaps the term is not entirely correct - I will be grateful for the amendment). In the previous version, the LED was on when voltage was applied to the output of the microcontroller (MK), and the power was off when there was no voltage. The new version will be the opposite. If there is no voltage at the output, the diode will burn, and if it does, it will go out. To implement this ingenious scheme, we connect the cathodes of the diode through the resistance to the processor terminals. And the anode is connected to + 5V. Now, if there is +5 V at the MK pin, there will be no current through the diode, and if there is no voltage, the current will flow and light the LED. Naturally, when writing a program, we need to remember the inverted logic.

I was advised to use the same inverted logic when connecting buttons - because, given the possibility of a short circuit, it is safer to pull the wire with the ground than with the voltage. To be honest, the logic is not quite clear to me - after all, electrons “flow” not from a positive, but from a negative conclusion. Deciding to deal with this later, I connected the buttons a little differently than in the previous version. Now, when the button is released, the MK output connected to it will read HIGH, and when the button is pressed, it will read LOW.

Why do we need piezo-squeaker. When typing points and dashes, I would like, without looking at the screen, to receive confirmation that the typed letter was accepted and sent to the laptop. Otherwise, with fast typing, having made an insufficient pause between letters, we can, for example, instead of AE characters (* - *) send a letter P (* - *) to a computer. If the user receives a signal when sending each letter, he will make fewer errors. Of course, instead of a “tweeter,” it would be worth using a vibration motor, but it is easier to connect a squeaker. And I will deal with the vibra in the next part of the Morse-epic. So for now, instead of vibrating, each character sent by the keyboard will be accompanied by a short sound.

Let's collect the scheme:





I drew the scheme in the Fritzing program, but nowhere could I find an RGB LED for it. Therefore, I independently assembled this “part” by gluing together red, green and blue LEDs. When connecting a diode, it is worth looking at its specification to see where the leg is. It will also be useful to test the diode by connecting a common anode to the battery plus, and each of the cathodes is alternately to its minus. To test my LED, I used two semi-volt-connected (total 3 volts) AA-batteries from the camera.

Let's compile and load the code (it is commented out in detail, so just give the listing):

 / / ===================================== ==================================== === ============== // Button settings.  // =============================================== ==================================== === ============== // The button-point will be on the left.  #define BUTTON_DOT 6 // The dash button is on the right.  #define BUTTON_TIRE 7 int buttons, buttonsPrev;  // Here we will keep the current and previous state of the keys in the form of bit masks.  #define BUTTON_DOT_MASK 1 #define BUTTON_TIRE_MASK 2 // With these variables, we detect when the button was last pressed and released.  Any.  From this // time, we determine whether the input of the current character is complete and whether it is time to go into sleep mode.  unsigned long int timeRelease, timePress;  // Change the state of the button to a time less than 0.03s will be considered bounce and ignored.  #define DEBOUNCING_TIME 30 // In these variables we will save time to filter out contact bounce.  unsigned long int timeDotDebouncing;  unsigned long int timeTireDebouncing;  // Maximum pause between points and a dash in a letter - 0.4 seconds.  // If the pause is longer, we consider the input of the letter completed and proceed to the next letter.  #define DELIMITER_TIME 500 // If the button is not pressed for more than a minute, go to sleep mode.  #define SLEEP_TIME 60000 // Exit from sleep mode by holding down any button for 1 second.  #define WAKEUP_TIME 2000 // To switch the layout to Cyrillic, press the button-point and, without releasing it, press the button-dash.  // To switch the layout to Latin, press the right button-dash and, without releasing it, press the button-point.  // =============================================== ================================================= ============== // RGB-LED settings.  // =============================================== ================================================= ============== // For feedback, we will use the RGB-LED: # define LED_R 11 # define LED_G 10 # define LED_B 9 // The diode colors will be specified as a number-bit mask: 00000RGB, remember the colors in the good old EGA and Yamaha MSX.  // Seven colors (black does not count) are more than enough for us.  #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 Cyrillic 7 // - green, Latin - Yellow sleeping mode indicator - purple flashing.  #define COLOR_CYRILLIC_LAYOUT COLOR_GREEN #define COLOR_LATIN_LAYOUT COLOR_YELLOW #define COLOR_SLEEP_MODE COLOR_MAGENTA // Flashing brightness for print mode and sleep mode.  Do not forget that our logic is inverted // and 0 means the maximum brightness, and 255 is an off LED.  #define BRIGHTNESS_TYPING_LOW (255-1) #define BRIGHTNESS_TYPING_aaaaaaaa (255-7) #define BRIGHTNESS_TYPING_TIRE (255-15) #define BRIGHTNESS_SLEEP_LOW (255-0) #define BRIGHTNESS_SLEEP_HEktha of the Waktha (575) deptineLive_LOW (255-0) #define BRIGHTNESS_TYPING_DOT (255-128) #define BRIGHTNESS_TYPING_TIRE (255-255) #define BRIGHTNESS_SLEEP_LOW (255-8) #define BRIGHTNESS_SLEEP_HIGH (255-128) * / // ========== ================================================= ================================================ // Piezo dynamics settings.  // =============================================== ================================================= ============== #define PIEZO 12 byte piezoData;  unsigned long int piezoShutUpTime;  // =============================================== ================================================= ============== // Morse Code.  // =============================================== ================================================= ============== // These symbols will denote dots and dashes.  #define MORSE_DOT '*' #define MORSE_TIRE '-' // A point or a dash has not been entered yet.  #define MORSE_EMPTY 0 // This is for blocking the entry of points / dashes when changing the layout or resuming from sleep mode.  #define MORSE_LOCKED '!'  // Maximum length of Morse code symbol (in dots and dashes) #define MAX_MORSE_SYMBOL_LENGTH 8 // Buffer for recording Morse character.  byte morseSymbol [MAX_MORSE_SYMBOL_LENGTH];  unsigned int morseSymbolLen;  byte newMorseSignal;  // New signal entered - point or dash.  // Morse code table.  The nth element of the code corresponds to the nth character of the layout.  char * code [] = {"* -", "- ***", "* -", "- *", "- **", "*", "*** -", "- - ** "," ** "," * --- "," - * - "," * - ** "," - "," - * "," --- "," * - * "," * - * "," *** "," - "," ** - "," ** - * "," **** "," - * - * "," --- * "," ---- "," - * - "," - * - "," - ** - "," ** - ** "," ** - "," * - * - "," * ---- "," ** --- "," *** - "," **** - "," ***** "," - **** " , "- ***", "--- **", "---- *", "-----", "......", "* - * - * -" , "--- ***", "- * - * -", "- * - * -", "* ---- *", "* - ** - *", "- *** * - "," - ** - * "," ** - ** "," - ** - "," - *** - "," ******** "," * - * - * "," ** - * - "," "};  // Cyrillic layout.  char * layoutCyrillic [] = {"a", "b", "c", "g", "d", "e", "g", "g", "u", "d", "k" , "l", "m", "n", "o", "p", "p", "s", "t", "y", "f", "x", "c", " h "," sh "," u "," s ","  "," e "," yu "," i "," 1 "," 2 "," 3 "," 4 "," 5 " , "6", "7", "8", "9", "0", ".", ",", ":", ";", "(", "\ '", "\" " , "-", "/", "?", "!", "* DELIMITER *", "* ERR *", "@", "* END *", "" "; // Latin layout. 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 ; // ============================================== ================================================= =============== // Modes of operation. // ============================ ================================================= ================================== #define TYPING_MODE 0 #define SLEEP_MODE 1 in  t mode; boolean flagWakeUp; // This flag will be used to exit sleep mode.  byte ledLevelSleepCounter;  // Brightness switch for flashing diode in sleep mode.  // =============================================== ================================================= ============== void setup () {Serial.begin (9600);  pinMode (LED_R, OUTPUT);  pinMode (LED_G, OUTPUT);  pinMode (LED_B, OUTPUT);  // Both the buttons and the LED work with inverted logic: the button is pressed = LOW, released = HIGH, // the LED is on full brightness = LOW, turned off = HIGH.  Extinguish the LEDs: 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;  } // ============================================== ================================================= =============== // Light the LED with the desired color and brightness.  Do not forget that our logic is inverted and 0 is the brightest // light, and 255 is the extinguished LED.  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);  }} // ============================================= ================================================= ================ // Working with void doPiezo piezo speaker (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;  } // ============================================== ================================================= =============== // Read the state of the button, taking into account the possible bounce of contacts.  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) {// Mark the time that the button will be pressed - so as not to confuse the bounce of contacts with pressing.  * timeDebouncing = currentTime;  // While we do not perceive pressing, considering it as a bounce of contacts.  btnState = LOW;  } else {if ((currentTime - * timeDebouncing) <DEBOUNCING_TIME) {// While we do not perceive the click, considering it as a contact bounce.  btnState = LOW;  } else {// This is not the bounce of contacts, this is the real button click.  btnState = HIGH;  * timeDebouncing = 0;  }}} else {* timeDebouncing = 0;  }} else {if (btnPrevState == HIGH) {if (* timeDebouncing == 0) {// Mark the time that the button will be pressed - so as not to confuse the bounce of contacts with pressing.  * timeDebouncing = currentTime;  // While we do not perceive the release, considering it as a bounce of contacts.  btnState = HIGH;  } else {if ((currentTime - * timeDebouncing) <DEBOUNCING_TIME) {// While we do not perceive release, considering it as a bounce of contacts.  btnState = HIGH;  } else {// This is not a bounce of contacts, this is a real button out.  btnState = LOW;  * timeDebouncing = 0;  }}} else {* timeDebouncing = 0;  }} return btnState;  } // ============================================== ================================================= =============== // Send the entered character to the computer.  void sendMorseSymbol () {int i, j;  if (morseSymbolLen <1) {return;  } playPiezo (50, millis ());  for (i = 0; code [i] [0]! = '\ 0'; i ++) {// Compare the entered character with the characters from the Morse code table.  for (j = 0; (j <morseSymbolLen) && (code [i] [j]! = '\ 0'); j ++) {if (code [i] [j]! = morseSymbol [j]) {j = -one;  break;  }} if ((j! = -1) && (j == morseSymbolLen) && (code [i] [j] == '\ 0')) {// The symbol from the Morse code table corresponds to the entered character.  // Send the character to the computer.  Serial.print (currentLayout [i]);  morseSymbolLen = 0;  return;  }} // Character not found in the table.  Print the unrecognized symbol.  Serial.print ("[");  for (i = 0; i <morseSymbolLen; i ++) {Serial.print (morseSymbol [i]);  } Serial.print ("]");  morseSymbolLen = 0;  } // ============================================== ================================================= =============== // Print mode void typingLoop () {unsigned long int t, dt;  // These variables will be used for time measurements.  int btnDotState, btnTireState;  // In these variables, count the states of the buttons.  In principle, they could immediately be entered into the buttons variable, but the code will be clearer.  int ledLevel;  // Diode brightness int ledColor;  // Color of the diode, the bit mask - 00000RGB.  // analogWrite (PIEZO, 0);  t = millis ();  // Do not forget that our logic is inverted, and the pressed button is 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) {// Both buttons are released, you can add the entered point, a dash or switch the layout.  // If the pause is longer than SLEEP_TIME - go to sleep mode.  // If the pause is longer DELIMITER_TIME - send the symbol.  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) {// Pressed point.  newMorseSignal = MORSE_DOT;  timePress = t;  } break;  // BUTTON_DOT_MASK case BUTTON_TIRE_MASK: if (newMorseSignal == MORSE_EMPTY) {// Press the "dash".  newMorseSignal = MORSE_TIRE;  timePress = t;  } break;  // BUTTON_DOT_MASK case BUTTON_DOT_MASK |  BUTTON_TIRE_MASK: // Both buttons are pressed.  Change the layout.  switch (buttonsPrev) {case 0: // It is unlikely that both buttons are pressed simultaneously, but in this case we will switch to Cyrillic.  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}} // Let's do the LED.  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);  } // ============================================== ================================================= =============== // Hibernate void sleepLoop () {unsigned long int t, dt;  // These variables will be used for time measurements.  int btnDotState, btnTireState;  // In these variables, count the states of the buttons.  In principle, they could immediately be entered into the buttons variable, but the code will be clearer.  int ledLevel;  // Diode brightness int ledColor;  // Color of the diode, the bit mask - 00000RGB.  // We are sleeping - so we will rarely check the status of the buttons - once every 0.3 s.  delay (300);  t = millis ();  // Do not forget that our logic is inverted, and the pressed button is 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;  } // Determine whether the button was pressed long enough to exit hibernation.  if (! flagWakeUp && ((t - timePress)> = WAKEUP_TIME)) {flagWakeUp = true;  }} else {if (buttonsPrev! = 0) {timeRelease = t;  } if (flagWakeUp) {// Wake up.  flagWakeUp = false;  mode = TYPING_MODE;  Serial.println ("\ nTYPING_MODE \ n");  return;  }} // We blink the LED.  if (flagWakeUp) {// Light the color corresponding to the current layout.  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;  } // ============================================== ================================================= =============== // The main loop.  void loop () {switch (mode) {case TYPING_MODE: typingLoop ();  break;  case SLEEP_MODE: sleepLoop ();  break;  }} 


Voila! What can our device? The left button gives a point. Right - dash. In this case, the diode flashes in time to the pushed buttons (for a dash - brighter than for a point). If you press the left and, without releasing it, the right - switch to Cyrillic (green color of the diode). And pressing the right and, without releasing it, the left - in Latin (yellow). If no button was pressed for a minute, the device will send to sleep mode, and the diode will begin to flash purple.

Theoretically, the keyboard is ready. But in practice it is not very convenient to click the buttons located on the breadboard. Let's try to give our design a more functional look.

As a corpus, I took an old, dried-up felt-tip pen for writing on CDs. Next time I will choose a more capacious case - it is very inconvenient to stretch a bunch of wires in such a narrow tube. And we get them as much as 10: 4 from the LED and 3 from each button. We solder the necessary wires and resistors to each button and diode:


(it seems that in this photo I messed up the wires for the voltage and the wires for the earth - fortunately, nothing burned, but I had to rewire everything)

Of course, it was possible to optimize by welding together 3 “voltages” and 2 “earths” inside, but then it would become more difficult to mount all this inside a felt pen. In addition, having soldered the buttons and the diode together, I would not have been able to experiment with various housing options and the placement of the buttons and the diode. Therefore, I clung to the cathodes of the LED for a 330-ohm resistor and the wiring of the corresponding color (R - orange, G - green, B - blue), and to the anode - orange-white. It did the same with the buttons, soldering each with 10-kilo-ohm resistance and 3 wires for the ground (brown), power (red-white) and for connecting to the Arduino terminals. When soldering wires, you should pay attention to which color should be connected to, so that, looking at the dozen wires coming out of the case, you won’t guess which one is what. I wrapped the exposed places of the wires with adhesive tape. Although, apparently, it would be more intelligent to isolate them using shrinkable tubing, which is part of the Seeeduino Catalyst Pack. It will be necessary to ask knowledgeable people about it :)

Before you put the buttons and the diode in the case, I made sure that the soldered parts work:



Everyone is here:



Perhaps the resulting device can be called a beta version. The functionality is almost fully implemented, it remains to turn the craft into a complete device.

For the next version of the morse-keyboard, I'm going to pick up a more convenient case in which all the electronics fit (need to upgrade to a more compact version of the Arduino), replace the beeper with a vibrator motor, and maybe add a keyboard with an LCD screen to which you can display hints .

Source: https://habr.com/ru/post/85530/


All Articles