Add files via upload
12
Kinetic_POV/README.md
Normal 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.
|
||||
432
Kinetic_POV/bikewheel/bikewheel.ino
Normal 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;
|
||||
}
|
||||
6975
Kinetic_POV/bikewheel/graphics.h
Normal file
BIN
Kinetic_POV/convert/adafruit.gif
Normal file
|
After Width: | Height: | Size: 206 B |
343
Kinetic_POV/convert/convert.py
Normal 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]))"
|
||||
BIN
Kinetic_POV/convert/fire.gif
Normal file
|
After Width: | Height: | Size: 384 B |
BIN
Kinetic_POV/convert/pride.gif
Normal file
|
After Width: | Height: | Size: 235 B |
BIN
Kinetic_POV/convert/spectrum.gif
Normal file
|
After Width: | Height: | Size: 87 B |
BIN
Kinetic_POV/convert/usa.gif
Normal file
|
After Width: | Height: | Size: 100 B |
BIN
Kinetic_POV/convert/usa2.gif
Normal file
|
After Width: | Height: | Size: 127 B |
BIN
Kinetic_POV/convert/wales.gif
Normal file
|
After Width: | Height: | Size: 264 B |
390
Kinetic_POV/dblstaff/dblstaff.ino
Normal 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;
|
||||
}
|
||||
1190
Kinetic_POV/dblstaff/graphics.h
Normal file
334
Kinetic_POV/poi/graphics.h
Normal 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
|
|
@ -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;
|
||||
}
|
||||
7301
Kinetic_POV/supernova_poi/graphics.h
Normal file
306
Kinetic_POV/supernova_poi/supernova_poi.ino
Normal 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
|
||||
}
|
||||
}
|
||||