{"id":586,"date":"2025-07-13T16:28:09","date_gmt":"2025-07-13T14:28:09","guid":{"rendered":"http:\/\/www.f4irx.com\/wordpress\/?p=586"},"modified":"2025-07-13T16:28:09","modified_gmt":"2025-07-13T14:28:09","slug":"pico-vband","status":"publish","type":"post","link":"http:\/\/www.f4irx.com\/wordpress\/index.php\/2025\/07\/13\/pico-vband\/","title":{"rendered":"PICO VBAND"},"content":{"rendered":"\n<p>Ref : <a href=\"https:\/\/github.com\/f4goh\/pico_vband\">https:\/\/github.com\/f4goh\/pico_vband<\/a><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"324\" height=\"450\" src=\"http:\/\/www.f4irx.com\/wordpress\/wp-content\/uploads\/2025\/07\/zero_box.jpg\" alt=\"\" class=\"wp-image-587\"\/><figcaption class=\"wp-element-caption\">oplus_32<\/figcaption><\/figure>\n\n\n\n<p>schema<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"560\" height=\"382\" src=\"http:\/\/www.f4irx.com\/wordpress\/wp-content\/uploads\/2025\/07\/3.png\" alt=\"\" class=\"wp-image-588\" style=\"width:376px;height:auto\"\/><\/figure>\n\n\n\n<p>Code<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">\/\/ SPDX-License-Identifier: MIT\n\/\/\n\/\/ VBAND CW USB dongle based on a Pi Pico.\n\/\/ Also supports other key outputs for Vail and morsecode.me etc.\n\/\/\n\/\/ Inspired from similar projects at:\n\/\/ https:\/\/github.com\/sipsmi\/vband_dongle\/tree\/main\n\/\/ https:\/\/github.com\/mgiugliano\/MorsePaddle2USB\n\/\/ https:\/\/github.com\/nealey\/vail-adapter\n\/\/\n\/\/ There is also a PiPico circuit python implementation shown on \n\/\/ https:\/\/www.qrz.com\/db\/KG5QNO\n\n\n#include &lt;Keyboard.h&gt;\n#include &lt;KeyboardLayout.h&gt;\n#include &lt;ClickButton.h&gt;\t\/\/ https:\/\/github.com\/marcobrianza\/ClickButton\n#include &lt;singleLEDLibrary.h&gt;\t\/\/ https:\/\/github.com\/SethSenpai\/singleLEDLibrary\n#include &lt;arduino-timer.h&gt;\t\/\/ https:\/\/github.com\/contrem\/arduino-timer\n\n#define VERSION 1.1\n\n#define DEBUG 0\n\n\/\/ Pick your dit and dah pins - we go with the normal 'Dah on the right' convention\n#define LEFT_PIN 3  \/\/Dit\n#define RIGHT_PIN 2 \/\/Dah\n\n\/\/ Mode\/multi-function button pin\n#define BUTTON_PIN 4\n\n\/\/#define LED_PIN LED_BUILTIN\n\/\/ If you want a convinient external LED connection then I used GPIO9\/pin12 along with the convinient\n\/\/ GND on pin13. Enabling both LEDs at once is not completely trivial with the singleLEDLibrary, so\n\/\/ right now you can have one or the other (onboard or outboard).\n#define LED_PIN 8   \/\/ 9 physically convenient next to a gnd\n\n\/\/ We are going to try and support:\n\/\/ vband - https:\/\/hamradio.solutions\/vband\/\n\/\/ vail - https:\/\/vail.woozle.org\/#\n\/\/ morsecode.me - https:\/\/morsecode.me\/#\/help\/practice\n\n\/\/ VBAND can use a number of different key combinations in the web browswer:\n\/\/ - [ ]\n\/\/ - CTRL-L CTRL-R\n\n#define VBAND_LEFT '['\n#define VBAND_RIGHT ']'\n\n\/\/ Vail can use a number of different key combinations in the web browswer:\n\/\/ - [ ]\n\/\/ - CTRL-L CTRL-R\n\/\/ - . \/\n\/\/ - x z\n\/\/ We are deliberate choosing some different to VBAND\n#define VAIL_LEFT 'x'\n#define VAIL_RIGHT 'z'\n\n\/\/ morsecode.me feels like it only supports a straight key, and has a number of keyboard\n\/\/ chars it supports\n\/\/ - .\n\/\/ - i\n\/\/ - e\n\/\/ - [space]\n\/\/ We are deliberate choosing something different to the others\n#define MORSECODE_LEFT 'e'\n#define MORSECODE_RIGHT 'i'\n\n\/\/ And finally, support the CTRL key mode, as that can in some cases\n\/\/ work I think when the target window does not even have focus, which\n\/\/ might be useful\n#define CTRL_LEFT KEY_LEFT_CTRL\n#define CTRL_RIGHT KEY_RIGHT_CTRL\n\n#define LEFT_INDEX 0  \/\/Dit\n#define RIGHT_INDEX 1 \/\/Dah\n\nchar mode_array[][2] = {\n  {VBAND_LEFT, VBAND_RIGHT},\n  {VAIL_LEFT, VAIL_RIGHT},\n  {MORSECODE_LEFT, MORSECODE_RIGHT},\n  {CTRL_LEFT, CTRL_RIGHT}\n};\n\nint current_mode = 0; \/\/Default to VBAND\nconst int max_mode = sizeof(mode_array)\/sizeof(mode_array[1]);\n\n#define MODE_BLINK_ON_TIME 100\n#define MODE_BLINK_OFF_TIME 50\n\/\/ Make sure this array is at least twice as long as the number of modes we have,\n\/\/ as it encompasses both on\/off times\nint ledflash[] = {\n  MODE_BLINK_ON_TIME, MODE_BLINK_OFF_TIME,\n  MODE_BLINK_ON_TIME, MODE_BLINK_OFF_TIME,\n  MODE_BLINK_ON_TIME, MODE_BLINK_OFF_TIME,\n  MODE_BLINK_ON_TIME, MODE_BLINK_OFF_TIME,\n  MODE_BLINK_ON_TIME, MODE_BLINK_OFF_TIME\n};\n\n\/\/Our mode change button\nClickButton mode_button(BUTTON_PIN, LOW, CLICKBTN_PULLUP); \/\/Active low button\n\n\/\/Our activity indicator led\nsllib led(LED_PIN);\n\nauto timer = timer_create_default();\n\n\/\/Track key states so we can flash LED in time.\nbool left_down = false;\nbool right_down = false;\n\n\/\/Try to debounce the keys a bit. Let's see if we only need to debounce from the down time - that is,\n\/\/ ignore any 'ups' for this amount of time after the first down.\nunsigned long key_debounce_ms = 10;\nunsigned long left_down_ms = 0;\nunsigned long right_down_ms = 0;\n\n\/\/Used with the timer blob to turn off some LED activity after a specified\n\/\/period - mainly because the LED library does not have oneshot calls sadly\nbool cancel_leds(void *arg) {\n  led.setOffSingle();\n  return true;\n}\n\n\/\/ setup the pins and HID (device)\nvoid setup() {\n  if (DEBUG) { Serial.begin(115200); delay(3000); }\n  pinMode(LEFT_PIN, INPUT_PULLUP);\n  pinMode(RIGHT_PIN, INPUT_PULLUP);\n  Keyboard.begin(); \/\/ initialise HID library\n\n  mode_button.longClickTime = 350;  \/\/Shorter time for a long click, not 1s default\n\n  \/\/We presume we start with the keys not down!, so don't bother reading them here.\n  \/\/left_down = digitalRead(LEFT_PIN);\n  \/\/right_down = digitalRead(RIGHT_PIN);\n\n  \/\/We start with a 'heartbeat' to show we are alive. Once a key is pressed this will stop and we move into\n  \/\/flashing the LED for each further activity.\n  led.setBreathSingle(1000);  \/\/We start with the breathing LED.\n}\n\nvoid loop(){\n  int clicks;\n  bool left_current = false;\n  bool right_current = false;\n\n  timer.tick();\n  led.update();\n  mode_button.Update();\n\n  if (mode_button.changed ) {\n    clicks = mode_button.clicks;\n    \n    if (DEBUG) Serial.println(\" changed \" + String(mode_button.changed) + \":\" + String(mode_button.clicks));    \n  \n    if (clicks != 0 ) {\n      if (DEBUG) Serial.println(\"click \" + String(clicks));\n      if (clicks &gt; 0 ) {\n        \/\/short click(s) - cycle through the modes\n        current_mode = (current_mode + clicks) % max_mode;\n        \n        if (DEBUG) Serial.println(\" mode \" + String(current_mode));\n        led.setPatternSingle(ledflash, (current_mode+1)*2);  \/\/Flash LED with mode no.\n        int flash_length = ((current_mode+1)* MODE_BLINK_ON_TIME) +\n          ((current_mode+1)* MODE_BLINK_OFF_TIME);\n        timer.in(flash_length, cancel_leds);\n      } else {\n        \/\/Long press - reset to mode0\n        current_mode = 0;\n        \n        if (DEBUG) Serial.println(\" reset mode \" + String(current_mode));\n        led.setPatternSingle(ledflash, (current_mode+1)*2);  \/\/Flash LED with mode no.\n        int flash_length = ((current_mode+1)* MODE_BLINK_ON_TIME) +\n          ((current_mode+1)* MODE_BLINK_OFF_TIME);\n        timer.in(flash_length, cancel_leds);\n      }\n    }\n  }\n\n  \/\/We should explain our debounce philosophy here a little.\n  \/\/Normally debounce routines wait for a set period of time (say, 10ms)\n  \/\/and look for 'stability' of the button value before registering the\n  \/\/button (key) has been pressed. What I want is to know as soon as the\n  \/\/button is pressed (or released), so acknowledge the press\/release as\n  \/\/soon as we detect a change in state - but then to avoid the following\n  \/\/potential bouneces, we ignore all other changes for a set time (say, 10ms).\n  \/\/\n  \/\/One potential downside of this method is that it is likely not noise immune.\n  \/\/That is, if some noise turns up and briefly twiddles the pin, we will take that\n  \/\/as a press or release. But, that's the price we pay.\n  left_current = digitalRead(LEFT_PIN) == LOW;  \/\/active low!\n  right_current = digitalRead(RIGHT_PIN) == LOW;  \/\/active low!\n\n  if (left_current != left_down ) { \/\/did the key change state\n    unsigned long ms = millis();\n    \n    if (DEBUG) Serial.println(\"LEFT change: \" + String(left_current) + \" to \" + String(left_down));\n\n    \/\/Are we still in a debounce time slot...\n    if (left_down_ms + key_debounce_ms &gt; ms ) {\n      \/\/Still debouncing\n      if (DEBUG) Serial.println(\" deb \" + String(ms - left_down_ms));\n    } else {    \n      \/\/Not debouncing, so lets process it\n      if (DEBUG) Serial.println(\" process \" + String(ms - left_down_ms));\n      if (left_current) { \/\/button down\n        left_down_ms = ms;\n        \/\/led.setBlinkSingle(5000); \/\/A hack - as the old version of the library is missing 'On'\n        digitalWrite(LED_PIN, HIGH);\n        if (DEBUG) Serial.println(\"LEFT down\");\n        Keyboard.press(mode_array[current_mode][LEFT_INDEX]);        \n      } else {\n        \/\/Button up\n        left_down_ms = ms;\n        led.setOffSingle();\n        if (DEBUG) Serial.println(\"LEFT up\");\n        Keyboard.release(mode_array[current_mode][LEFT_INDEX]);      \n      }\n      \/\/And remember which state we are in...\n      left_down = left_current;\n    }\n  }\n\n  if (right_current != right_down ) { \/\/did the key change state\n    unsigned long ms = millis();\n    \n    if (DEBUG) Serial.println(\"RIGHT change: \" + String(right_current) + \" to \" + String(right_down));\n\n    \/\/Are we still in a debounce time slot...\n    if (right_down_ms + key_debounce_ms &gt; ms ) {\n      \/\/Still debouncing\n      if (DEBUG) Serial.println(\" deb \" + String(ms - right_down_ms));\n    } else {    \n      \/\/Not debouncing, so lets process it\n      if (DEBUG) Serial.println(\" process \" + String(ms - right_down_ms));\n      if (right_current) { \/\/button down\n        right_down_ms = ms;\n        \/\/led.setBlinkSingle(5000); \/\/A hack - as the old version of the library is missing 'On'\n        digitalWrite(LED_PIN, HIGH);\n        if (DEBUG) Serial.println(\"RIGHT down\");\n        Keyboard.press(mode_array[current_mode][RIGHT_INDEX]);        \n      } else {\n        \/\/Button up\n        right_down_ms = ms;\n        led.setOffSingle();\n        if (DEBUG) Serial.println(\"RIGHT up\");\n        Keyboard.release(mode_array[current_mode][RIGHT_INDEX]);      \n      }\n      \/\/And remember which state we are in...\n      right_down = right_current;\n    }\n  }\n}<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>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 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-586","post","type-post","status-publish","format-standard","hentry","category-projet"],"_links":{"self":[{"href":"http:\/\/www.f4irx.com\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/586","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.f4irx.com\/wordpress\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.f4irx.com\/wordpress\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.f4irx.com\/wordpress\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.f4irx.com\/wordpress\/index.php\/wp-json\/wp\/v2\/comments?post=586"}],"version-history":[{"count":1,"href":"http:\/\/www.f4irx.com\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/586\/revisions"}],"predecessor-version":[{"id":589,"href":"http:\/\/www.f4irx.com\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/586\/revisions\/589"}],"wp:attachment":[{"href":"http:\/\/www.f4irx.com\/wordpress\/index.php\/wp-json\/wp\/v2\/media?parent=586"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.f4irx.com\/wordpress\/index.php\/wp-json\/wp\/v2\/categories?post=586"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.f4irx.com\/wordpress\/index.php\/wp-json\/wp\/v2\/tags?post=586"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}