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