After checking your conductive thread circuit for shorts, upload the NeoPixel test code (refer to the FLORA NeoPixel tutorial if necessary). If any of your connections are flaky, reinforce them with conductive thread. Once all your pixels are working flawlessly, load the code below, which will make a chasing pac-man-inspired animation appear across the suspenders.
// "Retro gaming" suspenders using Adafruit NeoPixel LEDs. // by Phillip Burgess for Adafruit Industries #include <Adafruit_NeoPixel.h> #define LED_PIN 6 #define N_LEDS 30 #define N_SPRITES 5 // 4 ghosts + 1 mouth #define PILL_INDEX (N_LEDS - 5) // Position of 'power pill' along strip #define PILL_R 255 // Power pill is slightly pink-ish, #define PILL_G 184 // not pure white. #define PILL_B 151 #define DOT_R (PILL_R / 3) // Dots are same hue as power pill, #define DOT_G (PILL_G / 3) // just dimmer. #define DOT_B (PILL_B / 3) #define DOT_FADE_SPEED 2 // New screenful of dots fades in this fast uint8_t gamma[] PROGMEM = { // Gamma correction table for LED brightness 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x02,0x02,0x02,0x02, 0x02,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x04,0x04,0x04,0x04,0x04,0x05,0x05,0x05, 0x05,0x06,0x06,0x06,0x06,0x07,0x07,0x07,0x07,0x08,0x08,0x08,0x09,0x09,0x09,0x0A, 0x0A,0x0A,0x0B,0x0B,0x0B,0x0C,0x0C,0x0D,0x0D,0x0D,0x0E,0x0E,0x0F,0x0F,0x10,0x10, 0x11,0x11,0x12,0x12,0x13,0x13,0x14,0x14,0x15,0x15,0x16,0x16,0x17,0x18,0x18,0x19, 0x19,0x1A,0x1B,0x1B,0x1C,0x1D,0x1D,0x1E,0x1F,0x20,0x20,0x21,0x22,0x23,0x23,0x24, 0x25,0x26,0x27,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x32, 0x33,0x34,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,0x40,0x42,0x43,0x44, 0x45,0x46,0x48,0x49,0x4A,0x4B,0x4D,0x4E,0x4F,0x51,0x52,0x53,0x55,0x56,0x57,0x59, 0x5A,0x5C,0x5D,0x5F,0x60,0x62,0x63,0x65,0x66,0x68,0x69,0x6B,0x6D,0x6E,0x70,0x72, 0x73,0x75,0x77,0x78,0x7A,0x7C,0x7E,0x7F,0x81,0x83,0x85,0x87,0x89,0x8A,0x8C,0x8E, 0x90,0x92,0x94,0x96,0x98,0x9A,0x9C,0x9E,0xA0,0xA2,0xA4,0xA7,0xA9,0xAB,0xAD,0xAF, 0xB1,0xB4,0xB6,0xB8,0xBA,0xBD,0xBF,0xC1,0xC4,0xC6,0xC8,0xCB,0xCD,0xD0,0xD2,0xD5, 0xD7,0xDA,0xDC,0xDF,0xE1,0xE4,0xE7,0xE9,0xEC,0xEF,0xF1,0xF4,0xF7,0xF9,0xFC,0xFF }; Adafruit_NeoPixel strip = Adafruit_NeoPixel(N_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800); // 100% of animation logic is in a timer interrupt, hence all globals are volatile. // Lots of sub-pixel smoothing shenanigans are going on in an attempt to better // translate the smooth animation of the original game into this coarse 1D format. // Image is buffered independently of the NeoPixel color data, to allow for // blending, gamma correction and horizontal flip on alternate passes. volatile uint32_t pixel[N_LEDS][3]; // Game character sprites are always 1 pixel wide here to simplify certain code. // Horizontal coordinates/speeds are in 1/256 pixel units. volatile struct { int16_t x; // Current position int16_t prevX; // Position in prior frame int8_t speed; int16_t x1, x2; // Span covered by prior-to-current movement uint8_t r, g, b, a; // Color & opacity uint8_t moveDuringPause; // If set, sprite is immune to animation pauses } sprite[N_SPRITES]; #define SPRITE_RED 0 // Ghost indices #define SPRITE_PINK 1 #define SPRITE_CYAN 2 #define SPRITE_ORANGE 3 #define SPRITE_MOUTH 4 // Mouth index volatile uint8_t dotState[N_LEDS], // 1 = dot enabled, 0 = eaten pillBlink = 0, // Power pill blink counter pauseCounter = 0, // Counter for 'chomp-a-ghost' pause dotFade, // Counter as dots fade into view flip = 1; // If bit 1 set, flip playfield volatile uint16_t ghostTime = 0; // Countdown timer for blue ghosts //---------------------------------------------------------------------------------- void setup() { memset((void *)pixel, 0, sizeof(pixel)); // Clear screen buffer memset((void *)dotState, 0, sizeof(dotState)); // Set all dots to 'off' reset_animation(); // Init all other values strip.begin(); // Set up Timer1 interrupt: Mode 14 (fast PWM, top=ICR1), 1:8 prescale, OC1A/B off TCCR1A = _BV(WGM11); TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11); ICR1 = F_CPU / 8 / 64; // 64 Hz TIMSK1 |= _BV(TOIE1); // Enable Timer1 overflow interrupt sei(); // Enable global interrupts } //---------------------------------------------------------------------------------- // This is called once at program start, and periodically each time the animation // needs to reset. The playfield is flipped horizontally on each call, for variety. void reset_animation() { uint8_t i, j, temp; sprite[SPRITE_RED].r = 255; // Red ghost color sprite[SPRITE_RED].g = 0; sprite[SPRITE_RED].b = 0; sprite[SPRITE_PINK].r = 255; // Pink ghost color sprite[SPRITE_PINK].g = 184; sprite[SPRITE_PINK].b = 222; sprite[SPRITE_CYAN].r = 0; // Cyan ghost color sprite[SPRITE_CYAN].g = 255; sprite[SPRITE_CYAN].b = 222; sprite[SPRITE_ORANGE].r = 255; // Orange ghost color sprite[SPRITE_ORANGE].g = 184; sprite[SPRITE_ORANGE].b = 71; sprite[SPRITE_MOUTH].r = 255; // Mouth color sprite[SPRITE_MOUTH].g = 255; sprite[SPRITE_MOUTH].b = 0; // Ghost positions aren't perfectly spaced; they're a bit scrambled for // variety. Mouth starts off left side, ghosts are further left of that. sprite[SPRITE_MOUTH].x = -400; // Mouth position sprite[SPRITE_RED].x = sprite[SPRITE_MOUTH].x - 800; // Red ghost position sprite[SPRITE_PINK].x = sprite[SPRITE_RED].x - 440; // Pink ghost position sprite[SPRITE_CYAN].x = sprite[SPRITE_PINK].x - 500; // Cyan ghost position sprite[SPRITE_ORANGE].x = sprite[SPRITE_CYAN].x - 530; // Orange ghost position for(i=0; i<N_SPRITES; i++) { sprite[i].prevX = sprite[i].x; sprite[i].moveDuringPause = false; sprite[i].a = 255; // Opaque sprite[i].speed = 14; } sprite[4].speed = 13; // Mouth moves a little slower when eating dots flip++; // Reverse layout of display // Flip residual screen dots to match new layout for(i=0, j=N_LEDS-1; i<N_LEDS/2; i++, j--) { temp = dotState[i]; dotState[i] = dotState[j]; dotState[j] = temp; } dotFade = 0; // Fade in new screenload of dots } //---------------------------------------------------------------------------------- // Nothing happens in loop() -- everything is in the interrupt handler below. void loop() { } //---------------------------------------------------------------------------------- // Timer1 overflow interrupt handler ISR(TIMER1_OVF_vect) { uint8_t i; uint16_t fracX, weightL, weightR, inv, w; int8_t x1, x2; // First thing is to push data for the PRIOR frame to the strip. This is done // so the animation timing will be rock steady. Some frames may take slightly // different times to process than others, and putting the show() at the end // would cause a slightly different refresh time for each. strip.show(); // If new screenload of dots is fading into view, that's the only processing // that occurs -- no sprite animation or collision detection, etc. happens. if(dotFade < 255) { for(i=0; i<N_LEDS; i++) { // For each dot... if(dotState[i] == 1) { // Already something here? pixel[i][0] = DOT_R; // Yep, show uneaten 'on' dot. pixel[i][1] = DOT_G; pixel[i][2] = DOT_B; } else { // Fade new dot into view if(i == PILL_INDEX) { // Is it the power pill? if(pillBlink & 16) { // Blinking 'on' right now? w = (uint16_t)dotFade + 1; // Blend factor pixel[i][0] = (PILL_R * w) >> 8; pixel[i][1] = (PILL_G * w) >> 8; pixel[i][2] = (PILL_B * w) >> 8; } else { pixel[i][0] = pixel[i][1] = pixel[i][2] = 0; // Blink off } } else { // Regular dot, not power pill w = (uint16_t)dotFade + 1; // Blend factor pixel[i][0] = (DOT_R * w) >> 8; pixel[i][1] = (DOT_G * w) >> 8; pixel[i][2] = (DOT_B * w) >> 8; } } } // Increment dot fade counter if(dotFade >= (255 - DOT_FADE_SPEED)) { // About to hit/exceed max? dotFade = 255; // Set to max value, we're done for(i=0; i<N_LEDS; i++) dotState[i] = 1; // Make all dots edible } else { dotFade += DOT_FADE_SPEED; // Not maxed, keep counting up } } else { //------------------------------------------------------------------------------ // Dots are 'active,' not fading into view. Do the full 'game' logic... // Update sprite positions for(i=0; i<N_SPRITES; i++) { sprite[i].prevX = sprite[i].x; if((pauseCounter == 0) || sprite[i].moveDuringPause) { sprite[i].x += sprite[i].speed; } } if(pauseCounter) { // Is animation paused? (During a ghost chomp) if(--pauseCounter == 0) { // Count down to resume. Done? sprite[SPRITE_MOUTH].a = 255; // Mouth = opaque for(i=0; i<4; i++) { // Set non-opaque ghosts (being chomped) to eye color if(sprite[i].a == 255) continue; // Is opaque - don't change sprite[i].r = sprite[i].g = sprite[i].b = 255; // White sprite[i].a = 96; // Mostly translucent sprite[i].speed = 48; // Eyes move fast sprite[i].moveDuringPause = 1; // Eyes move when others are paused } } } else { // Animation isn't paused...do collision tests... // Calc X extents of each sprite since prior frame for(i=0; i<N_SPRITES; i++) { if(sprite[i].x >= sprite[i].prevX) { sprite[i].x1 = sprite[i].prevX; sprite[i].x2 = sprite[i].x; } else { sprite[i].x1 = sprite[i].x; sprite[i].x2 = sprite[i].prevX; } } // Check mouth motion against dots, eating any that are crossed x1 = sprite[SPRITE_MOUTH].x1 >> 8; x2 = sprite[SPRITE_MOUTH].x2 >> 8; if((x1 >= 0) && (x2 < N_LEDS)) { if(x1 < 0) x1 = 0; if(x2 >= N_LEDS) x2 = N_LEDS - 1; if(dotState[PILL_INDEX]) { // Is power pill uneaten? if((x1 <= PILL_INDEX) && (x2 >= PILL_INDEX)) { // Eating it now? // Switch to 'chasing ghosts' mode. Turn around, a little faster sprite[SPRITE_MOUTH].speed = -14; for(i=0;i<4;i++) { sprite[i].speed = -10; // Turn around, slow down sprite[i].r = sprite[i].g = 33; // Blue ghost sprite[i].b = 255; } ghostTime = 64 * 10; // Countdown timer } } for(i=x1; i<=x2; i++) dotState[i] = 0; // Erase eaten dot(s) } // If in ghost-chasing mode, check mouth collision against eligible ghosts if(ghostTime) { if(--ghostTime == 0) { reset_animation(); } else { for(i=0; i<4; i++) { if(sprite[i].a != 255) continue; // Don't compare against ghost eyes if((sprite[SPRITE_MOUTH].x1 <= sprite[i].x2) && (sprite[SPRITE_MOUTH].x2 >= sprite[i].x1)) { // Ate a ghost! pauseCounter = 48; // Make animation pause (except eyes) sprite[SPRITE_MOUTH].a = 0; // Mouth disappears momentarily sprite[i].r = 0; sprite[i].g = sprite[i].b = 255; sprite[i].a = 220; // Ghost is replaced with 'point' display } } if(ghostTime < (64 * 6)) { // Make ghosts blink blue/white toward end for(i=0; i<4; i++) { if(sprite[i].a != 255) continue; // Ghost is in eye state; ignore if(ghostTime & 32) { sprite[i].r = sprite[i].g = 222; } else { sprite[i].r = sprite[i].g = 33; } } } } } } // Draw background dots into pixel[] array for(i=0; i<N_LEDS; i++) { if(dotState[i] == 1) { if(i == PILL_INDEX) { if(pillBlink & 16) { pixel[i][0] = PILL_R; pixel[i][1] = PILL_G; pixel[i][2] = PILL_B; } else { pixel[i][0] = pixel[i][1] = pixel[i][2] = 0; } } else { pixel[i][0] = DOT_R; pixel[i][1] = DOT_G; pixel[i][2] = DOT_B; } } else { pixel[i][0] = 0; pixel[i][1] = 0; pixel[i][2] = 0; } } // Overlay sprites for(i=0; i<N_SPRITES; i++) { x1 = sprite[i].x >> 8; // Left pixel x2 = x1 + 1; // Right pixel if((x2 >= 0) && (x1 < N_LEDS)) { // Gross clipping fracX = sprite[i].x & 255; // Sub-pixel position (0-255) weightL = 1 + ((256 - (uint16_t)fracX) * sprite[i].a) >> 8; // Left pixel weight (1-256) weightR = 1 + (( 1 + (uint16_t)fracX) * sprite[i].a) >> 8; // Right pixel weight (1-256) if(x1 >= 0) { // Process left pixel inv = 257 - weightL; // 1-256 pixel[x1][0] = ((sprite[i].r * weightL) + (pixel[x1][0] * inv)) >> 8; pixel[x1][1] = ((sprite[i].g * weightL) + (pixel[x1][1] * inv)) >> 8; pixel[x1][2] = ((sprite[i].b * weightL) + (pixel[x1][2] * inv)) >> 8; } if(x2 < N_LEDS) { // Process right pixel inv = 257 - weightR; // 1-256 pixel[x2][0] = ((sprite[i].r * weightR) + (pixel[x2][0] * inv)) >> 8; pixel[x2][1] = ((sprite[i].g * weightR) + (pixel[x2][1] * inv)) >> 8; pixel[x2][2] = ((sprite[i].b * weightR) + (pixel[x2][2] * inv)) >> 8; } } } } pillBlink++; // Apply gamma correction / flip to pixel data while passing to NeoPixel lib... for(i=0; i<N_LEDS; i++) { x1 = (flip & 1) ? N_LEDS - 1 - i : i; strip.setPixelColor(i, pgm_read_byte(&gamma[pixel[x1][0]]), pgm_read_byte(&gamma[pixel[x1][1]]), pgm_read_byte(&gamma[pixel[x1][2]])); } }
Page last edited June 04, 2013
Text editor powered by tinymce.