xref: /aosp_15_r20/external/walt/arduino/walt/walt.ino (revision bf47c6829f95be9dd55f4c5bbc44a71c90aad403)
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define VERSION                 "6"
18
19// Commands
20// Digits 1 to 9 reserved for clock sync
21#define CMD_PING_DELAYED        'D' // Ping/Pong with a delay
22#define CMD_RESET               'F' // Reset all vars
23#define CMD_SYNC_SEND           'I' // Send some digits for clock sync
24#define CMD_PING                'P' // Ping/Pong with a single byte
25#define CMD_VERSION             'V' // Determine which version is running
26#define CMD_SYNC_READOUT        'R' // Read out sync times
27#define CMD_GSHOCK              'G' // Send last shock time and watch for another shock.
28#define CMD_TIME_NOW            'T' // Current time
29#define CMD_SYNC_ZERO           'Z' // Initial zero
30
31#define CMD_AUTO_SCREEN_ON      'C'
32#define CMD_AUTO_SCREEN_OFF     'c'
33#define CMD_SEND_LAST_SCREEN    'E'
34#define CMD_BRIGHTNESS_CURVE    'U'
35
36#define CMD_AUTO_LASER_ON       'L'
37#define CMD_AUTO_LASER_OFF      'l'
38#define CMD_SEND_LAST_LASER     'J'
39
40#define CMD_AUDIO               'A'
41#define CMD_BEEP                'B'
42#define CMD_BEEP_STOP           'S'
43
44#define CMD_SAMPLE_ALL          'Q'
45
46#define CMD_MIDI                'M'
47#define CMD_NOTE                'N'
48
49#define CMD_ACCELEROMETER_CURVE 'O'
50
51#define NOTE_DELAY 10000 // 10 ms
52
53// Message types for MIDI encapsulation
54#define MIDI_MODE_TYPE 4  // Program Change
55#define MIDI_COMMAND_TYPE 5  // Channel Pressure
56
57#define MIDI_SYSEX_BEGIN '\xF0'
58#define MIDI_SYSEX_END '\xF7'
59
60// LEDs
61#define LED_PIN_INT 13 // Built-in LED
62#define DEBUG_LED1 11  // On r0.7 PCB: D4 - Red
63#define DEBUG_LED2 12  // On r0.7 PCB: D3 - Green
64
65// WALT sensors
66#define PD_LASER_PIN 14
67#define PD_SCREEN_PIN 20  // Same as A6
68#define G_PIN 15          // Same as A1
69#define GZ_PIN 16         // Same as A2
70#define AUDIO_PIN 22      // Same as A8
71#define MIC_PIN 23        // Same as A9
72
73// Threshold and hysteresis for screen on/off reading
74#define SCREEN_THRESH_HIGH  800
75#define SCREEN_THRESH_LOW   300
76
77// Shock threshold
78#define GSHOCK_THRESHOLD    500
79
80elapsedMicros time_us;
81
82boolean led_state;
83char tmp_str[256];
84
85boolean serial_over_midi;
86String send_buffer;
87
88struct trigger {
89  long t;  // time of latest occurrence in microseconds
90  int value; // value at latest occurrence
91  int count;  // occurrences since last readout
92  boolean probe; // whether currently probing
93  boolean autosend; // whether sending over serial each time
94  char tag;
95};
96
97#define TRIGGER_COUNT 5
98struct trigger laser, screen, sound, midi, gshock, copy_trigger;
99struct trigger * triggers[TRIGGER_COUNT] = {&laser, &screen, &sound, &midi, &gshock};
100
101#define CLOCK_SYNC_N 9
102struct clock_sync {
103  boolean is_synced;
104  int last_sent;
105  unsigned long sync_times[CLOCK_SYNC_N];
106};
107
108struct clock_sync clock;
109
110// Interrupt handler for laser photodiode
111void irq_laser(void) {
112  laser.t = time_us;
113  // May need to remove the 'not' if not using internal pullup resistor
114  laser.value = !digitalRead(PD_LASER_PIN);
115  laser.count++;
116
117  digitalWrite(DEBUG_LED2, laser.value);
118  // led_state = !led_state;
119}
120
121void send(char c) { send_buffer += c; }
122void send(String s) { send_buffer += s; }
123
124void send(long l) {
125  char s[32];
126  sprintf(s, "%ld", l);
127  send(s);
128}
129
130void send(unsigned long l) {
131  char s[32];
132  sprintf(s, "%lu", l);
133  send(s);
134}
135
136void send(short i) { send((long)i); }
137void send(int i) { send((long)i); }
138void send(unsigned short i) { send ((unsigned long)i); }
139void send(unsigned int i) { send ((unsigned int)i); }
140
141void send_now() {
142  if (serial_over_midi) {
143    usbMIDI.sendSysEx(send_buffer.length(), (const uint8_t *)send_buffer.c_str());
144    usbMIDI.send_now();
145    send_buffer = MIDI_SYSEX_BEGIN;
146  } else {
147    Serial.write(send_buffer.c_str(), send_buffer.length());
148    Serial.send_now();
149    send_buffer = String();
150  }
151}
152
153void send_line() {
154  if (!serial_over_midi) {
155    send('\n');
156  } else {
157    send(MIDI_SYSEX_END);
158  }
159  send_now();
160}
161
162void send_trigger(struct trigger t) {
163  char s[256];
164  sprintf(s, "G %c %ld %d %d", t.tag, t.t, t.value, t.count);
165  send(s);
166  send_line();
167}
168
169// flips case for a give char. Unchanged if not in [A-Za-z].
170char flip_case(char c) {
171  if (c >= 'A' && c <= 'Z') {
172    return c + 32;
173  }
174  if (c >= 'a' && c <= 'z') {
175    return c - 32;
176  }
177  return c;
178}
179
180// Print the same char as the cmd but with flipped case
181void send_ack(char cmd) {
182  send(flip_case(cmd));
183  send_line();
184}
185
186void init_clock() {
187  memset(&clock, 0, sizeof(struct clock_sync));
188  clock.last_sent = -1;
189}
190
191void init_vars() {
192  noInterrupts();
193  init_clock();
194
195  for (int i = 0; i < TRIGGER_COUNT; i++) {
196    memset(triggers[i], 0, sizeof(struct trigger));
197  }
198
199  laser.tag = 'L';
200  screen.tag = 'S';
201  gshock.tag = 'G';
202  sound.tag = 'A';  // for Audio
203  midi.tag = 'M';
204
205  interrupts();
206}
207
208void setup() {
209    // LEDs
210  pinMode(DEBUG_LED1, OUTPUT);
211  pinMode(DEBUG_LED2, OUTPUT);
212  pinMode(LED_PIN_INT, OUTPUT);
213
214  // Sensors
215  pinMode(PD_SCREEN_PIN, INPUT);
216  pinMode(G_PIN, INPUT);
217  pinMode(GZ_PIN, INPUT);
218  pinMode(PD_LASER_PIN, INPUT_PULLUP);
219  attachInterrupt(PD_LASER_PIN, irq_laser, CHANGE);
220
221  Serial.begin(115200);
222  serial_over_midi = false;
223  init_vars();
224
225  led_state = HIGH;  // Turn on all LEDs on startup
226  digitalWrite(LED_PIN_INT, led_state);
227  digitalWrite(DEBUG_LED1, HIGH);
228  digitalWrite(DEBUG_LED2, HIGH);
229}
230
231
232void run_brightness_curve() {
233  int i;
234  long t;
235  short v;
236  digitalWrite(DEBUG_LED1, HIGH);
237  for (i = 0; i < 1000; i++) {
238    v = analogRead(PD_SCREEN_PIN);
239    t = time_us;
240    send(t);
241    send(' ');
242    send(v);
243    send_line();
244    delayMicroseconds(450);
245  }
246  digitalWrite(DEBUG_LED1, LOW);
247  send("end");
248  send_line();
249}
250
251void run_accelerometer_curve() {
252  int i;
253  long t;
254  int v;
255  digitalWrite(DEBUG_LED1, HIGH);
256  for (i = 0; i < 4000; i++) {
257    v = analogRead(GZ_PIN);
258    t = time_us;
259    send(t);
260    send(' ');
261    send(v);
262    send_line();
263    delayMicroseconds(450);
264  }
265  digitalWrite(DEBUG_LED1, LOW);
266  send("end");
267  send_line();
268}
269
270void process_command(char cmd) {
271  int i;
272  if (cmd == CMD_SYNC_ZERO) {
273    noInterrupts();
274    time_us = 0;
275    init_clock();
276    clock.is_synced = true;
277    interrupts();
278    led_state = LOW;
279    digitalWrite(DEBUG_LED1, LOW);
280    digitalWrite(DEBUG_LED2, LOW);
281    send_ack(CMD_SYNC_ZERO);
282  } else if (cmd == CMD_TIME_NOW) {
283    send("t ");
284    send(time_us);
285    send_line();
286  } else if (cmd == CMD_PING) {
287    send_ack(CMD_PING);
288  } else if (cmd == CMD_PING_DELAYED) {
289    delay(10);
290    send_ack(CMD_PING_DELAYED);
291  } else if (cmd >= '1' && cmd <= '9') {
292    clock.sync_times[cmd - '1'] = time_us;
293    clock.last_sent = -1;
294  } else if (cmd == CMD_SYNC_READOUT) {
295    clock.last_sent++;
296    int t = 0;
297    if (clock.last_sent < CLOCK_SYNC_N) {
298      t = clock.sync_times[clock.last_sent];
299    }
300    send(clock.last_sent + 1);
301    send(':');
302    send(t);
303    send_line();
304  } else if (cmd == CMD_SYNC_SEND) {
305    clock.last_sent = -1;
306    // Send CLOCK_SYNC_N times
307    for (i = 0; i < CLOCK_SYNC_N; ++i) {
308      delayMicroseconds(737); // TODO: change to some congifurable random
309      char c = '1' + i;
310      clock.sync_times[i] = time_us;
311      send(c);
312      send_line();
313    }
314  } else if (cmd == CMD_RESET) {
315    init_vars();
316    send_ack(CMD_RESET);
317  } else if (cmd == CMD_VERSION) {
318    send(flip_case(cmd));
319    send(' ');
320    send(VERSION);
321    send_line();
322  } else if (cmd == CMD_GSHOCK) {
323    send(gshock.t);  // TODO: Serialize trigger
324    send_line();
325    gshock.t = 0;
326    gshock.count = 0;
327    gshock.probe = true;
328  } else if (cmd == CMD_AUDIO) {
329    sound.t = 0;
330    sound.count = 0;
331    sound.probe = true;
332    sound.autosend = true;
333    send_ack(CMD_AUDIO);
334  } else if (cmd == CMD_BEEP) {
335    long beep_time = time_us;
336    tone(MIC_PIN, 5000 /* Hz */);
337    send(flip_case(cmd));
338    send(' ');
339    send(beep_time);
340    send_line();
341  } else if (cmd == CMD_BEEP_STOP) {
342    noTone(MIC_PIN);
343    send_ack(CMD_BEEP_STOP);
344  } else if (cmd == CMD_MIDI) {
345    midi.t = 0;
346    midi.count = 0;
347    midi.probe = true;
348    midi.autosend = true;
349    send_ack(CMD_MIDI);
350  } else if (cmd == CMD_NOTE) {
351    unsigned long note_time = time_us + NOTE_DELAY;
352    send(flip_case(cmd));
353    send(' ');
354    send(note_time);
355    send_line();
356    while (time_us < note_time);
357    usbMIDI.sendNoteOn(60, 99, 1);
358    usbMIDI.send_now();
359  } else if (cmd == CMD_AUTO_SCREEN_ON) {
360    screen.value = analogRead(PD_SCREEN_PIN) > SCREEN_THRESH_HIGH;
361    screen.autosend = true;
362    screen.probe = true;
363    send_ack(CMD_AUTO_SCREEN_ON);
364  } else if (cmd == CMD_AUTO_SCREEN_OFF) {
365    screen.autosend = false;
366    screen.probe = false;
367    send_ack(CMD_AUTO_SCREEN_OFF);
368  } else if (cmd == CMD_SEND_LAST_SCREEN) {
369    send_trigger(screen);
370    screen.count = 0;
371  } else if (cmd == CMD_AUTO_LASER_ON) {
372    laser.autosend = true;
373    laser.count = 0;
374    send_ack(CMD_AUTO_LASER_ON);
375  } else if (cmd == CMD_AUTO_LASER_OFF) {
376    laser.autosend = false;
377    send_ack(CMD_AUTO_LASER_OFF);
378  } else if (cmd == CMD_SEND_LAST_LASER) {
379    send_trigger(laser);
380    laser.count = 0;
381  } else if (cmd == CMD_BRIGHTNESS_CURVE) {
382    send_ack(CMD_BRIGHTNESS_CURVE);
383    // This blocks all other execution for about 1 second
384    run_brightness_curve();
385  } else if (cmd == CMD_ACCELEROMETER_CURVE) {
386    send_ack(CMD_ACCELEROMETER_CURVE);
387    // This blocks all other execution for about 2 seconds
388    run_accelerometer_curve();
389  } else if (cmd == CMD_SAMPLE_ALL) {
390    send(flip_case(cmd));
391    send(" G:");
392    send(analogRead(G_PIN));
393    send(" PD_screen:");
394    send(analogRead(PD_SCREEN_PIN));
395    send(" PD_laser:");
396    send(analogRead(PD_LASER_PIN));
397    send_line();
398  } else {
399    send("Unknown command: ");
400    send(cmd);
401    send_line();
402  }
403}
404
405void loop() {
406  digitalWrite(LED_PIN_INT, led_state);
407
408  // Probe the accelerometer
409  if (gshock.probe) {
410    int v = analogRead(G_PIN);
411    if (v > GSHOCK_THRESHOLD) {
412      gshock.t = time_us;
413      gshock.count++;
414      gshock.probe = false;
415      led_state = !led_state;
416    }
417  }
418
419  // Probe audio
420  if (sound.probe) {
421    int v = analogRead(AUDIO_PIN);
422    if (v > 20) {
423      sound.t = time_us;
424      sound.count++;
425      sound.probe = false;
426      led_state = !led_state;
427    }
428  }
429
430  // Probe MIDI
431  boolean has_midi = usbMIDI.read(1);
432  if(has_midi && midi.probe && usbMIDI.getType() == 0) {  // Type 1: note on
433    midi.t = time_us;
434    midi.count++;
435    midi.probe = false;
436    led_state = !led_state;
437  }
438
439  // Probe screen
440  if (screen.probe) {
441    int v = analogRead(PD_SCREEN_PIN);
442    if ((screen.value == LOW && v > SCREEN_THRESH_HIGH) || (screen.value != LOW && v < SCREEN_THRESH_LOW)) {
443      screen.t = time_us;
444      screen.count++;
445      led_state = !led_state;
446      screen.value = !screen.value;
447    }
448  }
449
450  // Send out any triggers with autosend and pending data
451  for (int i = 0; i < TRIGGER_COUNT; i++) {
452    boolean should_send = false;
453
454    noInterrupts();
455    if (triggers[i]->autosend && triggers[i]->count > 0) {
456      should_send = true;
457      copy_trigger = *(triggers[i]);
458      triggers[i]->count = 0;
459    }
460    interrupts();
461
462    if (should_send) {
463      send_trigger(copy_trigger);
464    }
465  }
466
467  // Check if we got incoming commands from the host
468  if (has_midi) {
469    if (usbMIDI.getType() == MIDI_MODE_TYPE) {
470      short program = usbMIDI.getData1();
471      serial_over_midi = (program == 1);
472      send_buffer = (serial_over_midi ? MIDI_SYSEX_BEGIN : String());
473    } else if (usbMIDI.getType() == MIDI_COMMAND_TYPE) {
474      char cmd = usbMIDI.getData1();
475      process_command(cmd);
476    }
477  }
478  if (Serial.available()) {
479    char cmd = Serial.read();
480    process_command(cmd);
481  }
482}
483
484