diff --git a/fw/HackSpaceCon/.gitignore b/fw/HackSpaceCon/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/fw/HackSpaceCon/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/fw/HackSpaceCon/src/main.cpp b/fw/HackSpaceCon/src/main.cpp index e9dd2f8..e2eb578 100644 --- a/fw/HackSpaceCon/src/main.cpp +++ b/fw/HackSpaceCon/src/main.cpp @@ -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++; } } } \ No newline at end of file diff --git a/fw/HackSpaceCon/src/rgbled.h b/fw/HackSpaceCon/src/rgbled.h index b0c4e65..c75d2a3 100644 --- a/fw/HackSpaceCon/src/rgbled.h +++ b/fw/HackSpaceCon/src/rgbled.h @@ -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;};