hacks/christmas/christmas.cc

189 lines
3.9 KiB
C++

/// Christmas star lights.
///
/// Mainly white, but transitions white -> colour -> white and cycles
/// through colours.
///
/// Michael Hope <michaelh@juju.net.nz> 2013
///
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
class HAL
{
public:
enum Clock {
Timer0Prescaler = 64,
Timer0Rate = F_CPU / Timer0Prescaler / 256,
};
enum class Pin {
};
};
class Christmas
{
public:
static void init();
static void run();
static volatile uint8_t ticks;
private:
/// A single point in the cycle.
struct Point
{
/// How long to stay at this point.
uint8_t seconds;
uint8_t r, g, b;
};
static void delay(uint16_t count);
static uint8_t correct(uint8_t v);
static uint8_t project(uint8_t from, uint8_t to, uint8_t at, uint8_t limit);
static const uint8_t ciel8_[];
static const Point points_[];
};
volatile uint8_t Christmas::ticks;
// Output compare comes out of:
// OC0A (PB0)
// OC0B (PB1)
// OC1B (PB4)
void Christmas::init()
{
DDRB = _BV(0) | _BV(1) | _BV(4);
// Clear the output on compare.
TCCR0A = 0
| (3 << COM0A0)
| (3 << COM0B0)
// Fast PWM.
| (3 << WGM00)
;
static_assert(HAL::Timer0Prescaler == 64, "Update the prescaler below.");
TCCR0B = 0
// Prescale by 64.
| (3 << CS00)
;
TIMSK = _BV(TOIE0);
TCCR1 = 0
| (3 << CS10)
;
GTCCR = 0
| (1 << PWM1B)
| (2 << COM1B0)
;
}
/// Convert an intensity to PWM.
const uint8_t Christmas::ciel8_[] = {
0, 0, 0, 0, 0, 1, 1, 1,
1, 1, 1, 2, 2, 2, 2, 3,
3, 3, 4, 4, 5, 5, 6, 7,
7, 8, 9, 10, 11, 12, 13, 14,
16, 17, 19, 21, 23, 25, 27, 30,
33, 36, 39, 43, 47, 51, 56, 61,
67, 73, 80, 88, 96, 105, 115, 125,
137, 149, 163, 178, 195, 213, 233, 255,
};
#define MAX 63
/// All points to cycle through.
const Christmas::Point Christmas::points_[] =
{
{ 60, MAX, MAX, MAX }, // White
{ 10, 0, 0, MAX }, // Blue
{ 60, MAX, MAX, MAX },
{ 10, MAX, 0, MAX }, // Purple
{ 60, MAX, MAX, MAX },
{ 10, MAX, 0, 0 }, // Red
{ 60, MAX, MAX, MAX },
{ 10, MAX, MAX, 0 }, // Yellow
{ 60, MAX, MAX, MAX },
{ 10, 0, MAX, 0 }, // Green
{ 60, MAX, MAX, MAX },
{ 10, 0, MAX, MAX }, // Cyan
// End of list.
{ 0 },
};
/// Convert intensity to PWM.
uint8_t Christmas::correct(uint8_t level)
{
return ciel8_[level];
}
/// Mix the from and to levels.
uint8_t Christmas::project(uint8_t from, uint8_t to, uint8_t at, uint8_t limit)
{
int delta = to - from;
delta = delta * at / limit;
return (uint8_t)(from + delta);
}
/// Delay the given number of ticks.
void Christmas::delay(uint16_t count)
{
uint8_t now = ticks;
while (count != 0) {
while (now == ticks) {
sleep_mode();
}
count--;
now++;
}
}
void Christmas::run()
{
sei();
const Point* plast = points_ + 0;
for (;;) {
for (const Point* ppoint = points_; ppoint->seconds != 0; ppoint++) {
const int steps = 64;
// Transition between colours.
for (int i = 0; i < steps; i++) {
OCR0A = correct(project(plast->r, ppoint->r, i, steps));
OCR0B = correct(project(plast->g, ppoint->g, i, steps));
OCR1B = 255 - correct(project(plast->b, ppoint->b, i, steps));
// Change over 10s.
delay(HAL::Clock::Timer0Rate * 10 / steps);
}
plast = ppoint;
// Delay on this colour.
delay(HAL::Clock::Timer0Rate * ppoint->seconds);
}
}
}
ISR(TIMER0_OVF_vect)
{
Christmas::ticks++;
}
int main()
{
Christmas::init();
Christmas::run();
return 0;
}