Add files via upload

This commit is contained in:
Mike Barela 2019-06-06 17:21:54 -04:00 committed by GitHub
parent e85f906ea1
commit cc49907e3a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 17720 additions and 0 deletions

12
Kinetic_POV/README.md Normal file
View file

@ -0,0 +1,12 @@
Kinetic_POV
===========
Software related to DotStar LED persistence-of-vision performance items -- initially just the Trinket-based "Genesis Poi," but might devise more POV projects later...this is where they'll go.
Guide: https://learn.adafruit.com/genesis-poi-dotstar-led-persistence-of-vision-poi
Requires DotStar library for Arduino: https://github.com/adafruit/Adafruit_DotStar
Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit!
Written by Phil Burgess / Paint Your Dragon for Adafruit Industries. MIT license, all text above must be included in any redistribution. See 'COPYING' file for additional notes.

View file

@ -0,0 +1,432 @@
/*------------------------------------------------------------------------
POV LED bike wheel sketch. Uses the following Adafruit parts:
- Pro Trinket 5V (www.adafruit.com/product/2000)
(NOT Trinket or 3V Pro Trinket)
- Waterproof 3xAA battery holder with on/off switch (#771)
- 144 LED/m DotStar strip (#2328 or #2329) ONE is enough for
both sides of one bike wheel
- Tactile switch button (#1119) (optional)
Needs Adafruit_DotStar library: github.com/adafruit/Adafruit_DotStar
Full instructions: https://learn.adafruit.com/bike-wheel-pov-display
This project is based on Phil B's Genesis Poi:
learn.adafruit.com/genesis-poi-dotstar-led-persistence-of-vision-poi
and has been adapted to the Pro Trinket to accomodate more and larger
images than Trinket.
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
MIT license, all text above must be included in any redistribution.
See 'COPYING' file for additional notes.
------------------------------------------------------------------------*/
#include <Arduino.h>
#include <Adafruit_DotStar.h>
#include <avr/power.h>
#include <avr/sleep.h>
#include <SPI.h> // Enable this line on Pro Trinket
#ifdef __AVR_ATtiny85__
typedef uint8_t line_t; // Max 255 lines/image on Trinket
#else
typedef uint16_t line_t; // Bigger images OK on other boards
#endif
// CONFIGURABLE STUFF ------------------------------------------------------
#include "graphics.h" // Graphics data is contained in this header file.
// It's generated using the 'convert.py' Python script. Various image
// formats are supported, trading off color fidelity for PROGMEM space
// (particularly limited on Trinket). Handles 1-, 4- and 8-bit-per-pixel
// palette-based images, plus 24-bit truecolor. 1- and 4-bit palettes can
// be altered in RAM while running to provide additional colors, but be
// mindful of peak & average current draw if you do that! Power limiting
// is normally done in convert.py (keeps this code relatively small & fast).
// 1/4/8/24 were chosen because the AVR can handle these operations fairly
// easily (have idea for handing arbitrary bit depth w/328P, but this margin
// is too narrow to contain).
// Ideally you use hardware SPI as it's much faster, though limited to
// specific pins. If you really need to bitbang DotStar data & clock on
// different pins, optionally define those here:
//#define LED_DATA_PIN 0
//#define LED_CLOCK_PIN 1
// Select from multiple images using tactile button (#1489) between pin and
// ground. Requires suitably-built graphics.h file w/more than one image.
#define SELECT_PIN 3
// Optional feature -- not enabled here, no space -- a vibration switch
// (aligned perpendicular to leash) is used as a poor man's accelerometer.
// Poi then lights only when moving, saving some power. The 'fast'
// vibration switch is VERY sensitive and will trigger at the slightest
// bump, while the 'medium' switch requires a certain spin rate which may
// not trigger if you're doing mellow spins. Neither is perfect. To leave
// that out and simply have the poi run always-on, comment out this line:
//#define MOTION_PIN 2
// Another optional feature not enable due to physical size -- powering down
// DotStars when idle conserves more battery. Use a PNP transistor (e.g.
// 2N2907) (w/220 Ohm resistor to base) as a 'high side' switch to DotStar
// +V. DON'T do this NPN/low-side, may damage strip. MOTION_PIN must also
// be defined to use this (pointless without).
//#define POWER_PIN 4
#define SLEEP_TIME 2000 // Not-spinning time before sleep, in milliseconds
// Empty and full thresholds (millivolts) used for battery level display:
#define BATT_MIN_MV 3350 // Some headroom over battery cutoff near 2.9V
#define BATT_MAX_MV 4000 // And little below fresh-charged battery near 4.1V
// These figures are based on LiPoly cell and will need to be tweaked for
// 3X NiMH or alkaline batteries!
boolean autoCycle = true; // Set to true to cycle images by default
#define CYCLE_TIME 10 // Time, in seconds, between auto-cycle images
// -------------------------------------------------------------------------
#if defined(LED_DATA_PIN) && defined(LED_CLOCK_PIN)
// Older DotStar LEDs use GBR order. If colors are wrong, edit here.
Adafruit_DotStar strip = Adafruit_DotStar(NUM_LEDS,
LED_DATA_PIN, LED_CLOCK_PIN, DOTSTAR_BRG);
#else
Adafruit_DotStar strip = Adafruit_DotStar(NUM_LEDS, DOTSTAR_BGR);
#endif
void imageInit(void);
uint16_t readVoltage(void);
#ifdef MOTION_PIN
void sleep(void);
#endif
void setup() {
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000L)
clock_prescale_set(clock_div_1); // Enable 16 MHz on Trinket
#endif
#ifdef POWER_PIN
pinMode(POWER_PIN, OUTPUT);
digitalWrite(POWER_PIN, LOW); // Power-on LED strip
#endif
strip.begin(); // Allocate DotStar buffer, init SPI
strip.clear(); // Make sure strip is clear
strip.show(); // before measuring battery
// Display battery level bargraph on startup. It's just a vague estimate
// based on cell voltage (drops with discharge) but doesn't handle curve.
uint16_t mV = readVoltage();
uint8_t lvl = (mV >= BATT_MAX_MV) ? NUM_LEDS : // Full (or nearly)
(mV <= BATT_MIN_MV) ? 1 : // Drained
1 + ((mV - BATT_MIN_MV) * NUM_LEDS + (NUM_LEDS / 2)) /
(BATT_MAX_MV - BATT_MIN_MV + 1); // # LEDs lit (1-NUM_LEDS)
for(uint8_t i=0; i<lvl; i++) { // Each LED to batt level...
uint8_t g = (i * 5 + 2) / NUM_LEDS; // Red to green
strip.setPixelColor(i, 4-g, g, 0);
strip.show(); // Animate a bit
delay(250 / NUM_LEDS);
}
delay(1500); // Hold last state a moment
strip.clear(); // Then clear strip
strip.show();
imageInit(); // Initialize pointers for default image
#ifdef SELECT_PIN
pinMode(SELECT_PIN, INPUT_PULLUP);
#endif
#ifdef MOTION_PIN
pinMode(MOTION_PIN, INPUT_PULLUP);
sleep(); // Sleep until motion detected
#endif
}
// GLOBAL STATE STUFF ------------------------------------------------------
uint32_t lastImageTime = 0L; // Time of last image change
#ifdef MOTION_PIN
uint32_t prev = 0L; // Used for sleep timing
#endif
uint8_t imageNumber = 0, // Current image being displayed
imageType, // Image type: PALETTE[1,4,8] or TRUECOLOR
*imagePalette, // -> palette data in PROGMEM
*imagePixels, // -> pixel data in PROGMEM
palette[16][3]; // RAM-based color table for 1- or 4-bit images
line_t imageLines, // Number of lines in active image
imageLine; // Current line number in image
#ifdef SELECT_PIN
uint8_t debounce = 0; // Debounce counter for image select pin
#endif
void imageInit() { // Initialize global image state for current imageNumber
imageType = pgm_read_byte(&images[imageNumber].type);
#ifdef __AVR_ATtiny85__
imageLines = pgm_read_byte(&images[imageNumber].lines);
#else
imageLines = pgm_read_word(&images[imageNumber].lines);
#endif
imageLine = 0;
imagePalette = (uint8_t *)pgm_read_word(&images[imageNumber].palette);
imagePixels = (uint8_t *)pgm_read_word(&images[imageNumber].pixels);
// 1- and 4-bit images have their color palette loaded into RAM both for
// faster access and to allow dynamic color changing. Not done w/8-bit
// because that would require inordinate RAM (328P could handle it, but
// I'd rather keep the RAM free for other features in the future).
if(imageType == PALETTE1) memcpy_P(palette, imagePalette, 2 * 3);
else if(imageType == PALETTE4) memcpy_P(palette, imagePalette, 16 * 3);
lastImageTime = millis(); // Save time of image init for next auto-cycle
}
void nextImage(void) {
if(++imageNumber >= NUM_IMAGES) imageNumber = 0;
imageInit();
}
// MAIN LOOP ---------------------------------------------------------------
void loop() {
uint32_t t = millis(); // Current time, milliseconds
#ifdef MOTION_PIN
// Tried to do this with watchdog timer but encountered gas pains, so...
if(!digitalRead(MOTION_PIN)) { // Vibration switch pulled down?
prev = t; // Yes, reset timer
} else if((t - prev) > SLEEP_TIME) { // No, SLEEP_TIME elapsed w/no switch?
sleep(); // Power down
prev = t; // Reset timer on wake
}
#endif
if(autoCycle) {
if((t - lastImageTime) >= (CYCLE_TIME * 1000L)) nextImage();
// CPU clocks vary slightly; multiple poi won't stay in perfect sync.
// Keep this in mind when using auto-cycle mode, you may want to cull
// the image selection to avoid unintentional regrettable combinations.
}
#ifdef SELECT_PIN
if(digitalRead(SELECT_PIN)) { // Image select?
debounce = 0; // Not pressed -- reset counter
} else { // Pressed...
if(++debounce >= 25) { // Debounce input
nextImage(); // Switch to next image
while(!digitalRead(SELECT_PIN)); // Wait for release
// If held 1+ sec, toggle auto-cycle mode on/off
if((millis() - t) >= 1000L) autoCycle = !autoCycle;
debounce = 0;
}
}
#endif
// Transfer one scanline from pixel data to LED strip:
// If you're really pressed for graphics space and need just a few extra
// scanlines, and know for a fact you won't be using certain image modes,
// you can comment out the corresponding blocks below. e.g. PALETTE8 and
// TRUECOLOR are somewhat impractical on Trinket, and commenting them out
// can free up nearly 200 bytes of extra image storage.
switch(imageType) {
case PALETTE1: { // 1-bit (2 color) palette-based image
uint8_t pixelNum = 0, byteNum, bitNum, pixels, idx,
*ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS / 8];
for(byteNum = NUM_LEDS/8; byteNum--; ) { // Always padded to next byte
pixels = pgm_read_byte(ptr++); // 8 pixels of data (pixel 0 = LSB)
for(bitNum = 8; bitNum--; pixels >>= 1) {
idx = pixels & 1; // Color table index for pixel (0 or 1)
strip.setPixelColor(pixelNum++,
palette[idx][0], palette[idx][1], palette[idx][2]);
}
}
break;
}
case PALETTE4: { // 4-bit (16 color) palette-based image
uint8_t pixelNum, p1, p2,
*ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS / 2];
for(pixelNum = 0; pixelNum < NUM_LEDS; ) {
p2 = pgm_read_byte(ptr++); // Data for two pixels...
p1 = p2 >> 4; // Shift down 4 bits for first pixel
p2 &= 0x0F; // Mask out low 4 bits for second pixel
strip.setPixelColor(pixelNum++,
palette[p1][0], palette[p1][1], palette[p1][2]);
strip.setPixelColor(pixelNum++,
palette[p2][0], palette[p2][1], palette[p2][2]);
}
break;
}
case PALETTE8: { // 8-bit (256 color) PROGMEM-palette-based image
uint16_t o;
uint8_t pixelNum,
*ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS];
for(pixelNum = 0; pixelNum < NUM_LEDS; pixelNum++) {
o = pgm_read_byte(ptr++) * 3; // Offset into imagePalette
strip.setPixelColor(pixelNum,
pgm_read_byte(&imagePalette[o]),
pgm_read_byte(&imagePalette[o + 1]),
pgm_read_byte(&imagePalette[o + 2]));
}
break;
}
case TRUECOLOR: { // 24-bit ('truecolor') image (no palette)
uint8_t pixelNum, r, g, b,
*ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS * 3];
for(pixelNum = 0; pixelNum < NUM_LEDS; pixelNum++) {
r = pgm_read_byte(ptr++);
g = pgm_read_byte(ptr++);
b = pgm_read_byte(ptr++);
strip.setPixelColor(pixelNum, r, g, b);
}
break;
}
}
strip.show(); // Refresh LEDs
#if !defined(LED_DATA_PIN) && !defined(LED_CLOCK_PIN)
delayMicroseconds(900); // Because hardware SPI is ludicrously fast
#endif
if(++imageLine >= imageLines) imageLine = 0; // Next scanline, wrap around
}
// POWER-SAVING STUFF -- Relentlessly non-portable -------------------------
#ifdef MOTION_PIN
void sleep() {
// Turn off LEDs...
strip.clear(); // Issue '0' data
strip.show();
#ifdef POWER_PIN
digitalWrite(POWER_PIN, HIGH); // Cut power
#if !defined(LED_DATA_PIN) && !defined(LED_CLOCK_PIN)
#ifdef __AVR_ATtiny85__
pinMode(1, INPUT); // Set SPI data & clock to inputs else
pinMode(2, INPUT); // DotStars power parasitically, jerks.
#else
pinMode(11, INPUT);
pinMode(13, INPUT);
#endif // ATtiny
#endif // Data/clock/pins
#endif // POWER_PIN
power_all_disable(); // Peripherals ALL OFF, best sleep-state battery use
// Enable pin-change interrupt on motion pin
#ifdef __AVR_ATtiny85__
PCMSK = _BV(MOTION_PIN); // Pin mask
GIMSK = _BV(PCIE); // Interrupt enable
#else
volatile uint8_t *p = portInputRegister(digitalPinToPort(MOTION_PIN));
if(p == &PIND) { // Pins 0-7 = PCINT16-23
PCMSK2 = _BV(MOTION_PIN);
PCICR = _BV(PCIE2);
} else if(p == &PINB) { // Pins 8-13 = PCINT0-5
PCMSK0 = _BV(MOTION_PIN- 8);
PCICR = _BV(PCIE0);
} else if(p == &PINC) { // Pins 14-20 = PCINT8-14
PCMSK1 = _BV(MOTION_PIN-14);
PCICR = _BV(PCIE1);
}
#endif
// If select pin is enabled, that wakes too!
#ifdef SELECT_PIN
debounce = 0;
#ifdef __AVR_ATtiny85__
PCMSK |= _BV(SELECT_PIN); // Add'l pin mask
#else
volatile uint8_t *p = portInputRegister(digitalPinToPort(SELECT_PIN));
if(p == &PIND) { // Pins 0-7 = PCINT16-23
PCMSK2 = _BV(SELECT_PIN);
PCICR = _BV(PCIE2);
} else if(p == &PINB) { // Pins 8-13 = PCINT0-5
PCMSK0 = _BV(SELECT_PIN- 8);
PCICR = _BV(PCIE0);
} else if(p == &PINC) { // Pins 14-20 = PCINT8-14
PCMSK1 = _BV(SELECT_PIN-14);
PCICR = _BV(PCIE1);
}
#endif // ATtiny
#endif // SELECT_PIN
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Deepest sleep mode
sleep_enable();
interrupts();
sleep_mode(); // Power down
// Resumes here on wake
// Clear pin change settings so interrupt won't fire again
#ifdef __AVR_ATtiny85__
GIMSK = PCMSK = 0;
#else
PCICR = PCMSK0 = PCMSK1 = PCMSK2 = 0;
#endif
power_timer0_enable(); // Used by millis()
#if !defined(LED_DATA_PIN) && !defined(LED_CLOCK_PIN)
#ifdef __AVR_ATtiny85__
pinMode(1, OUTPUT); // Re-enable SPI pins
pinMode(2, OUTPUT);
power_usi_enable(); // Used by DotStar
#else
pinMode(11, OUTPUT); // Re-enable SPI pins
pinMode(13, OUTPUT);
power_spi_enable(); // Used by DotStar
#endif // ATtiny
#endif // Data/clock pins
#ifdef POWER_PIN
digitalWrite(POWER_PIN, LOW); // Power-up LEDs
#endif
prev = millis(); // Save wake time
}
EMPTY_INTERRUPT(PCINT0_vect); // Pin change (does nothing, but required)
#ifndef __AVR_ATtiny85__
ISR(PCINT1_vect, ISR_ALIASOF(PCINT0_vect));
ISR(PCINT2_vect, ISR_ALIASOF(PCINT0_vect));
#endif
#endif // MOTION_PIN
// Battery monitoring idea adapted from JeeLabs article:
// jeelabs.org/2012/05/04/measuring-vcc-via-the-bandgap/
// Code from Adafruit TimeSquare project, added Trinket support.
// In a pinch, the poi code can work on a 3V Trinket, but the battery
// monitor will not work correctly (due to the 3.3V regulator), so
// maybe just comment out any reference to this code in that case.
uint16_t readVoltage() {
int i, prev;
uint8_t count;
uint16_t mV;
// Select AVcc voltage reference + Bandgap (1.8V) input
#ifdef __AVR_ATtiny85__
ADMUX = _BV(MUX3) | _BV(MUX2);
#else
ADMUX = _BV(REFS0) |
_BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
ADCSRA = _BV(ADEN) | // Enable ADC
_BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 1/128 prescaler (125 KHz)
// Datasheet notes that the first bandgap reading is usually garbage as
// voltages are stabilizing. It practice, it seems to take a bit longer
// than that. Tried various delays, but still inconsistent and kludgey.
// Instead, repeated readings are taken until four concurrent readings
// stabilize within 10 mV.
for(prev=9999, count=0; count<4; ) {
for(ADCSRA |= _BV(ADSC); ADCSRA & _BV(ADSC); ); // Start, await ADC conv.
i = ADC; // Result
mV = i ? (1100L * 1023 / i) : 0; // Scale to millivolts
if(abs((int)mV - prev) <= 10) count++; // +1 stable reading
else count = 0; // too much change, start over
prev = mV;
}
ADCSRA = 0; // ADC off
return mV;
}

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

View file

@ -0,0 +1,343 @@
# Image converter script for POV LED poi project. Reads one or more images
# as input, generates tables which can be copied-and-pasted or redirected
# to a .h file, e.g.:
#
# $ python convert.py image1.gif image2.png > graphics.h
#
# Ideal image dimensions are determined by hardware setup, e.g. LED poi
# project uses 16 LEDs, so image height should match. Width is limited
# by AVR PROGMEM capacity -- very limited on Trinket!
#
# Adafruit invests time and resources providing this open source code,
# please support Adafruit and open-source hardware by purchasing
# products from Adafruit!
#
# Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
# MIT license, all text above must be included in any redistribution.
# See 'COPYING' file for additional notes.
# --------------------------------------------------------------------------
from PIL import Image
import sys
# Establish peak and average current limits - a function of battery
# capacity and desired run time.
# These you can edit to match your build:
batterySize = 150 # Battery capacity, in milliamp-hours (mAh)
runTime = 1.1 # Est. max run time, in hours (longer = dimmer LEDs)
parallelStrips = 2 # Same data is issued to this many LED strips
# These probably don't need editing:
mcuCurrent = 20 # Est. current used by microcontrolled board (mA)
wireLimit = 1500 # Ampacity of battery wires (est 26 gauge) (milliamps)
# Estimate average and peak LED currents, within some safety thresholds:
if(runTime < 1.0): runTime = 1.0 # Don't exceed 1C rate from battery
cl = batterySize - mcuCurrent * runTime # After MCU, charge left for LEDs
if cl < 0: cl = 0 # Must be non-negative
avgC = cl / runTime / parallelStrips
if avgC > wireLimit: avgC = wireLimit # Don't exceed battery wire ampacity
peakC = avgC * 2.2 # Battery+wires OK w/brief peaks
bR = 1.0 # Can adjust
bG = 1.0 # color balance
bB = 1.0 # for whiter whites!
gamma = 2.7 # For more linear-ish perceived brightness
# Current estimates are averages measured from strip on LiPoly cell
mA0 = 1.3 # LED current when off (driver logic still needs some)
mAR = 15.2 * bR # + current for 100% red
mAG = 8.7 * bG # + current for 100% green
mAB = 8.0 * bB # + current for 100% blue
# --------------------------------------------------------------------------
cols = 0 # Current column number in output
byteNum = 0
numBytes = 0
def writeByte(n):
global cols, byteNum, numBytes
cols += 1 # Increment column #
if cols >= 8: # If max column exceeded...
print # end current line
sys.stdout.write(" ") # and start new one
cols = 0 # Reset counter
sys.stdout.write("{0:#0{1}X}".format(n, 4))
byteNum += 1
if byteNum < numBytes:
sys.stdout.write(",")
if cols < 7:
sys.stdout.write(" ")
# --------------------------------------------------------------------------
numLEDs = 0
images = []
# Initial pass loads each image & tracks tallest size overall
for name in sys.argv[1:]: # For each image passed to script...
image = Image.open(name)
image.pixels = image.load()
# Determine if image is truecolor vs. colormapped.
image.colors = image.getcolors(256)
if image.colors == None:
image.numColors = 257 # Image is truecolor
else:
# If 256 colors or less, that doesn't necessarily mean
# it's a non-truecolor image yet, just that it has few
# colors. Check the image type and if it's truecolor or
# similar, convert the image to a paletted mode so it can
# be more efficiently stored. Since there are few colors,
# this operation is lossless.
if (image.mode != '1' and image.mode != 'L' and
image.mode != 'P'):
image = image.convert("P", palette="ADAPTIVE")
image.pixels = image.load()
image.colors = image.getcolors(256)
# image.colors is an unsorted list of tuples where each
# item is a pixel count and a color palette index.
# Unused palette indices (0 pixels) are not in list,
# so its length tells us the unique color count...
image.numColors = len(image.colors)
# The image & palette aren't necessarily optimally packed,
# e.g. might have a 216-color 'web safe' palette but only
# use a handful of colors. In order to reduce the palette
# storage requirements, only the colors in use will be
# output. The pixel indices in the image must be remapped
# to this new palette sequence...
remap = [0] * 256
for c in range(image.numColors): # For each color used...
# The original color index (image.colors[c][1])
# is reassigned to a sequential 'packed' index (c):
remap[image.colors[c][1]] = c
# Every pixel in image is then remapped through this table:
for y in range(image.size[1]):
for x in range(image.size[0]):
image.pixels[x, y] = remap[image.pixels[x, y]]
# The color palette associated with the image is still in
# its unpacked/unoptimal order; image pixel values no longer
# point to correct entries. This is OK and we'll compensate
# for it later in the code.
image.name = name
image.bph = image.size[1] # Byte-padded height (tweaked below)
images.append(image)
# 1- and 4-bit images are padded to the next byte boundary.
# Image size not fully validated - on purpose - in case of quick
# test with an existing (but non-optimal) file. If too big or too
# small for the LED strip, just wastes some PROGMEM space or some
# LEDs will be lit wrong, usually no biggie.
if image.numColors <= 2: # 1 bit/pixel, use 8-pixel blocks
if image.bph & 7: image.bph += 8 - (image.bph & 7)
elif image.numColors <= 16: # 4 bits/pixel, use 2-pixel blocks
if image.bph & 1: image.bph += 1
if image.bph > numLEDs: numLEDs = image.bph
print "// Don't edit this file! It's software-generated."
print "// See convert.py script instead."
print
print "#define PALETTE1 0"
print "#define PALETTE4 1"
print "#define PALETTE8 2"
print "#define TRUECOLOR 3"
print
print "#define NUM_LEDS %d" % numLEDs
print
# Second pass estimates current of each column, then peak & overall average
for imgNum, image in enumerate(images): # For each image in list...
sys.stdout.write("// %s%s\n\n" % (image.name,
' '.ljust(73 - len(image.name),'-')))
if image.numColors <= 256:
# Palette optimization requires some weird shenanigans...
# first, make a duplicate image where width=image.numColors
# and height=1. This will have the same color palette as
# the original image, which may contain many unused entries.
lut = image.resize((image.numColors, 1))
lut.pixels = lut.load()
# The image.colors[] list contains the original palette
# indices of the colors actually in use. Draw one pixel
# into the 'lut' image for each color index in use, in the
# order they appear in the color list...
for x in range(image.numColors):
lut.pixels[x, 0] = image.colors[x][1]
# ...then convert the lut image to RGB format to provide a
# list of (R,G,B) values representing the packed color list.
lut = list(lut.convert("RGB").getdata())
# Estimate current for each element of palette:
paletteCurrent = []
for i in range(image.numColors):
paletteCurrent.append(mA0 +
pow((lut[i][0] / 255.0), gamma) * mAR +
pow((lut[i][1] / 255.0), gamma) * mAG +
pow((lut[i][2] / 255.0), gamma) * mAB)
# Estimate peak and average current for each column of image
colMaxC = 0.0 # Maximum column current
colAvgC = 0.0 # Average column current
for x in range(image.size[0]): # For each row...
mA = 0.0 # Sum current of each pixel's palette entry
for y in range(image.size[1]):
if image.numColors <= 256:
mA += paletteCurrent[image.pixels[x, y]]
else:
mA += (mA0 +
pow((image.pixels[x, y][0] / 255.0),
gamma) * mAR +
pow((image.pixels[x, y][1] / 255.0),
gamma) * mAG +
pow((image.pixels[x, y][2] / 255.0),
gamma) * mAB)
colAvgC += mA # Accumulate average (div later)
if mA > colMaxC: colMaxC = mA # Monitor peak
colAvgC /= image.size[0] # Sum div into average
s1 = peakC / colMaxC # Scaling factor for peak current constraint
s2 = avgC / colAvgC # Scaling factor for average current constraint
if s2 < s1: s1 = s2 # Use smaller of two (so both constraints met),
if s1 > 1.0: s1 = 1.0 # but never increase brightness
s1 *= 255.0 # (0.0-1.0) -> (0.0-255.0)
bR1 = bR * s1 # Scale color balance values
bG1 = bG * s1
bB1 = bB * s1
p = 0 # Current pixel number in image
cols = 7 # Force wrap on 1st output
byteNum = 0
if image.numColors <= 256:
# Output gamma- and brightness-adjusted color palette:
print ("const uint8_t PROGMEM palette%02d[][3] = {" % imgNum)
for i in range(image.numColors):
sys.stdout.write(" { %3d, %3d, %3d }" % (
int(pow((lut[i][0]/255.0),gamma)*bR1+0.5),
int(pow((lut[i][1]/255.0),gamma)*bG1+0.5),
int(pow((lut[i][2]/255.0),gamma)*bB1+0.5)))
if i < (image.numColors - 1): print ","
print " };"
print
sys.stdout.write(
"const uint8_t PROGMEM pixels%02d[] = {" % imgNum)
if image.numColors <= 2:
numBytes = image.size[0] * numLEDs / 8
elif image.numColors <= 16:
numBytes = image.size[0] * numLEDs / 2
elif image.numColors <= 256:
numBytes = image.size[0] * numLEDs
else:
numBytes = image.size[0] * numLEDs * 3
for x in range(image.size[0]):
if image.numColors <= 2:
for y in range(0, numLEDs, 8):
sum = 0
for bit in range(8):
y1 = y + bit
if y1 < image.size[1]:
sum += (
image.pixels[x,
y1] << bit)
writeByte(sum)
elif image.numColors <= 16:
for y in range(0, numLEDs, 2):
if y < image.size[1]:
p1 = image.pixels[x, y]
else:
p1 = 0
if (y + 1) < image.size[1]:
p2 = image.pixels[x, y + 1]
else:
p2 = 0
writeByte(p1 * 16 + p2)
elif image.numColors <= 256:
for y in range(numLEDs):
if y < image.size[1]:
writeByte(image.pixels[x, y])
else:
writeByte(0)
else:
for y in range(numLEDs):
if y < image.size[1]:
writeByte(image.pixels[x, y][0])
writeByte(image.pixels[x, y][1])
writeByte(image.pixels[x, y][2])
else:
writeByte(0)
writeByte(0)
writeByte(0)
else:
# Perform gamma- and brightness-adjustment on pixel data
sys.stdout.write(
"const uint8_t PROGMEM pixels%02d[] = {" % imgNum)
numBytes = image.size[0] * numLEDs * 3
for x in range(image.size[0]):
for y in range(numLEDs):
if y < image.size[1]:
writeByte(int(pow((
image.pixels[x, y][0] / 255.0),
gamma) * bR1 + 0.5))
writeByte(int(pow((
image.pixels[x, y][1] / 255.0),
gamma) * bG1 + 0.5))
writeByte(int(pow((
image.pixels[x, y][2] / 255.0),
gamma) * bB1 + 0.5))
else:
writeByte(0)
writeByte(0)
writeByte(0)
print " };" # end pixels[] array
print
# Last pass, print table of images...
print "typedef struct {"
print " uint8_t type; // PALETTE[1,4,8] or TRUECOLOR"
print " line_t lines; // Length of image (in scanlines)"
print " const uint8_t *palette; // -> PROGMEM color table (NULL if truecolor)"
print " const uint8_t *pixels; // -> Pixel data in PROGMEM"
print "} image;"
print
print "const image PROGMEM images[] = {"
for imgNum, image in enumerate(images): # For each image in list...
sys.stdout.write(" { ")
if image.numColors <= 2:
sys.stdout.write("PALETTE1 , ")
elif image.numColors <= 16:
sys.stdout.write("PALETTE4 , ")
elif image.numColors <= 256:
sys.stdout.write("PALETTE8 , ")
else:
sys.stdout.write("TRUECOLOR, ")
sys.stdout.write(" %3d, " % image.size[0])
if image.numColors <= 256:
sys.stdout.write("(const uint8_t *)palette%02d, " % imgNum)
else:
sys.stdout.write("NULL , ")
sys.stdout.write("pixels%02d }" % imgNum)
if imgNum < len(images) - 1:
print(",")
else:
print
print "};"
print
print "#define NUM_IMAGES (sizeof(images) / sizeof(images[0]))"

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 B

BIN
Kinetic_POV/convert/usa.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

View file

@ -0,0 +1,390 @@
/*------------------------------------------------------------------------
POV LED double staff sketch. Uses the following Adafruit parts
(X2 for two staffs):
- Pro Trinket 5V https://www.adafruit.com/product/2000
- 2200 mAh Lithium Ion Battery https://www.adafruit.com/product/1781
- LiPoly Backpack https://www.adafruit.com/product/2124
- Tactile On/Off Switch with Leads https://www.adafruit.com/product/1092
- 144 LED/m DotStar strip (#2328 or #2329)
(ONE METER is enough for ONE STAFF, TWO METERS for TWO staffs)
- Infrared Sensor: https://www.adafruit.com/product/157
- Mini Remote Control: https://www.adafruit.com/product/389
(only one remote is required for multiple staffs)
Needs Adafruit_DotStar library: github.com/adafruit/Adafruit_DotStar
This is based on the LED poi code (also included in the repository),
but ATtiny-specific code has been stripped out for brevity, since the
staffs pretty much require Pro Trinket or better (lots more LEDs here).
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
MIT license, all text above must be included in any redistribution.
See 'COPYING' file for additional notes.
------------------------------------------------------------------------*/
#include <Arduino.h>
#include <Adafruit_DotStar.h>
#include <avr/power.h>
#include <avr/sleep.h>
#include <SPI.h>
typedef uint16_t line_t;
// CONFIGURABLE STUFF ------------------------------------------------------
#include "graphics.h" // Graphics data is contained in this header file.
// It's generated using the 'convert.py' Python script. Various image
// formats are supported, trading off color fidelity for PROGMEM space.
// Handles 1-, 4- and 8-bit-per-pixel palette-based images, plus 24-bit
// truecolor. 1- and 4-bit palettes can be altered in RAM while running
// to provide additional colors, but be mindful of peak & average current
// draw if you do that! Power limiting is normally done in convert.py
// (keeps this code relatively small & fast).
// Ideally you use hardware SPI as it's much faster, though limited to
// specific pins. If you really need to bitbang DotStar data & clock on
// different pins, optionally define those here:
//#define LED_DATA_PIN 0
//#define LED_CLOCK_PIN 1
// Empty and full thresholds (millivolts) used for battery level display:
#define BATT_MIN_MV 3350 // Some headroom over battery cutoff near 2.9V
#define BATT_MAX_MV 4000 // And little below fresh-charged battery near 4.1V
boolean autoCycle = true; // Set to true to cycle images by default
#define CYCLE_TIME 15 // Time, in seconds, between auto-cycle images
#define IR_PIN 3 // MUST be INT1 pin!
// Adafruit IR Remote Codes:
// Button Code Button Code
// ----------- ------ ------ -----
// VOL-: 0x0000 0/10+: 0x000C
// Play/Pause: 0x0001 1: 0x0010
// VOL+: 0x0002 2: 0x0011
// SETUP: 0x0004 3: 0x0012
// STOP/MODE: 0x0006 4: 0x0014
// UP: 0x0005 5: 0x0015
// DOWN: 0x000D 6: 0x0016
// LEFT: 0x0008 7: 0x0018
// RIGHT: 0x000A 8: 0x0019
// ENTER/SAVE: 0x0009 9: 0x001A
// Back: 0x000E
#define BTN_BRIGHT_UP 0x0002
#define BTN_BRIGHT_DOWN 0x0000
#define BTN_RESTART 0x0001
#define BTN_BATTERY 0x0004
#define BTN_FASTER 0x0005
#define BTN_SLOWER 0x000D
#define BTN_OFF 0x0006
#define BTN_PATTERN_PREV 0x0008
#define BTN_PATTERN_NEXT 0x000A
#define BTN_NONE 0xFFFF
#define BTN_AUTOPLAY 0x0009
// -------------------------------------------------------------------------
#if defined(LED_DATA_PIN) && defined(LED_CLOCK_PIN)
// Older DotStar LEDs use GBR order. If colors are wrong, edit here.
Adafruit_DotStar strip = Adafruit_DotStar(NUM_LEDS,
LED_DATA_PIN, LED_CLOCK_PIN, DOTSTAR_BGR);
#else
Adafruit_DotStar strip = Adafruit_DotStar(NUM_LEDS, DOTSTAR_BGR);
#endif
void imageInit(void),
IRinterrupt(void),
showBatteryLevel(void);
uint16_t readVoltage(void);
void setup() {
strip.begin(); // Allocate DotStar buffer, init SPI
strip.clear(); // Make sure strip is clear
strip.show(); // before measuring battery
showBatteryLevel();
imageInit(); // Initialize pointers for default image
attachInterrupt(1, IRinterrupt, CHANGE); // IR remote interrupt
}
void showBatteryLevel(void) {
// Display battery level bargraph on startup. It's just a vague estimate
// based on cell voltage (drops with discharge) but doesn't handle curve.
uint16_t mV = readVoltage();
uint8_t lvl = (mV >= BATT_MAX_MV) ? NUM_LEDS : // Full (or nearly)
(mV <= BATT_MIN_MV) ? 1 : // Drained
1 + ((mV - BATT_MIN_MV) * NUM_LEDS + (NUM_LEDS / 2)) /
(BATT_MAX_MV - BATT_MIN_MV + 1); // # LEDs lit (1-NUM_LEDS)
for(uint8_t i=0; i<lvl; i++) { // Each LED to batt level...
uint8_t g = (i * 5 + 2) / NUM_LEDS; // Red to green
strip.setPixelColor(i, 4-g, g, 0);
strip.show(); // Animate a bit
delay(250 / NUM_LEDS);
}
delay(1500); // Hold last state a moment
strip.clear(); // Then clear strip
strip.show();
}
// GLOBAL STATE STUFF ------------------------------------------------------
uint32_t lastImageTime = 0L, // Time of last image change
lastLineTime = 0L;
uint8_t imageNumber = 0, // Current image being displayed
imageType, // Image type: PALETTE[1,4,8] or TRUECOLOR
*imagePalette, // -> palette data in PROGMEM
*imagePixels, // -> pixel data in PROGMEM
palette[16][3]; // RAM-based color table for 1- or 4-bit images
line_t imageLines, // Number of lines in active image
imageLine; // Current line number in image
volatile uint16_t irCode = BTN_NONE; // Last valid IR code received
const uint8_t PROGMEM brightness[] = { 15, 31, 63, 127, 255 };
uint8_t bLevel = sizeof(brightness) - 1;
// Microseconds per line for various speed settings
const uint16_t PROGMEM lineTable[] = { // 375 * 2^(n/3)
1000000L / 375, // 375 lines/sec = slowest
1000000L / 472,
1000000L / 595,
1000000L / 750, // 750 lines/sec = mid
1000000L / 945,
1000000L / 1191,
1000000L / 1500 // 1500 lines/sec = fastest
};
uint8_t lineIntervalIndex = 3;
uint16_t lineInterval = 1000000L / 750;
void imageInit() { // Initialize global image state for current imageNumber
imageType = pgm_read_byte(&images[imageNumber].type);
imageLines = pgm_read_word(&images[imageNumber].lines);
imageLine = 0;
imagePalette = (uint8_t *)pgm_read_word(&images[imageNumber].palette);
imagePixels = (uint8_t *)pgm_read_word(&images[imageNumber].pixels);
// 1- and 4-bit images have their color palette loaded into RAM both for
// faster access and to allow dynamic color changing. Not done w/8-bit
// because that would require inordinate RAM (328P could handle it, but
// I'd rather keep the RAM free for other features in the future).
if(imageType == PALETTE1) memcpy_P(palette, imagePalette, 2 * 3);
else if(imageType == PALETTE4) memcpy_P(palette, imagePalette, 16 * 3);
lastImageTime = millis(); // Save time of image init for next auto-cycle
}
void nextImage(void) {
if(++imageNumber >= NUM_IMAGES) imageNumber = 0;
imageInit();
}
void prevImage(void) {
imageNumber = imageNumber ? imageNumber - 1 : NUM_IMAGES - 1;
imageInit();
}
// MAIN LOOP ---------------------------------------------------------------
void loop() {
uint32_t t = millis(); // Current time, milliseconds
if(autoCycle) {
if((t - lastImageTime) >= (CYCLE_TIME * 1000L)) nextImage();
// CPU clocks vary slightly; multiple poi won't stay in perfect sync.
// Keep this in mind when using auto-cycle mode, you may want to cull
// the image selection to avoid unintentional regrettable combinations.
}
// Transfer one scanline from pixel data to LED strip:
// If you're really pressed for graphics space and need just a few extra
// scanlines, and know for a fact you won't be using certain image modes,
// you can comment out the corresponding blocks below. e.g. disabling
// PALETTE8 and TRUECOLOR support can free up nearly 200 bytes of extra
// image storage.
switch(imageType) {
case PALETTE1: { // 1-bit (2 color) palette-based image
uint8_t pixelNum = 0, byteNum, bitNum, pixels, idx,
*ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS / 8];
for(byteNum = NUM_LEDS/8; byteNum--; ) { // Always padded to next byte
pixels = pgm_read_byte(ptr++); // 8 pixels of data (pixel 0 = LSB)
for(bitNum = 8; bitNum--; pixels >>= 1) {
idx = pixels & 1; // Color table index for pixel (0 or 1)
strip.setPixelColor(pixelNum++,
palette[idx][0], palette[idx][1], palette[idx][2]);
}
}
break;
}
case PALETTE4: { // 4-bit (16 color) palette-based image
uint8_t pixelNum, p1, p2,
*ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS / 2];
for(pixelNum = 0; pixelNum < NUM_LEDS; ) {
p2 = pgm_read_byte(ptr++); // Data for two pixels...
p1 = p2 >> 4; // Shift down 4 bits for first pixel
p2 &= 0x0F; // Mask out low 4 bits for second pixel
strip.setPixelColor(pixelNum++,
palette[p1][0], palette[p1][1], palette[p1][2]);
strip.setPixelColor(pixelNum++,
palette[p2][0], palette[p2][1], palette[p2][2]);
}
break;
}
case PALETTE8: { // 8-bit (256 color) PROGMEM-palette-based image
uint16_t o;
uint8_t pixelNum,
*ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS];
for(pixelNum = 0; pixelNum < NUM_LEDS; pixelNum++) {
o = pgm_read_byte(ptr++) * 3; // Offset into imagePalette
strip.setPixelColor(pixelNum,
pgm_read_byte(&imagePalette[o]),
pgm_read_byte(&imagePalette[o + 1]),
pgm_read_byte(&imagePalette[o + 2]));
}
break;
}
case TRUECOLOR: { // 24-bit ('truecolor') image (no palette)
uint8_t pixelNum, r, g, b,
*ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS * 3];
for(pixelNum = 0; pixelNum < NUM_LEDS; pixelNum++) {
r = pgm_read_byte(ptr++);
g = pgm_read_byte(ptr++);
b = pgm_read_byte(ptr++);
strip.setPixelColor(pixelNum, r, g, b);
}
break;
}
}
if(++imageLine >= imageLines) imageLine = 0; // Next scanline, wrap around
while(((t = micros()) - lastLineTime) < lineInterval) {
if(irCode != BTN_NONE) {
if(!strip.getBrightness()) { // If strip is off...
// Set brightness to last level
strip.setBrightness(pgm_read_byte(&brightness[bLevel]));
// and ignore button press (don't fall through)
// effectively, first press is 'wake'
} else {
switch(irCode) {
case BTN_BRIGHT_UP:
if(bLevel < (sizeof(brightness) - 1))
strip.setBrightness(pgm_read_byte(&brightness[++bLevel]));
break;
case BTN_BRIGHT_DOWN:
if(bLevel)
strip.setBrightness(pgm_read_byte(&brightness[--bLevel]));
break;
case BTN_FASTER:
if(lineIntervalIndex < (sizeof(lineTable) / sizeof(lineTable[0]) - 1))
lineInterval = pgm_read_word(&lineTable[++lineIntervalIndex]);
break;
case BTN_SLOWER:
if(lineIntervalIndex)
lineInterval = pgm_read_word(&lineTable[--lineIntervalIndex]);
break;
case BTN_RESTART:
imageNumber = 0;
imageInit();
break;
case BTN_BATTERY:
strip.clear();
strip.show();
delay(250);
strip.setBrightness(255);
showBatteryLevel();
strip.setBrightness(pgm_read_byte(&brightness[bLevel]));
break;
case BTN_OFF:
strip.setBrightness(0);
break;
case BTN_PATTERN_PREV:
prevImage();
break;
case BTN_PATTERN_NEXT:
nextImage();
break;
case BTN_AUTOPLAY:
autoCycle = !autoCycle;
break;
}
}
irCode = BTN_NONE;
}
}
strip.show(); // Refresh LEDs
lastLineTime = t;
}
void IRinterrupt() {
static uint32_t pulseStartTime = 0, pulseDuration = 0;
static uint8_t irValue, irBits, irBytes, irBuf[4];
uint32_t t = micros();
if(PIND & 0b00001000) { // Low-to-high (start of new pulse)
pulseStartTime = t;
} else { // High-to-low (end of current pulse)
uint32_t pulseDuration = t - pulseStartTime;
if((pulseDuration > 4000) && (pulseDuration < 5000)) { // ~4.5 ms?
irValue = irBits = irBytes = 0; // IR code start, reset counters
} else if(pulseDuration < 2500) { // Data bit?
irValue >>= 1; // Shift data in, LSB first
if(pulseDuration >= 1125) irValue |= 0x80; // Longer pulse = 1
if((++irBits == 8) && (irBytes < 4)) { // Full byte recv'd?
irBuf[irBytes] = irValue; // Store byte
irValue = irBits = 0; // and reset counters
if((++irBytes == 4) && ((irBuf[2] ^ irBuf[3]) == 0xFF)) {
uint16_t code = 0xFFFF;
if((irBuf[0] ^ irBuf[1]) == 0xFF) {
irCode = (irBuf[0] << 8) | irBuf[1];
} else if((irBuf[0] == 0) && (irBuf[1] == 0xBF)) {
irCode = irBuf[2];
}
}
}
}
}
}
// Battery monitoring idea adapted from JeeLabs article:
// jeelabs.org/2012/05/04/measuring-vcc-via-the-bandgap/
// Code from Adafruit TimeSquare project, added Trinket support.
// In a pinch, the poi code can work on a 3V Trinket, but the battery
// monitor will not work correctly (due to the 3.3V regulator), so
// maybe just comment out any reference to this code in that case.
uint16_t readVoltage() {
int i, prev;
uint8_t count;
uint16_t mV;
// Select AVcc voltage reference + Bandgap (1.8V) input
ADMUX = _BV(REFS0) |
_BV(MUX3) | _BV(MUX2) | _BV(MUX1);
ADCSRA = _BV(ADEN) | // Enable ADC
_BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 1/128 prescaler (125 KHz)
// Datasheet notes that the first bandgap reading is usually garbage as
// voltages are stabilizing. It practice, it seems to take a bit longer
// than that. Tried various delays, but still inconsistent and kludgey.
// Instead, repeated readings are taken until four concurrent readings
// stabilize within 10 mV.
for(prev=9999, count=0; count<4; ) {
for(ADCSRA |= _BV(ADSC); ADCSRA & _BV(ADSC); ); // Start, await ADC conv.
i = ADC; // Result
mV = i ? (1100L * 1023 / i) : 0; // Scale to millivolts
if(abs((int)mV - prev) <= 10) count++; // +1 stable reading
else count = 0; // too much change, start over
prev = mV;
}
ADCSRA = 0; // ADC off
return mV;
}

File diff suppressed because it is too large Load diff

334
Kinetic_POV/poi/graphics.h Normal file
View file

@ -0,0 +1,334 @@
// Don't edit this file! It's software-generated.
// See convert.py script instead.
#define PALETTE1 0
#define PALETTE4 1
#define PALETTE8 2
#define TRUECOLOR 3
#define NUM_LEDS 16
// adafruit.gif ------------------------------------------------------------
const uint8_t PROGMEM palette00[][3] = {
{ 0, 0, 0 },
{ 74, 74, 74 } };
const uint8_t PROGMEM pixels00[] = {
0X00, 0X7C, 0XE0, 0XFE, 0XF0, 0XFE, 0X70, 0XE6,
0X70, 0XC6, 0X30, 0XC6, 0X30, 0X66, 0X70, 0X66,
0XF0, 0XFF, 0XF0, 0XFF, 0XC0, 0XFF, 0X00, 0X00,
0XC0, 0X3F, 0XE0, 0X7F, 0XF0, 0XFF, 0X70, 0XE0,
0X70, 0XE0, 0X70, 0XE0, 0X60, 0X60, 0X60, 0X60,
0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0X00, 0X00,
0X00, 0X30, 0XE0, 0XFC, 0XF0, 0XFE, 0XF0, 0XEE,
0X70, 0XC6, 0X30, 0XC6, 0X30, 0X46, 0X70, 0X66,
0XF0, 0XFF, 0XF0, 0XFF, 0XE0, 0XFF, 0X00, 0X00,
0XFC, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0X77, 0X00,
0X73, 0X00, 0X73, 0X00, 0X00, 0X00, 0XE0, 0XFF,
0XF0, 0XFF, 0XF0, 0XFF, 0XE0, 0X00, 0X60, 0X00,
0X70, 0X00, 0X70, 0X00, 0X00, 0X00, 0X00, 0X00,
0XF0, 0X7F, 0XF0, 0XFF, 0XF0, 0XFF, 0X00, 0XE0,
0X00, 0XE0, 0X00, 0XE0, 0X00, 0X60, 0XF0, 0XFF,
0XF0, 0XFF, 0XF0, 0XFF, 0X00, 0X00, 0X00, 0X00,
0XF3, 0XFF, 0XF3, 0XFF, 0XF3, 0XFF, 0X00, 0X00,
0XF8, 0X3F, 0XF8, 0XFF, 0XFC, 0XFF, 0XF8, 0XFF,
0X70, 0XE0, 0X70, 0XE0, 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,
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 };
// fire.gif ----------------------------------------------------------------
const uint8_t PROGMEM palette01[][3] = {
{ 91, 83, 3 },
{ 91, 91, 24 },
{ 91, 35, 0 },
{ 91, 4, 0 },
{ 91, 51, 0 },
{ 91, 10, 0 },
{ 0, 0, 0 },
{ 91, 90, 11 },
{ 91, 17, 0 },
{ 17, 0, 0 },
{ 91, 1, 0 },
{ 91, 0, 0 },
{ 91, 70, 1 },
{ 34, 0, 0 },
{ 1, 0, 0 },
{ 7, 0, 0 } };
const uint8_t PROGMEM pixels01[] = {
0X66, 0X6E, 0X95, 0XC7, 0X05, 0XD9, 0XEB, 0XE6,
0X66, 0X6E, 0X93, 0XCC, 0X4A, 0X9F, 0X9B, 0XE6,
0X66, 0X66, 0X93, 0X4C, 0X8D, 0X99, 0XB9, 0XE6,
0X66, 0X6E, 0XD3, 0X42, 0X39, 0X9B, 0XB9, 0XE6,
0X66, 0X6F, 0XD5, 0X88, 0XD9, 0X9B, 0XB9, 0XE6,
0X66, 0X6F, 0XB8, 0X23, 0XDF, 0X9B, 0XBD, 0XE6,
0X66, 0XE9, 0XA8, 0X2A, 0X9E, 0X9B, 0XBD, 0XF6,
0X66, 0XE9, 0X32, 0X8B, 0XFE, 0X9B, 0XAB, 0XDE,
0X66, 0XED, 0X52, 0X5D, 0XE6, 0XFB, 0XA3, 0XB9,
0X66, 0XFB, 0X22, 0X59, 0XE6, 0XED, 0XA3, 0XAD,
0X66, 0X9A, 0X24, 0X39, 0X66, 0XEF, 0XA3, 0X3B,
0X6E, 0XF3, 0X24, 0X39, 0XE6, 0X6F, 0XD3, 0X3B,
0X66, 0X93, 0X44, 0X39, 0XE6, 0X6E, 0X93, 0X3A,
0X6E, 0X93, 0X44, 0X59, 0X66, 0X6E, 0X93, 0X5A,
0X6E, 0X93, 0X2C, 0X59, 0XE6, 0X6E, 0XD3, 0X5A,
0X66, 0X9A, 0X44, 0X8D, 0XF6, 0XEF, 0XA8, 0X8A,
0X66, 0XFA, 0X2C, 0X45, 0X9F, 0XFD, 0X52, 0X8B,
0X66, 0XED, 0X8C, 0X04, 0X5D, 0XD8, 0X44, 0X3D,
0X66, 0XE9, 0X3C, 0X77, 0XC4, 0X4C, 0XC4, 0XA9,
0X66, 0X69, 0XAC, 0X71, 0X77, 0X77, 0XC5, 0XBF,
0X66, 0XEF, 0XB2, 0X71, 0X11, 0X70, 0X2A, 0X9E,
0X66, 0X6F, 0XD8, 0X01, 0X11, 0X74, 0XA9, 0XF6,
0X66, 0X6E, 0X93, 0XC1, 0X17, 0XC3, 0XDE, 0X66,
0X6E, 0X6E, 0XFA, 0X47, 0X17, 0X2D, 0XF6, 0X66,
0X6E, 0XBF, 0XFD, 0X50, 0X7C, 0X89, 0XE6, 0X66,
0X6E, 0XBF, 0XFD, 0XA4, 0XCC, 0X39, 0XE6, 0X66,
0X6E, 0XFB, 0X9F, 0XD8, 0X44, 0X39, 0XE6, 0X66,
0X6E, 0X9B, 0XBD, 0X93, 0X22, 0X39, 0XE6, 0X66,
0X6E, 0XFB, 0XB9, 0X9D, 0X52, 0X5D, 0XE6, 0X66,
0X6E, 0XDB, 0XB9, 0XF9, 0X32, 0X8A, 0XF6, 0X66,
0X6F, 0XDB, 0XB9, 0XFF, 0XA2, 0X8A, 0X9E, 0X66,
0XED, 0XBA, 0XB9, 0XE9, 0XB8, 0X23, 0X9E, 0X66,
0X9B, 0XA3, 0XBF, 0XEE, 0XD5, 0X25, 0XDF, 0X66,
0X9A, 0X3A, 0XDF, 0X6E, 0X93, 0X28, 0XBF, 0X66,
0XB3, 0X5B, 0X96, 0X66, 0X93, 0X22, 0XAF, 0XE6,
0XB3, 0X3D, 0XE6, 0X6E, 0X93, 0X24, 0X39, 0XE6,
0XA3, 0X39, 0XE6, 0X66, 0X93, 0X42, 0X39, 0XE6,
0XA5, 0X39, 0XE6, 0X6E, 0X93, 0X44, 0X39, 0XE6,
0XA5, 0X3D, 0XE6, 0X66, 0XD5, 0X44, 0XA9, 0X66,
0XA8, 0X8A, 0X96, 0X6F, 0XD2, 0X44, 0XAF, 0X66,
0XB8, 0X25, 0XD9, 0XFD, 0X3C, 0XC8, 0XAF, 0XE6,
0XD5, 0X44, 0X5D, 0XD3, 0XC0, 0X08, 0XDE, 0X66,
0X9A, 0X20, 0XC4, 0X4C, 0X77, 0XC3, 0XDE, 0X66,
0XFB, 0X8C, 0X77, 0X71, 0X71, 0X43, 0XFE, 0X66,
0XE9, 0XA2, 0X01, 0X11, 0X17, 0X4B, 0XF6, 0X66,
0X6E, 0XDA, 0X47, 0X11, 0X17, 0X8D, 0XF6, 0X66,
0X66, 0XE9, 0X3C, 0X11, 0X1C, 0X39, 0XE6, 0X66,
0X66, 0X6F, 0XD2, 0X71, 0X74, 0XAF, 0XEE, 0X66 };
// pride.gif ---------------------------------------------------------------
const uint8_t PROGMEM palette02[][3] = {
{ 35, 0, 77 },
{ 77, 77, 0 },
{ 0, 52, 0 },
{ 0, 0, 0 },
{ 77, 0, 0 },
{ 77, 18, 0 },
{ 0, 6, 77 } };
const uint8_t PROGMEM pixels02[] = {
0X34, 0X44, 0X44, 0X44, 0X44, 0X44, 0X44, 0X43,
0X33, 0X44, 0X44, 0X44, 0X44, 0X44, 0X44, 0X33,
0X53, 0X34, 0X44, 0X44, 0X44, 0X44, 0X43, 0X35,
0X55, 0X33, 0X44, 0X44, 0X44, 0X44, 0X33, 0X55,
0X55, 0X53, 0X34, 0X44, 0X44, 0X43, 0X35, 0X55,
0X55, 0X55, 0X33, 0X44, 0X44, 0X33, 0X55, 0X55,
0X55, 0X55, 0X53, 0X34, 0X43, 0X35, 0X55, 0X55,
0X55, 0X55, 0X55, 0X33, 0X33, 0X55, 0X55, 0X55,
0X55, 0X55, 0X55, 0X53, 0X35, 0X55, 0X55, 0X55,
0X35, 0X55, 0X55, 0X55, 0X55, 0X55, 0X55, 0X53,
0X33, 0X55, 0X55, 0X55, 0X55, 0X55, 0X55, 0X33,
0X13, 0X35, 0X55, 0X55, 0X55, 0X55, 0X53, 0X31,
0X11, 0X33, 0X55, 0X55, 0X55, 0X55, 0X33, 0X11,
0X11, 0X13, 0X35, 0X55, 0X55, 0X53, 0X31, 0X11,
0X11, 0X11, 0X33, 0X55, 0X55, 0X33, 0X11, 0X11,
0X11, 0X11, 0X13, 0X35, 0X53, 0X31, 0X11, 0X11,
0X11, 0X11, 0X11, 0X33, 0X33, 0X11, 0X11, 0X11,
0X11, 0X11, 0X11, 0X13, 0X31, 0X11, 0X11, 0X11,
0X31, 0X11, 0X11, 0X11, 0X11, 0X11, 0X11, 0X13,
0X33, 0X11, 0X11, 0X11, 0X11, 0X11, 0X11, 0X33,
0X23, 0X31, 0X11, 0X11, 0X11, 0X11, 0X13, 0X32,
0X22, 0X33, 0X11, 0X11, 0X11, 0X11, 0X33, 0X22,
0X22, 0X23, 0X31, 0X11, 0X11, 0X13, 0X32, 0X22,
0X22, 0X22, 0X33, 0X11, 0X11, 0X33, 0X22, 0X22,
0X22, 0X22, 0X23, 0X31, 0X13, 0X32, 0X22, 0X22,
0X22, 0X22, 0X22, 0X33, 0X33, 0X22, 0X22, 0X22,
0X22, 0X22, 0X22, 0X23, 0X32, 0X22, 0X22, 0X22,
0X32, 0X22, 0X22, 0X22, 0X22, 0X22, 0X22, 0X23,
0X33, 0X22, 0X22, 0X22, 0X22, 0X22, 0X22, 0X33,
0X63, 0X32, 0X22, 0X22, 0X22, 0X22, 0X23, 0X36,
0X66, 0X33, 0X22, 0X22, 0X22, 0X22, 0X33, 0X66,
0X66, 0X63, 0X32, 0X22, 0X22, 0X23, 0X36, 0X66,
0X66, 0X66, 0X33, 0X22, 0X22, 0X33, 0X66, 0X66,
0X66, 0X66, 0X63, 0X32, 0X23, 0X36, 0X66, 0X66,
0X66, 0X66, 0X66, 0X33, 0X33, 0X66, 0X66, 0X66,
0X66, 0X66, 0X66, 0X63, 0X36, 0X66, 0X66, 0X66,
0X36, 0X66, 0X66, 0X66, 0X66, 0X66, 0X66, 0X63,
0X33, 0X66, 0X66, 0X66, 0X66, 0X66, 0X66, 0X33,
0X03, 0X36, 0X66, 0X66, 0X66, 0X66, 0X63, 0X30,
0X00, 0X33, 0X66, 0X66, 0X66, 0X66, 0X33, 0X00,
0X00, 0X03, 0X36, 0X66, 0X66, 0X63, 0X30, 0X00,
0X00, 0X00, 0X33, 0X66, 0X66, 0X33, 0X00, 0X00,
0X00, 0X00, 0X03, 0X36, 0X63, 0X30, 0X00, 0X00,
0X00, 0X00, 0X00, 0X33, 0X33, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X03, 0X30, 0X00, 0X00, 0X00,
0X30, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03,
0X33, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X33,
0X43, 0X30, 0X00, 0X00, 0X00, 0X00, 0X03, 0X34,
0X44, 0X33, 0X00, 0X00, 0X00, 0X00, 0X33, 0X44,
0X44, 0X43, 0X30, 0X00, 0X00, 0X03, 0X34, 0X44,
0X44, 0X44, 0X33, 0X00, 0X00, 0X33, 0X44, 0X44,
0X44, 0X44, 0X43, 0X30, 0X03, 0X34, 0X44, 0X44,
0X44, 0X44, 0X44, 0X33, 0X33, 0X44, 0X44, 0X44,
0X44, 0X44, 0X44, 0X43, 0X34, 0X44, 0X44, 0X44 };
// spectrum.gif ------------------------------------------------------------
const uint8_t PROGMEM palette03[][3] = {
{ 7, 0, 64 },
{ 64, 0, 0 },
{ 40, 0, 64 },
{ 0, 64, 0 },
{ 64, 0, 46 },
{ 46, 64, 0 },
{ 0, 64, 59 },
{ 64, 0, 5 },
{ 8, 64, 0 },
{ 0, 64, 18 },
{ 0, 64, 1 },
{ 64, 39, 0 },
{ 64, 4, 0 },
{ 0, 0, 64 },
{ 0, 25, 64 },
{ 0, 1, 64 } };
const uint8_t PROGMEM pixels03[] = {
0X1C, 0XB5, 0X83, 0XA9, 0X6E, 0XFD, 0X02, 0X47 };
// usa.gif -----------------------------------------------------------------
const uint8_t PROGMEM palette04[][3] = {
{ 56, 56, 56 },
{ 56, 0, 0 },
{ 0, 0, 0 },
{ 0, 3, 56 } };
const uint8_t PROGMEM pixels04[] = {
0X22, 0X22, 0X22, 0X22, 0X22, 0X22, 0X22, 0X22,
0X22, 0X22, 0X22, 0X22, 0X22, 0X22, 0X22, 0X22,
0X10, 0X10, 0X10, 0X10, 0X10, 0X10, 0X12, 0X22,
0X10, 0X10, 0X10, 0X10, 0X10, 0X10, 0X12, 0X22,
0X10, 0X10, 0X10, 0X10, 0X10, 0X10, 0X12, 0X22,
0X10, 0X10, 0X10, 0X10, 0X10, 0X10, 0X12, 0X22,
0X10, 0X10, 0X10, 0X10, 0X10, 0X10, 0X12, 0X22,
0X10, 0X10, 0X10, 0X10, 0X10, 0X10, 0X12, 0X22,
0X10, 0X10, 0X10, 0X10, 0X10, 0X10, 0X12, 0X22,
0X10, 0X10, 0X10, 0X10, 0X10, 0X10, 0X12, 0X22,
0X10, 0X10, 0X10, 0X10, 0X10, 0X10, 0X12, 0X22,
0X10, 0X10, 0X10, 0X10, 0X10, 0X10, 0X12, 0X22,
0X10, 0X10, 0X10, 0X10, 0X10, 0X10, 0X12, 0X22,
0X10, 0X10, 0X10, 0X10, 0X10, 0X10, 0X12, 0X22,
0X10, 0X10, 0X10, 0X10, 0X10, 0X10, 0X12, 0X22,
0X33, 0X33, 0X33, 0X30, 0X10, 0X10, 0X12, 0X22,
0X30, 0X30, 0X30, 0X30, 0X10, 0X10, 0X12, 0X22,
0X33, 0X03, 0X03, 0X30, 0X10, 0X10, 0X12, 0X22,
0X30, 0X30, 0X30, 0X30, 0X10, 0X10, 0X12, 0X22,
0X33, 0X03, 0X03, 0X30, 0X10, 0X10, 0X12, 0X22,
0X30, 0X30, 0X30, 0X30, 0X10, 0X10, 0X12, 0X22,
0X33, 0X03, 0X03, 0X30, 0X10, 0X10, 0X12, 0X22,
0X30, 0X30, 0X30, 0X30, 0X10, 0X10, 0X12, 0X22,
0X33, 0X33, 0X33, 0X30, 0X10, 0X10, 0X12, 0X22 };
// usa2.gif ----------------------------------------------------------------
const uint8_t PROGMEM palette05[][3] = {
{ 24, 28, 48 },
{ 0, 2, 48 },
{ 48, 0, 0 },
{ 32, 35, 48 },
{ 0, 4, 48 },
{ 16, 21, 48 },
{ 20, 24, 48 },
{ 13, 18, 48 },
{ 5, 9, 48 },
{ 28, 31, 48 },
{ 42, 43, 48 },
{ 3, 7, 48 },
{ 10, 15, 48 },
{ 37, 39, 48 },
{ 7, 12, 48 },
{ 48, 48, 48 } };
const uint8_t PROGMEM pixels05[] = {
0X11, 0X19, 0X11, 0X11, 0X12, 0XF2, 0XF2, 0XF2,
0X11, 0X1F, 0X71, 0X14, 0XC2, 0XF2, 0XF2, 0XF2,
0X11, 0X16, 0XF7, 0X9F, 0XC2, 0XF2, 0XF2, 0XF2,
0X1B, 0X7D, 0XFF, 0XF6, 0X12, 0XF2, 0XF2, 0XF2,
0X5A, 0XFF, 0XFF, 0XF4, 0X12, 0XF2, 0XF2, 0XF2,
0X14, 0XE3, 0XFF, 0XF3, 0X12, 0XF2, 0XF2, 0XF2,
0X11, 0X10, 0XA8, 0X53, 0X62, 0XF2, 0XF2, 0XF2,
0X11, 0X1F, 0X81, 0X11, 0XB2, 0XF2, 0XF2, 0XF2,
0X11, 0X15, 0X11, 0X11, 0X12, 0XF2, 0XF2, 0XF2 };
// wales.gif ---------------------------------------------------------------
const uint8_t PROGMEM palette06[][3] = {
{ 68, 59, 60 },
{ 0, 26, 1 },
{ 4, 5, 1 },
{ 74, 74, 74 },
{ 46, 17, 21 },
{ 37, 0, 1 },
{ 41, 5, 8 },
{ 58, 43, 45 },
{ 35, 1, 2 },
{ 34, 2, 4 },
{ 16, 1, 1 },
{ 2, 37, 8 },
{ 42, 11, 14 },
{ 53, 31, 34 },
{ 0, 14, 1 },
{ 27, 0, 1 } };
const uint8_t PROGMEM pixels06[] = {
0X33, 0X33, 0X33, 0X33, 0XB1, 0X11, 0X11, 0X11,
0X33, 0X03, 0X00, 0X33, 0XB1, 0X11, 0X11, 0X11,
0X33, 0X48, 0XFD, 0X03, 0XB1, 0X11, 0X11, 0X11,
0X33, 0X34, 0X89, 0X58, 0XE1, 0X11, 0X11, 0X11,
0X30, 0X33, 0X79, 0X69, 0XF1, 0X22, 0XEE, 0X2E,
0X34, 0X07, 0X36, 0X94, 0X52, 0XFF, 0XAF, 0XA1,
0X3D, 0X5F, 0X73, 0X7D, 0X5F, 0X5E, 0X1E, 0XA1,
0X33, 0X95, 0X56, 0X44, 0X55, 0XF1, 0X12, 0XAE,
0X7D, 0X95, 0X55, 0X86, 0X55, 0XA1, 0XE2, 0X21,
0X08, 0X5F, 0X55, 0X55, 0XF5, 0X21, 0XA1, 0X11,
0X3D, 0X55, 0XF5, 0X55, 0X55, 0X22, 0X52, 0X11,
0X33, 0XC5, 0XFF, 0X55, 0X5F, 0X55, 0XFF, 0X2E,
0X33, 0X39, 0X55, 0X5F, 0X55, 0XF5, 0X22, 0XA1,
0X33, 0X30, 0X95, 0X5F, 0X55, 0XFA, 0X1E, 0XA1,
0X33, 0X33, 0X30, 0XD5, 0X55, 0X5E, 0X12, 0XA1,
0X7D, 0X0C, 0XD7, 0X78, 0XF5, 0X5A, 0X1E, 0X21,
0X08, 0X85, 0X58, 0X8F, 0X5F, 0XF5, 0X21, 0X11,
0X36, 0X55, 0XFF, 0XFF, 0XF5, 0XF5, 0X21, 0XE1,
0X36, 0X88, 0X6F, 0XFF, 0XF5, 0X5A, 0XF2, 0X2E,
0X34, 0X8A, 0XC0, 0XC9, 0X8F, 0X52, 0X1A, 0XA1,
0X37, 0X9C, 0X73, 0X33, 0XBA, 0X5A, 0X1E, 0XF1,
0X30, 0X4C, 0X0D, 0X70, 0X65, 0X21, 0X12, 0XFE,
0X33, 0XD9, 0X79, 0XF5, 0XFE, 0X11, 0X11, 0XE1,
0X33, 0XD0, 0X3C, 0X47, 0X2E, 0X11, 0X11, 0X11,
0X33, 0X33, 0X33, 0X33, 0XB1, 0X11, 0X11, 0X11,
0X33, 0X33, 0X33, 0X33, 0XB1, 0X11, 0X11, 0X11 };
typedef struct {
uint8_t type; // PALETTE[1,4,8] or TRUECOLOR
line_t lines; // Length of image (in scanlines)
const uint8_t *palette; // -> PROGMEM color table (NULL if truecolor)
const uint8_t *pixels; // -> Pixel data in PROGMEM
} image;
const image PROGMEM images[] = {
{ PALETTE1 , 100, (const uint8_t *)palette00, pixels00 },
{ PALETTE4 , 48, (const uint8_t *)palette01, pixels01 },
{ PALETTE4 , 54, (const uint8_t *)palette02, pixels02 },
{ PALETTE4 , 1, (const uint8_t *)palette03, pixels03 },
{ PALETTE4 , 24, (const uint8_t *)palette04, pixels04 },
{ PALETTE4 , 9, (const uint8_t *)palette05, pixels05 },
{ PALETTE4 , 26, (const uint8_t *)palette06, pixels06 }
};
#define NUM_IMAGES (sizeof(images) / sizeof(images[0]))

437
Kinetic_POV/poi/poi.ino Normal file
View file

@ -0,0 +1,437 @@
/*------------------------------------------------------------------------
POV LED poi sketch. Uses the following Adafruit parts (X2 for two poi):
- Trinket 5V (adafruit.com/product/1501) (NOT Pro Trinket or 3V Trinket)
- 150 mAh LiPoly battery (#1317)
- LiPoly backpack (#2124)
- Tiny SPDT slide switch (#805)
- 144 LED/m DotStar strip (#2328 or #2329) ONE is enough for four poi!
- Small tactile button (#1489) (optional) ONE is enough for 20 poi!
Use 'soda bottle preform' for enclosure w/5.25" (133 mm) inside depth.
3D-printable cap and insert can be downloaded from Thingiverse:
http://www.thingiverse.com/thing:918847
Add leash - e.g. paracord, or fancy ones available from flowtoys.com.
Needs Adafruit_DotStar library: github.com/adafruit/Adafruit_DotStar
The poi project is designed around the Trinket board specifically for
its small size (Pro Trinket won't fit). Throughout the code you'll see
mentions of other boards and extra features -- future projects might
adapt this code for larger spinnythings like clubs or staves -- but
for the poi, no, there's just no space inside the soda bottle preform,
it's Trinket only and just with the most basic features.
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
MIT license, all text above must be included in any redistribution.
See 'COPYING' file for additional notes.
------------------------------------------------------------------------*/
#include <Arduino.h>
#include <Adafruit_DotStar.h>
#include <avr/power.h>
#include <avr/sleep.h>
// #include <SPI.h> // Enable this line on Pro Trinket
#ifdef __AVR_ATtiny85__
typedef uint8_t line_t; // Max 255 lines/image on Trinket
#else
typedef uint16_t line_t; // Bigger images OK on other boards
#endif
// CONFIGURABLE STUFF ------------------------------------------------------
#include "graphics.h" // Graphics data is contained in this header file.
// It's generated using the 'convert.py' Python script. Various image
// formats are supported, trading off color fidelity for PROGMEM space
// (particularly limited on Trinket). Handles 1-, 4- and 8-bit-per-pixel
// palette-based images, plus 24-bit truecolor. 1- and 4-bit palettes can
// be altered in RAM while running to provide additional colors, but be
// mindful of peak & average current draw if you do that! Power limiting
// is normally done in convert.py (keeps this code relatively small & fast).
// 1/4/8/24 were chosen because the AVR can handle these operations fairly
// easily (have idea for handing arbitrary bit depth w/328P, but this margin
// is too narrow to contain).
// Ideally you use hardware SPI as it's much faster, though limited to
// specific pins. If you really need to bitbang DotStar data & clock on
// different pins, optionally define those here:
//#define LED_DATA_PIN 0
//#define LED_CLOCK_PIN 1
// Select from multiple images using tactile button (#1489) between pin and
// ground. Requires suitably-built graphics.h file w/more than one image.
#define SELECT_PIN 3
// Optional feature -- not enabled here, no space -- a vibration switch
// (aligned perpendicular to leash) is used as a poor man's accelerometer.
// Poi then lights only when moving, saving some power. The 'fast'
// vibration switch is VERY sensitive and will trigger at the slightest
// bump, while the 'medium' switch requires a certain spin rate which may
// not trigger if you're doing mellow spins. Neither is perfect. To leave
// that out and simply have the poi run always-on, comment out this line:
//#define MOTION_PIN 2
// Another optional feature not enable due to physical size -- powering down
// DotStars when idle conserves more battery. Use a PNP transistor (e.g.
// 2N2907) (w/220 Ohm resistor to base) as a 'high side' switch to DotStar
// +V. DON'T do this NPN/low-side, may damage strip. MOTION_PIN must also
// be defined to use this (pointless without).
//#define POWER_PIN 4
#define SLEEP_TIME 2000 // Not-spinning time before sleep, in milliseconds
// Empty and full thresholds (millivolts) used for battery level display:
#define BATT_MIN_MV 3350 // Some headroom over battery cutoff near 2.9V
#define BATT_MAX_MV 4000 // And little below fresh-charged battery near 4.1V
boolean autoCycle = false; // Set to true to cycle images by default
#define CYCLE_TIME 15 // Time, in seconds, between auto-cycle images
// -------------------------------------------------------------------------
#if defined(LED_DATA_PIN) && defined(LED_CLOCK_PIN)
// Older DotStar LEDs use GBR order. If colors are wrong, edit here.
Adafruit_DotStar strip = Adafruit_DotStar(NUM_LEDS,
LED_DATA_PIN, LED_CLOCK_PIN, DOTSTAR_BRG);
#else
Adafruit_DotStar strip = Adafruit_DotStar(NUM_LEDS, DOTSTAR_BRG);
#endif
void imageInit(void);
uint16_t readVoltage(void);
#ifdef MOTION_PIN
void sleep(void);
#endif
void setup() {
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000L)
clock_prescale_set(clock_div_1); // Enable 16 MHz on Trinket
#endif
#ifdef POWER_PIN
pinMode(POWER_PIN, OUTPUT);
digitalWrite(POWER_PIN, LOW); // Power-on LED strip
#endif
strip.begin(); // Allocate DotStar buffer, init SPI
strip.clear(); // Make sure strip is clear
strip.show(); // before measuring battery
// Display battery level bargraph on startup. It's just a vague estimate
// based on cell voltage (drops with discharge) but doesn't handle curve.
uint16_t mV = readVoltage();
uint8_t lvl = (mV >= BATT_MAX_MV) ? NUM_LEDS : // Full (or nearly)
(mV <= BATT_MIN_MV) ? 1 : // Drained
1 + ((mV - BATT_MIN_MV) * NUM_LEDS + (NUM_LEDS / 2)) /
(BATT_MAX_MV - BATT_MIN_MV + 1); // # LEDs lit (1-NUM_LEDS)
for(uint8_t i=0; i<lvl; i++) { // Each LED to batt level...
uint8_t g = (i * 5 + 2) / NUM_LEDS; // Red to green
strip.setPixelColor(i, 4-g, g, 0);
strip.show(); // Animate a bit
delay(250 / NUM_LEDS);
}
delay(1500); // Hold last state a moment
strip.clear(); // Then clear strip
strip.show();
imageInit(); // Initialize pointers for default image
#ifdef SELECT_PIN
pinMode(SELECT_PIN, INPUT_PULLUP);
#endif
#ifdef MOTION_PIN
pinMode(MOTION_PIN, INPUT_PULLUP);
sleep(); // Sleep until motion detected
#endif
}
// GLOBAL STATE STUFF ------------------------------------------------------
uint32_t lastImageTime = 0L; // Time of last image change
#ifdef MOTION_PIN
uint32_t prev = 0L; // Used for sleep timing
#endif
uint8_t imageNumber = 0, // Current image being displayed
imageType, // Image type: PALETTE[1,4,8] or TRUECOLOR
*imagePalette, // -> palette data in PROGMEM
*imagePixels, // -> pixel data in PROGMEM
palette[16][3]; // RAM-based color table for 1- or 4-bit images
line_t imageLines, // Number of lines in active image
imageLine; // Current line number in image
#ifdef SELECT_PIN
uint8_t debounce = 0; // Debounce counter for image select pin
#endif
void imageInit() { // Initialize global image state for current imageNumber
imageType = pgm_read_byte(&images[imageNumber].type);
#ifdef __AVR_ATtiny85__
imageLines = pgm_read_byte(&images[imageNumber].lines);
#else
imageLines = pgm_read_word(&images[imageNumber].lines);
#endif
imageLine = 0;
imagePalette = (uint8_t *)pgm_read_word(&images[imageNumber].palette);
imagePixels = (uint8_t *)pgm_read_word(&images[imageNumber].pixels);
// 1- and 4-bit images have their color palette loaded into RAM both for
// faster access and to allow dynamic color changing. Not done w/8-bit
// because that would require inordinate RAM (328P could handle it, but
// I'd rather keep the RAM free for other features in the future).
if(imageType == PALETTE1) memcpy_P(palette, imagePalette, 2 * 3);
else if(imageType == PALETTE4) memcpy_P(palette, imagePalette, 16 * 3);
lastImageTime = millis(); // Save time of image init for next auto-cycle
}
void nextImage(void) {
if(++imageNumber >= NUM_IMAGES) imageNumber = 0;
imageInit();
}
// MAIN LOOP ---------------------------------------------------------------
void loop() {
uint32_t t = millis(); // Current time, milliseconds
#ifdef MOTION_PIN
// Tried to do this with watchdog timer but encountered gas pains, so...
if(!digitalRead(MOTION_PIN)) { // Vibration switch pulled down?
prev = t; // Yes, reset timer
} else if((t - prev) > SLEEP_TIME) { // No, SLEEP_TIME elapsed w/no switch?
sleep(); // Power down
prev = t; // Reset timer on wake
}
#endif
if(autoCycle) {
if((t - lastImageTime) >= (CYCLE_TIME * 1000L)) nextImage();
// CPU clocks vary slightly; multiple poi won't stay in perfect sync.
// Keep this in mind when using auto-cycle mode, you may want to cull
// the image selection to avoid unintentional regrettable combinations.
}
#ifdef SELECT_PIN
if(digitalRead(SELECT_PIN)) { // Image select?
debounce = 0; // Not pressed -- reset counter
} else { // Pressed...
if(++debounce >= 25) { // Debounce input
nextImage(); // Switch to next image
while(!digitalRead(SELECT_PIN)); // Wait for release
// If held 1+ sec, toggle auto-cycle mode on/off
if((millis() - t) >= 1000L) autoCycle = !autoCycle;
debounce = 0;
}
}
#endif
// Transfer one scanline from pixel data to LED strip:
// If you're really pressed for graphics space and need just a few extra
// scanlines, and know for a fact you won't be using certain image modes,
// you can comment out the corresponding blocks below. e.g. PALETTE8 and
// TRUECOLOR are somewhat impractical on Trinket, and commenting them out
// can free up nearly 200 bytes of extra image storage.
switch(imageType) {
case PALETTE1: { // 1-bit (2 color) palette-based image
uint8_t pixelNum = 0, byteNum, bitNum, pixels, idx,
*ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS / 8];
for(byteNum = NUM_LEDS/8; byteNum--; ) { // Always padded to next byte
pixels = pgm_read_byte(ptr++); // 8 pixels of data (pixel 0 = LSB)
for(bitNum = 8; bitNum--; pixels >>= 1) {
idx = pixels & 1; // Color table index for pixel (0 or 1)
strip.setPixelColor(pixelNum++,
palette[idx][0], palette[idx][1], palette[idx][2]);
}
}
break;
}
case PALETTE4: { // 4-bit (16 color) palette-based image
uint8_t pixelNum, p1, p2,
*ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS / 2];
for(pixelNum = 0; pixelNum < NUM_LEDS; ) {
p2 = pgm_read_byte(ptr++); // Data for two pixels...
p1 = p2 >> 4; // Shift down 4 bits for first pixel
p2 &= 0x0F; // Mask out low 4 bits for second pixel
strip.setPixelColor(pixelNum++,
palette[p1][0], palette[p1][1], palette[p1][2]);
strip.setPixelColor(pixelNum++,
palette[p2][0], palette[p2][1], palette[p2][2]);
}
break;
}
#if 0 // Yep, demo images need ALL THE SPACE (see comment above)
case PALETTE8: { // 8-bit (256 color) PROGMEM-palette-based image
uint16_t o;
uint8_t pixelNum,
*ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS];
for(pixelNum = 0; pixelNum < NUM_LEDS; pixelNum++) {
o = pgm_read_byte(ptr++) * 3; // Offset into imagePalette
strip.setPixelColor(pixelNum,
pgm_read_byte(&imagePalette[o]),
pgm_read_byte(&imagePalette[o + 1]),
pgm_read_byte(&imagePalette[o + 2]));
}
break;
}
case TRUECOLOR: { // 24-bit ('truecolor') image (no palette)
uint8_t pixelNum, r, g, b,
*ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS * 3];
for(pixelNum = 0; pixelNum < NUM_LEDS; pixelNum++) {
r = pgm_read_byte(ptr++);
g = pgm_read_byte(ptr++);
b = pgm_read_byte(ptr++);
strip.setPixelColor(pixelNum, r, g, b);
}
break;
}
#endif
}
strip.show(); // Refresh LEDs
#if !defined(LED_DATA_PIN) && !defined(LED_CLOCK_PIN)
delayMicroseconds(900); // Because hardware SPI is ludicrously fast
#endif
if(++imageLine >= imageLines) imageLine = 0; // Next scanline, wrap around
}
// POWER-SAVING STUFF -- Relentlessly non-portable -------------------------
#ifdef MOTION_PIN
void sleep() {
// Turn off LEDs...
strip.clear(); // Issue '0' data
strip.show();
#ifdef POWER_PIN
digitalWrite(POWER_PIN, HIGH); // Cut power
#if !defined(LED_DATA_PIN) && !defined(LED_CLOCK_PIN)
#ifdef __AVR_ATtiny85__
pinMode(1, INPUT); // Set SPI data & clock to inputs else
pinMode(2, INPUT); // DotStars power parasitically, jerks.
#else
pinMode(11, INPUT);
pinMode(13, INPUT);
#endif // ATtiny
#endif // Data/clock/pins
#endif // POWER_PIN
power_all_disable(); // Peripherals ALL OFF, best sleep-state battery use
// Enable pin-change interrupt on motion pin
#ifdef __AVR_ATtiny85__
PCMSK = _BV(MOTION_PIN); // Pin mask
GIMSK = _BV(PCIE); // Interrupt enable
#else
volatile uint8_t *p = portInputRegister(digitalPinToPort(MOTION_PIN));
if(p == &PIND) { // Pins 0-7 = PCINT16-23
PCMSK2 = _BV(MOTION_PIN);
PCICR = _BV(PCIE2);
} else if(p == &PINB) { // Pins 8-13 = PCINT0-5
PCMSK0 = _BV(MOTION_PIN- 8);
PCICR = _BV(PCIE0);
} else if(p == &PINC) { // Pins 14-20 = PCINT8-14
PCMSK1 = _BV(MOTION_PIN-14);
PCICR = _BV(PCIE1);
}
#endif
// If select pin is enabled, that wakes too!
#ifdef SELECT_PIN
debounce = 0;
#ifdef __AVR_ATtiny85__
PCMSK |= _BV(SELECT_PIN); // Add'l pin mask
#else
volatile uint8_t *p = portInputRegister(digitalPinToPort(SELECT_PIN));
if(p == &PIND) { // Pins 0-7 = PCINT16-23
PCMSK2 = _BV(SELECT_PIN);
PCICR = _BV(PCIE2);
} else if(p == &PINB) { // Pins 8-13 = PCINT0-5
PCMSK0 = _BV(SELECT_PIN- 8);
PCICR = _BV(PCIE0);
} else if(p == &PINC) { // Pins 14-20 = PCINT8-14
PCMSK1 = _BV(SELECT_PIN-14);
PCICR = _BV(PCIE1);
}
#endif // ATtiny
#endif // SELECT_PIN
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Deepest sleep mode
sleep_enable();
interrupts();
sleep_mode(); // Power down
// Resumes here on wake
// Clear pin change settings so interrupt won't fire again
#ifdef __AVR_ATtiny85__
GIMSK = PCMSK = 0;
#else
PCICR = PCMSK0 = PCMSK1 = PCMSK2 = 0;
#endif
power_timer0_enable(); // Used by millis()
#if !defined(LED_DATA_PIN) && !defined(LED_CLOCK_PIN)
#ifdef __AVR_ATtiny85__
pinMode(1, OUTPUT); // Re-enable SPI pins
pinMode(2, OUTPUT);
power_usi_enable(); // Used by DotStar
#else
pinMode(11, OUTPUT); // Re-enable SPI pins
pinMode(13, OUTPUT);
power_spi_enable(); // Used by DotStar
#endif // ATtiny
#endif // Data/clock pins
#ifdef POWER_PIN
digitalWrite(POWER_PIN, LOW); // Power-up LEDs
#endif
prev = millis(); // Save wake time
}
EMPTY_INTERRUPT(PCINT0_vect); // Pin change (does nothing, but required)
#ifndef __AVR_ATtiny85__
ISR(PCINT1_vect, ISR_ALIASOF(PCINT0_vect));
ISR(PCINT2_vect, ISR_ALIASOF(PCINT0_vect));
#endif
#endif // MOTION_PIN
// Battery monitoring idea adapted from JeeLabs article:
// jeelabs.org/2012/05/04/measuring-vcc-via-the-bandgap/
// Code from Adafruit TimeSquare project, added Trinket support.
// In a pinch, the poi code can work on a 3V Trinket, but the battery
// monitor will not work correctly (due to the 3.3V regulator), so
// maybe just comment out any reference to this code in that case.
uint16_t readVoltage() {
int i, prev;
uint8_t count;
uint16_t mV;
// Select AVcc voltage reference + Bandgap (1.8V) input
#ifdef __AVR_ATtiny85__
ADMUX = _BV(MUX3) | _BV(MUX2);
#else
ADMUX = _BV(REFS0) |
_BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
ADCSRA = _BV(ADEN) | // Enable ADC
_BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 1/128 prescaler (125 KHz)
// Datasheet notes that the first bandgap reading is usually garbage as
// voltages are stabilizing. It practice, it seems to take a bit longer
// than that. Tried various delays, but still inconsistent and kludgey.
// Instead, repeated readings are taken until four concurrent readings
// stabilize within 10 mV.
for(prev=9999, count=0; count<4; ) {
for(ADCSRA |= _BV(ADSC); ADCSRA & _BV(ADSC); ); // Start, await ADC conv.
i = ADC; // Result
mV = i ? (1100L * 1023 / i) : 0; // Scale to millivolts
if(abs((int)mV - prev) <= 10) count++; // +1 stable reading
else count = 0; // too much change, start over
prev = mV;
}
ADCSRA = 0; // ADC off
return mV;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,306 @@
/*------------------------------------------------------------------------
POV IR Supernova Poi sketch. Uses the following Adafruit parts
(X2 for two poi):
- Teensy 3.2 (required - NOT compatible w/AVR-based boards)
- 2200 mAh Lithium Ion Battery https://www.adafruit.com/product/1781
- LiPoly Backpack https://www.adafruit.com/product/2124
- 144 LED/m DotStar strip (#2328 or #2329)
(ONE METER is enough for TWO poi)
- Infrared Sensor: https://www.adafruit.com/product/157
- Mini Remote Control: https://www.adafruit.com/product/389
(only one remote is required for multiple poi)
Needs Adafruit_DotStar library: github.com/adafruit/Adafruit_DotStar
Also, uses version of IRremote library from the Teensyduino installer,
the stock IRremote lib will NOT work here!
This is based on the LED poi code (also included in the repository),
but AVR-specific code has been stripped out for brevity, since these
mega-poi pretty much require a Teensy 3.X.
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
MIT license, all text above must be included in any redistribution.
See 'COPYING' file for additional notes.
------------------------------------------------------------------------*/
#include <Arduino.h>
#include <Adafruit_DotStar.h>
#include <avr/power.h>
#include <avr/sleep.h>
#include <IRremote.h>
#include <SPI.h>
typedef uint16_t line_t;
// CONFIGURABLE STUFF ------------------------------------------------------
#include "graphics.h" // Graphics data is contained in this header file.
// It's generated using the 'convert.py' Python script. Various image
// formats are supported, trading off color fidelity for PROGMEM space.
// Handles 1-, 4- and 8-bit-per-pixel palette-based images, plus 24-bit
// truecolor. 1- and 4-bit palettes can be altered in RAM while running
// to provide additional colors, but be mindful of peak & average current
// draw if you do that! Power limiting is normally done in convert.py
// (keeps this code relatively small & fast).
// Ideally you use hardware SPI as it's much faster, though limited to
// specific pins. If you really need to bitbang DotStar data & clock on
// different pins, optionally define those here:
//#define LED_DATA_PIN 0
//#define LED_CLOCK_PIN 1
// Empty and full thresholds (millivolts) used for battery level display:
#define BATT_MIN_MV 3350 // Some headroom over battery cutoff near 2.9V
#define BATT_MAX_MV 4000 // And little below fresh-charged battery near 4.1V
boolean autoCycle = true; // Set to true to cycle images by default
uint32_t CYCLE_TIME = 12; // Time, in seconds, between auto-cycle images
int RECV_PIN = 5;
IRrecv irrecv(RECV_PIN);
decode_results results;
// Adafruit IR Remote Codes:
// Button Code Button Code
// ----------- ------ ------ -----
// VOL-: 0xFD00FF 0/10+: 0xFD30CF
// Play/Pause: 0xFD807F 1: 0xFD08F7
// VOL+: 0xFD40BF 2: 0xFD8877
// SETUP: 0xFD20DF 3: 0xFD48B7
// STOP/MODE: 0xFD609F 4: 0xFD28D7
// UP: 0xFDA05F 5: 0xFDA857
// DOWN: 0xFDB04F 6: 0xFD6897
// LEFT: 0xFD10EF 7: 0xFD18E7
// RIGHT: 0xFD50AF 8: 0xFD9867
// ENTER/SAVE: 0xFD906F 9: 0xFD58A7
// Back: 0xFD708F
#define BTN_BRIGHT_UP 0xFD40BF
#define BTN_BRIGHT_DOWN 0xFD00FF
#define BTN_RESTART 0xFD807F
#define BTN_BATTERY 0xFD20DF
#define BTN_FASTER 0xFD805F
#define BTN_SLOWER 0xFDB04F
#define BTN_OFF 0xFD609F
#define BTN_PATTERN_PREV 0xFD10EF
#define BTN_PATTERN_NEXT 0xFD50AF
#define BTN_AUTOPLAY 0XFD906F
#define BTN_NONE -1
// -------------------------------------------------------------------------
#if defined(LED_DATA_PIN) && defined(LED_CLOCK_PIN)
// Older DotStar LEDs use GBR order. If colors are wrong, edit here.
Adafruit_DotStar strip = Adafruit_DotStar(NUM_LEDS,
LED_DATA_PIN, LED_CLOCK_PIN, DOTSTAR_BGR);
#else
Adafruit_DotStar strip = Adafruit_DotStar(NUM_LEDS, DOTSTAR_BGR);
#endif
void imageInit(void),
IRinterrupt(void);
uint16_t readVoltage(void);
void setup() {
strip.begin(); // Allocate DotStar buffer, init SPI
strip.clear(); // Make sure strip is clear
strip.show(); // before measuring battery
imageInit(); // Initialize pointers for default image
irrecv.enableIRIn(); // Start the receiver
}
// GLOBAL STATE STUFF ------------------------------------------------------
uint32_t lastImageTime = 0L, // Time of last image change
lastLineTime = 0L;
uint8_t imageNumber = 0, // Current image being displayed
imageType, // Image type: PALETTE[1,4,8] or TRUECOLOR
*imagePalette, // -> palette data in PROGMEM
*imagePixels, // -> pixel data in PROGMEM
palette[16][3]; // RAM-based color table for 1- or 4-bit images
line_t imageLines, // Number of lines in active image
imageLine; // Current line number in image
volatile uint16_t irCode = BTN_NONE; // Last valid IR code received
const uint8_t PROGMEM brightness[] = { 15, 31, 63, 127, 255 };
uint8_t bLevel = sizeof(brightness) - 1;
// Microseconds per line for various speed settings
const uint16_t PROGMEM lineTable[] = { // 375 * 2^(n/3)
1000000L / 375, // 375 lines/sec = slowest
1000000L / 472,
1000000L / 595,
1000000L / 750, // 750 lines/sec = mid
1000000L / 945,
1000000L / 1191,
1000000L / 1500 // 1500 lines/sec = fastest
};
uint8_t lineIntervalIndex = 3;
uint16_t lineInterval = 1000000L / 750;
void imageInit() { // Initialize global image state for current imageNumber
imageType = images[imageNumber].type;
imageLines = images[imageNumber].lines;
imageLine = 0;
imagePalette = (uint8_t *)images[imageNumber].palette;
imagePixels = (uint8_t *)images[imageNumber].pixels;
// 1- and 4-bit images have their color palette loaded into RAM both for
// faster access and to allow dynamic color changing. Not done w/8-bit
// because that would require inordinate RAM (328P could handle it, but
// I'd rather keep the RAM free for other features in the future).
if(imageType == PALETTE1) memcpy_P(palette, imagePalette, 2 * 3);
else if(imageType == PALETTE4) memcpy_P(palette, imagePalette, 16 * 3);
lastImageTime = millis(); // Save time of image init for next auto-cycle
}
void nextImage(void) {
if(++imageNumber >= NUM_IMAGES) imageNumber = 0;
imageInit();
}
void prevImage(void) {
imageNumber = imageNumber ? imageNumber - 1 : NUM_IMAGES - 1;
imageInit();
}
// MAIN LOOP ---------------------------------------------------------------
void loop() {
uint32_t t = millis(); // Current time, milliseconds
if(autoCycle) {
if((t - lastImageTime) >= (CYCLE_TIME * 1000L)) nextImage();
// CPU clocks vary slightly; multiple poi won't stay in perfect sync.
// Keep this in mind when using auto-cycle mode, you may want to cull
// the image selection to avoid unintentional regrettable combinations.
}
// Transfer one scanline from pixel data to LED strip:
switch(imageType) {
case PALETTE1: { // 1-bit (2 color) palette-based image
uint8_t pixelNum = 0, byteNum, bitNum, pixels, idx,
*ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS / 8];
for(byteNum = NUM_LEDS/8; byteNum--; ) { // Always padded to next byte
pixels = *ptr++; // 8 pixels of data (pixel 0 = LSB)
for(bitNum = 8; bitNum--; pixels >>= 1) {
idx = pixels & 1; // Color table index for pixel (0 or 1)
strip.setPixelColor(pixelNum++,
palette[idx][0], palette[idx][1], palette[idx][2]);
}
}
break;
}
case PALETTE4: { // 4-bit (16 color) palette-based image
uint8_t pixelNum, p1, p2,
*ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS / 2];
for(pixelNum = 0; pixelNum < NUM_LEDS; ) {
p2 = *ptr++; // Data for two pixels...
p1 = p2 >> 4; // Shift down 4 bits for first pixel
p2 &= 0x0F; // Mask out low 4 bits for second pixel
strip.setPixelColor(pixelNum++,
palette[p1][0], palette[p1][1], palette[p1][2]);
strip.setPixelColor(pixelNum++,
palette[p2][0], palette[p2][1], palette[p2][2]);
}
break;
}
case PALETTE8: { // 8-bit (256 color) PROGMEM-palette-based image
uint16_t o;
uint8_t pixelNum,
*ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS];
for(pixelNum = 0; pixelNum < NUM_LEDS; pixelNum++) {
o = *ptr++ * 3; // Offset into imagePalette
strip.setPixelColor(pixelNum,
imagePalette[o],
imagePalette[o + 1],
imagePalette[o + 2]);
}
break;
}
case TRUECOLOR: { // 24-bit ('truecolor') image (no palette)
uint8_t pixelNum, r, g, b,
*ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS * 3];
for(pixelNum = 0; pixelNum < NUM_LEDS; pixelNum++) {
r = *ptr++;
g = *ptr++;
b = *ptr++;
strip.setPixelColor(pixelNum, r, g, b);
}
break;
}
}
if(++imageLine >= imageLines) imageLine = 0; // Next scanline, wrap around
IRinterrupt();
while(((t = micros()) - lastLineTime) < lineInterval) {
if(results.value != BTN_NONE) {
if(!strip.getBrightness()) { // If strip is off...
// Set brightness to last level
strip.setBrightness(brightness[bLevel]);
// and ignore button press (don't fall through)
// effectively, first press is 'wake'
} else {
switch(results.value) {
case BTN_BRIGHT_UP:
if(bLevel < (sizeof(brightness) - 1))
strip.setBrightness(brightness[++bLevel]);
break;
case BTN_BRIGHT_DOWN:
if(bLevel)
strip.setBrightness(brightness[--bLevel]);
break;
case BTN_FASTER:
CYCLE_TIME++;
if(lineIntervalIndex < (sizeof(lineTable) / sizeof(lineTable[0]) - 1))
lineInterval = lineTable[++lineIntervalIndex];
break;
case BTN_SLOWER:
if(CYCLE_TIME > 0) CYCLE_TIME--;
if(lineIntervalIndex)
lineInterval = lineTable[--lineIntervalIndex];
break;
case BTN_RESTART:
imageNumber = 0;
imageInit();
break;
case BTN_OFF:
strip.setBrightness(0);
break;
case BTN_PATTERN_PREV:
prevImage();
break;
case BTN_PATTERN_NEXT:
nextImage();
break;
case BTN_AUTOPLAY:
autoCycle = !autoCycle;
break;
}
}
results.value = BTN_NONE;
}
}
strip.show(); // Refresh LEDs
lastLineTime = t;
}
void IRinterrupt() {
if (irrecv.decode(&results)) {
Serial.println(results.value, HEX);
irrecv.resume(); // Receive the next value
}
}