expanded comments, minor cleanups, minor fixes
fixed inconsistent timing when re-enabling TCB0 timer fixed sleep wake on button release causes a fast frame bug
This commit is contained in:
parent
5b73fd93d3
commit
c1d0fef585
|
@ -0,0 +1,5 @@
|
|||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
|
@ -1,24 +1,41 @@
|
|||
/*
|
||||
|
||||
hackspacecon wand firmware
|
||||
|
||||
initially written by true
|
||||
rgb programs and further hacking by 000000widow
|
||||
|
||||
|
||||
operation workflow:
|
||||
- at poweron, set up GPIO and used peripherals, then go into standby mode.
|
||||
- when button interrupt happens, wake up and process. if button is pushed,
|
||||
prepare to run RGB program.
|
||||
- enable the TCB0 periodic timer, which will interrupt every ~61Hz.
|
||||
- initialize the rgb program. idle the CPU.
|
||||
- timer will interrupt every ~61Hz and the program function will be called
|
||||
again, until prog function returns 0.
|
||||
- run the RGB program. if program returns 0, disable periodic timer, and
|
||||
wait for the next button event to wake us up and run a program again.
|
||||
|
||||
- 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
|
||||
- actually process the RGB data in the sample rainbow puke program
|
||||
- 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
|
||||
|
@ -38,12 +55,37 @@ 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 falling edge interrupt input for button
|
||||
// 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;
|
||||
|
||||
|
@ -66,46 +108,52 @@ void setup() {
|
|||
|
||||
void loop() {
|
||||
switch (run_rgb_program) {
|
||||
case 1: { // just started running a program
|
||||
digitalWrite(PIN_PA6, HIGH); // enable LED power supply,
|
||||
delay(10); // wait a moment for LEDs to stabilize,
|
||||
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.
|
||||
rgb_program[0](1); // initialize the program,
|
||||
run_rgb_program++; // and set to running mode.
|
||||
|
||||
enable_rgb_timer(); // then start the RGB program timebase.
|
||||
enable_rgb_timer(); // then start the RGB program timebase.
|
||||
|
||||
// we can idle the CPU after running the program
|
||||
SLPCTRL.CTRLA = SLPCTRL_SMODE_IDLE_gc | SLPCTRL_SEN_bm;
|
||||
__asm("sleep");
|
||||
idle_cpu(); // we can idle CPU after running the program
|
||||
|
||||
break;
|
||||
}
|
||||
case 2: { // continuing to run a program
|
||||
|
||||
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 = 0;
|
||||
run_rgb_program = RGB_PROG_IDLE;
|
||||
break;
|
||||
}
|
||||
|
||||
// we can idle the CPU after running the program
|
||||
SLPCTRL.CTRLA = SLPCTRL_SMODE_IDLE_gc | SLPCTRL_SEN_bm;
|
||||
__asm("sleep");
|
||||
idle_cpu(); // we can idle CPU after running the program
|
||||
|
||||
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 = 0; // and clear run_rgb_program.
|
||||
|
||||
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
|
||||
SLPCTRL.CTRLA = SLPCTRL_SMODE_STDBY_gc | SLPCTRL_SEN_bm;
|
||||
__asm("sleep");
|
||||
sleep_cpu();
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -125,6 +173,10 @@ ISR(PORTA_PORT_vect)
|
|||
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++;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
|
||||
|
||||
#define enable_rgb_timer() {TCB0.CTRLA |= 1;};
|
||||
#define enable_rgb_timer() {TCB0.CNT = 0; TCB0.CTRLA |= 1;};
|
||||
#define disable_rgb_timer() {TCB0.CTRLA &= ~1;};
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue