Home | History | Annotate | Download | only in walt
      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 
     77 elapsedMicros time_us;
     78 
     79 boolean led_state;
     80 char tmp_str[256];
     81 
     82 boolean serial_over_midi;
     83 String send_buffer;
     84 
     85 struct 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
     95 struct trigger laser, screen, sound, midi, gshock, copy_trigger;
     96 struct trigger * triggers[TRIGGER_COUNT] = {&laser, &screen, &sound, &midi, &gshock};
     97 
     98 #define CLOCK_SYNC_N 9
     99 struct clock_sync {
    100   boolean is_synced;
    101   int last_sent;
    102   unsigned long sync_times[CLOCK_SYNC_N];
    103 };
    104 
    105 struct clock_sync clock;
    106 
    107 // Interrupt handler for laser photodiode
    108 void 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 
    118 void send(char c) { send_buffer += c; }
    119 void send(String s) { send_buffer += s; }
    120 
    121 void send(long l) {
    122   char s[32];
    123   sprintf(s, "%ld", l);
    124   send(s);
    125 }
    126 
    127 void send(unsigned long l) {
    128   char s[32];
    129   sprintf(s, "%lu", l);
    130   send(s);
    131 }
    132 
    133 void send(short i) { send((long)i); }
    134 void send(int i) { send((long)i); }
    135 void send(unsigned short i) { send ((unsigned long)i); }
    136 void send(unsigned int i) { send ((unsigned int)i); }
    137 
    138 void 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 
    150 void send_line() {
    151   if (!serial_over_midi) {
    152     send('\n');
    153   } else {
    154     send(MIDI_SYSEX_END);
    155   }
    156   send_now();
    157 }
    158 
    159 void 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].
    167 char 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
    178 void send_ack(char cmd) {
    179   send(flip_case(cmd));
    180   send_line();
    181 }
    182 
    183 void init_clock() {
    184   memset(&clock, 0, sizeof(struct clock_sync));
    185   clock.last_sent = -1;
    186 }
    187 
    188 void 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 
    205 void 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 
    228 void 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 
    247 void 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 
    378 void 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