Ref : https://github.com/f4goh/pico_vband

schema

Code
// SPDX-License-Identifier: MIT
//
// VBAND CW USB dongle based on a Pi Pico.
// Also supports other key outputs for Vail and morsecode.me etc.
//
// Inspired from similar projects at:
// https://github.com/sipsmi/vband_dongle/tree/main
// https://github.com/mgiugliano/MorsePaddle2USB
// https://github.com/nealey/vail-adapter
//
// There is also a PiPico circuit python implementation shown on
// https://www.qrz.com/db/KG5QNO
#include <Keyboard.h>
#include <KeyboardLayout.h>
#include <ClickButton.h> // https://github.com/marcobrianza/ClickButton
#include <singleLEDLibrary.h> // https://github.com/SethSenpai/singleLEDLibrary
#include <arduino-timer.h> // https://github.com/contrem/arduino-timer
#define VERSION 1.1
#define DEBUG 0
// Pick your dit and dah pins - we go with the normal 'Dah on the right' convention
#define LEFT_PIN 3 //Dit
#define RIGHT_PIN 2 //Dah
// Mode/multi-function button pin
#define BUTTON_PIN 4
//#define LED_PIN LED_BUILTIN
// If you want a convinient external LED connection then I used GPIO9/pin12 along with the convinient
// GND on pin13. Enabling both LEDs at once is not completely trivial with the singleLEDLibrary, so
// right now you can have one or the other (onboard or outboard).
#define LED_PIN 8 // 9 physically convenient next to a gnd
// We are going to try and support:
// vband - https://hamradio.solutions/vband/
// vail - https://vail.woozle.org/#
// morsecode.me - https://morsecode.me/#/help/practice
// VBAND can use a number of different key combinations in the web browswer:
// - [ ]
// - CTRL-L CTRL-R
#define VBAND_LEFT '['
#define VBAND_RIGHT ']'
// Vail can use a number of different key combinations in the web browswer:
// - [ ]
// - CTRL-L CTRL-R
// - . /
// - x z
// We are deliberate choosing some different to VBAND
#define VAIL_LEFT 'x'
#define VAIL_RIGHT 'z'
// morsecode.me feels like it only supports a straight key, and has a number of keyboard
// chars it supports
// - .
// - i
// - e
// - [space]
// We are deliberate choosing something different to the others
#define MORSECODE_LEFT 'e'
#define MORSECODE_RIGHT 'i'
// And finally, support the CTRL key mode, as that can in some cases
// work I think when the target window does not even have focus, which
// might be useful
#define CTRL_LEFT KEY_LEFT_CTRL
#define CTRL_RIGHT KEY_RIGHT_CTRL
#define LEFT_INDEX 0 //Dit
#define RIGHT_INDEX 1 //Dah
char mode_array[][2] = {
{VBAND_LEFT, VBAND_RIGHT},
{VAIL_LEFT, VAIL_RIGHT},
{MORSECODE_LEFT, MORSECODE_RIGHT},
{CTRL_LEFT, CTRL_RIGHT}
};
int current_mode = 0; //Default to VBAND
const int max_mode = sizeof(mode_array)/sizeof(mode_array[1]);
#define MODE_BLINK_ON_TIME 100
#define MODE_BLINK_OFF_TIME 50
// Make sure this array is at least twice as long as the number of modes we have,
// as it encompasses both on/off times
int ledflash[] = {
MODE_BLINK_ON_TIME, MODE_BLINK_OFF_TIME,
MODE_BLINK_ON_TIME, MODE_BLINK_OFF_TIME,
MODE_BLINK_ON_TIME, MODE_BLINK_OFF_TIME,
MODE_BLINK_ON_TIME, MODE_BLINK_OFF_TIME,
MODE_BLINK_ON_TIME, MODE_BLINK_OFF_TIME
};
//Our mode change button
ClickButton mode_button(BUTTON_PIN, LOW, CLICKBTN_PULLUP); //Active low button
//Our activity indicator led
sllib led(LED_PIN);
auto timer = timer_create_default();
//Track key states so we can flash LED in time.
bool left_down = false;
bool right_down = false;
//Try to debounce the keys a bit. Let's see if we only need to debounce from the down time - that is,
// ignore any 'ups' for this amount of time after the first down.
unsigned long key_debounce_ms = 10;
unsigned long left_down_ms = 0;
unsigned long right_down_ms = 0;
//Used with the timer blob to turn off some LED activity after a specified
//period - mainly because the LED library does not have oneshot calls sadly
bool cancel_leds(void *arg) {
led.setOffSingle();
return true;
}
// setup the pins and HID (device)
void setup() {
if (DEBUG) { Serial.begin(115200); delay(3000); }
pinMode(LEFT_PIN, INPUT_PULLUP);
pinMode(RIGHT_PIN, INPUT_PULLUP);
Keyboard.begin(); // initialise HID library
mode_button.longClickTime = 350; //Shorter time for a long click, not 1s default
//We presume we start with the keys not down!, so don't bother reading them here.
//left_down = digitalRead(LEFT_PIN);
//right_down = digitalRead(RIGHT_PIN);
//We start with a 'heartbeat' to show we are alive. Once a key is pressed this will stop and we move into
//flashing the LED for each further activity.
led.setBreathSingle(1000); //We start with the breathing LED.
}
void loop(){
int clicks;
bool left_current = false;
bool right_current = false;
timer.tick();
led.update();
mode_button.Update();
if (mode_button.changed ) {
clicks = mode_button.clicks;
if (DEBUG) Serial.println(" changed " + String(mode_button.changed) + ":" + String(mode_button.clicks));
if (clicks != 0 ) {
if (DEBUG) Serial.println("click " + String(clicks));
if (clicks > 0 ) {
//short click(s) - cycle through the modes
current_mode = (current_mode + clicks) % max_mode;
if (DEBUG) Serial.println(" mode " + String(current_mode));
led.setPatternSingle(ledflash, (current_mode+1)*2); //Flash LED with mode no.
int flash_length = ((current_mode+1)* MODE_BLINK_ON_TIME) +
((current_mode+1)* MODE_BLINK_OFF_TIME);
timer.in(flash_length, cancel_leds);
} else {
//Long press - reset to mode0
current_mode = 0;
if (DEBUG) Serial.println(" reset mode " + String(current_mode));
led.setPatternSingle(ledflash, (current_mode+1)*2); //Flash LED with mode no.
int flash_length = ((current_mode+1)* MODE_BLINK_ON_TIME) +
((current_mode+1)* MODE_BLINK_OFF_TIME);
timer.in(flash_length, cancel_leds);
}
}
}
//We should explain our debounce philosophy here a little.
//Normally debounce routines wait for a set period of time (say, 10ms)
//and look for 'stability' of the button value before registering the
//button (key) has been pressed. What I want is to know as soon as the
//button is pressed (or released), so acknowledge the press/release as
//soon as we detect a change in state - but then to avoid the following
//potential bouneces, we ignore all other changes for a set time (say, 10ms).
//
//One potential downside of this method is that it is likely not noise immune.
//That is, if some noise turns up and briefly twiddles the pin, we will take that
//as a press or release. But, that's the price we pay.
left_current = digitalRead(LEFT_PIN) == LOW; //active low!
right_current = digitalRead(RIGHT_PIN) == LOW; //active low!
if (left_current != left_down ) { //did the key change state
unsigned long ms = millis();
if (DEBUG) Serial.println("LEFT change: " + String(left_current) + " to " + String(left_down));
//Are we still in a debounce time slot...
if (left_down_ms + key_debounce_ms > ms ) {
//Still debouncing
if (DEBUG) Serial.println(" deb " + String(ms - left_down_ms));
} else {
//Not debouncing, so lets process it
if (DEBUG) Serial.println(" process " + String(ms - left_down_ms));
if (left_current) { //button down
left_down_ms = ms;
//led.setBlinkSingle(5000); //A hack - as the old version of the library is missing 'On'
digitalWrite(LED_PIN, HIGH);
if (DEBUG) Serial.println("LEFT down");
Keyboard.press(mode_array[current_mode][LEFT_INDEX]);
} else {
//Button up
left_down_ms = ms;
led.setOffSingle();
if (DEBUG) Serial.println("LEFT up");
Keyboard.release(mode_array[current_mode][LEFT_INDEX]);
}
//And remember which state we are in...
left_down = left_current;
}
}
if (right_current != right_down ) { //did the key change state
unsigned long ms = millis();
if (DEBUG) Serial.println("RIGHT change: " + String(right_current) + " to " + String(right_down));
//Are we still in a debounce time slot...
if (right_down_ms + key_debounce_ms > ms ) {
//Still debouncing
if (DEBUG) Serial.println(" deb " + String(ms - right_down_ms));
} else {
//Not debouncing, so lets process it
if (DEBUG) Serial.println(" process " + String(ms - right_down_ms));
if (right_current) { //button down
right_down_ms = ms;
//led.setBlinkSingle(5000); //A hack - as the old version of the library is missing 'On'
digitalWrite(LED_PIN, HIGH);
if (DEBUG) Serial.println("RIGHT down");
Keyboard.press(mode_array[current_mode][RIGHT_INDEX]);
} else {
//Button up
right_down_ms = ms;
led.setOffSingle();
if (DEBUG) Serial.println("RIGHT up");
Keyboard.release(mode_array[current_mode][RIGHT_INDEX]);
}
//And remember which state we are in...
right_down = right_current;
}
}
}

