From f103733e13dda9fc557152c20c16c8a92d5b1a50 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Sat, 16 May 2020 10:33:46 -0600 Subject: [PATCH] Add arduino code --- README.md | 6 +- arduino/README.adoc | 147 +++++++++++++++++++++++++++++ arduino/bounce2.cpp | 138 +++++++++++++++++++++++++++ arduino/bounce2.h | 222 ++++++++++++++++++++++++++++++++++++++++++++ arduino/keyer.ino | 102 ++++++++++++++++++++ 5 files changed, 614 insertions(+), 1 deletion(-) create mode 100644 arduino/README.adoc create mode 100644 arduino/bounce2.cpp create mode 100644 arduino/bounce2.h create mode 100644 arduino/keyer.ino diff --git a/README.md b/README.md index 5e31c7f..b741500 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,8 @@ You can use any key on the keyboard, or a mouse button, to key down. -Network jitter is going to make this horrible. +You can also connect a physical key or paddle, +through an Arduino micro running a keyer program. +This program is in the [arduino directory](arduino/), +or [in Arduino Create](https://create.arduino.cc/editor/neale/f94bb765-47bd-4bc4-9cbf-b978f7124bdc). + diff --git a/arduino/README.adoc b/arduino/README.adoc new file mode 100644 index 0000000..82a9c68 --- /dev/null +++ b/arduino/README.adoc @@ -0,0 +1,147 @@ +:Author: neale +:Email: neale@woozle.org +:Date: 2020-May-3 +:Revision: 1 +:License: MIT + += Project: USB Morse Adapter + +This translates Morse key inputs into USB events, +either MIDI or MIDI+Keyboard, +so you can use a computer. + +I use this with an Internet morse code repeater I wrote, +available at https://vail.woozle.org/. + +This project requires an Arduino that can send USB. +I've only used the Micro, +although I have no doubt the Leonardo will work too, +as I've used Leonardos for similar projects. + + +== Step 1: Installation + +This needs the MidiUSB and Keyboard libraries. + +Installing those is documented all over the Internet. +I'm using https://create.arduino.cc/ so this is all magic for me. + + +== Step 2: Assmble the circuit + +Morse code keyers are very simple devices, +they just connect two wires together. +You could use a button if you wanted to, +or even touch wires together. + +The only real complication here is that some browsers +need to get keyboard events instead of musical instrument events. + + +=== Decide what browser you're going to use + +Firefox needs a jumper between pin 9 and ground. +Just take a wire, and connect pin 9 directly to ground. +This puts the Arduino into "keyboard mode", +where it sends a comma for straight key, +and period and slash for iambic. + +If you don't connect pin 9 to ground, +the Arudino only sends MIDI (Musical Instrument Digital Interface) +events, so it looks like you hooked a piano up. +This works great in Chrome, +and lets you switch to other windows while still keying into vail. + +=== Wire up your input device + +Hook a straight key into ground on one side, +pin 10 on the other. +It's okay to leave pin 10 disconnected if you don't have a straight key. + +Hook an iambic paddle in ground in the middle, +pins 11 and 12 on the outside posts. +It's okay to leave pins 11 and 12 disconnected if you don't have a paddle. + +=== Using a headphone jack + +If you prefer, you can wire a headphone jack up to GND, 11, and 12. +GND should be the sleeve, 11 the ring, and 12 the tip. + + o --- 12 + |_| --- 11 + | | --- GND + | | + +Make sure any straight key you plug in has a TS adapter (mono plug): +this will short pin 11 to ground and signal to the Arduino to +go into straight key mode. + +If you plug in your straight key and it looks like DAH is being held down, +it means you need to switch 11 and 12. + + +== Step 3: Load the code + +Upload the code contained in this sketch on to your board. + +== Step 4: Test it out + +Make sure it's plugged in to your computer's USB port. + +If you connected pin 9 to ground, +Open anything where you can type, +type in "hello", and hit the straight key. +You should see a comma after your hello. + +Now you can open https://vail.woozle.org/, +click the "KEY" button once to let the browser know it's okay to make sound, +and you should be able to wail away on your new USB keyer. + + +=== License + +This project is released under an MIT License. + +Copyright © 2020 Neale Pickett +Copyright © 2013 thomasfredericks + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The software is provided "as is", without warranty of any kind, express or +implied, including but not limited to the warranties of merchantability, fitness +for a particular purpose, and noninfringement. In no event shall the authors or +copyright holders be liable for any claim, damages, or other liability, whether +in an action of contract, tort or otherwise, arising from, out of, or in +connection with the software or the use or other dealings in the software. + + +=== Contributing +To contribute to this project please contact neale@woozle.org +https://id.arduino.cc/neale + + +=== BOM + +In addition to a key, some hookup wires, and a USB cable, +you only need an Arduino. + +|=== +| ID | Part name | Part number | Quantity +| A1 | Arduino Micro | ABX00053 | 1 +|=== + + +=== Help + +This document is written in the _AsciiDoc_ format, a markup language to describe documents. +If you need help you can search the http://www.methods.co.nz/asciidoc[AsciiDoc homepage] +or consult the http://powerman.name/doc/asciidoc[AsciiDoc cheatsheet] + diff --git a/arduino/bounce2.cpp b/arduino/bounce2.cpp new file mode 100644 index 0000000..d175805 --- /dev/null +++ b/arduino/bounce2.cpp @@ -0,0 +1,138 @@ +// Please read Bounce2.h for information about the liscence and authors + + +#include "bounce2.h" + + + + +Bounce::Bounce() + : previous_millis(0) + , interval_millis(10) + , state(0) + , pin(0) +{} + +void Bounce::attach(int pin) { + this->pin = pin; + state = 0; + if (readCurrentState()) { + setStateFlag(DEBOUNCED_STATE | UNSTABLE_STATE); + } +#ifdef BOUNCE_LOCK_OUT + previous_millis = 0; +#else + previous_millis = millis(); +#endif +} + +void Bounce::attach(int pin, int mode){ + setPinMode(pin, mode); + this->attach(pin); +} + +void Bounce::interval(uint16_t interval_millis) +{ + this->interval_millis = interval_millis; +} + +bool Bounce::update() +{ + + unsetStateFlag(CHANGED_STATE); +#ifdef BOUNCE_LOCK_OUT + + // Ignore everything if we are locked out + if (millis() - previous_millis >= interval_millis) { + bool currentState = readCurrentState(); + if ( currentState != getStateFlag(DEBOUNCED_STATE) ) { + previous_millis = millis(); + changeState(); + } + } + + +#elif defined BOUNCE_WITH_PROMPT_DETECTION + // Read the state of the switch port into a temporary variable. + bool readState = readCurrentState(); + + + if ( readState != getStateFlag(DEBOUNCED_STATE) ) { + // We have seen a change from the current button state. + + if ( millis() - previous_millis >= interval_millis ) { + // We have passed the time threshold, so a new change of state is allowed. + // set the STATE_CHANGED flag and the new DEBOUNCED_STATE. + // This will be prompt as long as there has been greater than interval_misllis ms since last change of input. + // Otherwise debounced state will not change again until bouncing is stable for the timeout period. + changeState(); + } + } + + // If the readState is different from previous readState, reset the debounce timer - as input is still unstable + // and we want to prevent new button state changes until the previous one has remained stable for the timeout. + if ( readState != getStateFlag(UNSTABLE_STATE) ) { + // Update Unstable Bit to macth readState + toggleStateFlag(UNSTABLE_STATE); + previous_millis = millis(); + } + + +#else + // Read the state of the switch in a temporary variable. + bool currentState = readCurrentState(); + + + // If the reading is different from last reading, reset the debounce counter + if ( currentState != getStateFlag(UNSTABLE_STATE) ) { + previous_millis = millis(); + toggleStateFlag(UNSTABLE_STATE); + } else + if ( millis() - previous_millis >= interval_millis ) { + // We have passed the threshold time, so the input is now stable + // If it is different from last state, set the STATE_CHANGED flag + if (currentState != getStateFlag(DEBOUNCED_STATE) ) { + previous_millis = millis(); + + + changeState(); + } + } + + +#endif + + return changed(); + +} + +// WIP HELD +unsigned long Bounce::previousDuration() { + return durationOfPreviousState; +} + +unsigned long Bounce::duration() { + return (millis() - stateChangeLastTime); +} + +inline void Bounce::changeState() { + toggleStateFlag(DEBOUNCED_STATE); + setStateFlag(CHANGED_STATE) ; + durationOfPreviousState = millis() - stateChangeLastTime; + stateChangeLastTime = millis(); +} + +bool Bounce::read() +{ + return getStateFlag(DEBOUNCED_STATE); +} + +bool Bounce::rose() +{ + return getStateFlag(DEBOUNCED_STATE) && getStateFlag(CHANGED_STATE); +} + +bool Bounce::fell() +{ + return !getStateFlag(DEBOUNCED_STATE) && getStateFlag(CHANGED_STATE); +} diff --git a/arduino/bounce2.h b/arduino/bounce2.h new file mode 100644 index 0000000..14d4c66 --- /dev/null +++ b/arduino/bounce2.h @@ -0,0 +1,222 @@ +/* + The MIT License (MIT) + + Copyright (c) 2013 thomasfredericks + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * + Main code by Thomas O Fredericks (tof@t-o-f.info) + Previous contributions by Eric Lowry, Jim Schimpf and Tom Harkaway + * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/** + * @todo Make Bounce2 more abstract. Split it from the hardware layer. + * @body Remove deboucing code from Bounce2 and make a new Debounce class from that code. + * Bounce2 should extend Debounce. + */ + + +#ifndef Bounce2_h +#define Bounce2_h + +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +// Uncomment the following line for "LOCK-OUT" debounce method +//#define BOUNCE_LOCK_OUT + +// Uncomment the following line for "BOUNCE_WITH_PROMPT_DETECTION" debounce method +//#define BOUNCE_WITH_PROMPT_DETECTION + +#include + +/** + @example bounce.ino + Simple example of the Bounce library that switches the debug LED when a button is pressed. +*/ + +/** + @example change.ino + This example toggles the debug LED (pin 13) on or off when a button on pin 2 is pressed. +*/ + +/** + @example bounce_multiple.ino + Detect the falling edge of multiple buttons. Eight buttons with internal pullups. Toggles a + LED when any button is pressed. Buttons on pins 2,3,4,5,6,7,8,9 +*/ + +/** + @example bounce2buttons.ino + Example of two instances of the Bounce class that switches the debug LED when either one of + the two buttons is pressed. + */ + +static const uint8_t DEBOUNCED_STATE = 0b00000001; +static const uint8_t UNSTABLE_STATE = 0b00000010; +static const uint8_t CHANGED_STATE = 0b00000100; + +/** + The Bounce class. + */ +class Bounce +{ + public: + +/*! + @brief Create an instance of the Bounce class. + + @code + + // Create an instance of the Bounce class. + Bounce() button; + + @endcode +*/ + Bounce(); + + +/*! + @brief Attach to a pin and sets that pin's mode (INPUT, INPUT_PULLUP or OUTPUT). + + @param pin + The pin that is to be debounced. + @param mode + A valid Arduino pin mode (INPUT, INPUT_PULLUP or OUTPUT). +*/ + void attach(int pin, int mode); + + /** + Attach to a pin for advanced users. Only attach the pin this way once you have previously + set it up. Otherwise use attach(int pin, int mode). + */ + void attach(int pin); + + + /** + @brief Sets the debounce interval in milliseconds. + + @param interval_millis + The interval time in milliseconds. + + */ + void interval(uint16_t interval_millis); + + +/*! + @brief Updates the pin's state. + + Because Bounce does not use interrupts, you have to "update" the object before reading its + value and it has to be done as often as possible (that means to include it in your loop()). + Only call update() once per loop(). + + @return True if the pin changed state. +*/ + + bool update(); + + /** + @brief Returns the pin's state (HIGH or LOW). + + @return HIGH or LOW. + */ + bool read(); + + /** + @brief Returns true if pin signal transitions from high to low. + */ + bool fell(); + + /** + @brief Returns true if pin signal transitions from low to high. + */ + bool rose(); + + + /** + @brief Deprecated (i.e. do not use). Included for partial compatibility for programs written + with Bounce version 1 + */ + bool risingEdge() { return rose(); } + /** + @brief Deprecated (i.e. do not use). Included for partial compatibility for programs written + with Bounce version 1 + */ + bool fallingEdge() { return fell(); } + /** + @brief Deprecated (i.e. do not use). Included for partial compatibility for programs written + with Bounce version 1 + */ + Bounce(uint8_t pin, unsigned long interval_millis ) : Bounce() { + attach(pin); + interval(interval_millis); + } + + /** + @brief Returns the duration in milliseconds of the current state. + + Is reset to 0 once the pin rises ( rose() ) or falls ( fell() ). + + @return The duration in milliseconds (unsigned long) of the current state. + */ + + unsigned long duration(); + + /** + @brief Returns the duration in milliseconds of the previous state. + + Takes the values of duration() once the pin changes state. + + @return The duration in milliseconds (unsigned long) of the previous state. + */ + unsigned long previousDuration(); + + protected: + unsigned long previous_millis; + uint16_t interval_millis; + uint8_t state; + uint8_t pin; + unsigned long stateChangeLastTime; + unsigned long durationOfPreviousState; + virtual bool readCurrentState() { return digitalRead(pin); } + virtual void setPinMode(int pin, int mode) { +#if defined(ARDUINO_ARCH_STM32F1) + pinMode(pin, (WiringPinMode)mode); +#else + pinMode(pin, mode); +#endif + } + + private: + inline void changeState(); + inline void setStateFlag(const uint8_t flag) {state |= flag;} + inline void unsetStateFlag(const uint8_t flag) {state &= ~flag;} + inline void toggleStateFlag(const uint8_t flag) {state ^= flag;} + inline bool getStateFlag(const uint8_t flag) {return((state & flag) != 0);} + + public: + bool changed( ) { return getStateFlag(CHANGED_STATE); } + +}; + +#endif diff --git a/arduino/keyer.ino b/arduino/keyer.ino new file mode 100644 index 0000000..86f1ecb --- /dev/null +++ b/arduino/keyer.ino @@ -0,0 +1,102 @@ +// MIDIUSB - Version: Latest +#include +#include +#include "bounce2.h" + +#define DIT_PIN 12 +#define DAH_PIN 11 +#define KEY_PIN 10 +#define KBD_PIN 9 + +#define STRAIGHT_KEY ',' +#define DIT_KEY '.' +#define DAH_KEY '/' + +bool iambic = true; +Bounce kbd = Bounce(); +Bounce dit = Bounce(); +Bounce dah = Bounce(); +Bounce key = Bounce(); + +void setup() { + pinMode(LED_BUILTIN, OUTPUT); + kbd.attach(KBD_PIN, INPUT_PULLUP); + dit.attach(DIT_PIN, INPUT_PULLUP); + dah.attach(DAH_PIN, INPUT_PULLUP); + key.attach(KEY_PIN, INPUT_PULLUP); + + Keyboard.begin(); + + // Straight keys need to wire the dah pin to ground somehow. + // The easiest way I can think of to do this is to use a TS connector + // instead of a TRS connector. + for (int i = 0; i < 16; i++) { + dah.update(); + } + if (dah.read() == LOW) { + iambic = false; + } else { + iambic = true; + } + + digitalWrite(LED_BUILTIN, !iambic); +} + +void midiKey(bool down, uint8_t key) { + midiEventPacket_t event = {down?9:8, down?0x90:0x80, key, 0x7f}; + MidiUSB.sendMIDI(event); + MidiUSB.flush(); +} + +void loop() { + bool keyboard; + + kbd.update(); + keyboard = !kbd.read(); + + // Monitor straight key pin + if (key.update()) { + midiKey(key.fell(), 0); + if (keyboard) { + if (key.fell()) { + Keyboard.press(STRAIGHT_KEY); + } else { + Keyboard.release(STRAIGHT_KEY); + } + } + } + + // Monitor dit pin, which could be straight key if dah was closed on boot + if (dit.update()) { + uint8_t kbdKey, mKey; + if (iambic) { + kbdKey = DIT_KEY; + mKey = 1; + } else { + kbdKey = STRAIGHT_KEY; + mKey = 0; + } + + midiKey(dit.fell(), mKey); + if (keyboard) { + if (dit.fell()) { + Keyboard.press(kbdKey); + } else { + Keyboard.release(kbdKey); + } + } + } + + // Monitor dah pin + if (iambic && dah.update()) { + midiKey(dah.fell(), 2); + + if (keyboard) { + if (dah.fell()) { + Keyboard.press(DAH_KEY); + } else { + Keyboard.release(DAH_KEY); + } + } + } +}