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