/ 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 }