diff --git a/.clangd b/.clangd deleted file mode 100644 index 882b8cd..0000000 --- a/.clangd +++ /dev/null @@ -1,10 +0,0 @@ -CompileFlags: - Add: - - "--include-directory=/opt/arduino/hardware/arduino/avr/cores/arduino" - - "--include-directory=/opt/arduino/hardware/arduino/avr/variants/standard" - - "--include-directory=/opt/arduino/hardware/arduino/avr/libraries/HID/src" - - "--include-directory=/opt/arduino/hardware/tools/avr/avr/include" - - "--include-directory=/opt/arduino/libraries/Keyboard/src" - - "--include-directory=/opt/arduino/libraries/HID/src" - - "--include-directory=/home/dartcatcher/Arduino/libraries/MIDIUSB/src" - \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 84a82d3..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "clangd.arguments": [ - "--enable-config" - ] -} \ No newline at end of file diff --git a/adapter.cpp b/adapter.cpp deleted file mode 100644 index a53946f..0000000 --- a/adapter.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include -#include -#include -#include -#include "keyers.h" -#include "adapter.h" -#include "polybuzzer.h" - -#define MILLISECOND 1 -#define SECOND (1000 * MILLISECOND) - -VailAdapter::VailAdapter(unsigned int PiezoPin) { - this->buzzer = new PolyBuzzer(PiezoPin); -} - -bool VailAdapter::KeyboardMode() { - return this->keyboardMode; -} - -// Send a MIDI Key Event -void VailAdapter::midiKey(uint8_t key, bool down) { - midiEventPacket_t event = {uint8_t(down?9:8), uint8_t(down?0x90:0x80), key, 0x7f}; - MidiUSB.sendMIDI(event); - MidiUSB.flush(); -} - -// Send a keyboard key event -void VailAdapter::keyboardKey(uint8_t key, bool down) { - if (down) { - Keyboard.press(key); - } else { - Keyboard.release(key); - } -} - -// Begin transmitting -void VailAdapter::BeginTx() { - this->buzzer->Note(0, this->txNote); - if (this->keyboardMode) { - this->keyboardKey(KEY_LEFT_CTRL, true); - } else { - this->midiKey(0, true); - } -} - -// Stop transmitting -void VailAdapter::EndTx() { - this->buzzer->NoTone(0); - if (this->keyboardMode) { - this->keyboardKey(KEY_LEFT_CTRL, false); - } else { - this->midiKey(0, false); - } -} - -// Handle a paddle being pressed. -// -// The caller needs to debounce keys and deal with keys wired in parallel. -void VailAdapter::HandlePaddle(Paddle paddle, bool pressed) { - switch (paddle) { - case PADDLE_STRAIGHT: - if (pressed) { - this->BeginTx(); - } else { - this->EndTx(); - } - return; - case PADDLE_DIT: - if (this->keyer) { - this->keyer->Key(paddle, pressed); - } else if (this->keyboardMode) { - this->keyboardKey(KEY_LEFT_CTRL, pressed); - } else { - this->midiKey(1, pressed); - } - break; - case PADDLE_DAH: - if (this->keyer) { - this->keyer->Key(paddle, pressed); - } else if (this->keyboardMode) { - this->keyboardKey(KEY_RIGHT_CTRL, pressed); - } else { - this->midiKey(2, pressed); - } - break; - } -} - -// Handle a MIDI event. -// -// We act as a MIDI -void VailAdapter::HandleMIDI(midiEventPacket_t event) { - uint16_t msg = (event.byte1 << 8) | (event.byte2 << 0); - switch (event.byte1) { - case 0xB0: // Controller Change - switch (event.byte2) { - case 0: // turn keyboard mode on/off - this->keyboardMode = (event.byte3 > 0x3f); - MidiUSB.sendMIDI(event); // Send it back to acknowledge - break; - case 1: // set dit duration (0-254) *2ms - this->ditDuration = event.byte3 * 2 * MILLISECOND; - if (this->keyer) { - this->keyer->SetDitDuration(this->ditDuration); - } - break; - case 2: // set tx note - this->txNote = event.byte3; - break; - } - break; - case 0xC0: // Program Change - if (this->keyer) { - this->keyer->Release(); - } - this->keyer = GetKeyerByNumber(event.byte2, this); - this->keyer->SetDitDuration(this->ditDuration); - break; - case 0x80: // Note off - this->buzzer->NoTone(1); - break; - case 0x90: // Note on - this->buzzer->Note(1, event.byte2); - break; - } -} - -void VailAdapter::Tick(unsigned millis) { - if (this->keyer) { - this->keyer->Tick(millis); - } -} \ No newline at end of file diff --git a/adapter.h b/adapter.h deleted file mode 100644 index 97cdc9c..0000000 --- a/adapter.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include -#include "keyers.h" -#include "polybuzzer.h" - -class VailAdapter: public Transmitter { -private: - unsigned int txNote = 69; - unsigned int ditDuration = 100; - bool keyboardMode = true; - Keyer *keyer = NULL; - PolyBuzzer *buzzer = NULL; - - void midiKey(uint8_t key, bool down); - void keyboardKey(uint8_t key, bool down); - - -public: - VailAdapter(unsigned int PiezoPin); - bool KeyboardMode(); - void HandlePaddle(Paddle key, bool pressed); - void HandleMIDI(midiEventPacket_t event); - void BeginTx(); - void EndTx(); - void Tick(unsigned millis); -}; diff --git a/install.sh b/install.sh deleted file mode 100755 index 3ca4683..0000000 --- a/install.sh +++ /dev/null @@ -1,15 +0,0 @@ -#! /bin/sh - -src=$1 -dst=$2 - -info=$dst/INFO_UF2.txt -echo -n "Waiting for $info to appear..." -while ! [ -f $info ]; do - echo -n "." - sleep 1 -done -echo "👍" - -cp $src $dst -echo "Installed!" diff --git a/keyers.cpp b/keyers.cpp index 857de1c..b955925 100644 --- a/keyers.cpp +++ b/keyers.cpp @@ -47,31 +47,24 @@ public: class StraightKeyer: public Keyer { public: - Transmitter *output; - unsigned int ditDuration; + bool transmitting = false; + unsigned int ditDuration = 100; bool txRelays[2]; StraightKeyer() { this->Reset(); } - void SetOutput(Transmitter *output) { - this->output = output; - } - - void Reset() { - if (this->output) { - this->output->EndTx(); - } - this->ditDuration = 100; + char *Name() { + return "straight"; } void SetDitDuration(unsigned int duration) { this->ditDuration = duration; } - void Release() { - this->Reset(); + void Reset() { + this->transmitting = false; } bool TxClosed() { @@ -90,22 +83,16 @@ public: void Tx(int relay, bool closed) { bool wasClosed = this->TxClosed(); this->txRelays[relay] = closed; - bool nowClosed = this->TxClosed(); - - if (wasClosed != nowClosed) { - if (nowClosed) { - this->output->BeginTx(); - } else { - this->output->EndTx(); - } - } + this->transmitting = this->TxClosed(); } void Key(Paddle key, bool pressed) { this->Tx(key, pressed); } - void Tick(unsigned int millis) {}; + bool Tick(unsigned long now) { + return this->transmitting; + } }; class BugKeyer: public StraightKeyer { @@ -115,6 +102,10 @@ public: using StraightKeyer::StraightKeyer; + char *Name() { + return "bug"; + } + void Reset() { StraightKeyer::Reset(); this->nextPulse = 0; @@ -131,10 +122,11 @@ public: } } - void Tick(unsigned int millis) { - if (this->nextPulse && (millis >= this->nextPulse)) { - this->pulse(millis); + bool Tick(unsigned long now) { + if (this->nextPulse && (now >= this->nextPulse)) { + this->pulse(now); } + return this->transmitting; } void beginPulsing() { @@ -143,7 +135,7 @@ public: } } - virtual void pulse(unsigned int millis) { + virtual void pulse(unsigned int now) { if (this->TxClosed(0)) { this->Tx(0, false); } else if (this->keyPressed[0]) { @@ -152,7 +144,7 @@ public: this->nextPulse = 0; return; } - this->nextPulse = millis + this->ditDuration; + this->nextPulse = now + this->ditDuration; } }; @@ -162,6 +154,10 @@ public: using BugKeyer::BugKeyer; + char *Name() { + return "el bug"; + } + void Reset() { BugKeyer::Reset(); this->nextRepeat = -1; @@ -204,7 +200,7 @@ public: return this->nextRepeat; } - virtual void pulse(unsigned int millis) { + virtual void pulse(unsigned int now) { int nextPulse = 0; if (this->TxClosed(0)) { // Pause if we're currently transmitting @@ -219,7 +215,7 @@ public: } if (nextPulse) { - this->nextPulse = millis + nextPulse; + this->nextPulse = now + nextPulse; } else { this->nextPulse = 0; } @@ -232,6 +228,10 @@ public: using ElBugKeyer::ElBugKeyer; + char *Name() { + return "ultimatic"; + } + void Key(Paddle key, bool pressed) { if (pressed) { this->queue.add(key); @@ -253,7 +253,11 @@ public: QSet queue; using ElBugKeyer::ElBugKeyer; - + + char *Name() { + return "single dot"; + } + void Key(Paddle key, bool pressed) { if (pressed && (key == PADDLE_DIT)) { this->queue.add(key); @@ -274,9 +278,12 @@ public: class IambicKeyer: public ElBugKeyer { public: - using ElBugKeyer::ElBugKeyer; + char *Name() { + return "iambic plain"; + } + virtual int nextTx() { int next = ElBugKeyer::nextTx(); if (this->keyPressed[PADDLE_DIT] && this->keyPressed[PADDLE_DAH]) { @@ -292,6 +299,10 @@ public: using IambicKeyer::IambicKeyer; + char *Name() { + return "iambic a"; + } + void Key(Paddle key, bool pressed) { if (pressed && (key == PADDLE_DIT)) { this->queue.add(key); @@ -312,15 +323,21 @@ public: class IambicBKeyer: public IambicKeyer { public: QSet queue; + int sending; using IambicKeyer::IambicKeyer; + char *Name() { + return "iambic b"; + } + void Reset() { + this->sending = -1; IambicKeyer::Reset(); } void Key(Paddle key, bool pressed) { - if (pressed) { + if (pressed && (this->sending != key)) { this->queue.add(key); } IambicKeyer::Key(key, pressed); @@ -333,7 +350,8 @@ public: } } - return this->queue.shift(); + this->sending = this->queue.shift(); + return this->sending; } }; @@ -344,6 +362,10 @@ public: using ElBugKeyer::ElBugKeyer; + char *Name() { + return "keyahead"; + } + void Reset() { ElBugKeyer::Reset(); this->qlen = 0; @@ -381,8 +403,7 @@ IambicAKeyer iambicAKeyer = IambicAKeyer(); IambicBKeyer iambicBKeyer = IambicBKeyer(); KeyaheadKeyer keyaheadKeyer = KeyaheadKeyer(); -Keyer *keyers[] = { - NULL, +Keyer *AllKeyers[] = { &straightKeyer, &bugKeyer, &elBugKeyer, @@ -394,12 +415,10 @@ Keyer *keyers[] = { &keyaheadKeyer, }; -Keyer *GetKeyerByNumber(int n, Transmitter *output) { - if (n >= len(keyers)) { - return NULL; - } +Keyer *GetKeyerByNumber(int n) { + if (n >= len(AllKeyers)) { + return NULL; + } - Keyer *k = keyers[n]; - k->SetOutput(output); - return k; -} + return AllKeyers[n]; +} \ No newline at end of file diff --git a/keyers.h b/keyers.h index 956049f..b9f398a 100644 --- a/keyers.h +++ b/keyers.h @@ -5,26 +5,18 @@ typedef enum { PADDLE_DIT = 0, PADDLE_DAH = 1, - PADDLE_STRAIGHT, } Paddle; -class Transmitter { -public: - virtual void BeginTx(); - virtual void EndTx(); -}; - class Keyer { public: - virtual void SetOutput(Transmitter *output); - virtual void Reset(); - virtual void SetDitDuration(unsigned int d); - virtual void Release(); - virtual bool TxClosed(); - virtual bool TxClosed(int relay); - virtual void Tx(int relay, bool closed); - virtual void Key(Paddle key, bool pressed); - virtual void Tick(unsigned int millis); + virtual char *Name() = 0; + virtual void Reset() = 0; + virtual void SetDitDuration(unsigned int d) = 0; + virtual void Key(Paddle key, bool pressed) = 0; + + // Tick updates internal state, + // and returns whether the keyer is transmitting at time now. + virtual bool Tick(unsigned long now) = 0; }; -Keyer *GetKeyerByNumber(int n, Transmitter *output); +Keyer *GetKeyerByNumber(int n); \ No newline at end of file diff --git a/touchbounce.cpp b/touchbounce.cpp deleted file mode 100644 index 1baee42..0000000 --- a/touchbounce.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "touchbounce.h" - -void TouchBounce::attach(int pin) { - this->qt = Adafruit_FreeTouch(pin, OVERSAMPLE_2, RESISTOR_0, FREQ_MODE_SPREAD); - this->qt.begin(); -} - -bool TouchBounce::readCurrentState() { - int val = this->qt.measure(); - return val > QT_THRESHOLD; -} diff --git a/touchbounce.h b/touchbounce.h deleted file mode 100644 index e4c1d1e..0000000 --- a/touchbounce.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include -#include "bounce2.h" - -#define QT_THRESHOLD 450 - -class TouchBounce: public Bounce { -public: - // attach a touch pin - void attach(int pin); - -protected: - bool readCurrentState(); - Adafruit_FreeTouch qt; -}; diff --git a/vail-adapter.ino b/vail-adapter.ino index 9cd6581..643a927 100644 --- a/vail-adapter.ino +++ b/vail-adapter.ino @@ -2,119 +2,164 @@ // Distributed under the MIT license // Please see https://github.com/nealey/vail-adapter/ -// MIDIUSB - Version: Latest -#include -#include -#include +#include +#include +#include +#include +#include #include "bounce2.h" -#include "touchbounce.h" -#include "adapter.h" - -#define DIT_PIN 2 -#define DAH_PIN 1 -#define KEY_PIN 0 -#define QT_DIT_PIN A6 -#define QT_DAH_PIN A7 -#define QT_KEY_PIN A8 -#define PIEZO 10 -#define LED_ON false // Xiao inverts this logic for some reason -#define LED_OFF (!LED_ON) - -#define DIT_KEYBOARD_KEY KEY_LEFT_CTRL -#define DAH_KEYBOARD_KEY KEY_RIGHT_CTRL -#define TONE 3000 +#include "keyers.h" +#include "polybuzzer.h" +#define DIT_PIN 1 +#define DAH_PIN 3 +#define S1_PIN 4 +#define S2_PIN 9 +#define SPEAKER 7 +#define SOUNDER 8 #define MILLISECOND 1 #define SECOND (1 * MILLISECOND) -bool trs = false; // true if a TRS plug is in a TRRS jack uint16_t iambicDelay = 80 * MILLISECOND; Bounce dit = Bounce(); Bounce dah = Bounce(); -Bounce key = Bounce(); -TouchBounce qt_dit = TouchBounce(); -TouchBounce qt_dah = TouchBounce(); -TouchBounce qt_key = TouchBounce(); -VailAdapter adapter = VailAdapter(PIEZO); +Bounce s1 = Bounce(); +Bounce s2 = Bounce(); +PolyBuzzer buzzer = PolyBuzzer(SPEAKER); + +Adafruit_NeoPixel pixels(1, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800); +Adafruit_SSD1306 display(128, 32, &Wire, -1); + +// Tones default to a minor third +unsigned int txNote = 72; // C5 +unsigned int rxNote = 69; // A4 + +int keyerCurrentN = 0; +Keyer *keyerCurrent = NULL; + +void led(uint32_t color = 0) { + pixels.fill(color); + pixels.show(); +} + + +// A reentrant doodad to blink out the letter V at startup +#define HELLO_BITS 0b0000101010111000 +void hello(unsigned long now) { + static bool beeping = false; + int beat = now / iambicDelay; + + if (beat < 16) { + bool on = HELLO_BITS & (1 << (15-beat)); + if (on != beeping) { + if (on) { + buzzer.Note(0, rxNote); + led(0x220000); + } else { + buzzer.NoTone(0); + led(0); + } + beeping = on; + } + } +} + +int loadKeyer(int n) { + keyerCurrent = GetKeyerByNumber(n); + if (NULL == keyerCurrent) { + n = 0; + keyerCurrent = GetKeyerByNumber(n); + } + + display.clearDisplay(); + display.setCursor(1, 12); + display.println(keyerCurrent->Name()); + display.display(); + + return n; +} + +void void setup() { - pinMode(LED_BUILTIN, OUTPUT); dit.attach(DIT_PIN, INPUT_PULLUP); dah.attach(DAH_PIN, INPUT_PULLUP); - key.attach(KEY_PIN, INPUT_PULLUP); - qt_dit.attach(QT_DIT_PIN); - qt_dah.attach(QT_DAH_PIN); - qt_key.attach(QT_KEY_PIN); + s1.attach(S1_PIN, INPUT_PULLUP); + s2.attach(S2_PIN, INPUT_PULLUP); + pinMode(SOUNDER, OUTPUT); - Keyboard.begin(); + Serial.begin(115200); + Serial.println("Vail Client / " __DATE__ " " __TIME__); + + pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED) + pixels.setBrightness(20); // not so bright + + Wire.begin(); + display.begin(SSD1306_SWITCHCAPVCC); // This never returns false, in my testing + display.setFont(&FreeSans9pt7b); + display.setTextColor(SSD1306_WHITE); + + display.clearDisplay(); + display.setCursor(1, 12); + display.println(__DATE__); + display.display(); // To auto-sense a straight key in a TRRS jack, - // we just check to see if DAH is closed. + // check to see if DAH is closed. // The sleeve on the straight key's TRS plug // will short the second ring to the sleeve. + // In this case, set the keyer to straight. + // Since keyers are edge triggered, + // nothing will happen if the key stays closed. + // If it opens, it falls back to being a straight key. for (int i = 0; i < 16; i++) { + hello(millis()); delay(20); dah.update(); } if (dah.read() == LOW) { - trs = true; - key = dit; + loadKeyer(0); // Straight + } else { + loadKeyer(7); // Iambic B } } -// A reentrant doodad to blink out the letter V at startup -// After startup, display the status of the keyboard -#define HELLO_BITS 0b0000101010111000 -void setLED() { - static bool beepin = false; - int beat = millis() / iambicDelay; - bool on = adapter.KeyboardMode(); // If we're not in intro, display status of keyboard - - if (beat < 16) { - on = HELLO_BITS & (1 << (15-beat)); - if (on != beepin) { - if (on) { - tone(PIEZO, TONE); - } else { - noTone(PIEZO); - } - beepin = on; - } - } - - digitalWrite(LED_BUILTIN, on?LED_ON:LED_OFF); -} - void loop() { - unsigned now = millis(); - midiEventPacket_t event = MidiUSB.read(); + static unsigned long txBegin = 0; + unsigned long now = millis(); - setLED(); - adapter.Tick(now); + hello(now); - if (event.header) { - adapter.HandleMIDI(event); + s1.update(); + s2.update(); + if (s1.fell() || s2.fell()) { + keyerCurrentN = loadKeyer(keyerCurrentN + 1); + // XXX: menu } - // Monitor straight key pin - if (key.update() || qt_key.update()) { - bool pressed = !key.read() || qt_key.read(); - adapter.HandlePaddle(PADDLE_STRAIGHT, pressed); - } - - // If we made dit = dah, we have a straight key on the dit pin, - // so we skip other keys polling. - if (trs) { - return; - } - - if (dit.update() || qt_dit.update()) { - bool pressed = !dit.read() || qt_dit.read(); - adapter.HandlePaddle(PADDLE_DIT, pressed); + if (dit.update()) { + keyerCurrent->Key(PADDLE_DIT, !dit.read()); } - if (dah.update() || qt_dah.update()) { - bool pressed = !dah.read() || qt_dah.read(); - adapter.HandlePaddle(PADDLE_DAH, pressed); + if (dah.update()) { + keyerCurrent->Key(PADDLE_DAH, !dah.read()); + } + + bool sounding = keyerCurrent->Tick(now); + if (sounding) { + if (!txBegin) { + txBegin = now; + buzzer.Note(0, txNote); + digitalWrite(SOUNDER, HIGH); + led(0xff0000); + } + } else { + if (txBegin) { + Serial.printf("%d %d\n", txBegin, now - txBegin); + txBegin = 0; + buzzer.NoTone(0); + digitalWrite(SOUNDER, LOW); + led(); + } } }