182 lines
5.0 KiB
C++
182 lines
5.0 KiB
C++
/*
|
|
hackspacecon wand firmware
|
|
|
|
initially written by true
|
|
rgb programs and further hacking by 000000widow
|
|
|
|
|
|
operation workflow:
|
|
|
|
- at initial reset (battery inserted), set up GPIO and peripherals. this
|
|
includes setting up button wake interrupt. sleep MCU into standby mode.
|
|
|
|
- when button state changes, MCU will wake up and process the interrupt.
|
|
if button is pushed (btn gpio is low), prepare to run next RGB program.
|
|
|
|
- rgb program setup involves turning on power to the LEDs and enabling
|
|
TCB0 periodic timer to interrupt every ~61Hz. this timer interrupt
|
|
does not handle the rgb program, but will wake the CPU which resumes
|
|
processing in the loop() function.
|
|
|
|
- call the rgb program with the `init` parameter set to 1. idle the CPU.
|
|
|
|
- rgb prog timer will interrupt every ~61Hz. this will wake the CPU and
|
|
send the previously rendered LED data, then call the rgb prog function.
|
|
afterward, the CPU idles. process repeats until the rgb prog fn returns 0.
|
|
|
|
- every time the rgb program is run, the rgb data to send is updated.
|
|
the data is not actually sent until next wakeup in less than 16.4ms.
|
|
this allows the functions to have variable processing time, but the
|
|
output to have a consistent ~61Hz (~16.4ms) timing.
|
|
|
|
- once rgb program has finalized, clean up state, disable LED power, and sleep
|
|
the CPU in standby mode. MCU will wake again once the button is activated.
|
|
|
|
|
|
todo:
|
|
- test the code
|
|
- set the RGB output data in the sample rainbow puke program
|
|
- add more programs
|
|
- support incrementing or randomly selecting the next program on each button push
|
|
- run TCB0 in standby mode to save a little more power
|
|
*/
|
|
|
|
#include <Arduino.h>
|
|
#include <avr/io.h>
|
|
#include <avr/interrupt.h>
|
|
|
|
#include "rgbled.h"
|
|
|
|
|
|
|
|
#include <tinyNeoPixel_Static.h>
|
|
|
|
tinyNeoPixel rgb = tinyNeoPixel(RGB_COUNT, PIN_PA1, NEO_GRB, rgbled);
|
|
|
|
|
|
|
|
enum {
|
|
RGB_PROG_IDLE,
|
|
RGB_PROG_INIT,
|
|
RGB_PROG_RUNNING,
|
|
RGB_PROG_BTN_RELEASE
|
|
};
|
|
|
|
uint8_t run_rgb_program = 0;
|
|
|
|
|
|
|
|
void idle_cpu()
|
|
{
|
|
SLPCTRL.CTRLA = SLPCTRL_SMODE_IDLE_gc | SLPCTRL_SEN_bm;
|
|
__asm("sleep");
|
|
}
|
|
|
|
inline void sleep_cpu()
|
|
{
|
|
SLPCTRL.CTRLA = SLPCTRL_SMODE_STDBY_gc | SLPCTRL_SEN_bm;
|
|
__asm("sleep");
|
|
}
|
|
|
|
|
|
|
|
void setup() {
|
|
// configure PA3 as both edge interrupt input for button
|
|
// note: only PA2 and PA6 support async wakeup and thus we can't check for
|
|
// falling edge on PA3. we're not using PA6 for the button as a
|
|
// future program may use SPI MISO to drive the LEDs.
|
|
// because of this, we need to wake up on both edges.
|
|
PORTA.DIRCLR = PIN3_bm;
|
|
PORTA.PIN3CTRL = PORT_PULLUPEN_bm | PORT_ISC_BOTHEDGES_gc;
|
|
|
|
// configure other hardware pins as appropriate
|
|
pinMode(PIN_PA1, INPUT_PULLUP); // unused
|
|
pinMode(PIN_PA7, INPUT_PULLUP); // unused
|
|
|
|
digitalWrite(PIN_PA6, LOW);
|
|
pinMode(PIN_PA6, OUTPUT); // LED boost regulator enable
|
|
|
|
digitalWrite(PIN_PA2, LOW);
|
|
pinMode(PIN_PA2, OUTPUT); // LED data
|
|
|
|
// set up the RGB ~61Hz periodic timer
|
|
conf_rgb_timer();
|
|
|
|
// enable global interrupts (though they may already be enabled? fuck if I know)
|
|
sei();
|
|
}
|
|
|
|
void loop() {
|
|
switch (run_rgb_program) {
|
|
case RGB_PROG_INIT: { // just started running a program
|
|
digitalWrite(PIN_PA6, HIGH); // enable LED power supply,
|
|
delay(10); // wait a moment for LEDs to stabilize,
|
|
|
|
rgb_program[0](1); // initialize the program,
|
|
run_rgb_program++; // and set to running mode.
|
|
|
|
enable_rgb_timer(); // then start the RGB program timebase.
|
|
|
|
idle_cpu(); // we can idle CPU after running the program
|
|
|
|
break;
|
|
}
|
|
|
|
case RGB_PROG_RUNNING: { // continuing to run a program
|
|
// send updates to the led
|
|
rgb.show();
|
|
|
|
// then process the next program frame
|
|
if (!rgb_program[0](0)) {
|
|
// until the program says it's done
|
|
run_rgb_program = RGB_PROG_IDLE;
|
|
break;
|
|
}
|
|
|
|
idle_cpu(); // we can idle CPU after running the program
|
|
|
|
break;
|
|
}
|
|
|
|
case RGB_PROG_BTN_RELEASE: { // button released wakes MCU, but we're still running a program
|
|
// skip processing and go back to stage 2
|
|
run_rgb_program = RGB_PROG_RUNNING;
|
|
|
|
idle_cpu(); // we can idle the CPU after doing nothing
|
|
|
|
break;
|
|
}
|
|
|
|
default: { // no longer running a program
|
|
disable_rgb_timer(); // disable RGB program timer,
|
|
digitalWrite(PIN_PA6, LOW); // disable LED power supply,
|
|
run_rgb_program = RGB_PROG_IDLE; // and clear run_rgb_program.
|
|
|
|
// finally, go to sleep in standby mode
|
|
sleep_cpu();
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// button interrupt
|
|
ISR(PORTA_PORT_vect)
|
|
{
|
|
// reset the INTFLAGS - necessary on this series
|
|
uint8_t intflags = PORTA.INTFLAGS;
|
|
PORTA.INTFLAGS = intflags;
|
|
|
|
// was our pin changed?
|
|
if (intflags & PIN3_bm) {
|
|
// is the pin low?
|
|
if (!digitalRead(PIN_PA3)) {
|
|
// start running a program if one isn't running already
|
|
if (!run_rgb_program) run_rgb_program = 1;
|
|
} else if (run_rgb_program == 2) {
|
|
// if we're running a program when the button is released (likely),
|
|
// then skip this interrupt
|
|
run_rgb_program++;
|
|
}
|
|
}
|
|
} |