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