Introducing Arduino, Part 3. Morse Keyboard: Beta

    In early February, I tried to assemble a Morse keyboard based on the Arduino “radio designer”. It turned out to be a fully functional prototype with a single button, by clicking which you can “generate” dots and dashes - from which the microcontroller will collect letters and send them to a computer. The device (if you can call the device with half a dozen details on the breadboard) turned out to be quite workable. But for practical use unsuitable, so I was going to improve the design. And here is what I did.



    First of all, in the new version of the Morse keyboard, it would be worth refusing to enter points and dashes with one button. One-touch input is the need to adjust your print speed to the time intervals prescribed in the program so that the user's dots and dashes “fall” into the dots and dashes of the keyboard. And to enter dash-points is much more convenient, without thinking about the duration of pressing. Radio operators and telegraphists came to this option long ago, switching to similar keys:



    So, we will use two buttons - one for points, the second for dashes. At the same time, we replace the uninformative single-color LED with an RGB diode. Color will inform the user about the current layout. In addition, it would be worth supplementing the device with feedback tactile communication - for example, through a vibration motor. However, as it turned out during the operation, the vibromotor must be connected through a special chip - the motor control driver . Therefore, while I replaced the vibromotor with a piezo speaker, which 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, you could get by with much weaker variations of the microcontroller)
    2)Development board
    3) 2 buttons
    4) RGB LED with a common anode
    5) 2 resistors 10 kOhm
    6) 3 resistors 330 Ohms
    7) Piezo speaker ( see Piezo Buzzer in the picture below )
    8) dozens of wires for connecting parts on the breadboard

    To create an alpha version, I additionally needed:
    9) Half a meter of twisted pair cable. As it turned out, wiring from a twisted pair is quite suitable as connecting wires for a breadboard.
    10) Old non-writing felt-tip pen for inscriptions on compact disks.
    11) Soldering iron, solder, electrical tape.

    How to connect an RGB LED.

    In the previous version of the Morse keyboard, we used a simple single-color LED, with one anode and one cathode. It was not difficult to connect it: we connect the anode 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 little about the connection (thanks to Arduino guru who gave me the right advice!). The essence of the solution turned out to be the use of inverted logic (perhaps the term is not entirely correct - I will be grateful for the correction). In the previous version, the LED was on when voltage was applied to the output of the microcontroller (MK), and turned off when there was no voltage. In the new version, everything will be the other way around. In the absence of voltage at the output, the diode will burn, and if present, it will go out. To implement this ingenious circuit, we connect the cathode of the diode through the resistance to the terminals of the processor. And the anode - connect to + 5V. Now, if there is + 5V at the MK output, there will be no current through the diode, and if there is no voltage, the current will flow and the LED will light. Naturally, when writing a program, we need to remember about inverted logic.

    I was advisedapply the inverted logic when connecting the buttons - because, taking into account the possibility of a short circuit, it is safer to pull the wire with the ground than with the voltage. Honestly, the logic is not entirely clear to me - after all, the electrons "flow" not from the positive, but from the negative output. Having decided 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 will read HIGH from the output connected to it, and when the button is pressed - LOW.

    Why do we need a piezo tweeter. When typing dots and dashes, I would like, without looking at the screen, to receive confirmation that the typed letter is accepted and sent to the computer. Otherwise, during quick typing, after making an insufficient pause between letters, we can, for example, send a letter P (* - *) to the computer instead of the characters AE (* - *). If the user receives a signal when sending each letter, he will make fewer mistakes. Of course, instead of a "tweeter" it would be worth using a vibro motor, but a tweeter is easier to connect. And I’ll deal with vibra in the next part of the morse epic. So for now, instead of vibration, each character sent by the keyboard will be accompanied by a short sound.

    Let's assemble the diagram:





    I drew the diagram in the Fritzing program, but I couldn’t find an RGB LED for it anywhere. Therefore, I myself assembled this “detail” by gluing red, green and blue LEDs. When connecting a diode, it is worth looking at its specification in order to understand where which leg is. It will be useful to test the diode by connecting a common anode to the plus of the battery, and each of the cathodes - alternately to its minus. To test my LED, I used two series-connected one-and-a-half-volt (total 3 volts) AA batteries from the camera.

    We will compile and load the code (it has been commented out in great detail, so I will just give a listing):

    // ================================================= =================================================== ===============
    // Button settings.
    // ================================================= =================================================== ===============
    // The dot button 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 store the current and previous state of the keys in the form of bit masks.
    #define BUTTON_DOT_MASK 1
    #define BUTTON_TIRE_MASK 2
    // Using these variables, we pinpoint when the button was last pressed and released. Any. Therefore
    // time, we determine whether the input of the current character is completed and whether it is time to go into sleep mode.
    unsigned long int timeRelease, timePress;
    // Changing the state of the button for a time less than 0.03 s will be considered a bounce and ignored.
    #define DEBOUNCING_TIME 30
    // We will save time in these variables for filtering contact bounce.
    unsigned long int timeDotDebouncing;
    unsigned long int timeTireDebouncing;
    // The maximum pause between dots and dashes in the letter is 0.4 seconds.
    // If the pause is longer, we consider the input of the letter completed and proceed to enter the next letter.
    #define DELIMITER_TIME 500
    // If the button has not been pressed for more than a minute, go to sleep mode.
    #define SLEEP_TIME 60000
    // You can exit sleep mode by holding down any button for 1 second.
    #define WAKEUP_TIME 2000
    // To switch the layout to the Cyrillic alphabet, press the dot button and, without releasing it, press the dash button. 
    // To switch the layout to Latin, press the right dash button and, without releasing it, press the dot button. 
    // ================================================= =================================================== ===============
    // Settings for the RGB LED.
    // ================================================= =================================================== ===============
    // For feedback we will use the RGB LED:
    #define LED_R 11
    #define LED_G 10
    #define LED_B 9
    // We will set the colors of the diode as a number-bit mask: 00000RGB, recall the colors in the good old EGA and Yamaha MSX.
    // Seven colors (black does not count) we have more than enough.
    #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
    // Cyrillic - green, Latin - yellow, sleeping mode - blinking purple.
    #define COLOR_CYRILLIC_LAYOUT COLOR_GREEN
    #define COLOR_LATIN_LAYOUT COLOR_YELLOW
    #define COLOR_SLEEP_MODE COLOR_MAGENTA
    // Blinking brightness for print mode and sleep mode. Do not forget that our logic is inverted
    // and 0 means maximum brightness, and 255 - off LED.
    #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)
    * /
    // ================================================= =================================================== ===============
    // Piezo speaker settings.
    // ================================================= =================================================== ===============
    #define PIEZO 12
    byte piezoData;
    unsigned long int piezoShutUpTime;
    // ================================================= =================================================== ===============
    // Morse code.
    // ================================================= =================================================== ===============
    // With these symbols we will designate points and dashes.
    #define MORSE_DOT '*'
    #define MORSE_TIRE '-'
    // A dot or dash has not been entered yet. 
    #define MORSE_EMPTY 0
    // This is to block the entry of dots / dashes when changing layouts or exiting sleep mode.
    #define MORSE_LOCKED '!'
    // The maximum length of the Morse code symbol (in points and dashes)
    #define MAX_MORSE_SYMBOL_LENGTH 8
    // Buffer for writing morse symbol.
    byte morseSymbol [MAX_MORSE_SYMBOL_LENGTH];
    unsigned int morseSymbolLen;
    byte newMorseSignal; // The newly entered signal is a dot or dash.
    // Morse code table. The nth code element corresponds to the nth character of the layout.
    char * code [] = {
      "* -", "- ***", "* -", "- *", "- **", "*", "*** -", "- **", "* * "," * --- ",
      "- * -", "* - **", "-", "- *", "---", "* - *", "* - *", "***", "- "," ** - ",
      "** - *", "****", "- * - *", "--- *", "----", "- * -", "- * -", " - ** - "," ** - ** "," ** - ",
      "* - * -",
      "* ----", "** ---", "*** -", "**** -", "*****", "- ****", "- - *** "," --- ** "," ---- * "," ----- ",
      "......", "* - * - * -", "--- ***", "- * - * -", "- * - * -", "* ---- * "," * - ** - * "," - **** - "," - ** - * "," ** - ** "," - ** - ",
      "- *** -", "********", "* - * - *", "** - * -",
      ""
    };
    // Cyrillic layout.
    char * layoutCyrillic [] = {
      "a", "b", "c", "g", "d", "e", "g", "s", "and", "y",
      “k”, “l”, “m”, “n”, “o”, “p”, “p”, “s”, “t”, “y”,
      "f", "x", "c", "h", "w", "u", "s", "b", "e", "y",
      "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
    int mode;
    boolean flagWakeUp; // This flag will be used to exit sleep mode.
    byte ledLevelSleepCounter; // Brightness switch for blinking 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,
      // LED is on full brightness = LOW, off = HIGH. Turn off 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 we have inverted logic and 0 is the brightest
    // light, and 255 is the off 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);
      }
    }
    // ================================================= =================================================== ===============
    // Work with the piezo speaker
    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;
    }
    // ================================================= =================================================== ===============
    // Read the state of the button, taking into account 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) {
            // Note the time that the button is pressed - so as not to confuse the rattling of contacts with the press.
            * timeDebouncing = currentTime;
            // While we do not perceive pressing, considering it a bounce of contacts.
            btnState = LOW;
          } else {
            if ((currentTime - * timeDebouncing) <DEBOUNCING_TIME) {
              // While we do not perceive pressing, considering it a bounce of contacts.
              btnState = LOW;
            } else {
              // This is not a bounce of contacts, this is a real button press.
              btnState = HIGH;
              * timeDebouncing = 0;
            }
          }
        } else {
          * timeDebouncing = 0;
        }
      } else {
        if (btnPrevState == HIGH) {
          if (* timeDebouncing == 0) {
            // Note the time that the button is pressed - so as not to confuse the rattling of contacts with the press.
            * timeDebouncing = currentTime;
            // While we do not perceive letting go, considering it a bounce of contacts.
            btnState = HIGH;
          } else {
            if ((currentTime - * timeDebouncing) <DEBOUNCING_TIME) {
              // While we do not perceive letting go, considering it a bounce of contacts.
              btnState = HIGH;
            } else {
              // This is not a rattling of contacts, this is a real push of a button.
              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 is -1;
            break;
          }
        }
        if ((j! = -1) && (j == morseSymbolLen) && (code [i] [j] == '\ 0')) {
          // The character from the Morse code table corresponds to the character entered.
          // Send the character to the computer.
          Serial.print (currentLayout [i]);
          morseSymbolLen = 0;
          return
        }
      }
      // A character was not found in the table. Print an unrecognized character.
      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; // We will use these variables for time measurements.
      int btnDotState, btnTireState; // In these variables we consider the state of the buttons. In principle, they could be immediately
                                      // put buttons in the variable, but this will make the code clearer.
      int ledLevel; // Diode brightness
      int ledColor; // Diode color, 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, dash, or switch the layout.
        // If the pause is longer than SLEEP_TIME, we will go into sleep mode.
        // If the pause is longer than DELIMITER_TIME - send the character.
        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) {
            // The dot is pressed.
            newMorseSignal = MORSE_DOT;
            timePress = t;
          }
          break; // BUTTON_DOT_MASK
        case BUTTON_TIRE_MASK:
          if (newMorseSignal == MORSE_EMPTY) {
            // Dash pressed.
            newMorseSignal = MORSE_TIRE;
            timePress = t;
          }
          break; // BUTTON_DOT_MASK
        case BUTTON_DOT_MASK | BUTTON_TIRE_MASK:
          // Both buttons pressed. Change the layout.
          switch (buttonsPrev) {
          case 0: // It is unlikely that both buttons are pressed at the same time, but in this case, switch to the Cyrillic alphabet.
          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
        }
      }
      // Take care of 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; // We will use these variables for time measurements.
      int btnDotState, btnTireState; // In these variables we consider the state of the buttons. In principle, they could be immediately
                                      // put buttons in the variable, but this will make the code clearer.
      int ledLevel; // Diode brightness
      int ledColor; // Diode color, bit mask - 00000RGB.
      // We are sleeping - therefore, we will rarely check the status of 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 if 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
        }
      }
      // Blink the LED.
      if (flagWakeUp) {
        // Set 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;
    }
    // ================================================= =================================================== ===============
    // Main loop.
    void loop () {
      switch (mode) {
        case TYPING_MODE:
          typingLoop ();
          break;
        case SLEEP_MODE:
          sleepLoop ();
          break;
      }
    }
    


    Voila! What can our device do? The left button gives a point. The right one is a dash. In this case, the diode flashes to the beat of the pressed buttons (for a dash - brighter than for a point). If you press the left and, without releasing it, the right, we switch to the Cyrillic alphabet (green color of the diode). And pressing the right and, without releasing it, the left - in Latin (yellow). If no button is pressed for a minute, the device will go into sleep mode, and the diode will start blinking purple.

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

    As a case, I took an old dried-up felt-tip pen for inscriptions on CDs. Next time I will choose a more capacious case - it’s 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. Solder the necessary wires and resistors to each button and diode:


    (it seems that in this photo I mixed up the wires for voltage and the wires for ground - fortunately, nothing burned out, but I had to solder everything)

    It was possible, of course, to optimize it by soldering together 3 “voltages” and 2 “earths” together, but then it would be more difficult to mount all this inside the felt-tip pen. In addition, by soldering the buttons and the diode together, I would not be able to experiment with various housing options and the placement of the buttons and the diode. Therefore, I fell to the cathodes of the LED through the 330-ohm resistor and the wires of the corresponding color (R - orange, G - green, B - blue), and to the anode - orange-white. The same thing happened with the buttons, soldered to each of 10-kilo-ohm resistance and 3 wires - for ground (brown), power (red-white) and for connecting to Arduino pins. Soldering wires, it is worth paying attention to what color should be connected to, so that, looking at a dozen wires emerging from the case, not to guess which one to what. I wrapped the exposed wires with tape. Although apparently it would be more competent to isolate them using a heat-shrink tubing included in the Seeeduino Catalyst Pack. You will need to ask knowledgeable people about this :)

    Before putting the buttons and the diode into the case, I made sure that the soldered parts work:



    Everything is assembled:



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

    For the next version of the morse keyboard, I’m going to pick up a more convenient case in which all the electronics will fit (you will need to switch to a more compact version of the Arduino), replace the squeaker with a vibration motor and, possibly, supplement the keyboard with an LCD screen, on which you can display prompts .

    Also popular now: