|
| 1 | +// "Make-Believe" sketch for Adafruit Circuit Playground. |
| 2 | +// Lights and sounds react to motion and taps. |
| 3 | + |
| 4 | +#include <Adafruit_CircuitPlayground.h> |
| 5 | + |
| 6 | +// Enable ONE of these lines to select a theme: |
| 7 | +//#include "knight.h" // Swoosh & clank metal sword |
| 8 | +//#include "laser.h" // MWAAAW! "laser" sword |
| 9 | +#include "wand.h" // Magic wand |
| 10 | + |
| 11 | +void setup() { |
| 12 | + // Initialize Circuit Playground board, accelerometer, NeoPixels |
| 13 | + CircuitPlayground.begin(); |
| 14 | + CircuitPlayground.setAccelRange(LIS3DH_RANGE_16_G); |
| 15 | + CircuitPlayground.strip.setBrightness(255); // Full brightness |
| 16 | + |
| 17 | + // Start default "idle" looping animation and sound |
| 18 | + playAnim( idlePixelData, idleFPS , sizeof(idlePixelData), true); |
| 19 | + playSound(idleAudioData, idleSampleRate, sizeof(idleAudioData), true); |
| 20 | +} |
| 21 | + |
| 22 | +// Global values used by the animation and sound functions |
| 23 | +uint16_t *pixelBaseAddr, // Address of active animation table |
| 24 | + pixelLen, // Number of pixels in active table |
| 25 | + pixelIdx, // Index of first pixel of current frame |
| 26 | + audioLen; // Number of audio samples in active table |
| 27 | +volatile uint16_t audioIdx; // Index of current audio sample |
| 28 | +uint8_t pixelFPS, // Frames/second for active animation |
| 29 | + *audioBaseAddr; // Address of active sound table |
| 30 | +boolean pixelLoop, // If true, animation repeats |
| 31 | + audioLoop; // If true, audio repeats |
| 32 | + |
| 33 | +// Begin playing a sound from PROGMEM table (unless NULL) |
| 34 | +void playSound(const uint8_t *addr, uint16_t rate, uint16_t len, boolean repeat) { |
| 35 | + audioBaseAddr = addr; |
| 36 | + if(addr) { |
| 37 | + CircuitPlayground.speaker.begin(); |
| 38 | + audioLen = len; |
| 39 | + audioLoop = repeat; |
| 40 | + audioIdx = 0; |
| 41 | + |
| 42 | + // Timer/Counter 1 interrupt is used to play sound in background |
| 43 | + TCCR1A = 0; // Timer1: No PWM output |
| 44 | + TCCR1B = _BV(WGM12) | _BV(CS10); // CTC mode, no prescale |
| 45 | + OCR1A = (F_CPU + (rate / 2)) / rate; // Value for timer match |
| 46 | + TCNT1 = 0; // Reset counter |
| 47 | + TIMSK1 = _BV(OCIE1A); // Start interrupt |
| 48 | + } else { // NULL addr = audio OFF |
| 49 | + TIMSK1 = 0; // Stop interrupt |
| 50 | + while(OCR4A) { // Ease speaker into off position |
| 51 | + OCR4A--; // to avoid audible 'pop' at end |
| 52 | + delayMicroseconds(3); |
| 53 | + } |
| 54 | + CircuitPlayground.speaker.end(); |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +// Timer/Counter 1 interrupt; periodically sets PWM speaker output from audio table |
| 59 | +ISR(TIMER1_COMPA_vect) { |
| 60 | + OCR4A = pgm_read_byte(&audioBaseAddr[audioIdx]); |
| 61 | + if(++audioIdx >= audioLen) { // Past end of table? |
| 62 | + if(audioLoop) { // Loop sound? |
| 63 | + audioIdx = 0; // Reset counter to start of table |
| 64 | + } else { // Don't loop... |
| 65 | + playSound(idleAudioData, idleSampleRate, sizeof(idleAudioData), true); |
| 66 | + } |
| 67 | + } |
| 68 | +} |
| 69 | + |
| 70 | +// Begin playing a NeoPixel animation from a PROGMEM table |
| 71 | +void playAnim(const uint16_t *addr, uint8_t fps, uint16_t bytes, boolean repeat) { |
| 72 | + pixelBaseAddr = addr; |
| 73 | + if(addr) { |
| 74 | + pixelFPS = fps; |
| 75 | + pixelLen = bytes / 2; |
| 76 | + pixelLoop = repeat; |
| 77 | + pixelIdx = 0; |
| 78 | + } else { |
| 79 | + CircuitPlayground.strip.clear(); |
| 80 | + } |
| 81 | +} |
| 82 | + |
| 83 | +uint32_t prev = 0; // Time of last NeoPixel refresh |
| 84 | + |
| 85 | +void loop() { |
| 86 | + uint32_t t; // Current time in milliseconds |
| 87 | + // Until the next animation frame interval has elapsed... |
| 88 | + while(((t = millis()) - prev) < (1000 / pixelFPS)) { |
| 89 | + // Read accelerometer, test for swing/tap thresholds |
| 90 | + float x = CircuitPlayground.motionX() / 9.8, // m/s2 to G |
| 91 | + y = CircuitPlayground.motionY() / 9.8, |
| 92 | + z = CircuitPlayground.motionZ() / 9.8, |
| 93 | + d = sqrt(x * x + y * y + z * z); // Acceleration magnitude in 3D |
| 94 | + d = fabs(d - 1.0); // Neutral is 1G; d is relative acceleration now |
| 95 | + if(d >= TAP_THRESHOLD) { // Big acceleration? |
| 96 | + if(audioBaseAddr != tapAudioData) { // If not already playing tap sound, |
| 97 | + // Start playing tap sound and animation |
| 98 | + playSound(tapAudioData, tapSampleRate, sizeof(tapAudioData), false); |
| 99 | + playAnim(tapPixelData, tapFPS, sizeof(tapPixelData), false); |
| 100 | + } |
| 101 | + } else if(d >= SWING_THRESHOLD) { // Medium acceleration? |
| 102 | + if(audioBaseAddr == idleAudioData) { // If not already swinging or tapping, |
| 103 | + // start playing swing sound and animation |
| 104 | + playSound(swingAudioData, swingSampleRate, sizeof(swingAudioData), false); |
| 105 | + playAnim(swingPixelData, swingFPS, sizeof(swingPixelData), false); |
| 106 | + } |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + // Show LEDs rendered on prior pass. It's done this way so animation timing |
| 111 | + // is a bit more consistent (frame rendering time may vary slightly). |
| 112 | + CircuitPlayground.strip.show(); |
| 113 | + prev = t; // Save refresh time for next frame sync |
| 114 | + |
| 115 | + if(pixelBaseAddr) { |
| 116 | + for(uint8_t i=0; i<10; i++) { // For each NeoPixel... |
| 117 | + // Read pixel color from PROGMEM table |
| 118 | + uint16_t rgb = pgm_read_word(&pixelBaseAddr[pixelIdx++]); |
| 119 | + // Expand 16-bit color to 24 bits using gamma tables |
| 120 | + // RRRRRGGGGGGBBBBB -> RRRRRRRR GGGGGGGG BBBBBBBB |
| 121 | + CircuitPlayground.strip.setPixelColor(i, |
| 122 | + pgm_read_byte(&gamma5[ rgb >> 11 ]), |
| 123 | + pgm_read_byte(&gamma6[(rgb >> 5) & 0x3F]), |
| 124 | + pgm_read_byte(&gamma5[ rgb & 0x1F])); |
| 125 | + } |
| 126 | + |
| 127 | + if(pixelIdx >= pixelLen) { // End of animation table reached? |
| 128 | + if(pixelLoop) { // Repeat animation? |
| 129 | + pixelIdx = 0; // Reset index to start of table |
| 130 | + } else { // else switch back to "idle" animation |
| 131 | + playAnim(idlePixelData, idleFPS, sizeof(idlePixelData), true); |
| 132 | + } |
| 133 | + } |
| 134 | + } |
| 135 | +} |
0 commit comments