WrathPak
·
2025-05-28
vail-adapter.ino
1#include "config.h"
2
3#include <MIDIUSB.h>
4#include <Keyboard.h>
5#include <Adafruit_FreeTouch.h>
6#include <FlashStorage_SAMD.h>
7#include "bounce2.h"
8#include "touchbounce.h"
9#include "adapter.h"
10#include "equal_temperament.h"
11
12bool trs = false;
13
14Bounce dit = Bounce();
15Bounce dah = Bounce();
16Bounce key = Bounce();
17TouchBounce qt_dit = TouchBounce();
18TouchBounce qt_dah = TouchBounce();
19TouchBounce qt_key = TouchBounce();
20
21VailAdapter adapter = VailAdapter(PIEZO_PIN);
22
23uint8_t loadToneFromEEPROM();
24
25void playDot(uint8_t noteNumber) {
26 digitalWrite(LED_BUILTIN, LED_ON);
27 tone(PIEZO_PIN, equalTemperamentNote[noteNumber]);
28 delay(DOT_DURATION);
29 digitalWrite(LED_BUILTIN, LED_OFF);
30 noTone(PIEZO_PIN);
31 delay(ELEMENT_SPACE);
32}
33
34void playDash(uint8_t noteNumber) {
35 digitalWrite(LED_BUILTIN, LED_ON);
36 tone(PIEZO_PIN, equalTemperamentNote[noteNumber]);
37 delay(DASH_DURATION);
38 digitalWrite(LED_BUILTIN, LED_OFF);
39 noTone(PIEZO_PIN);
40 delay(ELEMENT_SPACE);
41}
42
43void playVAIL(uint8_t noteNumber) {
44 playDot(noteNumber); playDot(noteNumber); playDot(noteNumber); playDash(noteNumber);
45 delay(CHAR_SPACE - ELEMENT_SPACE);
46 playDot(noteNumber); playDash(noteNumber);
47 delay(CHAR_SPACE - ELEMENT_SPACE);
48 playDot(noteNumber); playDot(noteNumber);
49 delay(CHAR_SPACE - ELEMENT_SPACE);
50 playDot(noteNumber); playDash(noteNumber); playDot(noteNumber); playDot(noteNumber);
51 noTone(PIEZO_PIN);
52}
53
54uint8_t loadToneFromEEPROM() {
55 if (EEPROM.read(EEPROM_VALID_FLAG_ADDR) == EEPROM_VALID_VALUE) {
56 uint8_t txNote = EEPROM.read(EEPROM_TX_NOTE_ADDR);
57 return txNote;
58 } else {
59 Serial.println("EEPROM not initialized, using default tone");
60 return DEFAULT_TONE_NOTE;
61 }
62}
63
64void saveSettingsToEEPROM(uint8_t keyerType, uint16_t ditDuration, uint8_t txNote) {
65 EEPROM.write(EEPROM_KEYER_TYPE_ADDR, keyerType);
66 EEPROM.put(EEPROM_DIT_DURATION_ADDR, ditDuration);
67 EEPROM.write(EEPROM_TX_NOTE_ADDR, txNote);
68 EEPROM.write(EEPROM_VALID_FLAG_ADDR, EEPROM_VALID_VALUE);
69 EEPROM.commit();
70 Serial.print("Saved to EEPROM - Keyer: "); Serial.print(keyerType);
71 Serial.print(", Dit Duration: "); Serial.print(ditDuration);
72 Serial.print(", TX Note: "); Serial.println(txNote);
73}
74
75void loadSettingsFromEEPROM() {
76 if (EEPROM.read(EEPROM_VALID_FLAG_ADDR) == EEPROM_VALID_VALUE) {
77 uint8_t keyerType = EEPROM.read(EEPROM_KEYER_TYPE_ADDR);
78 uint16_t ditDurationVal;
79 EEPROM.get(EEPROM_DIT_DURATION_ADDR, ditDurationVal);
80 uint8_t txNoteVal = EEPROM.read(EEPROM_TX_NOTE_ADDR);
81
82 Serial.print("EEPROM values - Keyer: "); Serial.print(keyerType);
83 Serial.print(", Dit Duration: "); Serial.print(ditDurationVal);
84 Serial.print(", TX Note: "); Serial.println(txNoteVal);
85
86 midiEventPacket_t event;
87 event.header = 0x0B; event.byte1 = 0xB0;
88 event.byte2 = 1;
89 event.byte3 = ditDurationVal / (2 * MILLISECOND);
90 adapter.HandleMIDI(event);
91
92 event.byte2 = 2;
93 event.byte3 = txNoteVal;
94 adapter.HandleMIDI(event);
95
96 if (keyerType >= 0 && keyerType <= 9) {
97 event.header = 0x0C; event.byte1 = 0xC0;
98 event.byte2 = keyerType; event.byte3 = 0;
99 adapter.HandleMIDI(event);
100 }
101 } else {
102 Serial.println("EEPROM initializing with default values...");
103 EEPROM.write(EEPROM_KEYER_TYPE_ADDR, 1);
104 EEPROM.put(EEPROM_DIT_DURATION_ADDR, (uint16_t)DEFAULT_ADAPTER_DIT_DURATION_MS);
105 EEPROM.write(EEPROM_TX_NOTE_ADDR, DEFAULT_TONE_NOTE);
106 EEPROM.write(EEPROM_VALID_FLAG_ADDR, EEPROM_VALID_VALUE);
107 EEPROM.commit();
108 Serial.println("EEPROM initialized. Loading these defaults now.");
109 loadSettingsFromEEPROM();
110 }
111}
112
113void setup() {
114 Serial.begin(9600);
115 delay(500);
116 Serial.print("\n\nVail Adapter starting on: ");
117 Serial.println(BOARD_NAME);
118
119 pinMode(LED_BUILTIN, OUTPUT);
120 digitalWrite(LED_BUILTIN, LED_OFF);
121
122 dit.attach(DIT_PIN, INPUT_PULLUP);
123 dah.attach(DAH_PIN, INPUT_PULLUP);
124 key.attach(KEY_PIN, INPUT_PULLUP);
125
126 qt_dit.attach(QT_DIT_PIN);
127 qt_dah.attach(QT_DAH_PIN);
128 qt_key.attach(QT_KEY_PIN);
129
130#ifdef HAS_RADIO_OUTPUT
131 pinMode(RADIO_DIT_PIN, OUTPUT);
132 pinMode(RADIO_DAH_PIN, OUTPUT);
133 digitalWrite(RADIO_DIT_PIN, RADIO_INACTIVE_LEVEL); // Use configured inactive level
134 digitalWrite(RADIO_DAH_PIN, RADIO_INACTIVE_LEVEL); // Use configured inactive level
135 Serial.print("Radio Output Pins Initialized. Inactive Level: ");
136 Serial.println(RADIO_INACTIVE_LEVEL == LOW ? "LOW" : "HIGH");
137#endif
138
139 uint8_t startupTone = loadToneFromEEPROM();
140 Serial.println("Playing VAIL in Morse code at 20 WPM");
141 playVAIL(startupTone);
142
143 loadSettingsFromEEPROM();
144
145 Serial.print("Adapter settings loaded - Keyer: "); Serial.print(adapter.getCurrentKeyerType());
146 Serial.print(", Dit Duration (ms): "); Serial.print(adapter.getDitDuration());
147 Serial.print(", TX Note: "); Serial.println(adapter.getTxNote());
148 Serial.print("Buzzer initially: "); Serial.println(adapter.isBuzzerEnabled() ? "ON" : "OFF");
149 Serial.print("Radio Mode initially: "); Serial.println(adapter.isRadioModeActive() ? "ON" : "OFF");
150
151 Keyboard.begin();
152 MidiUSB.flush();
153
154 for (int i = 0; i < 16; i++) {
155 delay(20);
156 dah.update();
157 }
158 if (dah.read() == LOW) {
159 trs = true;
160 Serial.println("TRS plug potentially detected (DAH pin grounded).");
161 }
162}
163
164void setLED() {
165 bool finalLedState = false;
166
167 if (adapter.isRadioModeActive()) {
168 finalLedState = (millis() % 400 < 200);
169 } else if (!adapter.isBuzzerEnabled()) {
170 finalLedState = (millis() % 2000 < 1000);
171 } else {
172 finalLedState = adapter.KeyboardMode();
173 }
174 digitalWrite(LED_BUILTIN, finalLedState ? LED_ON : LED_OFF);
175}
176
177void loop() {
178 unsigned int currentTime = millis();
179 midiEventPacket_t event = MidiUSB.read();
180
181 setLED();
182 adapter.Tick(currentTime);
183
184 if (event.header) {
185 adapter.HandleMIDI(event);
186 }
187
188 if (key.update()) {
189 adapter.ProcessPaddleInput(PADDLE_STRAIGHT, !key.read(), false);
190 }
191
192 if (trs) {
193 // If DAH pin is grounded (TRS), this suggests a straight key might be plugged in
194 // where DIT line (tip) is the key and DAH line (ring) is shorted to GND (sleeve).
195 // The current Bounce objects 'dit' and 'dah' are still attached to their original pins.
196 // If your TRS straight key uses the DIT_PIN for keying and grounds DAH_PIN:
197 // You might want to only read the 'dit' object as a straight key when trs is true.
198 // if (dit.update()) {
199 // adapter.ProcessPaddleInput(PADDLE_STRAIGHT, !dit.read(), false);
200 // }
201 // And then skip the separate DIT/DAH paddle processing below if trs == true.
202 // This part depends on your exact TRS wiring and desired behavior.
203 // For now, assuming all inputs are polled and 'trs' is just an indicator.
204 }
205
206
207 if (dit.update()) {
208 adapter.ProcessPaddleInput(PADDLE_DIT, !dit.read(), false);
209 }
210 if (dah.update()) {
211 adapter.ProcessPaddleInput(PADDLE_DAH, !dah.read(), false);
212 }
213
214 if (qt_key.update()) {
215 adapter.ProcessPaddleInput(PADDLE_STRAIGHT, qt_key.read(), true);
216 }
217 if (qt_dit.update()) {
218 adapter.ProcessPaddleInput(PADDLE_DIT, qt_dit.read(), true);
219 }
220 if (qt_dah.update()) {
221 adapter.ProcessPaddleInput(PADDLE_DAH, qt_dah.read(), true);
222 }
223}