Compare commits

...

34 Commits
v2.0 ... master

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
Neale Pickett 3521861f18 fix headings 2022-05-21 20:17:15 -06:00
Neale Pickett 00d8223f35 Lots of changes for V3.0: 2022-05-21 20:14:03 -06:00
Neale Pickett 86691b88a9 Fix bug where dit is never released in kbd mode 2022-04-22 10:35:42 -06:00
Neale Pickett d589d3f3b8 Fix TRS/TRRS detection 2022-04-21 22:21:07 -06:00
Neale Pickett 6787095a6c Straight key buzzer on pin 7 2022-04-21 22:16:32 -06:00
Neale Pickett 8e77322461 Sound piezo buzzer for straight key 2022-04-21 22:07:39 -06:00
Neale Pickett cb19012d1a Blink out a V at startup, why not. 2022-04-20 19:55:30 -06:00
Neale Pickett 14f2f78def Better flashing instructions in README 2022-04-19 22:34:33 -06:00
Neale Pickett 8f60e9b28f Update firmware filename to reflect board 2022-04-19 22:31:45 -06:00
32 changed files with 533141 additions and 257 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.ino.uf2
install: build/vail-adapter.ino.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) > $@
%.uf2: %.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) $<

199
README.md
View File

@ -4,174 +4,38 @@ Email: neale@woozle.org
License: MIT
---
# Project: USB Morse Adapter
# Vail Adapter: Morse Code Key/Paddle to USB
![Vail adapter, assembled and connected](https://lh3.googleusercontent.com/pw/ACtC-3d9xbLxL23QeLm-3gy3-Yt0VHE3IlQ-qyMDqTfdF6Bo7fHkkokACdIs68pmXevu14VzrrCeKj1JmRUiekUNiZe9J9rYIh_pTagvCbKSzpY8Ynp1m6cF4G_jTvtiU5eRtoNCsmU5OLy2SR9kYcCDYSt-AA=s1471-no)
This translates Morse key inputs into USB events,
either MIDI or MIDI+Keyboard,
so you can use a computer.
It is fully compatible with [VBand](https://hamradio.solutions/vband/)
and anything else that uses the VBand adapter.
I use this with an Internet morse code repeater I wrote,
available at https://vail.woozle.org/.
This project requires an Arduino that can send USB.
The following should work:
### Verified Working
* Seeeduino Xiao **(recommended)**
* Arduino Micro
* KeeYees Pro Micro
### Unverified but should work
Any USB-capable Arduino should be fine, including:
* Arduino Leonardo
* Arduino Zero
* Adafruit Trinket M0
* Adafruit GEMMA M0
* Adafruit Feather M0
It may also be possible to get this working on a DigiSpark.
I'm trying to convince Michele Giugliano to try :)
=== Similar projects
* Vail user Michele Giugliano's [MorsePaddle2USB](https://github.com/mgiugliano/MorsePaddle2USB),
which runs on a DigiSpark (attiny85). It only sends keystrokes, so you must keep the Vail
window focused: you can't switch to other apps and still transimit.
* Ham Radio Solutions sells a [USB Paddle Interface](https://hamradio.solutions/vband/)
which appears to be very similar to Michele's project. You must keep the Vail window focused.
## Step 1: Installation
#### The Easy Way
I plan to begin releasing precompiled images for the Seeeduino XIAO.
You should be able to
[enter bootloader mode](https://wiki.seeedstudio.com/Seeeduino-XIAO/#enter-bootloader-mode)
and just drag the `.uf2` file onto the new drive it provides,
then you're done.
#### The Other Way
Install the MidiUSB and Keyboard libraries.
These are highly popular libraries,
and there is much better documentation elsewhere on the Internet.
The code should then build in the Arduino IDE.
## Step 2: Assmble the circuit
### For the impatient:
[Photos of assembled circuit](https://github.com/nealey/vail-adapter/wiki)
### For the inquisitive:
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.
### Wire up your input device
Hook a straight key into ground on one side,
pin 10 on the other.
It's okay to leave pin 10 disconnected if you don't have a straight key.
Hook an iambic paddle in ground in the middle,
pins 11 and 12 on the outside posts.
It's okay to leave pins 11 and 12 disconnected if you don't have a paddle.
=== Using a headphone jack
If you prefer, you can wire a headphone jack up to GND, 11, and 12.
GND should be the sleeve, 11 the ring, and 12 the tip.
o --- 12
|_| --- 11
| | --- GND
| |
Make sure any straight key you plug in has a TS adapter (mono plug):
this will short pin 11 to ground and signal to the Arduino to
go into straight key mode.
If you change from an iambic key to a straight key,
you'll have to reset the adapter by unplugging it from the computer.
![Vail adapter, assembled and connected](doc/vail-adapter-v2.jpg)
## Step 3: Load the code
# Features
Upload the code contained in this sketch on to your board.
* 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/), 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
## Step 4: Test it out
[Vail Adapter benefits video](https://www.youtube.com/watch?v=XQ-mwdyLkOY) (4:46)
Make sure it's plugged in to your computer's USB port.
# Setting Up
If you connected pin 9 to ground,
Open anything where you can type,
type in "hello", and hit the straight key.
You should see a comma after your hello.
Now you can open https://vail.woozle.org/,
click the "KEY" button once to let the browser know it's okay to make sound,
and you should be able to wail away on your new USB keyer.
## Step 5: Debugging
If you plug in your straight key and it looks like DAH is being held down,
it means you need to switch the connections to pins 11 and 12.
If you plug in your straight key and it looks like DIT is being held down,
it means you need to reset the Arduino to make it detect the key type again.
* [Easy Setup](doc/easy-install.md)
* [Advanced Setup](doc/advanced-install.md)
# License
# Future Work
This project is released under an MIT License.
Things I plan to add:
Copyright © 2020 Neale Pickett
Copyright © 2013 thomasfredericks
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The software is provided "as is", without warranty of any kind, express or
implied, including but not limited to the warranties of merchantability, fitness
for a particular purpose, and noninfringement. In no event shall the authors or
copyright holders be liable for any claim, damages, or other liability, whether
in an action of contract, tort or otherwise, arising from, out of, or in
connection with the software or the use or other dealings in the software.
* [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
@ -179,15 +43,16 @@ To contribute to this project please contact neale@woozle.org
https://id.arduino.cc/neale
# BOM
In addition to a key, some hookup wires, and a USB cable,
you only need a USB-capable Arduino: see above.
# Similar projects
Since I don't know what might try to parse this section,
I'm calling for an Arduino Micro. But, really, many options
will work fine.
| ID | Part name | Part number | Quantity |
| --- | ------- | ------ | ------ |
| A1 | Seeeduino XIAO | 102010328 | 1 |
* Vail user Michele Giugliano's
[MorsePaddle2USB](https://github.com/mgiugliano/MorsePaddle2USB),
which runs on a DigiSpark (attiny85). It only sends keystrokes, so you must keep the Vail
window focused: you can't switch to other apps and still transimit.
* Ham Radio Solutions sells a
[USB Paddle Interface](https://hamradio.solutions/vband/)
which appears to be very similar to Michele's project. You must keep the
Vail window focused.
* [CWKeyboard](https://github.com/kevintechie/CWKeyboard) looks almost
exactly the same as the VBand adapter.

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=\"$*\" $<

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);
}

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.

90
doc/advanced-install.md Normal file
View File

@ -0,0 +1,90 @@
# Advanced Install
In the Arduino IDE, edit [vail-adapter.ino](../vail-adapter.ino) with the pins
you want to use on your device.
You will need the MidiUSB and Keyboard libraries installed.
You can do this through the Library manager.
Then compile and upload the sketch.
## Works with no source code changes
* Seeeduino Xiao SAMD21
* Adafruit Qt Py SAMD21
## Known to work with source code changes
* Arduino Micro
* KeeYees Pro Micro
* Arduino Leonardo
* Arduino Zero
* Adafruit Trinket M0
* 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
![XIAO Pinout](https://files.seeedstudio.com/wiki/Seeeduino-XIAO/img/Seeeduino-XIAO-pinout-1.jpg)
* GND: Ground
* D2: Dit
* D1: Dah
* D0: Straight Key
* D10: Speaker or Passive piezo buzzer
* A6: Capacative Dit
* A7: Capacative Dah
* A8: Capacative Straight Key
## Using a headphone jack
You can wire a headphone jack up to GND, D1, and D2.
GND should be the sleeve, D1 the ring, and D2 the tip.
o --- D2 (tip, dit)
|_| --- D1 (ring, dah)
| | --- GND (sleeve)
| |
## Sidetone generator
If you connect a buzzer or speaker to pin 10 on one leg,
and ground on the other,
the adapter will beep when you press the straight key.
This will help a lot if there is a noticeable delay between when you press the key
and when your computer starts making a local beeping sound.
If you feel like no matter what you do,
you're always getting DAH with your straight key,
you should try this.
## Capacative Touch
The adapter works as a capacative touch sensor,
like a touch lamp.
You might wire these pins to screws or conductive pads.
These can be used instead of, or in additon to, the normal pins D0, D1, and D2.
You do not need a ground wire with capacative touch!
* Pin A6: Dit capacative touch
* Pin A7: Dah capacative touch
* Pin A8: Straight key capacative touch

26
doc/easy-install.md Normal file
View File

@ -0,0 +1,26 @@
# Easy Installation
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 SAMD21
[Wideo walkthrough of firmware upload](https://www.youtube.com/watch?v=IgOdkUe5SMY) (3:07)
If you would like to use a different microcontroller,
see the [advanced install instructions](advanced-install.md).
# Wiring
![XIAO Pinout](https://files.seeedstudio.com/wiki/Seeeduino-XIAO/img/Seeeduino-XIAO-pinout-1.jpg)
* GND: Ground (usually in the middle of the paddle)
* Pin D2: Dit (usually left paddle)
* Pin D1: Dah (usually right paddle)
If you'd like to wire up a TRS (headphone) jack;
or take advantage of some of the adapters other features,
such as dedicated straight key, sidetone generation,
or capacative touch,
see the [advanced install instructions](advanced-install.md).

BIN
doc/vail-adapter-v2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

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);
};

11
touchbounce.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "touchbounce.h"
void TouchBounce::attach(int pin) {
this->qt = Adafruit_FreeTouch(pin, OVERSAMPLE_2, RESISTOR_0, FREQ_MODE_SPREAD);
this->qt.begin();
}
bool TouchBounce::readCurrentState() {
int val = this->qt.measure();
return val > QT_THRESHOLD;
}

16
touchbounce.h Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include <Adafruit_FreeTouch.h>
#include "bounce2.h"
#define QT_THRESHOLD 450
class TouchBounce: public Bounce {
public:
// attach a touch pin
void attach(int pin);
protected:
bool readCurrentState();
Adafruit_FreeTouch qt;
};

View File

@ -5,120 +5,116 @@
// MIDIUSB - Version: Latest
#include <MIDIUSB.h>
#include <Keyboard.h>
#include <Adafruit_FreeTouch.h>
#include "bounce2.h"
#include "touchbounce.h"
#include "adapter.h"
#ifdef ARDUINO_SEEED_XIAO_M0
# define DIT_PIN 2
# define DAH_PIN 1
# define KEY_PIN 0
#else
# define DIT_PIN 12
# define DAH_PIN 11
# define KEY_PIN 10
#endif
#define DIT_PIN 2
#define DAH_PIN 1
#define KEY_PIN 0
#define QT_DIT_PIN A6
#define QT_DAH_PIN A7
#define QT_KEY_PIN A8
#define PIEZO 10
#define LED_ON false // Xiao inverts this logic for some reason
#define LED_OFF (!LED_ON)
#define STRAIGHT_KEY ','
#define DIT_KEY KEY_LEFT_CTRL
#define DAH_KEY KEY_RIGHT_CTRL
#define DIT_KEYBOARD_KEY KEY_LEFT_CTRL
#define DAH_KEYBOARD_KEY KEY_RIGHT_CTRL
#define TONE 3000
bool iambic = true;
bool keyboard = true;
#define MILLISECOND 1
#define SECOND (1 * MILLISECOND)
bool trs = false; // true if a TRS plug is in a TRRS jack
uint16_t iambicDelay = 80 * MILLISECOND;
Bounce dit = Bounce();
Bounce dah = Bounce();
Bounce key = Bounce();
TouchBounce qt_dit = TouchBounce();
TouchBounce qt_dah = TouchBounce();
TouchBounce qt_key = TouchBounce();
VailAdapter adapter = VailAdapter(PIEZO);
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
dit.attach(DIT_PIN, INPUT_PULLUP);
dah.attach(DAH_PIN, INPUT_PULLUP);
key.attach(KEY_PIN, INPUT_PULLUP);
qt_dit.attach(QT_DIT_PIN);
qt_dah.attach(QT_DAH_PIN);
qt_key.attach(QT_KEY_PIN);
Keyboard.begin();
// Straight keys need to wire the dah pin to ground somehow.
// The easiest way I can think of to do this is to use a TS connector
// instead of a TRS connector.
// To auto-sense a straight key in a TRRS jack,
// we just check to see if DAH is closed.
// The sleeve on the straight key's TRS plug
// will short the second ring to the sleeve.
for (int i = 0; i < 16; i++) {
delay(20);
dah.update();
}
if (dah.read() == LOW) {
iambic = false;
} else {
iambic = true;
trs = true;
key = dit;
}
// Blink 4 times
for (int i = 0; i < 8; i += 1) {
digitalWrite(LED_BUILTIN, i % 2);
delay(250);
}
}
void midiKey(bool down, uint8_t key) {
midiEventPacket_t event = {down?9:8, down?0x90:0x80, key, 0x7f};
MidiUSB.sendMIDI(event);
MidiUSB.flush();
}
// A reentrant doodad to blink out the letter V at startup
// After startup, display the status of the keyboard
#define HELLO_BITS 0b0000101010111000
void setLED() {
static bool beepin = false;
int beat = millis() / iambicDelay;
bool on = adapter.KeyboardMode(); // If we're not in intro, display status of keyboard
void midiProbe() {
midiEventPacket_t event = MidiUSB.read();
if (event.byte1 == 0x80) {
// Key release, channel 0
if (event.byte2 == 0x00) {
// C0 note
keyboard = false;
if (beat < 16) {
on = HELLO_BITS & (1 << (15-beat));
if (on != beepin) {
if (on) {
tone(PIEZO, TONE);
} else {
noTone(PIEZO);
}
beepin = on;
}
}
}
digitalWrite(LED_BUILTIN, on?LED_ON:LED_OFF);
}
void loop() {
midiProbe();
digitalWrite(LED_BUILTIN, !keyboard);
unsigned now = millis();
midiEventPacket_t event = MidiUSB.read();
setLED();
adapter.Tick(now);
if (event.header) {
adapter.HandleMIDI(event);
}
// Monitor straight key pin
if (key.update()) {
midiKey(key.fell(), 0);
if (keyboard) {
if (key.fell()) {
Keyboard.press(STRAIGHT_KEY);
} else {
Keyboard.release(STRAIGHT_KEY);
}
}
if (key.update() || qt_key.update()) {
bool pressed = !key.read() || qt_key.read();
adapter.HandlePaddle(PADDLE_STRAIGHT, pressed);
}
// If we made dit = dah, we have a straight key on the dit pin,
// so we skip other keys polling.
if (trs) {
return;
}
if (dit.update() || qt_dit.update()) {
bool pressed = !dit.read() || qt_dit.read();
adapter.HandlePaddle(PADDLE_DIT, pressed);
}
// Monitor dit pin, which could be straight key if dah was closed on boot
if (dit.update()) {
uint8_t kbdKey, mKey;
if (iambic) {
kbdKey = DIT_KEY;
mKey = 1;
} else {
kbdKey = STRAIGHT_KEY;
mKey = 0;
}
midiKey(dit.fell(), mKey);
if (keyboard) {
if (dit.fell()) {
Keyboard.press(kbdKey);
} else {
Keyboard.release(kbdKey);
}
}
}
// Monitor dah pin
if (iambic && dah.update()) {
midiKey(dah.fell(), 2);
if (keyboard) {
if (dah.fell()) {
Keyboard.press(DAH_KEY);
} else {
Keyboard.release(DAH_KEY);
}
}
if (dah.update() || qt_dah.update()) {
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>