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