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:
true 2025-04-03 14:06:21 -07:00
parent 5b73fd93d3
commit c1d0fef585
3 changed files with 90 additions and 33 deletions

5
fw/HackSpaceCon/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

View File

@ -1,24 +1,41 @@
/* /*
hackspacecon wand firmware hackspacecon wand firmware
initially written by true initially written by true
rgb programs and further hacking by 000000widow rgb programs and further hacking by 000000widow
operation workflow: 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, - at initial reset (battery inserted), set up GPIO and peripherals. this
prepare to run RGB program. includes setting up button wake interrupt. sleep MCU into standby mode.
- enable the TCB0 periodic timer, which will interrupt every ~61Hz.
- initialize the rgb program. idle the CPU. - when button state changes, MCU will wake up and process the interrupt.
- timer will interrupt every ~61Hz and the program function will be called if button is pushed (btn gpio is low), prepare to run next RGB program.
again, until prog function returns 0.
- run the RGB program. if program returns 0, disable periodic timer, and - rgb program setup involves turning on power to the LEDs and enabling
wait for the next button event to wake us up and run a program again. 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: todo:
- test the code - 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 - add more programs
- support incrementing or randomly selecting the next program on each button push - support incrementing or randomly selecting the next program on each button push
- run TCB0 in standby mode to save a little more power - 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; 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() { 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.DIRCLR = PIN3_bm;
PORTA.PIN3CTRL = PORT_PULLUPEN_bm | PORT_ISC_BOTHEDGES_gc; PORTA.PIN3CTRL = PORT_PULLUPEN_bm | PORT_ISC_BOTHEDGES_gc;
@ -66,7 +108,7 @@ void setup() {
void loop() { void loop() {
switch (run_rgb_program) { switch (run_rgb_program) {
case 1: { // just started running a program case RGB_PROG_INIT: { // just started running a program
digitalWrite(PIN_PA6, HIGH); // enable LED power supply, digitalWrite(PIN_PA6, HIGH); // enable LED power supply,
delay(10); // wait a moment for LEDs to stabilize, delay(10); // wait a moment for LEDs to stabilize,
@ -75,37 +117,43 @@ void loop() {
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 idle_cpu(); // we can idle CPU after running the program
SLPCTRL.CTRLA = SLPCTRL_SMODE_IDLE_gc | SLPCTRL_SEN_bm;
__asm("sleep");
break; break;
} }
case 2: { // continuing to run a program
case RGB_PROG_RUNNING: { // continuing to run a program
// send updates to the led // send updates to the led
rgb.show(); rgb.show();
// then process the next program frame // then process the next program frame
if (!rgb_program[0](0)) { if (!rgb_program[0](0)) {
// until the program says it's done // until the program says it's done
run_rgb_program = 0; run_rgb_program = RGB_PROG_IDLE;
break; break;
} }
// we can idle the CPU after running the program idle_cpu(); // we can idle CPU after running the program
SLPCTRL.CTRLA = SLPCTRL_SMODE_IDLE_gc | SLPCTRL_SEN_bm;
__asm("sleep");
break; 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 default: { // no longer running a program
disable_rgb_timer(); // disable RGB program timer, disable_rgb_timer(); // disable RGB program timer,
digitalWrite(PIN_PA6, LOW); // disable LED power supply, digitalWrite(PIN_PA6, LOW); // disable LED power supply,
run_rgb_program = 0; // and clear run_rgb_program. run_rgb_program = RGB_PROG_IDLE; // and clear run_rgb_program.
// finally, go to sleep in standby mode // finally, go to sleep in standby mode
SLPCTRL.CTRLA = SLPCTRL_SMODE_STDBY_gc | SLPCTRL_SEN_bm; sleep_cpu();
__asm("sleep");
break; break;
} }
@ -125,6 +173,10 @@ ISR(PORTA_PORT_vect)
if (!digitalRead(PIN_PA3)) { if (!digitalRead(PIN_PA3)) {
// start running a program if one isn't running already // start running a program if one isn't running already
if (!run_rgb_program) run_rgb_program = 1; 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++;
} }
} }
} }

View File

@ -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;}; #define disable_rgb_timer() {TCB0.CTRLA &= ~1;};