proton

Firmware for Ghostbusters proton pack
git clone https://git.woozle.org/neale/proton.git

commit
c3f635e
parent
e87a3f6
author
Neale Pickett
date
2016-09-25 21:51:15 -0600 MDT
Now with classes, debugging music+lights=gaps
5 files changed,  +207, -305
A MusicPlayer.cpp
+72, -0
 1@@ -0,0 +1,72 @@
 2+#include <Arduino.h>
 3+#include <SPI.h>
 4+#include <Adafruit_VS1053.h>
 5+#include <SD.h>
 6+#include "MusicPlayer.h"
 7+
 8+
 9+
10+MusicPlayer::MusicPlayer(int8_t cs, int8_t dcs, int8_t dreq, int8_t cardcs)
11+{
12+  musicPlayer = new Adafruit_VS1053_FilePlayer(cs, dcs, dreq, cardcs);
13+  musicPlayer->begin();
14+  musicPlayer->setVolume(20, 20); // lower = louder
15+  musicPlayer->sineTest(0x44, 500);
16+  SD.begin(cardcs);
17+}
18+
19+void
20+MusicPlayer::setVolume(uint8_t left, uint8_t right)
21+{
22+  musicPlayer->setVolume(left, right);
23+}
24+
25+
26+boolean
27+MusicPlayer::startPlayingFile(const char *trackname)
28+{
29+  return musicPlayer->startPlayingFile(trackname);
30+}
31+
32+void
33+MusicPlayer::stopPlaying()
34+{
35+  musicPlayer->stopPlaying();
36+}
37+
38+void
39+MusicPlayer::poll(unsigned long jiffies)
40+{
41+  /* Cleverness ensues
42+   * 
43+   * The Adafruit library is written to let you go off and do whatever you need,
44+   * hooking into an interrupt to sort of act like a multitasking operating system,
45+   * interrupting your program periodically.
46+   * 
47+   * That's smart, since it makes it easy to use,
48+   * but we want this to be responsive, and can't handle something barging in and taking up lots of time:
49+   * it makes things look really uneven as our display code pauses to fill the buffer.
50+   * Fortunately, we don't have to fill the entire buffer at once, we can trickle data in.
51+   * That's what this does. 
52+   * 
53+   * Since the entire program is polling, without ever calling delay,
54+   * and hopefully doing what needs to be done quickly,
55+   * we check to see if the music chip wants more data.
56+   * If it does, we give it one chunk, and only one chunk,
57+   * rather than filling its buffer back up completely.
58+   * 
59+   * There is still some weirdness with this loop,
60+   * possibly because the SPI routines are masking interrupts used to increment millis.
61+   * But it's remarkably more fluid than the other way.
62+   */
63+  
64+  if (musicPlayer->playingMusic && musicPlayer->readyForData()) {
65+    int bytesread = musicPlayer->currentTrack.read(musicPlayer->mp3buffer, VS1053_DATABUFFERLEN);
66+    if (bytesread == 0) {
67+      musicPlayer->playingMusic = false;
68+      musicPlayer->currentTrack.close();
69+    } else {
70+      musicPlayer->playData(musicPlayer->mp3buffer, bytesread);
71+    }
72+  }
73+}
A MusicPlayer.h
+14, -0
 1@@ -0,0 +1,14 @@
 2+#pragma once
 3+#include <Arduino.h>
 4+#include <SD.h>
 5+#include <Adafruit_VS1053.h>
 6+
 7+class MusicPlayer {
 8+  Adafruit_VS1053_FilePlayer *musicPlayer;
 9+public:
10+  MusicPlayer(int8_t cs, int8_t dcs, int8_t dreq, int8_t cardcs);
11+  boolean startPlayingFile(const char *trackname);
12+  void setVolume(uint8_t left, uint8_t right);
13+  void stopPlaying();
14+  void poll(unsigned long jiffies);			// Call this once per loop()
15+};
M ProtonPack.ino
+39, -305
  1@@ -3,343 +3,77 @@
  2 
  3 #include <SPI.h>
  4 #include <Wire.h>
  5-#include <SD.h>
  6 #include <Adafruit_NeoPixel.h>
  7-#include <Adafruit_VS1053.h>
  8 #include <Adafruit_LEDBackpack.h>
  9 #include <Adafruit_GFX.h>
 10+#include "MusicPlayer.h"
 11+#include "Synchrotron.h"
 12 
 13-#define DEBUG 12
 14+// Music Player
 15+#define MUSIC_CS 7
 16+#define MUSIC_DATA 6
 17+#define MUSIC_CARDCS 4
 18+#define MUSIC_REQ 3
 19+MusicPlayer *music;
 20 
 21-// Music Player object
 22-#define SHIELD_RESET  -1      // VS1053 reset pin (unused!)
 23-#define SHIELD_CS     7      // VS1053 chip select pin (output)
 24-#define SHIELD_DCS    6      // VS1053 Data/command select pin (output)
 25-#define CARDCS 4 // Card chip select
 26-#define DREQ 1 // VS1053 Data request (an interrupt pin)
 27-Adafruit_VS1053_FilePlayer musicPlayer = Adafruit_VS1053_FilePlayer(SHIELD_RESET, SHIELD_CS, SHIELD_DCS, DREQ, CARDCS);
 28+// Synchrotron
 29+#define SYNC1_NPIXELS 24
 30+#define SYNC1_DATA 5
 31+Synchrotron *sync1;
 32 
 33-// NeoPixel: so cool
 34-#define SYNCHROTRON_PIN 5
 35-#define SYNCHROTRON_PIXELS 24 // I'm using the middle-sized NeoPixel ring
 36-Adafruit_NeoPixel synchrotron = Adafruit_NeoPixel(SYNCHROTRON_PIXELS, SYNCHROTRON_PIN, NEO_GRB | NEO_KHZ800);
 37-
 38-// 7-segment displays
 39-Adafruit_7segment disp1 = Adafruit_7segment();
 40+// Debug LED
 41+#define DEBUG 13
 42 
 43 // Inputs
 44-#define TRIGGER 8
 45-
 46-// Nominal brightness
 47-#define brightness 64
 48+#define TRIGGER 9
 49 
 50-const byte dispBright = 10;
 51+// global time counter
 52 unsigned long jiffies = 0;
 53 
 54-void rgbPWM(byte r, byte g, byte b) {
 55-  // XXX: do this
 56-}
 57-
 58-void rgb(byte r, byte g, byte b) {
 59-  for (int i = 0; i < SYNCHROTRON_PIXELS; i += 1) {
 60-    synchrotron.setPixelColor(i, synchrotron.Color(r, g, b));
 61-  }
 62-  synchrotron.show();
 63-}
 64-
 65 void setup() {
 66   randomSeed(analogRead(12));
 67 
 68-  // synchrotron
 69-  synchrotron.begin();
 70-  synchrotron.show(); // Turn everything off
 71-
 72   // inputs
 73   pinMode(TRIGGER, INPUT_PULLUP);
 74 
 75-  // music player, this sets up SPI for us
 76-  SD.begin(CARDCS);
 77-  musicPlayer.begin();
 78-  musicPlayer.setVolume(20, 20); // lower = louder
 79-  // We don't set useInterrupt, since we do our own polling for smoother operations
 80-
 81-  // 7-segment displays.
 82-  // These also use SPI, in i2c mode.
 83-  // Since the music player has a CS line,
 84-  // and we're unlikely to send the right i2c command to the 7-segment to wake it up,
 85-  // it's okay to use the same SPI bus for both.
 86-  disp1.begin(0x70);
 87-}
 88-
 89-// Synchrotron needs to "spin up"
 90-// We start slow, with red, then work our way through the rainbow to blue
 91-bool charge() {
 92-  static uint32_t count = 0;
 93-  static int every = 9;
 94-  static int reps = 0;
 95-  uint32_t color_count;
 96-  byte r, g, b;
 97-  static int whichout = 0;
 98-
 99-  // Play startup sound at the start
100-  if (count == 0) {
101-    musicPlayer.startPlayingFile("track001.mp3");
102-  }
103-
104-  // Make the animation play out a little more slowly,
105-  // while still allowing a nice fast rotation
106-  color_count = count / 4;
107-
108-  // Give the illusion of something spinning up
109-  if (every == 1) {
110-    whichout = (whichout + 1) % SYNCHROTRON_PIXELS;
111-  } else if (count % every == 0) {
112-    whichout = (whichout + 1) % SYNCHROTRON_PIXELS;
113-    reps += 1;
114-    if (reps == 20 - every) {
115-      every -= 1;
116-      reps = 0;
117-    }
118-  }
119-
120-  // Start at blue, go through hue to red
121-  switch (color_count / brightness) {
122-  case 0:
123-    r = color_count % brightness;
124-    g = 0;
125-    b = 0;
126-    break;
127-  case 1:
128-    r = brightness - (color_count % brightness) - 1;
129-    g = color_count % brightness;
130-    b = 0;
131-    break;
132-  case 2:
133-    r = 0;
134-    g = brightness - (color_count % brightness) - 1;
135-    b = color_count % brightness;
136-    break;
137-  default:
138-    rgb(brightness, 0, 0);
139-    return true;
140-  }
141-
142-  // Set 'em up pixels
143-  for (int i = 0; i < SYNCHROTRON_PIXELS; i += 1) {
144-    if (whichout == i) {
145-      synchrotron.setPixelColor(i, 0);
146-    } else if ((whichout == (i+1) % SYNCHROTRON_PIXELS) || ((whichout+1) % SYNCHROTRON_PIXELS == i)) {
147-      synchrotron.setPixelColor(i, synchrotron.Color(r/4, g/4, b/4));
148-    } else {
149-      synchrotron.setPixelColor(i, synchrotron.Color(r, g, b));
150-    }
151-  }
152-  synchrotron.show();
153-
154-  disp1.clear();
155-  disp1.printNumber(0xb00, HEX);
156-  disp1.setBrightness(dispBright);
157-  disp1.writeDisplay();
158-
159-  count += 1;
160-
161-  return false;
162-}
163-
164-// Do a sort of mirrored KITT effect
165-bool kitt() {
166-  static int count = 0;
167-  int out = count % (SYNCHROTRON_PIXELS/2);
168-
169-  if (jiffies % 12 != 0) {
170-    return false;
171-  }
172-
173-  for (int i = 0; i < SYNCHROTRON_PIXELS; i += 1) {
174-    int pixnum = (SYNCHROTRON_PIXELS/2) - abs(i - (SYNCHROTRON_PIXELS / 2));
175-    int intensity;
176-
177-    if (count < SYNCHROTRON_PIXELS/2) {
178-      intensity = 100;
179-      if (pixnum == out) {
180-       intensity = 50;
181-      } else if (pixnum < out) {
182-       intensity = 10;
183-      }
184-    } else {
185-      intensity = 10;
186-      if (pixnum == out) {
187-        intensity = 50;
188-      } else if (pixnum < out) {
189-        intensity = 100;
190-      }
191-    }
192-    synchrotron.setPixelColor(i, synchrotron.Color(brightness * intensity / 100, 0, 0));
193-  }
194-  synchrotron.show();
195+  // outputs
196+  pinMode(DEBUG, OUTPUT);
197 
198-  count += 1;
199-  if (count > SYNCHROTRON_PIXELS) {
200-    rgb(brightness, 0, 0);
201-    count = 0;
202-    return true;
203-  }
204-  return false;
205-}
206-
207-bool glitch(int r, int g, int b) {
208-  static int state = 0;
209-  int i;
210-  
211-  if (jiffies % 10 != 0) {
212-    return false;
213-  }
214-  
215-  switch (state) {
216-  case 0:
217-    // glitch to a random color
218-    r = random(brightness / 6);
219-    g = random(brightness / 6);
220-    b = random(brightness / 6);
221-    rgb(r, g, b);
222-    state = 1;
223-    break;
224-  case 1:
225-    rgb(r, g, b);
226-    state = 0;
227-    return true;
228-    break;
229-  }  
230-  
231-  return false;
232-}
233+  // music player, this sets up SPI for us
234+  music = new MusicPlayer(MUSIC_CS, MUSIC_DATA, MUSIC_REQ, MUSIC_CARDCS);
235 
236-void fire() {
237-  rgb(0, brightness, brightness);
238+  // synchrotron
239+  sync1 = new Synchrotron(SYNC1_NPIXELS, SYNC1_DATA);
240 }
241 
242-void fireDone() {
243-  rgb(brightness, 0, 0);
244-}
245 
246 void flashDebug() {
247-  if (jiffies % 50 == 0) {
248-    int val = digitalRead(DEBUG);
249-    digitalWrite(DEBUG, (val==HIGH)?LOW:HIGH);
250-  }
251-}
252-
253-void tick() {
254-  static int doing = 0;
255-  static float val1 = 584.2;
256-  static bool firing = false;
257-  bool trigger;
258+  uint8_t val;
259 
260-  trigger = (digitalRead(TRIGGER) == LOW);
261-
262-  if (trigger) {
263-    firing = true;
264-    doing = 100;
265-  }  
266-  
267-  switch (doing) {
268-  case 0: // doing nothing
269-    if (jiffies % 300 == 0) {
270-      doing = 1; // KITT
271-    } else if (random(350) == 0) {
272-      doing = 2; // surge
273-    } else if (random(400) == 0) {
274-      doing = 3; // glitch
275-    }
276-    break;
277-  case 1:
278-    if (kitt()) {
279-      doing = 0;
280-    }
281-    break;
282-  case 2:
283-    doing = 0;
284-    break;
285-  case 3:
286-    if (glitch(brightness, 0, 0)) {
287-      doing = 0;
288-    }
289-    break;
290-  case 100:
291-    fire();
292-    if (! trigger) {
293-      doing = 101;
294-    }
295-    break;
296-  case 101:
297-    fireDone();
298-    doing = 0;
299-    break;
300-  default:
301-    doing = 0;
302-  }
303-  
304-  // screw around with the displays
305-  if (random(20) == 0) {
306-    val1 += (random(3) - 1) / 10.0;
307-    disp1.print(val1);
308-    disp1.setBrightness(dispBright);
309-    disp1.writeDisplay();
310-  } else if (random(150) == 0) {
311-    disp1.setBrightness(random(16));
312-    disp1.writeDisplay();
313-  } else if (random(150) == 0) {
314-    disp1.clear();
315-    disp1.writeDisplay();
316-  } else if (random(400) == 0) {
317-    int someNumber = random(9999);
318-    disp1.print(someNumber);
319-    disp1.writeDisplay();
320-  }
321-    
322-  flashDebug();
323+  val = (jiffies % 100) < 50;
324+  digitalWrite(DEBUG, val);
325 }
326 
327 void loop() {
328+  static int state = 0;
329   // 6 seems to be about what my overly-critical brain needs to buffer out
330   // any music player delays so that they're unnoticeable
331   unsigned long new_jiffies = millis() / 6;
332+  boolean trigger = ! digitalRead(TRIGGER);
333 
334-  if (new_jiffies > jiffies) {
335-    jiffies = new_jiffies;
336-    tick();
337-  }
338+  music->poll(jiffies);
339+
340+  if (state == 0) {
341+    if (new_jiffies > jiffies) {
342+      if (trigger) {
343+	state = 1;
344+	music->startPlayingFile("track001.mp3");
345+	sync1->charge();
346+      }
347 
348-  /* Cleverness ensues
349-   * 
350-   * The Adafruit library is written to let you go off and do whatever you need,
351-   * hooking into an interrupt to sort of act like a multitasking operating system,
352-   * interrupting your program periodically.
353-   * 
354-   * That's smart, since it makes it easy to use,
355-   * but we want this to be responsive, and can't handle something barging in and taking up lots of time:
356-   * it makes things look really uneven as our display code pauses to fill the buffer.
357-   * Fortunately, we don't have to fill the entire buffer at once, we can trickle data in.
358-   * That's what this does. 
359-   * 
360-   * Since the entire program is polling, without ever calling delay,
361-   * and hopefully doing what needs to be done quickly,
362-   * we check to see if the music chip wants more data.
363-   * If it does, we give it one chunk, and only one chunk,
364-   * rather than filling its buffer back up completely.
365-   * 
366-   * There is still some weirdness with this loop,
367-   * possibly because the SPI routines are masking interrupts used to increment millis.
368-   * But it's remarkably more fluid than the other way.
369-   */
370-  
371-  if (musicPlayer.playingMusic && musicPlayer.readyForData()) {
372-    int bytesread = musicPlayer.currentTrack.read(musicPlayer.mp3buffer, VS1053_DATABUFFERLEN);
373-    if (bytesread == 0) {
374-      musicPlayer.playingMusic = false;
375-      musicPlayer.currentTrack.close();
376-    } else {
377-      musicPlayer.playData(musicPlayer.mp3buffer, bytesread);
378+      jiffies = new_jiffies;
379+      sync1->tick(jiffies);
380+      flashDebug();
381     }
382   }
383 }
A Synchrotron.cpp
+62, -0
 1@@ -0,0 +1,62 @@
 2+#include <Arduino.h>
 3+#include <Adafruit_NeoPixel.h>
 4+#include "Synchrotron.h"
 5+
 6+#define brightness 255
 7+
 8+Synchrotron::Synchrotron(uint16_t n, uint8_t p, neoPixelType t)
 9+{
10+  pxl = new Adafruit_NeoPixel(n, p, t);
11+  npixels = n;
12+  cur = 0;
13+  pxl->begin();
14+  pxl->show();
15+  standby();
16+}
17+
18+Synchrotron::standby() {
19+  tickrate = 12;
20+  ticks = 0;
21+  r = brightness;
22+  g = 0;
23+  b = 0;
24+}
25+
26+Synchrotron::charge() {
27+  tickrate = 2;
28+  ticks = 0;
29+  r = brightness;
30+  g = brightness / 8;
31+  b = 0;
32+}
33+
34+Synchrotron::fire() {
35+}
36+
37+Synchrotron::discharge() {
38+}
39+
40+Synchrotron::tick(unsigned long jiffies) {
41+  float adj = (float)ticks / (float)tickrate;
42+  byte raa = r * adj;
43+  byte gaa = g * adj;
44+  byte baa = b * adj;
45+  byte ra = r - raa;
46+  byte ga = g - gaa;
47+  byte ba = b - baa;
48+
49+  pxl->clear();
50+  pxl->setPixelColor((cur + 1) % npixels, pxl->Color(raa, gaa, baa));
51+  for (int i = 0; i < 4; i += 1) {
52+    int div = 1 << (2*i);
53+    pxl->setPixelColor((cur + npixels - i) % npixels, pxl->Color(ra/div, ga/div, ba/div));
54+  }
55+
56+  pxl->show();
57+
58+  ticks += 1;
59+  if (ticks == tickrate) {
60+    ticks = 0;
61+    cur = (cur + 1) % npixels;
62+  }
63+}
A Synchrotron.h
+20, -0
 1@@ -0,0 +1,20 @@
 2+#pragma once
 3+#include <Arduino.h>
 4+#include <Adafruit_NeoPixel.h>
 5+
 6+
 7+class Synchrotron {
 8+  Adafruit_NeoPixel *pxl;
 9+  uint16_t npixels;  // How many pixels there are
10+  int cur;	     // Which pixel the synchrotron is on, currently
11+  int tickrate;	     // How many millis between pixel position changes
12+  int ticks; // How many ticks have elapsed since last position change
13+  byte r, g, b;			// Current color
14+public:
15+  Synchrotron(uint16_t n, uint8_t p=6, neoPixelType t=NEO_GRB + NEO_KHZ800);
16+  standby();
17+  charge();
18+  fire();
19+  discharge();
20+  tick(unsigned long jiffies);	// Try to call this every jiffy
21+};