diff options
Diffstat (limited to 'arduino/walt/walt.ino')
-rw-r--r-- | arduino/walt/walt.ino | 453 |
1 files changed, 453 insertions, 0 deletions
diff --git a/arduino/walt/walt.ino b/arduino/walt/walt.ino new file mode 100644 index 0000000..e63da3b --- /dev/null +++ b/arduino/walt/walt.ino @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define VERSION "5" + +// Commands +// Digits 1 to 9 reserved for clock sync +#define CMD_PING_DELAYED 'D' // Ping/Pong with a delay +#define CMD_RESET 'F' // Reset all vars +#define CMD_SYNC_SEND 'I' // Send some digits for clock sync +#define CMD_PING 'P' // Ping/Pong with a single byte +#define CMD_VERSION 'V' // Determine which version is running +#define CMD_SYNC_READOUT 'R' // Read out sync times +#define CMD_GSHOCK 'G' // Send last shock time and watch for another shock. +#define CMD_TIME_NOW 'T' // Current time +#define CMD_SYNC_ZERO 'Z' // Initial zero + +#define CMD_AUTO_SCREEN_ON 'C' +#define CMD_AUTO_SCREEN_OFF 'c' +#define CMD_SEND_LAST_SCREEN 'E' +#define CMD_BRIGHTNESS_CURVE 'U' + +#define CMD_AUTO_LASER_ON 'L' +#define CMD_AUTO_LASER_OFF 'l' +#define CMD_SEND_LAST_LASER 'J' + +#define CMD_AUDIO 'A' +#define CMD_BEEP 'B' +#define CMD_BEEP_STOP 'S' + +#define CMD_SAMPLE_ALL 'Q' + +#define CMD_MIDI 'M' +#define CMD_NOTE 'N' + +#define NOTE_DELAY 10000 // 10 ms + +// Message types for MIDI encapsulation +#define MIDI_MODE_TYPE 4 // Program Change +#define MIDI_COMMAND_TYPE 5 // Channel Pressure + +#define MIDI_SYSEX_BEGIN '\xF0' +#define MIDI_SYSEX_END '\xF7' + +// LEDs +#define LED_PIN_INT 13 // Built-in LED +#define DEBUG_LED1 11 // On r0.7 PCB: D4 - Red +#define DEBUG_LED2 12 // On r0.7 PCB: D3 - Green + +// WALT sensors +#define PD_LASER_PIN 14 +#define PD_SCREEN_PIN 20 // Same as A6 +#define G_PIN 15 // Same as A1 +#define AUDIO_PIN 22 // Same as A8 +#define MIC_PIN 23 // Same as A9 + +// Threshold and hysteresis for screen on/off reading +#define SCREEN_THRESH_HIGH 110 +#define SCREEN_THRESH_LOW 90 + +elapsedMicros time_us; + +boolean led_state; +char tmp_str[256]; + +boolean serial_over_midi; +String send_buffer; + +struct trigger { + long t; // time of latest occurrence in microseconds + int value; // value at latest occurrence + int count; // occurrences since last readout + boolean probe; // whether currently probing + boolean autosend; // whether sending over serial each time + char tag; +}; + +#define TRIGGER_COUNT 5 +struct trigger laser, screen, sound, midi, gshock, copy_trigger; +struct trigger * triggers[TRIGGER_COUNT] = {&laser, &screen, &sound, &midi, &gshock}; + +#define CLOCK_SYNC_N 9 +struct clock_sync { + boolean is_synced; + int last_sent; + unsigned long sync_times[CLOCK_SYNC_N]; +}; + +struct clock_sync clock; + +// Interrupt handler for laser photodiode +void irq_laser(void) { + laser.t = time_us; + // May need to remove the 'not' if not using internal pullup resistor + laser.value = !digitalRead(PD_LASER_PIN); + laser.count++; + + digitalWrite(DEBUG_LED2, laser.value); + // led_state = !led_state; +} + +void send(char c) { send_buffer += c; } +void send(String s) { send_buffer += s; } + +void send(long l) { + char s[32]; + sprintf(s, "%ld", l); + send(s); +} + +void send(unsigned long l) { + char s[32]; + sprintf(s, "%lu", l); + send(s); +} + +void send(short i) { send((long)i); } +void send(int i) { send((long)i); } +void send(unsigned short i) { send ((unsigned long)i); } +void send(unsigned int i) { send ((unsigned int)i); } + +void send_now() { + if (serial_over_midi) { + usbMIDI.sendSysEx(send_buffer.length(), (const uint8_t *)send_buffer.c_str()); + usbMIDI.send_now(); + send_buffer = MIDI_SYSEX_BEGIN; + } else { + Serial.write(send_buffer.c_str(), send_buffer.length()); + Serial.send_now(); + send_buffer = String(); + } +} + +void send_line() { + if (!serial_over_midi) { + send('\n'); + } else { + send(MIDI_SYSEX_END); + } + send_now(); +} + +void send_trigger(struct trigger t) { + char s[256]; + sprintf(s, "G %c %ld %d %d", t.tag, t.t, t.value, t.count); + send(s); + send_line(); +} + +// flips case for a give char. Unchanged if not in [A-Za-z]. +char flip_case(char c) { + if (c >= 'A' && c <= 'Z') { + return c + 32; + } + if (c >= 'a' && c <= 'z') { + return c - 32; + } + return c; +} + +// Print the same char as the cmd but with flipped case +void send_ack(char cmd) { + send(flip_case(cmd)); + send_line(); +} + +void init_clock() { + memset(&clock, 0, sizeof(struct clock_sync)); + clock.last_sent = -1; +} + +void init_vars() { + noInterrupts(); + init_clock(); + + for (int i = 0; i < TRIGGER_COUNT; i++) { + memset(triggers[i], 0, sizeof(struct trigger)); + } + + laser.tag = 'L'; + screen.tag = 'S'; + gshock.tag = 'G'; + sound.tag = 'A'; // for Audio + midi.tag = 'M'; + + interrupts(); +} + +void setup() { + // LEDs + pinMode(DEBUG_LED1, OUTPUT); + pinMode(DEBUG_LED2, OUTPUT); + pinMode(LED_PIN_INT, OUTPUT); + + // Sensors + pinMode(PD_SCREEN_PIN, INPUT); + pinMode(G_PIN, INPUT); + pinMode(PD_LASER_PIN, INPUT_PULLUP); + attachInterrupt(PD_LASER_PIN, irq_laser, CHANGE); + + Serial.begin(115200); + serial_over_midi = false; + init_vars(); + + led_state = HIGH; // Turn on all LEDs on startup + digitalWrite(LED_PIN_INT, led_state); + digitalWrite(DEBUG_LED1, HIGH); + digitalWrite(DEBUG_LED2, HIGH); +} + + +void run_brightness_curve() { + int i; + long t; + short v; + digitalWrite(DEBUG_LED1, HIGH); + for (i = 0; i < 1000; i++) { + v = analogRead(PD_SCREEN_PIN); + t = time_us; + send(t); + send(' '); + send(v); + send_line(); + delayMicroseconds(450); + } + digitalWrite(DEBUG_LED1, LOW); + send("end"); + send_line(); +} + +void process_command(char cmd) { + int i; + if (cmd == CMD_SYNC_ZERO) { + noInterrupts(); + time_us = 0; + init_clock(); + clock.is_synced = true; + interrupts(); + led_state = LOW; + digitalWrite(DEBUG_LED1, LOW); + digitalWrite(DEBUG_LED2, LOW); + send_ack(CMD_SYNC_ZERO); + } else if (cmd == CMD_TIME_NOW) { + send("t "); + send(time_us); + send_line(); + } else if (cmd == CMD_PING) { + send_ack(CMD_PING); + } else if (cmd == CMD_PING_DELAYED) { + delay(10); + send_ack(CMD_PING_DELAYED); + } else if (cmd >= '1' && cmd <= '9') { + clock.sync_times[cmd - '1'] = time_us; + clock.last_sent = -1; + } else if (cmd == CMD_SYNC_READOUT) { + clock.last_sent++; + int t = 0; + if (clock.last_sent < CLOCK_SYNC_N) { + t = clock.sync_times[clock.last_sent]; + } + send(clock.last_sent + 1); + send(':'); + send(t); + send_line(); + } else if (cmd == CMD_SYNC_SEND) { + clock.last_sent = -1; + // Send CLOCK_SYNC_N times + for (i = 0; i < CLOCK_SYNC_N; ++i) { + delayMicroseconds(737); // TODO: change to some congifurable random + char c = '1' + i; + clock.sync_times[i] = time_us; + send(c); + send_line(); + } + } else if (cmd == CMD_RESET) { + init_vars(); + send_ack(CMD_RESET); + } else if (cmd == CMD_VERSION) { + send(flip_case(cmd)); + send(' '); + send(VERSION); + send_line(); + } else if (cmd == CMD_GSHOCK) { + send(gshock.t); // TODO: Serialize trigger + send_line(); + gshock.t = 0; + gshock.count = 0; + gshock.probe = true; + } else if (cmd == CMD_AUDIO) { + sound.t = 0; + sound.count = 0; + sound.probe = true; + sound.autosend = true; + send_ack(CMD_AUDIO); + } else if (cmd == CMD_BEEP) { + long beep_time = time_us; + tone(MIC_PIN, 5000 /* Hz */); + send(flip_case(cmd)); + send(' '); + send(beep_time); + send_line(); + } else if (cmd == CMD_BEEP_STOP) { + noTone(MIC_PIN); + send_ack(CMD_BEEP_STOP); + } else if (cmd == CMD_MIDI) { + midi.t = 0; + midi.count = 0; + midi.probe = true; + midi.autosend = true; + send_ack(CMD_MIDI); + } else if (cmd == CMD_NOTE) { + unsigned long note_time = time_us + NOTE_DELAY; + send(flip_case(cmd)); + send(' '); + send(note_time); + send_line(); + while (time_us < note_time); + usbMIDI.sendNoteOn(60, 99, 1); + usbMIDI.send_now(); + } else if (cmd == CMD_AUTO_SCREEN_ON) { + screen.value = analogRead(PD_SCREEN_PIN) > SCREEN_THRESH_HIGH; + screen.autosend = true; + screen.probe = true; + send_ack(CMD_AUTO_SCREEN_ON); + } else if (cmd == CMD_AUTO_SCREEN_OFF) { + screen.autosend = false; + screen.probe = false; + send_ack(CMD_AUTO_SCREEN_OFF); + } else if (cmd == CMD_SEND_LAST_SCREEN) { + send_trigger(screen); + screen.count = 0; + } else if (cmd == CMD_AUTO_LASER_ON) { + laser.autosend = true; + laser.count = 0; + send_ack(CMD_AUTO_LASER_ON); + } else if (cmd == CMD_AUTO_LASER_OFF) { + laser.autosend = false; + send_ack(CMD_AUTO_LASER_OFF); + } else if (cmd == CMD_SEND_LAST_LASER) { + send_trigger(laser); + laser.count = 0; + } else if (cmd == CMD_BRIGHTNESS_CURVE) { + send_ack(CMD_BRIGHTNESS_CURVE); + // This blocks all other execution for about 1 second + run_brightness_curve(); + } else if (cmd == CMD_SAMPLE_ALL) { + send(flip_case(cmd)); + send(" G:"); + send(analogRead(G_PIN)); + send(" PD_screen:"); + send(analogRead(PD_SCREEN_PIN)); + send(" PD_laser:"); + send(analogRead(PD_LASER_PIN)); + send_line(); + } else { + send("Unknown command: "); + send(cmd); + send_line(); + } +} + +void loop() { + digitalWrite(LED_PIN_INT, led_state); + + // Probe the accelerometer + if (gshock.probe) { + int v = analogRead(G_PIN); + if (v > 900) { + gshock.t = time_us; + gshock.count++; + gshock.probe = false; + led_state = !led_state; + } + } + + // Probe audio + if (sound.probe) { + int v = analogRead(AUDIO_PIN); + if (v > 20) { + sound.t = time_us; + sound.count++; + sound.probe = false; + led_state = !led_state; + } + } + + // Probe MIDI + boolean has_midi = usbMIDI.read(1); + if(has_midi && midi.probe && usbMIDI.getType() == 0) { // Type 1: note on + midi.t = time_us; + midi.count++; + midi.probe = false; + led_state = !led_state; + } + + // Probe screen + if (screen.probe) { + int v = analogRead(PD_SCREEN_PIN); + if ((screen.value == LOW && v > SCREEN_THRESH_HIGH) || (screen.value != LOW && v < SCREEN_THRESH_LOW)) { + screen.t = time_us; + screen.count++; + led_state = !led_state; + screen.value = !screen.value; + } + } + + // Send out any triggers with autosend and pending data + for (int i = 0; i < TRIGGER_COUNT; i++) { + boolean should_send = false; + + noInterrupts(); + if (triggers[i]->autosend && triggers[i]->count > 0) { + should_send = true; + copy_trigger = *(triggers[i]); + triggers[i]->count = 0; + } + interrupts(); + + if (should_send) { + send_trigger(copy_trigger); + } + } + + // Check if we got incoming commands from the host + if (has_midi) { + if (usbMIDI.getType() == MIDI_MODE_TYPE) { + short program = usbMIDI.getData1(); + serial_over_midi = (program == 1); + send_buffer = (serial_over_midi ? MIDI_SYSEX_BEGIN : String()); + } else if (usbMIDI.getType() == MIDI_COMMAND_TYPE) { + char cmd = usbMIDI.getData1(); + process_command(cmd); + } + } + if (Serial.available()) { + char cmd = Serial.read(); + process_command(cmd); + } +} + |