Compare commits

...

25 Commits

Author SHA1 Message Date
Neale Pickett bc1f181963 Route buzzer to XIAO pin 10, not 9. New KiCAD.
Fixes #5
2024-03-14 14:17:41 -06:00
Neale Pickett 0f59df655f Merge branch 'master' of https://github.com/nealey/vail-adapter 2024-02-05 12:34:27 -07:00
Neale Pickett d1066752be Fix some build junk 2024-02-05 12:33:56 -07:00
Neale Pickett 533dd49bdc
It seems to be XIAO SAMD21, not XIAO M0 2023-10-24 08:47:10 -06:00
Neale Pickett 36cc138443
There is now >1 model of XIAO. List the specific one. 2023-10-23 13:38:40 -06:00
Neale Pickett 31bfadd71d Actually change pitch, fix VBand LED
* Fix VBand compatability LED
* Make it actually change pitch
2023-02-25 18:10:14 -07:00
Neale Pickett b04428276d More svelte PCB 2023-01-22 15:59:54 -07:00
Neale Pickett e95233c120 Merge branch 'master' of https://github.com/nealey/vail-adapter 2023-01-22 10:19:44 -07:00
Neale Pickett 1f61b21051 First try at a PCB 2023-01-22 10:19:36 -07:00
Neale Pickett 94b4c7f917
Update README.md 2022-07-15 10:27:54 -06:00
Neale Pickett 9aed973bb5
Update README.md 2022-07-15 10:26:54 -06:00
Neale Pickett bcce75feac Default to keyboard mode 2022-06-26 12:24:00 -06:00
Neale Pickett e9584169d1 Case work, bugfixes 2022-06-26 10:55:28 -06:00
Neale Pickett 2f8eab0b3b possibly-working pig 2022-06-25 16:45:38 -06:00
Neale Pickett e60c7bce05 More case work 2022-06-12 12:15:37 -06:00
Neale Pickett ac984445f9 Rework case to be rounded 2022-06-11 22:58:34 -06:00
Neale Pickett a5e691eab3 Pretty good piggy 2022-06-11 16:58:37 -06:00
Neale Pickett afe8b66261 make holes a bit bigger 2022-05-30 18:29:37 -06:00
Neale Pickett c2b8b96578 Version I built for dad 2022-05-30 18:27:53 -06:00
Neale Pickett bf1e62ec0e Smaller audio jack hole 2022-05-29 20:47:46 -06:00
Neale Pickett b67252e994 Update case design 2022-05-29 15:27:19 -04:00
Neale Pickett d99f59cffa Fix Iambic B over-memory-ing 2022-05-28 21:23:26 -06:00
Neale Pickett a6eaae4da1 All but Iambic B working 2022-05-28 15:38:10 -06:00
Neale Pickett d8a615813e Trying to get keyers working 2022-05-28 15:18:28 -06:00
Neale Pickett 012ee5ae31 Attempt to get local keyer modes going 2022-05-22 21:55:22 -06:00
36 changed files with 532934 additions and 641 deletions

10
.clangd Normal file
View File

@ -0,0 +1,10 @@
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"

2
.gitignore vendored
View File

@ -1 +1,3 @@
build/
case/adapter-case.stl
pcb/pcb-backups

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"clangd.arguments": [
"--enable-config"
]
}

View File

@ -1,11 +1,19 @@
FQBN = Seeeduino:samd:seeed_XIAO_m0
FQBN_qtpy = adafruit:samd:adafruit_qtpy_m0
FQBN_xiao = Seeeduino:samd:seeed_XIAO_m0
UF2_MOUNT = /mnt/chromeos/removable/Arduino
ARDUINO_DIR = /app/Arduino
BUILDER = flatpak run --command ${ARDUINO_DIR}/arduino-builder cc.arduino.arduinoide
default: build/vail-adapter.xiao.uf2
install: build/vail-adapter.xiao.uf2
default: build/vail-adapter.qtpy.uf2 build/vail-adapter.xiao.uf2
install: install-xiao
install-xiao: build/vail-adapter.xiao.uf2
./install.sh $< $(UF2_MOUNT)
install-qtpy: build/vail-adapter.qtpy.uf2
./install.sh $< /mnt/chromeos/removable/QTPY_BOOT
clean:
rm -rf build/*
# uf2conv.py is covered by an MIT license.
build/uf2conv.py: build/uf2families.json
@ -16,13 +24,19 @@ build/uf2families.json:
mkdir -p build
curl -L https://raw.githubusercontent.com/microsoft/uf2/master/utils/$(@F) > $@
%.xiao.uf2: %.ino.bin build/uf2conv.py
%.xiao.uf2: %.xiao.bin build/uf2conv.py
build/uf2conv.py -b 0x2000 -c -o $@ $<
build/%.bin: % *.cpp *.h
%.qtpy.uf2: %.qtpy.bin build/uf2conv.py
build/uf2conv.py -b 0x2000 -c -o $@ $<
build/%.qtpy.bin: FQBN = adafruit:samd:adafruit_qtpy_m0
build/%.xiao.bin: FQBN = Seeeduino:samd:seeed_XIAO_m0
build/vail-adapter.%.bin: vail-adapter.ino *.cpp *.h
mkdir -p build/$*
arduino-builder \
-build-cache ~/.cache/arduino \
-build-path build \
-build-path build/$* \
-core-api-version 10813 \
-fqbn $(FQBN) \
-hardware ~/.arduino15/packages \
@ -34,6 +48,7 @@ build/%.bin: % *.cpp *.h
-libraries ~/Arduino/libraries \
-compile \
$<
mv build/$*/vail-adapter.ino.bin $@
upload: vail-adapter.ino
arduino --upload --board $(FQBN) $<

View File

@ -13,8 +13,10 @@ License: MIT
* Lets you key even if you move focus to another window
* Works with [Vail](https://vail.woozle.org/)
* Works with [VBand](https://hamradio.solutions/vband/)
* Optional sidetone generator for straight keying, which helps with latency
* Works with [VBand](https://hamradio.solutions/vband/), but the window has to remain focused
* Optional sidetone generator, which helps with latency
* Implements all nine keyer modes from Vail, in the adapter, so you lunatics can try to key at 50WPM with no latency issues
* Plays received signals in the adapter, so you can turn off your computer speaker
* Free firmware updates for life
* Can be wired up in about 5 minutes
@ -30,10 +32,10 @@ License: MIT
Things I plan to add:
* Local keyer logic for Ultimatic, Iambic, etc.
* Local keyer generating sidetones
* Vail site sends MIDI note events to the adapter,
so you don't need your computer speaker on to listen.
* [x] PCB to ease assembly and make a more robust shippable product
* [ ] Debug tone changes
* [ ] PCB v2 to get the speaker on pin 10 instead of pin 9
* [ ] Unplug detection: send a pulse out one pin and detect it on the T pin to reset straight-key detection
# Contributing

132
adapter.cpp Normal file
View File

@ -0,0 +1,132 @@
#include <Arduino.h>
#include <Keyboard.h>
#include <MIDIUSB.h>
#include <cstddef>
#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);
}
}

27
adapter.h Normal file
View File

@ -0,0 +1,27 @@
#pragma once
#include <MIDIUSB.h>
#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);
};

4
case/Makefile Normal file
View File

@ -0,0 +1,4 @@
default: body.stl cap.stl
%.stl: case.scad
openscad -o $@ -D TARGET=\"$*\" $<

View File

@ -1,16 +0,0 @@
union() {
import("seeed_xaio_case.stl", convexity=3);
translate([-12.1, -2, 6]) cube([1.3, 4, 4]); // Plug up the component hole in the back
// Fill in the curved sides in front
translate([-13.5, -10.485, 0]) cube([5, 1.30, 10]);
translate([-13.5, 9.185, 0]) cube([5, 1.30, 10]);
// Big block for key
translate([-40, -10.485, 0]) cube([29, 10.485*2, 10]);
difference() {
translate([-40, -10.485, 0]) cube([28, 10.485*2, 30]);
}
}

75154
case/body.stl Normal file

File diff suppressed because it is too large Load Diff

11846
case/cap.stl Normal file

File diff suppressed because it is too large Load Diff

178
case/case.scad Normal file
View File

@ -0,0 +1,178 @@
// Make circles lovely and round
$fa = 1; $fs = 0.1;
outer_dimensions = [24.2, 21, 25];
outer_radius = 3.7;
wall_width = [1.3, 1.3, 1.3];
inner_dimensions = outer_dimensions - (wall_width * 2);
inner_radius = outer_radius * 0.64;
z_elevate = [[0, 0, 0], [0, 0, 0], [0, 0, 0.5]]; // Multiply by this to bring bottom to z=0
center = [[-0.5, 0, 0], [0, -0.5, 0], [0, 0, 0]]; // Multiply by this to center something on the xy axes
xiao_elevation = 5.2; // Center of the PCB
pcb_thickness = 1.8;
channel = [16, 1, 1]; // Difensions of cap channel
channel_height = 2; // How far into the case the clips need to go
cap_thickness = 1.3;
module roundedcube(size=[1, 1, 1], center=false, r=0.5) {
size = (size[0]==undef) ? [size, size, size] : size;
cubeSize = [size[0] - 2*r, size[1] - 2*r, size[2]];
translate(center ? [0,0,0] : size/2) {
union() {
cube([size[0], cubeSize[1], size[2]], center=true);
cube([cubeSize[0], size[1], size[2]], center=true);
for (ymul = [-0.5, 0.5]) {
for (xmul = [-0.5, 0.5]) {
translate([cubeSize[0]*xmul, cubeSize[1]*ymul, 0]) cylinder(h=size[2], r=r, center=true);
}
}
}
}
}
module trs_support() {
intersection() {
cube([10, 30, 12], center=true);
rotate(-45, [0, 1, 0]) cube([10, 30, 12], center=true);
}
}
module usb_c() {
cube([10, 6.5, 3.15], center=true);
translate([0, -3.25, 0]) rotate(90, [0, 1, 0]) cylinder(d=3.15, h=10, center=true);
translate([0, 3.25, 0]) rotate(90, [0, 1, 0]) cylinder(d=3.15, h=10, center=true);
}
module case() {
difference() {
translate((outer_dimensions * z_elevate) + ([0, 0, -1] * cap_thickness)) {
difference() {
roundedcube(outer_dimensions, center=true, r=outer_radius);
roundedcube(inner_dimensions, center=true, r=inner_radius);
translate(-outer_dimensions/2) cube([100, 100, cap_thickness]);
}
}
// USB C port
translate([inner_dimensions[0]/2, 0, 0]) {
translate([0, 0, xiao_elevation - 3.15/2]) usb_c();
cube([10, 9.65, xiao_elevation*2 - 3.15], center=true);
}
}
}
module body() {
difference() {
union() {
case();
// Add some supports
intersection() {
union() {
translate([13, 10, 13]) rotate(60, [0, 0, 1]) trs_support();
translate([13, -10, 13]) rotate(-60, [0, 0, 1]) trs_support();
// Shelf for board to rest against
difference() {
translate([-8, 0, xiao_elevation]) cube([6, 19, 4], center=true);
translate([-6, 0, xiao_elevation]) rotate(45, [0, 0, 1]) cube(15, center=true);
}
}
translate(outer_dimensions * center) roundedcube(outer_dimensions, r=outer_radius);
}
// Eyes
translate([-12, 4, 18]) sphere(r=1.2);
translate([-12, -4, 18]) sphere(r=1.2);
}
// Smirk grin
intersection() {
translate([0, 0, 11]) rotate(90, [0, 1, 0]) {
difference() {
cylinder(h=50, d=20, center=true);
cylinder(h=50, d=19, center=true);
}
}
translate([-18.7, 0, 0]) cube(7);
}
// Channel in the back to make it easier to squeeze in the XIAO
translate([-4.5, 0, xiao_elevation]) intersection() {
cube([13, 100, pcb_thickness], center=true);
roundedcube(inner_dimensions, center=true, r=cap_thickness);
}
// Channels for bottom insertion part thingy
for (i = [-0.5, 0.5]) {
translate([0, inner_dimensions[1]*i, channel_height-channel[2]/2]) {
cube(channel, center=true);
}
}
// Paddle contacts
translate([0, 0, 10]) {
// 8-32x 1/4 screws
rotate(90, [1, 0, 0]) cylinder(h=30, d=4.0, center=true);
// Hookup wire
translate([0, 0, -2.5]) cube([1, 30, 0.8], center=true);
}
// 3.5mm TRS jack
translate([10, 0, 14]) rotate(90, [0, 1, 0]) cylinder(h=3, d=6.0);
// Piezo buzzer
translate([0, 0, 10]) {
translate([-12, 3.5, 0]) cube([5, 1, 1], center=true);
translate([-12, -3.5, 0]) cube([5, 1, 1], center=true);
}
}
}
module cap() {
inner = inner_dimensions * 0.97; // leave 5% slop
union() {
intersection() {
roundedcube(outer_dimensions, center=true, r=outer_radius);
translate([0, 0, cap_thickness/2]) cube([100, 100, cap_thickness], center=true);
}
intersection() {
roundedcube(inner, center=true, r=inner_radius);
translate(center * inner) cube([100, 100, cap_thickness+2.2]);
}
difference() {
union() {
for (i = [-0.5, 0.5]) {
translate([0, inner[1]*i, cap_thickness + channel_height - channel[2]*0.4]) { // 40% of channel z for slop
difference() {
cube(channel, center=true);
translate([0, channel[1]*i, channel[2]/2]) rotate(90*i, [1, 0, 0]) {
cube([channel[0], channel[1], channel[2]], center=true);
}
}
}
}
}
cube([5, 200, 200], center=true);
for (i = [-1, 1]) {
translate([9.5*i, 0, 0]) cube([5, 200, 200], center=true);
}
}
}
}
if (TARGET == "body") {
body();
} else if (TARGET == "cap") {
cap();
} else if (true == true) {
color([0, 0.5, 0.5]) body();
color([0.5, 0, 0.5]) translate([0, 0, -cap_thickness]) cap();
} else {
roundedcube(10);
}

Binary file not shown.

View File

@ -1,521 +0,0 @@
/*
>>> COMPACT 3D-PRINTED PADDLES <<<
Torbjørn Skauli, LA4ZCA (tskauli@gmail.com)
v2.0, December 2018
Iambic paddles designed for 3D-printing. The design is simple, but provides precise movement with adjustable force and travel. Design features include a printed rocker hinge, force adjustment by a sliding spring, travel adjustment using a modified screw, ergonomic grip and general simplicity and precision.
Changes in v2.0: The paddles have been made narrower, and the base is thinner. There is an option to remove the bottom mounting holes. The base extends under the paddles to protect them from being pushed upwards. The cable exit is on the side, to fit mounting on the QCX transceiver.
TODO:
-Arms 1-2 mm higher, spring channel longer towards contacts?
Materials:
- 3 printed parts
- 2 screws M3x5mm, cylinder head, with washers for adjustment if needed
- 1 screw M4 x 18-20mm, cylinder head, with lockwasher and nut
- Compression spring, 6-8 mm in diameter
- Compression spring, 4-5 mm in diameter
- Cable with plug as required, up to 3.5 mm diameter
Note: Nickel plated brass screws have been found to give the most reliable contact operation. Dimensions of screws, springs and cable can be changed in the code.
Assembly:
- First, prepare the 3D-printed parts by removing support material in the arm spring well and in the ends of the cable holes. Also remove any protuding edges and bumps by gently filing the surfaces.
- Place the large spring so that it is held between the paddles approximately in the middle of the spring well. Also place the small spring in the holes at the hinge. Temporarily slide the two paddles in place. Check the spring force on the paddles and adjust as desired by either moving the spring along the well or by bending the spring to change its length. Make sure that the small spring keeps the arms in place at the hinges during use.
- Remove 6-8 cm of the outer isolation (if present) of the cable and 1 cm of the inner isolation of each wire. Insert the cable from the back through the diagonal hole, and temporarily pull it out from the side "window". Insert the cable back into the other hole and press the cable bend into the window so that the outer isolation ends in the interior wiring well. This forms a strain relief.
- Prepare a 18-20 mm M4 screw with cylindrical head by grinding the outer 5 mm to flatten two opposing sides. Preferably align the flattened screw end with the slot in the screw head.
- Enter two M3 screws with cylindrical heads into the paddle arms, with the heads facing inwards. Clamp the dot and dash wire ends under these screw heads.
- Enter the M4 screw from the bottom and clamp the ground wire underneath the lockwasher. Tighten the nut quite firmly, while allowing a small amount of adjustment of the screw angle to set the travel distance.
- Place the spring between the paddles and slide them in place. Adjust travel by rotating the M4 screw. If the travel is asymmetric, it may be necessary to correct the difference by placing a washer under the head of one of the M3 screws in the paddle arms.
*/
//*************** Rendering output control
mountholes=true;//Whether to have mounting holes in bottom
cacc=4; // accuracy of circles, multiplier for $fn. Use cacc=1 for dev/debug, =2-4 for final.
preview=0; // =0 for print layout,
// =1 or =2 for 1- or 2-arm assembly preview,
// =3 for base only
// =4 for arms print layout,
// =5 base+attachment preview
// =6 base modified for attachment to other cabinet
// =7 attachment for inclusion in other cabinet
// =8 rotation stopper for inclusion in other cabinet
//**************** main parameters of the design
wxbase=24; // overall width, sets arm thickness etc.
lybasemin=40; // length excl. knobs (normally 40, made longer below for QCX)
hzarm=20; // height of main part of arm
wallt=2; // wall thikckness
wallmin=2; // min wall thickness under cable holes
dfinger=30; // approx diameter of finger for curved knob. Also knob length.
lyknob=25; // total length of knob
rround=1; // radius of rounded edges on knob
txknobmin=2; // min. thickness of knob
yhinge=6.5; // y position of hinge relative to back
dxwedge=4; // height of hinge wedge
ahinginn=50; // angles for inner and outer part of hinges
ahingout=80;
dstopper=1.5; // diameter of stopper on top of hinge that keeps arm down
armsep=0.75; // arm separation from all walls
dyarmrests=2; // y width of resting and bounding surfaces for arms
minstroke=0.25; // minimum stroke length (at full dia of center screw)
//**************** parameters for non-printed parts
dscrew1=4; // screw dia, also scales screw head height
dscrew2=3; // screw dia, also scales screw head height
hscrhrel=2/3; // screw head height and diam rel to diameter
dscrhrel=7/4;
dzcontact=0.1; // extra height of all screws
dzsprwell=-1; // height adjustment of spring well
dcable=4; // cable diameter
dspring=9.5; // spring diameter
dhingespring=5; // diameter of spring keeping hinge in place
//*************** Parameters for cabinet attachment, needed to make integrated paddle
daxis=6; // Diameter of rotation axis tube
lyflange=22; // Length and height of flange on axis tube
hzflange=13;
atthick=1.5;
rotsnaph=1; // Height of snaps for paddle attachment rotation
rotstop=7; // Size of rotation stopper shelf
//**************** parameters for rendering
gap=0.2; // gap for loose fit
tol=0.025; // general tolerance
nil=0.001; // Negligible distance, to correct rendering
lybase=lybasemin; // length of basee ex. knobs
echo("Length of base (mm):",lybase);
hzwall=hzarm+armsep; // total height of walls
hzbase=2*wallmin+dcable; // height of base under arms
ycontact=lybase-1*dscrew1; // position of contact screw
wxarm=wxbase/2-wallt-armsep-hscrhrel*dscrew2-minstroke-dscrew1/2; // arm thickness is the remaining space after removing many contributions to total width
echo("Total height (mm):",hzwall+hzbase);
echo("Total width for QCX (mm):",wxbase+dcable);
echo("Max length of contact screws in arms (mm):",wxarm);
echo("Min length of contact screw in base (mm):",hzbase-hscrhrel*dscrew1+armsep+hzarm/2+dscrew2*dscrhrel/2);
echo("Min length to flatten contact screw in base (mm):",dscrew2*dscrhrel);
wxknob=wxarm+wallt+armsep+hscrhrel*dscrew2;
dxknob=hscrhrel*dscrew2; // x offset toward center rel to main arm
hzknob=hzarm; // height of knobs
lyarm=lybase-wallt-armsep/2; // knob spaced armsep/2 from base front
y0sprwell=yhinge-wallt-armsep+dxwedge*tan(ahingout/2); // starting pos of spring channel
sprfloort=wallt; // thickness of floor underneath spring
sprlen=wxbase-2*(wallt+armsep+sprfloort); // length of spring
dxtip=wxbase/4; // diameter of rounded tip with attachment hole
dytip=(wxbase-dxtip)/2; // length of tip ex rouned part
rtip=dxtip*sqrt(2)/2; // radius of tip
ycentip=lybase+dytip-rtip/sqrt(2); // center of rounded tip
module teardropHole(lh,rh){ // Hole with 45-degree teardrop shape
rotate([90,0,0])
rotate([0,0,45])
union(){
cylinder(h=lh,r1=rh,r2=rh,$fn=8*cacc);
cube([rh,rh,lh]);
};
};
module snap45(snaph,snapl){ // bumps to snap parts together, max angle 45 degrees
// snapl is length of bump, passed as parameter to allow tolerance
difference(){
rsnap=snaph/(1-1/sqrt(2));
translate([rsnap-snaph,-snapl/2,0])
rotate([-90,0,0])
cylinder(h=snapl,r=rsnap,$fn=5*cacc);
translate([nil,-snapl/2,-rsnap])
cube([2*rsnap,snapl,2*rsnap]);
};
}; // end snap
module wedge(a,wx,hz){ // Equilateral triangular block
//top angle a(deg), length hz and triangle height wx in x direction
linear_extrude(height=hz)
polygon([[0,0],[wx,wx*tan(a/2)],[wx,-wx*tan(a/2)]]);
};
module cone45(dc){ // cone for stopper that keeps arm down, 45-degree slope
rotate([180,0,0])
cylinder(d1=dc,d2=0,h=dc*1.0,$fn=8*cacc);
};
module wedge_hinge(){ //wedge on wall for hinge, with cutout so that arm rests on top and bottom
// cutout for stopper cone
translate([0,0,hzwall])
cone45(dstopper);
// wedge with cutout, centered on the edge between cone and armrest,
// to make arm better supported against up-down tilt
difference(){
wedge(a=ahinginn,wx=dxwedge,hz=hzwall);
translate([-hzarm/2/sqrt(2),hzarm/2,hzarm/2+armsep-dstopper*0.75/2])
rotate([90,0,0])
cylinder(h=hzarm,d=hzarm,$fn=12*cacc);
};
};
module bump45(bumph){ // Spherical bump with at most 45 degree angle
rotate([0,180,0])
difference(){
amax=60; // max overhang angle on bump, not necessarily 45 degerees
rsnap=bumph/(1-cos(amax));
translate([rsnap-bumph,0,0])
sphere(r=rsnap,$fn=5*cacc);
translate([nil,-rsnap,-rsnap])
cube([2*rsnap,2*rsnap,2*rsnap]);
};
};
module rotsnaps(snaph){ // Rotation snaps for paddle
rrot=(hzbase+hzwall)/2*sqrt(2)-snaph/(1-1/sqrt(2))-wallt;
for (a=[45:90:360])
rotate([a,0,0])
translate([0,rrot,0])
bump45(snaph);
};
module base_add(){ // parts of base that add to shape
// base plate
translate([-wxbase/2,0,0])
cube([wxbase,lybase+lyknob-rround,hzbase]);
//rounded front
translate([-wxbase/2+rround,lybase+lyknob-rround,0])
minkowski(){
cube([wxbase-2*rround,tol,hzbase]);
// rounding cy
cylinder(r=rround,h=tol,$fn=cacc*4);
};
// walls
translate([0,0,hzbase])
difference(){
translate([-wxbase/2,0,0])
cube([wxbase,lybase,hzwall]);
translate([-wxbase/2+wallt,wallt,0])
cube([wxbase-2*wallt,lybase,hzwall+tol]);
}
//wedge 1
translate([wxbase/2-wallt-dxwedge,yhinge,hzbase])
wedge_hinge();
//wedge 2
translate([-(wxbase/2-wallt-dxwedge),yhinge,hzbase])
rotate([0,0,180])
wedge_hinge();
// bottom arm resting surface, height armsep above base top
translate([0,yhinge,hzbase])
cube([wxbase-2*wallt-2*dxwedge-2,dyarmrests,2*armsep],center=true);
// front arm lower resting surface, normally with 2*gap airgap
translate([-wxbase/2,lybase-dyarmrests,hzbase])
cube([(wxbase-2*dscrew1)/2,dyarmrests,armsep-2*gap]);
translate([wxbase/2,lybase,hzbase])
rotate([0,0,180])
cube([(wxbase-2*dscrew1)/2,dyarmrests,armsep-2*gap]);
// outer end stops, 2mm wide
translate([-(wxbase/2-wallt),lybase-dyarmrests,hzbase])
cube([armsep,dyarmrests,hzwall]);
translate([(wxbase/2-wallt)-armsep,lybase-dyarmrests,hzbase])
cube([armsep,dyarmrests,hzwall]);
// extra column for center screw stability
translate([0,ycontact,hzbase])
cylinder(d=dscrew1*dscrhrel*1.5,h=dscrew1/2+dzcontact,$fn=8*cacc);
// QCX attachment: add 1x cable dia of wall thickness and rotation axis
if (preview>=5){
// Thicker wall
translate([wxbase/2,0,0])
cube([dcable,lybase,hzbase+hzwall]);
// Rotation stopper
translate([wxbase/2,lybase,0])
cube([dcable,rotstop,hzbase]);
// snaps for paddle rotation
translate([wxbase/2+dcable,lybase/2,(hzbase+hzwall)/2])
rotsnaps(rotsnaph);
};
};
module base_sub(){ // parts of base that cut away from shape
// center contact screw hole
translate([0,ycontact,0])
cylinder(d=dscrew1,h=hzbase*9,$fn=8*cacc);
// center contact screw head recess (20% enlarged) filled in by cylinder to avoid need for support
translate([0,ycontact,0])
cylinder(d=dscrew1*dscrhrel*1.2,h=dscrew1*hscrhrel*1.2,$fn=8*cacc);
// Front mounting screw hole with recess
if (mountholes && preview<5){
translate([0,ycentip,0])
cylinder(d=dscrew1+gap,h=hzbase,$fn=8*cacc);
translate([0,ycentip,wallt])
cylinder(d=dscrew1*dscrhrel*1.2,h=hzbase,$fn=8*cacc);
};
// wire well
wellw=wxbase/2; // wire well width and length
translate([wellw/2,ycontact-2*dscrew1,wallt])
rotate([0,0,180])
cube([wellw,wellw,hzbase]);
// Back mounting screw hole in well
if (mountholes && preview<5){
translate([0,ycontact-2*dscrew1-wellw/2,0])
cylinder(d=dscrew1+gap,h=hzbase,$fn=8*cacc);
};
//cable holes
if (preview<5) // Free standing paddle, no attachment
translate([-(wxbase/2-dcable/4),ycontact-2*dscrew1-wellw-dcable/2,dcable/2+wallmin]){
// cable hole 1
translate([0,-dcable/2,0])
rotate([0,0,90])
teardropHole(lh=wxbase*2,rh=dcable/2);
// cable hole 2
rotate([0,0,45+90])
teardropHole(lh=wxbase/sqrt(2)/2,rh=dcable/2);
//cable access opening
cube([dcable,2*dcable,dcable],center=true);
}
else{ // Cable routing for QCX
// cable hole from well
translate([0,lybase/2+dcable,dcable/2+wallmin])
rotate([0,0,90])
teardropHole(lh=wxbase,rh=dcable/2);
//cable access opening, printable without support, using breakaway wall
for (i=[-1,1])
translate([wxbase/2+0*wallt/2,lybase/2+i*(gap+dcable/2), wallmin]){
cube([dcable,dcable,dcable]);
translate([0,dcable/2,1.5*dcable])rotate([0,90,0])wedge(90,dcable/2,dcable);
};
//vertical cable hole
translate([wxbase/2+dcable-(wallt+dcable)/2,lybase/2,wallmin]){
hhole=hzbase+hzwall/2;
cylinder(d=dcable,h=hhole); // main hole
translate([0,0,hhole])
cylinder(d1=dcable,d2=0,h=dcable); // tapered top to avoid support
};
// rotation axis
translate([wxbase/2-dcable,lybase/2,(hzbase+hzwall)/2]){
// Axis hole
rotate([0,90,0])
cylinder(d=daxis+gap,h=99,$fn=8*cacc);
// Rotation snap
};
// Room for square flange soldered onto rotation axis
translate([wxbase/2-wallt,(lybase-lyflange)/2,(hzbase+hzwall-hzflange)/2]){
cube([dcable,lyflange,hzflange]);
translate([-tol,0,hzflange-tol])
rotate([90,-135,180])
wedge(90,dcable/sqrt(2),lyflange);
};
}; // End QCX attachment
};
module base(){ // complete base part
difference(){
base_add();
base_sub();
};
};
module attachment(){ // attachment that can be included in QCX (or other) cabinet
difference(){
union(){
// Attachment part of wall
translate([0,-2*rotstop,0])
cube([atthick,2*rotstop+lybase,hzbase+hzwall]);
}; // end union
// Axis hole
translate([0,lybase/2,(hzbase+hzwall)/2])
rotate([0,90,0])
cylinder(d=daxis+gap,h=99,$fn=8*cacc);
// snaps for paddle rotation, slightly tight to avoid play
translate([0,lybase/2,(hzbase+hzwall)/2])
rotsnaps(rotsnaph-gap/3);
}; // end difference
};
module rotlimit(){ // Rotation stopper for attachment, to be included in cabinet
limdim=wallt+dcable; // width of stopper cube, equal to stopper on paddle base
// stopper knob
translate([-dcable,lybase/2-limdim/2,(hzbase+hzwall)/2-limdim/2])// move to hole
rotate([0*30,0,0]) // rotate if desired
translate([0,-lybase/2-limdim/2-rotstop,(hzbase+hzwall)/2-limdim/2])
cube([dcable,limdim,limdim]);
};
module rotlimitEXPORT(){ // Rotation stopper for attachment, to be included in cabinet
translate([0,-lybase,-hzbase-hzwall])
rotlimit();
};
module attachmentEXPORT(){ // Rotation stopper for attachment, to be included in cabinet
translate([0,-lybase,-hzbase-hzwall])
attachment();
};
function attachHeightEXPORT()=hzbase+hzwall; // Export total height
module arm_add(){ // arm base shape, without knob
// main arm
cube([wxarm,lyarm,hzarm]);
// extra material for supporting hinge spring
translate([-dxknob,0,0])
cube([dxknob,y0sprwell,hzarm]);
// extra material near top of arm, for stiffness and appearance
hzarmextra=hzarm/2-dzcontact-(dscrew2*dscrhrel*1.3);
translate([-dxknob,0,hzarm-(hzarmextra-dxknob)]){
cube([dxknob,lyarm,hzarmextra-dxknob]);
// 45 deg underside of extra material, to avoid generation of support
translate([dxknob,0,0])
rotate([90,45,180])
wedge(90,dxknob/sqrt(2),lyarm);
}
};
module arm_sub(){ // arm shaping
// hinge groove
translate([wxarm-dxwedge+armsep,yhinge-wallt-armsep,0])
wedge(a=ahingout,wx=dxwedge,hz=hzarm);
// hinge stopper cutout
translate([wxarm-dxwedge+armsep,yhinge-wallt-armsep,hzarm])
cone45(dstopper+2*gap);
// tension spring channel
translate([0,y0sprwell,(hzarm-dspring)/2+dzsprwell]){
cube([wxarm-sprfloort,ycontact-y0sprwell-2.5*dscrew1,dspring]);
translate([-tol,0,dspring-tol])
rotate([90,-135,180])
wedge(90,(wxarm-sprfloort)/sqrt(2),ycontact-y0sprwell-2.5*dscrew1);
};
// contact screw hole
translate([-tol,ycontact-wallt-armsep,hzarm/2+dzcontact])
rotate([0,0,90])
teardropHole(lh=wxarm*2,rh=dscrew2/2-gap);
// hole for spring keeping hinge in place
translate([-dxknob-tol,yhinge-wallt-armsep,hzarm/2])
rotate([0,0,90])
teardropHole(lh=dxknob+wxarm-dxwedge+armsep-sprfloort+tol,rh=dhingespring/2);
// extra space for center screw column
translate([-(wxbase/2-wallt-armsep-wxarm),ycontact-wallt-armsep,0])
cylinder(d=dscrew1*dscrhrel*1.5+2*minstroke+dscrew1,h=dscrew1/2+dzcontact+dscrew1*hscrhrel*1.25,$fn=8*cacc);
};
module knob_curved(){ // finger-curved and rounded knob
intersection(){ // cutting to outer shape
minkowski(){ // rounding of edges
// un-rounded knob shrunk by rounding radius
difference(){ // shaping finger rest
// knob, to be shaped by subtraction
translate([-dxknob,lyarm,0])
cube([wxknob-rround,lyknob-rround,hzknob-rround]);
// shaping of knob
translate([-dxknob+txknobmin+dfinger/2,lyarm+0*wallt+dfinger/2,dfinger/2+0*wallt])
minkowski(){
cube([tol,wxbase,wxbase]);
sphere(r=dfinger/2,$fn=8*cacc);
};
};
// rounding sphere
sphere(r=rround,$fn=cacc*4);
};
// outer bound of knob
translate([-dxknob,lyarm,0])
cube([wxknob,dfinger,hzknob]);
};
};
module arm(){ // Generate final shape according to preview and cacc settings
difference(){
union(){
knob_curved();
arm_add();
};
arm_sub();
};
};
module build_all(){
if(preview==1){
base();
translate([wxbase/2-wallt-armsep-wxarm,wallt+armsep,hzbase+armsep])
arm();
}
else if (preview==2){
base();
translate([wxbase/2-wallt-armsep-wxarm,wallt+armsep,hzbase+armsep])
arm();
translate([-(wxbase/2-wallt-armsep-wxarm),wallt+armsep,hzbase+armsep])
scale([-1,1,1])
arm();
}
else if (preview==3){
base();
}
else if (preview==4){
translate([wxbase/2+wxknob+1,0,0])
scale([-1,1,1])
arm();
translate([wxbase/2+2*wxknob+2,0,0])
arm();
}
else if (preview==5){
base();
translate([wxbase/2+dcable+10,0,0]){ // spacing for rendering, for preview of fit
attachment();
rotlimit();
};
}
else if (preview==6){ // Base with additions for attachment
base();
}
else if (preview==7){ // Attachment for inclusion in cabinet
attachmentEXPORT();
}
else if (preview==8){ // Rotation stopper for inclusion in cabinet
rotlimitEXPORT();
}
else if (preview==9){ // Rotation stopper for inclusion in cabinet
attachmentEXPORT();
rotlimitEXPORT();
}
else{
base();
translate([wxbase/2+wxknob+1,0,0])
scale([-1,1,1])
arm();
translate([wxbase/2+2*wxknob+2,0,0])
arm();
};
};
build_all();

Binary file not shown.

55
doc/MIDI.md Normal file
View File

@ -0,0 +1,55 @@
# Vail MIDI Protocol
When it boots,
the Vail adapter sends left and right Control keyboard key up and down events.
It also shows up as a MIDI device.
The Vail web site sends MIDI control commands to enable MIDI keyer mode,
tells the keyer what sideband pitch to generate,
and can set the keyer mode.
## Controller 0 - MIDI Mode
`b0 00 ff` will enable MIDI mode and disable Keyboard mode
`b0 00 00` will enable Keyboard mode and disable MIDI mode
## Controller 1 - dit length
`b0 00 xx` will set the dit duration to `xx` times 2 milliseconds
## Controller 2 - sidetone note
`b0 00 xx` will play note `xx` as the sidetone note
## Program Change
`c0 xx` will change the keyer mode to `xx`.
### Keyer Modes
* 0: passthrough (sends C# and D for dit and dah)
* 1: cootie / straight key
* 2: bug
* 3: electric bug
* 4: single dot
* 5: ultimatic
* 6: plain iambic
* 7: iambic a
* 8: iambic b
* 9: keyahead
Any other mode will set to passthrough.
## Notes (key down / key up)
`90 00 xx` will begin playing note `xx`
`80 00 xx` will end playing note `xx`
These work just like a regular MIDI synthesizer.

View File

@ -11,8 +11,8 @@ Then compile and upload the sketch.
## Works with no source code changes
* Seeeduino Xiao
* Adafruit Qt Py
* Seeeduino Xiao SAMD21
* Adafruit Qt Py SAMD21
## Known to work with source code changes
@ -24,6 +24,18 @@ Then compile and upload the sketch.
* Adafruit GEMMA M0
* Adafruit Feather M0
## Will Not Work!
The RP2040 chip will not work, because it lacks a USB MCU,
needed by the MIDIUSB library.
I'm listing specific devices here
in the hopes that seeing them crossed out will prevent people from
making a purchasing mistake!
* ~~Seeeduino Xiao RP2040~~ Will not work!
* ~~Adafruit Qt Pi RP2040~~ Will not work!
* ~~Any RP2040 Device~~ Will not work!
# Advanced Wiring

View File

@ -1,10 +1,10 @@
# Easy Installation
1. Get a Seeeduino XIAO
2. Download the most recent xiao firmware from
1. Get a Seeeduino XIAO SAMD21
2. Download the most recent XIAO SAMD21 firmware from
[releases](https://github.com/nealey/vail-adapter/releases)
3. [Enter bootloader mode](https://wiki.seeedstudio.com/Seeeduino-XIAO/#enter-bootloader-mode)
4. Copy the firmware onto the XIAO
4. Copy the firmware onto the XIAO SAMD21
[Wideo walkthrough of firmware upload](https://www.youtube.com/watch?v=IgOdkUe5SMY) (3:07)

View File

@ -1,24 +0,0 @@
# MIDI Negotiation
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.
The Vail adapter boots into a mode that sends both keyboard events
and MIDI messages.
If it receives a MIDI key release event
on channel 0
for note C0,
it will disable keyboard events.
Vail sends this "disable keyboard" MIDI event, so as soon as you
load up Vail, the keyboard events are disabled, and your adapter
will no longer interfere with your typing.
If your browser doesn't support MIDI,
the disable command can't be sent,
and it keeps on sending keystrokes.

132
equal_temperament.h Normal file
View File

@ -0,0 +1,132 @@
#pragma once
const int equalTemperamentNote[] = {
8, // 0
8, // 1
9, // 2
9, // 3
10, // 4
10, // 5
11, // 6
12, // 7
12, // 8
13, // 9
14, // 10
15, // 11
16, // 12
17, // 13
18, // 14
19, // 15
20, // 16
21, // 17
23, // 18
24, // 19
25, // 20
27, // 21
29, // 22
30, // 23
32, // 24
34, // 25
36, // 26
38, // 27
41, // 28
43, // 29
46, // 30
49, // 31
51, // 32
55, // 33
58, // 34
61, // 35
65, // 36
69, // 37
73, // 38
77, // 39
82, // 40
87, // 41
92, // 42
98, // 43
103, // 44
110, // 45
116, // 46
123, // 47
130, // 48
138, // 49
146, // 50
155, // 51
164, // 52
174, // 53
185, // 54
196, // 55
207, // 56
220, // 57
233, // 58
247, // 59
261, // 60
277, // 61
293, // 62
311, // 63
329, // 64
349, // 65
370, // 66
392, // 67
415, // 68
440, // 69
466, // 70
494, // 71
523, // 72
554, // 73
587, // 74
622, // 75
659, // 76
698, // 77
740, // 78
784, // 79
831, // 80
880, // 81
932, // 82
988, // 83
1047, // 84
1109, // 85
1175, // 86
1245, // 87
1319, // 88
1397, // 89
1480, // 90
1568, // 91
1662, // 92
1760, // 93
1865, // 94
1976, // 95
2094, // 96
2218, // 97
2350, // 98
2490, // 99
2638, // 100
2795, // 101
2961, // 102
3137, // 103
3324, // 104
3521, // 105
3731, // 106
3953, // 107
4188, // 108
4437, // 109
4701, // 110
4980, // 111
5276, // 112
5590, // 113
5922, // 114
6275, // 115
6648, // 116
7043, // 117
7462, // 118
7906, // 119
8376, // 120
8874, // 121
9402, // 122
9961, // 123
10553, // 124
11181, // 125
11845, // 126
12550, // 127
};

405
keyers.cpp Normal file
View File

@ -0,0 +1,405 @@
#include <stddef.h>
#include "keyers.h"
#define len(t) (sizeof(t)/sizeof(*t))
// Queue Set: A Set you can shift and pop.
class QSet {
int arr[MAX_KEYER_QUEUE];
unsigned int arrlen = 0;
public:
int shift() {
if (arrlen == 0) {
return -1;
}
int ret = arr[0];
arrlen--;
for (int i = 0; i < arrlen; i++) {
arr[i] = arr[i+1];
}
return ret;
}
int pop() {
if (arrlen == 0) {
return -1;
}
int ret = arr[arrlen];
arrlen--;
return ret;
}
void add(int val) {
if (arrlen == MAX_KEYER_QUEUE) {
return;
}
for (int i = 0; i < arrlen; i++) {
if (arr[i] == val) {
return;
}
}
arr[arrlen] = val;
arrlen++;
}
};
class StraightKeyer: public Keyer {
public:
Transmitter *output;
unsigned int ditDuration;
bool txRelays[2];
StraightKeyer() {
this->Reset();
}
void SetOutput(Transmitter *output) {
this->output = output;
}
void Reset() {
if (this->output) {
this->output->EndTx();
}
this->ditDuration = 100;
}
void SetDitDuration(unsigned int duration) {
this->ditDuration = duration;
}
void Release() {
this->Reset();
}
bool TxClosed() {
for (int i = 0; i < len(this->txRelays); i++) {
if (this->TxClosed(i)) {
return true;
}
}
return false;
}
bool TxClosed(int relay) {
return this->txRelays[relay];
}
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();
}
}
}
void Key(Paddle key, bool pressed) {
this->Tx(key, pressed);
}
void Tick(unsigned int millis) {};
};
class BugKeyer: public StraightKeyer {
public:
unsigned int nextPulse = 0;
bool keyPressed[2];
using StraightKeyer::StraightKeyer;
void Reset() {
StraightKeyer::Reset();
this->nextPulse = 0;
this->keyPressed[0] = false;
this->keyPressed[1] = false;
}
void Key(Paddle key, bool pressed) {
this->keyPressed[key] = pressed;
if (key == 0) {
this->beginPulsing();
} else {
StraightKeyer::Key(key, pressed);
}
}
void Tick(unsigned int millis) {
if (this->nextPulse && (millis >= this->nextPulse)) {
this->pulse(millis);
}
}
void beginPulsing() {
if (!this->nextPulse) {
this->nextPulse = 1;
}
}
virtual void pulse(unsigned int millis) {
if (this->TxClosed(0)) {
this->Tx(0, false);
} else if (this->keyPressed[0]) {
this->Tx(0, true);
} else {
this->nextPulse = 0;
return;
}
this->nextPulse = millis + this->ditDuration;
}
};
class ElBugKeyer: public BugKeyer {
public:
unsigned int nextRepeat;
using BugKeyer::BugKeyer;
void Reset() {
BugKeyer::Reset();
this->nextRepeat = -1;
}
// Return which key is pressed. If none, return -1.
int whichKeyPressed() {
for (int i = 0; i < len(this->keyPressed); i++) {
if (this->keyPressed[i]) {
return i;
}
}
return -1;
}
void Key(Paddle key, bool pressed) {
this->keyPressed[key] = pressed;
if (pressed) {
this->nextRepeat = key;
this->beginPulsing();
} else {
this->nextRepeat = this->whichKeyPressed();
}
}
unsigned int keyDuration(int key) {
switch (key) {
case PADDLE_DIT:
return this->ditDuration;
case PADDLE_DAH:
return 3 * (this->ditDuration);
}
return this->ditDuration; // XXX
}
virtual int nextTx() {
if (this->whichKeyPressed() == -1) {
return -1;
}
return this->nextRepeat;
}
virtual void pulse(unsigned int millis) {
int nextPulse = 0;
if (this->TxClosed(0)) {
// Pause if we're currently transmitting
nextPulse = this->keyDuration(PADDLE_DIT);
this->Tx(0, false);
} else {
int next = this->nextTx();
if (next >= 0) {
nextPulse = this->keyDuration(next);
this->Tx(0, true);
}
}
if (nextPulse) {
this->nextPulse = millis + nextPulse;
} else {
this->nextPulse = 0;
}
}
};
class UltimaticKeyer: public ElBugKeyer {
public:
QSet queue;
using ElBugKeyer::ElBugKeyer;
void Key(Paddle key, bool pressed) {
if (pressed) {
this->queue.add(key);
}
ElBugKeyer::Key(key, pressed);
}
virtual int nextTx() {
int key = this->queue.shift();
if (key != -1) {
return key;
}
return ElBugKeyer::nextTx();
}
};
class SingleDotKeyer: public ElBugKeyer {
public:
QSet queue;
using ElBugKeyer::ElBugKeyer;
void Key(Paddle key, bool pressed) {
if (pressed && (key == PADDLE_DIT)) {
this->queue.add(key);
}
ElBugKeyer::Key(key, pressed);
}
virtual int nextTx() {
int key = this->queue.shift();
if (key != -1) {
return key;
}
if (this->keyPressed[1]) return 1;
if (this->keyPressed[0]) return 0;
return -1;
}
};
class IambicKeyer: public ElBugKeyer {
public:
using ElBugKeyer::ElBugKeyer;
virtual int nextTx() {
int next = ElBugKeyer::nextTx();
if (this->keyPressed[PADDLE_DIT] && this->keyPressed[PADDLE_DAH]) {
this->nextRepeat = 1 - this->nextRepeat;
}
return next;
}
};
class IambicAKeyer: public IambicKeyer {
public:
QSet queue;
using IambicKeyer::IambicKeyer;
void Key(Paddle key, bool pressed) {
if (pressed && (key == PADDLE_DIT)) {
this->queue.add(key);
}
IambicKeyer::Key(key, pressed);
}
virtual int nextTx() {
int next = IambicKeyer::nextTx();
int key = this->queue.shift();
if (key != -1) {
return key;
}
return next;
}
};
class IambicBKeyer: public IambicKeyer {
public:
QSet queue;
using IambicKeyer::IambicKeyer;
void Reset() {
IambicKeyer::Reset();
}
void Key(Paddle key, bool pressed) {
if (pressed) {
this->queue.add(key);
}
IambicKeyer::Key(key, pressed);
}
virtual int nextTx() {
for (int key = 0; key < len(this->keyPressed); key++) {
if (this->keyPressed[key]) {
this->queue.add(key);
}
}
return this->queue.shift();
}
};
class KeyaheadKeyer: public ElBugKeyer {
public:
int queue[MAX_KEYER_QUEUE];
unsigned int qlen;
using ElBugKeyer::ElBugKeyer;
void Reset() {
ElBugKeyer::Reset();
this->qlen = 0;
}
void Key(Paddle key, bool pressed) {
if (pressed) {
if (this->qlen < MAX_KEYER_QUEUE) {
this->queue[this->qlen++] = key;
}
}
ElBugKeyer::Key(key, pressed);
}
virtual int nextTx() {
if (this->qlen > 0) {
int next = this->queue[0];
this->qlen--;
for (int i = 0; i < this->qlen; i++) {
this->queue[i] = this->queue[i+1];
}
return next;
}
return ElBugKeyer::nextTx();
}
};
StraightKeyer straightKeyer = StraightKeyer();
BugKeyer bugKeyer = BugKeyer();
ElBugKeyer elBugKeyer = ElBugKeyer();
SingleDotKeyer singleDotKeyer = SingleDotKeyer();
UltimaticKeyer ultimaticKeyer = UltimaticKeyer();
IambicKeyer iambicKeyer = IambicKeyer();
IambicAKeyer iambicAKeyer = IambicAKeyer();
IambicBKeyer iambicBKeyer = IambicBKeyer();
KeyaheadKeyer keyaheadKeyer = KeyaheadKeyer();
Keyer *keyers[] = {
NULL,
&straightKeyer,
&bugKeyer,
&elBugKeyer,
&singleDotKeyer,
&ultimaticKeyer,
&iambicKeyer,
&iambicAKeyer,
&iambicBKeyer,
&keyaheadKeyer,
};
Keyer *GetKeyerByNumber(int n, Transmitter *output) {
if (n >= len(keyers)) {
return NULL;
}
Keyer *k = keyers[n];
k->SetOutput(output);
return k;
}

30
keyers.h Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#define MAX_KEYER_QUEUE 5
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);
};
Keyer *GetKeyerByNumber(int n, Transmitter *output);

87438
pcb/fp-info-cache Normal file

File diff suppressed because it is too large Load Diff

3
pcb/fp-lib-table Normal file
View File

@ -0,0 +1,3 @@
(fp_lib_table
(lib (name "Seeeduino XIAO")(type "KiCad")(uri "/home/dartcatcher/.var/app/org.kicad.KiCad/data/kicad/6.0/footprints/Seeeduino XIAO KICAD")(options "")(descr ""))
)

353757
pcb/pcb.kicad_pcb Normal file

File diff suppressed because it is too large Load Diff

83
pcb/pcb.kicad_prl Normal file
View File

@ -0,0 +1,83 @@
{
"board": {
"active_layer": 33,
"active_layer_preset": "",
"auto_track_width": true,
"hidden_netclasses": [],
"hidden_nets": [],
"high_contrast_mode": 0,
"net_color_mode": 1,
"opacity": {
"images": 0.6,
"pads": 1.0,
"tracks": 1.0,
"vias": 1.0,
"zones": 0.6
},
"ratsnest_display_mode": 0,
"selection_filter": {
"dimensions": true,
"footprints": true,
"graphics": true,
"keepouts": true,
"lockedItems": true,
"otherItems": true,
"pads": true,
"text": true,
"tracks": true,
"vias": true,
"zones": true
},
"visible_items": [
0,
1,
2,
3,
4,
5,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
32,
33,
34,
35,
36
],
"visible_layers": "fffffff_ffffffff",
"zone_display_mode": 0
},
"git": {
"repo_password": "",
"repo_type": "",
"repo_username": "",
"ssh_key": ""
},
"meta": {
"filename": "pcb.kicad_prl",
"version": 3
},
"project": {
"files": []
}
}

652
pcb/pcb.kicad_pro Normal file
View File

@ -0,0 +1,652 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.09999999999999999,
"copper_line_width": 0.19999999999999998,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.049999999999999996,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": false,
"text_position": 0,
"units_format": 1
},
"fab_line_width": 0.09999999999999999,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.15,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.4,
"height": 2.2,
"width": 1.2
},
"silk_line_width": 0.15,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.15,
"silk_text_upright": false,
"zones": {
"45_degree_only": false,
"min_clearance": 0.508
}
},
"diff_pair_dimensions": [
{
"gap": 0.0,
"via_gap": 0.0,
"width": 0.0
}
],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "error",
"hole_clearance": "error",
"hole_near_hole": "error",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "error",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_dangling": "warning",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zone_has_empty_net": "error",
"zones_intersect": "error"
},
"rules": {
"allow_blind_buried_vias": false,
"allow_microvias": false,
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.19999999999999998,
"min_microvia_drill": 0.09999999999999999,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.7999999999999999,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.19999999999999998,
"min_via_annular_width": 0.049999999999999996,
"min_via_diameter": 0.39999999999999997,
"solder_mask_clearance": 0.0,
"solder_mask_min_width": 0.0,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpadsmd": true,
"td_onroundshapesonly": false,
"td_ontrackend": false,
"td_onviapad": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [
0.0
],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [
{
"diameter": 0.0,
"drill": 0.0
}
],
"zones_allow_external_fillets": false,
"zones_use_no_outline": true
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_label_syntax": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"conflicting_netclasses": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_dangling": "error",
"lib_symbol_issues": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"similar_labels": "warning",
"simulation_model_issue": "ignore",
"unannotated": "error",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "pcb.kicad_pro",
"version": 1
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.5,
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 6
},
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "I2C",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.5,
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 6
},
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Power",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.8,
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 6
}
],
"meta": {
"version": 3
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": [
{
"netclass": "Power",
"pattern": "GND"
}
]
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
},
{
"group_by": false,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
}
],
"filter_string": "",
"group_symbols": true,
"name": "Grouped By Value",
"sort_asc": true,
"sort_field": "Reference"
},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"ngspice": {
"fix_include_paths": true,
"fix_passive_vals": false,
"meta": {
"version": 0
},
"model_mode": 0,
"workbook_filename": ""
},
"page_layout_descr_file": "",
"plot_directory": "",
"spice_adjust_passive_values": false,
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
[
"bc199109-bc84-4207-bd79-7d43ff8dd6f2",
"Root"
]
],
"text_variables": {}
}

2273
pcb/pcb.kicad_sch Normal file

File diff suppressed because it is too large Load Diff

559
pcb/pcb.kicad_sch-bak Normal file
View File

@ -0,0 +1,559 @@
(kicad_sch (version 20211123) (generator eeschema)
(uuid bc199109-bc84-4207-bd79-7d43ff8dd6f2)
(paper "A4")
(lib_symbols
(symbol "Connector:AudioJack3" (in_bom yes) (on_board yes)
(property "Reference" "J" (id 0) (at 0 8.89 0)
(effects (font (size 1.27 1.27)))
)
(property "Value" "AudioJack3" (id 1) (at 0 6.35 0)
(effects (font (size 1.27 1.27)))
)
(property "Footprint" "" (id 2) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "~" (id 3) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_keywords" "audio jack receptacle stereo headphones phones TRS connector" (id 4) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_description" "Audio Jack, 3 Poles (Stereo / TRS)" (id 5) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_fp_filters" "Jack*" (id 6) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(symbol "AudioJack3_0_1"
(rectangle (start -5.08 -5.08) (end -6.35 -2.54)
(stroke (width 0.254) (type default) (color 0 0 0 0))
(fill (type outline))
)
(polyline
(pts
(xy 0 -2.54)
(xy 0.635 -3.175)
(xy 1.27 -2.54)
(xy 2.54 -2.54)
)
(stroke (width 0.254) (type default) (color 0 0 0 0))
(fill (type none))
)
(polyline
(pts
(xy -1.905 -2.54)
(xy -1.27 -3.175)
(xy -0.635 -2.54)
(xy -0.635 0)
(xy 2.54 0)
)
(stroke (width 0.254) (type default) (color 0 0 0 0))
(fill (type none))
)
(polyline
(pts
(xy 2.54 2.54)
(xy -2.54 2.54)
(xy -2.54 -2.54)
(xy -3.175 -3.175)
(xy -3.81 -2.54)
)
(stroke (width 0.254) (type default) (color 0 0 0 0))
(fill (type none))
)
(rectangle (start 2.54 3.81) (end -5.08 -5.08)
(stroke (width 0.254) (type default) (color 0 0 0 0))
(fill (type background))
)
)
(symbol "AudioJack3_1_1"
(pin passive line (at 5.08 0 180) (length 2.54)
(name "~" (effects (font (size 1.27 1.27))))
(number "R" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 5.08 2.54 180) (length 2.54)
(name "~" (effects (font (size 1.27 1.27))))
(number "S" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 5.08 -2.54 180) (length 2.54)
(name "~" (effects (font (size 1.27 1.27))))
(number "T" (effects (font (size 1.27 1.27))))
)
)
)
(symbol "Connector_Generic:Conn_01x03" (pin_names (offset 1.016) hide) (in_bom yes) (on_board yes)
(property "Reference" "J" (id 0) (at 0 5.08 0)
(effects (font (size 1.27 1.27)))
)
(property "Value" "Conn_01x03" (id 1) (at 0 -5.08 0)
(effects (font (size 1.27 1.27)))
)
(property "Footprint" "" (id 2) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "~" (id 3) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_keywords" "connector" (id 4) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_description" "Generic connector, single row, 01x03, script generated (kicad-library-utils/schlib/autogen/connector/)" (id 5) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_fp_filters" "Connector*:*_1x??_*" (id 6) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(symbol "Conn_01x03_1_1"
(rectangle (start -1.27 -2.413) (end 0 -2.667)
(stroke (width 0.1524) (type default) (color 0 0 0 0))
(fill (type none))
)
(rectangle (start -1.27 0.127) (end 0 -0.127)
(stroke (width 0.1524) (type default) (color 0 0 0 0))
(fill (type none))
)
(rectangle (start -1.27 2.667) (end 0 2.413)
(stroke (width 0.1524) (type default) (color 0 0 0 0))
(fill (type none))
)
(rectangle (start -1.27 3.81) (end 1.27 -3.81)
(stroke (width 0.254) (type default) (color 0 0 0 0))
(fill (type background))
)
(pin passive line (at -5.08 2.54 0) (length 3.81)
(name "Pin_1" (effects (font (size 1.27 1.27))))
(number "1" (effects (font (size 1.27 1.27))))
)
(pin passive line (at -5.08 0 0) (length 3.81)
(name "Pin_2" (effects (font (size 1.27 1.27))))
(number "2" (effects (font (size 1.27 1.27))))
)
(pin passive line (at -5.08 -2.54 0) (length 3.81)
(name "Pin_3" (effects (font (size 1.27 1.27))))
(number "3" (effects (font (size 1.27 1.27))))
)
)
)
(symbol "Device:Speaker" (pin_names (offset 0) hide) (in_bom yes) (on_board yes)
(property "Reference" "LS" (id 0) (at 1.27 5.715 0)
(effects (font (size 1.27 1.27)) (justify right))
)
(property "Value" "Speaker" (id 1) (at 1.27 3.81 0)
(effects (font (size 1.27 1.27)) (justify right))
)
(property "Footprint" "" (id 2) (at 0 -5.08 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "~" (id 3) (at -0.254 -1.27 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_keywords" "speaker sound" (id 4) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_description" "Speaker" (id 5) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(symbol "Speaker_0_0"
(rectangle (start -2.54 1.27) (end 1.016 -3.81)
(stroke (width 0.254) (type default) (color 0 0 0 0))
(fill (type none))
)
(polyline
(pts
(xy 1.016 1.27)
(xy 3.556 3.81)
(xy 3.556 -6.35)
(xy 1.016 -3.81)
)
(stroke (width 0.254) (type default) (color 0 0 0 0))
(fill (type none))
)
)
(symbol "Speaker_1_1"
(pin input line (at -5.08 0 0) (length 2.54)
(name "1" (effects (font (size 1.27 1.27))))
(number "1" (effects (font (size 1.27 1.27))))
)
(pin input line (at -5.08 -2.54 0) (length 2.54)
(name "2" (effects (font (size 1.27 1.27))))
(number "2" (effects (font (size 1.27 1.27))))
)
)
)
(symbol "Seeeduino XIAO:SeeeduinoXIAO" (pin_names (offset 1.016)) (in_bom yes) (on_board yes)
(property "Reference" "U" (id 0) (at -19.05 22.86 0)
(effects (font (size 1.27 1.27)))
)
(property "Value" "SeeeduinoXIAO" (id 1) (at -12.7 21.59 0)
(effects (font (size 1.27 1.27)))
)
(property "Footprint" "" (id 2) (at -8.89 5.08 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "" (id 3) (at -8.89 5.08 0)
(effects (font (size 1.27 1.27)) hide)
)
(symbol "SeeeduinoXIAO_0_1"
(rectangle (start -19.05 20.32) (end 17.78 -19.05)
(stroke (width 0) (type default) (color 0 0 0 0))
(fill (type none))
)
)
(symbol "SeeeduinoXIAO_1_1"
(pin unspecified line (at -21.59 11.43 0) (length 2.54)
(name "PA02_A0_D0" (effects (font (size 1.27 1.27))))
(number "1" (effects (font (size 1.27 1.27))))
)
(pin unspecified line (at 20.32 -3.81 180) (length 2.54)
(name "PA5_A9_D9_MISO" (effects (font (size 1.27 1.27))))
(number "10" (effects (font (size 1.27 1.27))))
)
(pin unspecified line (at 20.32 0 180) (length 2.54)
(name "PA6_A10_D10_MOSI" (effects (font (size 1.27 1.27))))
(number "11" (effects (font (size 1.27 1.27))))
)
(pin unspecified line (at 20.32 3.81 180) (length 2.54)
(name "3V3" (effects (font (size 1.27 1.27))))
(number "12" (effects (font (size 1.27 1.27))))
)
(pin unspecified line (at 20.32 7.62 180) (length 2.54)
(name "GND" (effects (font (size 1.27 1.27))))
(number "13" (effects (font (size 1.27 1.27))))
)
(pin unspecified line (at 20.32 11.43 180) (length 2.54)
(name "5V" (effects (font (size 1.27 1.27))))
(number "14" (effects (font (size 1.27 1.27))))
)
(pin input line (at -2.54 -21.59 90) (length 2.54)
(name "5V" (effects (font (size 1.27 1.27))))
(number "15" (effects (font (size 1.27 1.27))))
)
(pin input line (at 2.54 -21.59 90) (length 2.54)
(name "GND" (effects (font (size 1.27 1.27))))
(number "16" (effects (font (size 1.27 1.27))))
)
(pin input line (at -5.08 22.86 270) (length 2.54)
(name "PA31_SWDIO" (effects (font (size 1.27 1.27))))
(number "17" (effects (font (size 1.27 1.27))))
)
(pin input line (at -1.27 22.86 270) (length 2.54)
(name "PA30_SWCLK" (effects (font (size 1.27 1.27))))
(number "18" (effects (font (size 1.27 1.27))))
)
(pin input line (at 2.54 22.86 270) (length 2.54)
(name "RESET" (effects (font (size 1.27 1.27))))
(number "19" (effects (font (size 1.27 1.27))))
)
(pin unspecified line (at -21.59 7.62 0) (length 2.54)
(name "PA4_A1_D1" (effects (font (size 1.27 1.27))))
(number "2" (effects (font (size 1.27 1.27))))
)
(pin input line (at 6.35 22.86 270) (length 2.54)
(name "GND" (effects (font (size 1.27 1.27))))
(number "20" (effects (font (size 1.27 1.27))))
)
(pin unspecified line (at -21.59 3.81 0) (length 2.54)
(name "PA10_A2_D2" (effects (font (size 1.27 1.27))))
(number "3" (effects (font (size 1.27 1.27))))
)
(pin unspecified line (at -21.59 0 0) (length 2.54)
(name "PA11_A3_D3" (effects (font (size 1.27 1.27))))
(number "4" (effects (font (size 1.27 1.27))))
)
(pin unspecified line (at -21.59 -3.81 0) (length 2.54)
(name "PA8_A4_D4_SDA" (effects (font (size 1.27 1.27))))
(number "5" (effects (font (size 1.27 1.27))))
)
(pin unspecified line (at -21.59 -7.62 0) (length 2.54)
(name "PA9_A5_D5_SCL" (effects (font (size 1.27 1.27))))
(number "6" (effects (font (size 1.27 1.27))))
)
(pin unspecified line (at -21.59 -11.43 0) (length 2.54)
(name "PB08_A6_D6_TX" (effects (font (size 1.27 1.27))))
(number "7" (effects (font (size 1.27 1.27))))
)
(pin unspecified line (at 20.32 -11.43 180) (length 2.54)
(name "PB09_A7_D7_RX" (effects (font (size 1.27 1.27))))
(number "8" (effects (font (size 1.27 1.27))))
)
(pin unspecified line (at 20.32 -7.62 180) (length 2.54)
(name "PA7_A8_D8_SCK" (effects (font (size 1.27 1.27))))
(number "9" (effects (font (size 1.27 1.27))))
)
)
)
(symbol "power:GND" (power) (pin_names (offset 0)) (in_bom yes) (on_board yes)
(property "Reference" "#PWR" (id 0) (at 0 -6.35 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Value" "GND" (id 1) (at 0 -3.81 0)
(effects (font (size 1.27 1.27)))
)
(property "Footprint" "" (id 2) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "" (id 3) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_keywords" "power-flag" (id 4) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_description" "Power symbol creates a global label with name \"GND\" , ground" (id 5) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(symbol "GND_0_1"
(polyline
(pts
(xy 0 0)
(xy 0 -1.27)
(xy 1.27 -1.27)
(xy 0 -2.54)
(xy -1.27 -1.27)
(xy 0 -1.27)
)
(stroke (width 0) (type default) (color 0 0 0 0))
(fill (type none))
)
)
(symbol "GND_1_1"
(pin power_in line (at 0 0 270) (length 0) hide
(name "GND" (effects (font (size 1.27 1.27))))
(number "1" (effects (font (size 1.27 1.27))))
)
)
)
)
(junction (at 179.07 71.12) (diameter 0) (color 0 0 0 0)
(uuid 96702e3d-509f-4a69-ac95-a9b197fc14de)
)
(wire (pts (xy 179.07 64.77) (xy 184.15 64.77))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 11ab5539-9afc-424d-a214-d23a8391cc2e)
)
(wire (pts (xy 176.53 92.71) (xy 176.53 110.49))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 259e20f0-df3e-4bdb-a78f-e183d985aa97)
)
(wire (pts (xy 115.57 71.12) (xy 124.46 71.12))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 42fa935b-b02e-446c-867d-a2a4383098a5)
)
(wire (pts (xy 115.57 66.04) (xy 115.57 58.42))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 5d9d222f-5c52-4809-8ee3-d095863a93b7)
)
(wire (pts (xy 119.38 67.31) (xy 119.38 68.58))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 78ce755e-88f6-4ea7-9add-f1f118d33ce9)
)
(wire (pts (xy 166.37 78.74) (xy 179.07 78.74))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 820fc704-964e-4f67-8158-e70a72bf2047)
)
(wire (pts (xy 119.38 90.17) (xy 124.46 90.17))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 88d19543-1cfe-4048-b1fa-b6ff80cefb7c)
)
(wire (pts (xy 173.99 87.63) (xy 182.88 87.63))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 9aee4852-816c-460e-b2ee-62fcf3d5cc6e)
)
(wire (pts (xy 166.37 86.36) (xy 173.99 86.36))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 9c196354-4c24-4cfb-9763-670e19c76460)
)
(wire (pts (xy 179.07 76.2) (xy 179.07 71.12))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 9eed885e-c80c-4839-a800-4fa88c39e5c3)
)
(wire (pts (xy 166.37 71.12) (xy 179.07 71.12))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid a2c04bf2-9ffb-49fd-a19d-c8bf1403cb15)
)
(wire (pts (xy 115.57 58.42) (xy 120.65 58.42))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid a9121f20-eef1-4824-a5a7-97be54cf7b03)
)
(wire (pts (xy 179.07 71.12) (xy 179.07 64.77))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid c87486aa-c692-42aa-9b06-8f3cc9614c1d)
)
(wire (pts (xy 166.37 90.17) (xy 182.88 90.17))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid cde43b1a-0a41-4215-9117-85257cc02e6c)
)
(wire (pts (xy 119.38 110.49) (xy 119.38 90.17))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid d7b056b3-9bc6-4e05-a3b4-b63cb4b0295d)
)
(wire (pts (xy 119.38 68.58) (xy 115.57 68.58))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid ee72e577-6a60-4cf3-8fdb-1a6ff0fae0c0)
)
(wire (pts (xy 124.46 67.31) (xy 119.38 67.31))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid f43e0a7c-e10c-4a2a-966d-dcfa64c28cc0)
)
(wire (pts (xy 173.99 86.36) (xy 173.99 87.63))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid f544b0d7-a59d-4d16-874a-887827d61d81)
)
(wire (pts (xy 176.53 110.49) (xy 119.38 110.49))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid fdc06068-6d59-4d1e-ab39-15e6bceaed6b)
)
(wire (pts (xy 182.88 92.71) (xy 176.53 92.71))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid fee40df5-d1e4-4668-80b3-be2cfdc5df44)
)
(symbol (lib_id "Seeeduino XIAO:SeeeduinoXIAO") (at 146.05 78.74 0) (unit 1)
(in_bom yes) (on_board yes) (fields_autoplaced)
(uuid 09be1eba-cac4-4f6b-8eb4-87d819840140)
(property "Reference" "U1" (id 0) (at 150.6094 99.6934 0)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Value" "SeeeduinoXIAO" (id 1) (at 150.6094 102.2303 0)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Footprint" "Seeeduino XIAO:Seeeduino XIAO-MOUDLE14P-2.54-21X17.8MM" (id 2) (at 137.16 73.66 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "" (id 3) (at 137.16 73.66 0)
(effects (font (size 1.27 1.27)) hide)
)
(pin "1" (uuid fa8ff7bd-280a-430a-90ce-4566eeaa888e))
(pin "10" (uuid bafa1981-6fa5-42d5-a6e9-bf4d117a6565))
(pin "11" (uuid e163a356-5a18-4550-b2f8-04426edfa537))
(pin "12" (uuid 645209ba-000d-4161-8079-a90c48f21972))
(pin "13" (uuid de14dca8-bf3f-497e-816c-11f9cf81219b))
(pin "14" (uuid a4692f34-d532-4459-88e3-b0550c8a2eee))
(pin "15" (uuid 496a7bd6-ee6c-43c8-8ebc-939c96d755f3))
(pin "16" (uuid 1284f43c-755b-46d4-8f24-a61527f2e347))
(pin "17" (uuid 50f5671f-9d26-467f-bcde-f5b9d8fc54df))
(pin "18" (uuid d372df45-d835-49ee-b010-2ee64c196b50))
(pin "19" (uuid 8dfae23e-614b-46bb-bdae-f341197dce2f))
(pin "2" (uuid 97a9a8d0-7188-48a2-8c2f-46c8abe86dd6))
(pin "20" (uuid bec1f1dd-62f3-4b41-b807-7d06f8d6cd2a))
(pin "3" (uuid 6417cc3b-7235-4a80-a2c1-d13faf64b1d7))
(pin "4" (uuid 9ed46697-310b-4a89-9326-ff97b2100523))
(pin "5" (uuid c40c67a9-b68b-499e-ab8a-2d082d8d0b7d))
(pin "6" (uuid 17515fd1-db3b-40e0-9d89-f8de0bd2313d))
(pin "7" (uuid 6be5e90f-7745-4b4f-a246-cdf69a470372))
(pin "8" (uuid ad72a37e-a57a-4468-bc8e-2de8107460b1))
(pin "9" (uuid 7b7ef057-2eaa-4d86-af51-083f44f7903a))
)
(symbol (lib_id "power:GND") (at 120.65 58.42 0) (unit 1)
(in_bom yes) (on_board yes) (fields_autoplaced)
(uuid 21b4a75e-d3ec-42e3-bfa0-19b1fa944cd6)
(property "Reference" "#PWR0104" (id 0) (at 120.65 64.77 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Value" "GND" (id 1) (at 120.65 62.8634 0))
(property "Footprint" "" (id 2) (at 120.65 58.42 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "" (id 3) (at 120.65 58.42 0)
(effects (font (size 1.27 1.27)) hide)
)
(pin "1" (uuid ab5f9a41-c6e0-47f7-af1b-c452558850d3))
)
(symbol (lib_id "power:GND") (at 184.15 64.77 0) (unit 1)
(in_bom yes) (on_board yes) (fields_autoplaced)
(uuid 523cd503-035b-4793-9ef9-a9ffc5f08847)
(property "Reference" "#PWR0102" (id 0) (at 184.15 71.12 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Value" "GND" (id 1) (at 184.15 69.2134 0))
(property "Footprint" "" (id 2) (at 184.15 64.77 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "" (id 3) (at 184.15 64.77 0)
(effects (font (size 1.27 1.27)) hide)
)
(pin "1" (uuid 8a4098fa-48a9-49ec-80d8-dec1073eed68))
)
(symbol (lib_id "Device:Speaker") (at 184.15 76.2 0) (unit 1)
(in_bom yes) (on_board yes) (fields_autoplaced)
(uuid 950312c9-0e5c-4da0-91fd-b7338a801460)
(property "Reference" "LS1" (id 0) (at 188.468 76.6353 0)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Value" "Speaker" (id 1) (at 188.468 79.1722 0)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Footprint" "Connector_Wire:SolderWire-1sqmm_1x02_P5.4mm_D1.4mm_OD2.7mm" (id 2) (at 184.15 81.28 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "~" (id 3) (at 183.896 77.47 0)
(effects (font (size 1.27 1.27)) hide)
)
(pin "1" (uuid cb10816e-6377-4a4b-b040-bd2614edb9b9))
(pin "2" (uuid 10868984-bd98-4ad9-8184-0bb81f331140))
)
(symbol (lib_id "Connector:AudioJack3") (at 110.49 68.58 0) (unit 1)
(in_bom yes) (on_board yes) (fields_autoplaced)
(uuid e3e5b867-9078-406e-a424-61c9632af7f6)
(property "Reference" "J1" (id 0) (at 108.585 61.0702 0))
(property "Value" "AudioJack3" (id 1) (at 108.585 63.6071 0))
(property "Footprint" "Connector_Audio:Jack_3.5mm_PJ320D_Horizontal" (id 2) (at 110.49 68.58 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "~" (id 3) (at 110.49 68.58 0)
(effects (font (size 1.27 1.27)) hide)
)
(pin "R" (uuid fabe6c60-bf57-479b-a5cc-b51e4ab4f9ab))
(pin "S" (uuid 8df3a703-5274-4af0-8e99-d46d16dd32b2))
(pin "T" (uuid 44db45bf-8f3e-43c3-a852-3c688a22c4a9))
)
(symbol (lib_id "Connector_Generic:Conn_01x03") (at 187.96 90.17 0) (mirror x) (unit 1)
(in_bom yes) (on_board yes) (fields_autoplaced)
(uuid ea989905-0b3c-45d4-b789-9cb8ba99b588)
(property "Reference" "J?" (id 0) (at 187.96 82.6602 0))
(property "Value" "Conn_01x03" (id 1) (at 187.96 85.1971 0))
(property "Footprint" "Connector_Wire:SolderWire-0.5sqmm_1x03_P4.8mm_D0.9mm_OD2.3mm" (id 2) (at 187.96 90.17 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "~" (id 3) (at 187.96 90.17 0)
(effects (font (size 1.27 1.27)) hide)
)
(pin "1" (uuid a7cb4827-649e-4a1f-b100-7bc64f6b49b3))
(pin "2" (uuid 8592b91a-c3a1-459e-a8f5-48b29ec88b66))
(pin "3" (uuid 8dedf67b-e6ba-4f01-84b9-4abd8a8a6d94))
)
(sheet_instances
(path "/" (page "1"))
)
(symbol_instances
(path "/523cd503-035b-4793-9ef9-a9ffc5f08847"
(reference "#PWR0102") (unit 1) (value "GND") (footprint "")
)
(path "/21b4a75e-d3ec-42e3-bfa0-19b1fa944cd6"
(reference "#PWR0104") (unit 1) (value "GND") (footprint "")
)
(path "/e3e5b867-9078-406e-a424-61c9632af7f6"
(reference "J1") (unit 1) (value "AudioJack3") (footprint "Connector_Audio:Jack_3.5mm_PJ320D_Horizontal")
)
(path "/ea989905-0b3c-45d4-b789-9cb8ba99b588"
(reference "J?") (unit 1) (value "Conn_01x03") (footprint "Connector_Wire:SolderWire-0.5sqmm_1x03_P4.8mm_D0.9mm_OD2.3mm")
)
(path "/950312c9-0e5c-4da0-91fd-b7338a801460"
(reference "LS1") (unit 1) (value "Speaker") (footprint "Connector_Wire:SolderWire-1sqmm_1x02_P5.4mm_D1.4mm_OD2.7mm")
)
(path "/09be1eba-cac4-4f6b-8eb4-87d819840140"
(reference "U1") (unit 1) (value "SeeeduinoXIAO") (footprint "Seeeduino XIAO:Seeeduino XIAO-MOUDLE14P-2.54-21X17.8MM")
)
)
)

3
pcb/sym-lib-table Normal file
View File

@ -0,0 +1,3 @@
(sym_lib_table
(lib (name "Seeeduino XIAO")(type "Legacy")(uri "/home/dartcatcher/.var/app/org.kicad.KiCad/data/kicad/6.0/symbols/Seeeduino XIAO.lib")(options "")(descr ""))
)

44
polybuzzer.cpp Normal file
View File

@ -0,0 +1,44 @@
#include <Arduino.h>
#include "polybuzzer.h"
#include "equal_temperament.h"
PolyBuzzer::PolyBuzzer(uint8_t pin) {
for (int i = 0; i < POLYBUZZER_MAX_TONES; i++) {
this->tones[i] = 0;
}
this->playing = 0;
this->pin = pin;
pinMode(pin, OUTPUT);
}
void PolyBuzzer::update() {
for (int i = 0; i < POLYBUZZER_MAX_TONES; i++) {
if (this->tones[i]) {
if (this->playing != this->tones[i]) {
this->playing = this->tones[i];
tone(this->pin, this->tones[i]);
}
return;
}
}
this->playing = 0;
noTone(this->pin);
}
void PolyBuzzer::Tone(int slot, unsigned int frequency) {
this->tones[slot] = frequency;
this->update();
}
void PolyBuzzer::Note(int slot, uint8_t note) {
if (note > 127) {
note = 127;
}
this->Tone(slot, equalTemperamentNote[note]);
}
void PolyBuzzer::NoTone(int slot) {
tones[slot] = 0;
this->update();
}

20
polybuzzer.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <Arduino.h>
#define POLYBUZZER_MAX_TONES 2
// PolyBuzzer provides a proritized monophonic buzzer.
//
// A given tone will only be played when all higher priority tones have stopped.
class PolyBuzzer {
public:
unsigned int tones[POLYBUZZER_MAX_TONES];
unsigned int playing;
uint8_t pin;
PolyBuzzer(uint8_t pin);
void update();
void Tone(int slot, unsigned int frequency);
void Note(int slot, uint8_t note);
void NoTone(int slot);
};

View File

@ -1,11 +1,11 @@
#include "touchbounce.h"
void TouchBounce::attach(int pin) {
this->qt = Adafruit_FreeTouch(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;
return val > QT_THRESHOLD;
}

View File

@ -3,7 +3,7 @@
#include <Adafruit_FreeTouch.h>
#include "bounce2.h"
#define QT_THRESHOLD 850
#define QT_THRESHOLD 450
class TouchBounce: public Bounce {
public:

View File

@ -8,6 +8,7 @@
#include <Adafruit_FreeTouch.h>
#include "bounce2.h"
#include "touchbounce.h"
#include "adapter.h"
#define DIT_PIN 2
#define DAH_PIN 1
@ -19,14 +20,13 @@
#define LED_ON false // Xiao inverts this logic for some reason
#define LED_OFF (!LED_ON)
#define DIT_KEY KEY_LEFT_CTRL
#define DAH_KEY KEY_RIGHT_CTRL
#define TONE 550
#define DIT_KEYBOARD_KEY KEY_LEFT_CTRL
#define DAH_KEYBOARD_KEY KEY_RIGHT_CTRL
#define TONE 3000
#define MILLISECOND 1
#define SECOND (1 * MILLISECOND)
bool keyboard = true;
bool trs = false; // true if a TRS plug is in a TRRS jack
uint16_t iambicDelay = 80 * MILLISECOND;
Bounce dit = Bounce();
@ -35,10 +35,10 @@ Bounce key = Bounce();
TouchBounce qt_dit = TouchBounce();
TouchBounce qt_dah = TouchBounce();
TouchBounce qt_key = TouchBounce();
VailAdapter adapter = VailAdapter(PIEZO);
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
pinMode(PIEZO, OUTPUT);
dit.attach(DIT_PIN, INPUT_PULLUP);
dah.attach(DAH_PIN, INPUT_PULLUP);
key.attach(KEY_PIN, INPUT_PULLUP);
@ -68,7 +68,7 @@ void setup() {
void setLED() {
static bool beepin = false;
int beat = millis() / iambicDelay;
bool on = keyboard; // If we're not in intro, display status of keyboard
bool on = adapter.KeyboardMode(); // If we're not in intro, display status of keyboard
if (beat < 16) {
on = HELLO_BITS & (1 << (15-beat));
@ -85,76 +85,36 @@ void setLED() {
digitalWrite(LED_BUILTIN, on?LED_ON:LED_OFF);
}
void midiKey(bool down, uint8_t key) {
midiEventPacket_t event = {down?9:8, down?0x90:0x80, key, 0x7f};
MidiUSB.sendMIDI(event);
MidiUSB.flush();
}
void midiProbe() {
void loop() {
unsigned now = millis();
midiEventPacket_t event = MidiUSB.read();
uint16_t msg = (event.byte1 << 8) | (event.byte2 << 0);
switch (msg) {
case 0x8B00: // Controller 0: turn keyboard mode on/off
keyboard = (event.byte3 > 0x3f);
break;
case 0x8B01: // Controller 1: set iambic speed (0-254)
// I am probably never going to use this,
// because as soon as I implement it,
// people are going to want a way to select mode A or B,
// or typeahead,
// or some other thing that I don't want to maintain
// simultaneously in both C and JavaScript
iambicDelay = event.byte3 << 1;
break;
}
}
void loop() {
midiProbe();
setLED();
adapter.Tick(now);
if (event.header) {
adapter.HandleMIDI(event);
}
// Monitor straight key pin
if (key.update() || qt_key.update()) {
bool fell = key.fell() || qt_key.fell();
midiKey(fell, 0);
if (fell) {
tone(PIEZO, TONE);
} else {
noTone(PIEZO);
}
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 iambic polling.
// so we skip other keys polling.
if (trs) {
return;
}
if (dit.update() || qt_dit.update()) {
bool fell = dit.fell() || qt_dit.fell();
midiKey(fell, 1);
if (keyboard) {
if (fell) {
Keyboard.press(DIT_KEY);
} else {
Keyboard.release(DIT_KEY);
}
}
bool pressed = !dit.read() || qt_dit.read();
adapter.HandlePaddle(PADDLE_DIT, pressed);
}
// Monitor dah pin
if (dah.update() || qt_dah.update()) {
bool fell = dah.fell() || qt_dah.fell();
midiKey(fell, 2);
if (keyboard) {
if (fell) {
Keyboard.press(DAH_KEY);
} else {
Keyboard.release(DAH_KEY);
}
}
bool pressed = !dah.read() || qt_dah.read();
adapter.HandlePaddle(PADDLE_DAH, pressed);
}
}

53
webhid-test.html Normal file
View File

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html>
<head>
<script>
let deviceFilter = { vendorId: 0x1234, productId: 0xabcd };
let requestParams = { filters: [deviceFilter] };
let outputReportId = 0x01;
let outputReport = new Uint8Array([42]);
function handleConnectedDevice(e) {
console.log("Device connected: " + e.device.productName);
}
function handleDisconnectedDevice(e) {
console.log("Device disconnected: " + e.device.productName);
}
function handleInputReport(e) {
console.log(e.device.productName + ": got input report " + e.reportId);
console.log(new Uint8Array(e.data.buffer));
}
navigator.hid.addEventListener("connect", handleConnectedDevice);
navigator.hid.addEventListener("disconnect", handleDisconnectedDevice);
function listen() {
navigator.hid.requestDevice(requestParams).then((devices) => {
if (devices.length == 0) return;
devices[0].open().then(() => {
console.log("Opened device: " + device.productName);
device.addEventListener("inputreport", handleInputReport);
device.sendReport(outputReportId, outputReport).then(() => {
console.log("Sent output report " + outputReportId);
});
});
});
}
function init() {
document.querySelector("#moo").addEventListener("click", listen)
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init)
} else {
init()
}
</script>
</head>
<body>
<button id="moo">start</button>
</body>
</html>