/* 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 #include #include #include "rgbled.h" #include 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++; } } }