000000widow-hackspacecon/fw/HackSpaceCon/src/main.cpp

156 lines
4.2 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"
#define PIN_LED_PWRENA PIN_PA6
enum {
RGB_IDLE,
RGB_INIT,
RGB_RUNNING
};
enum {
PROG_RUN = 0,
PROG_INIT = 1
};
uint8_t run_rgbprog = RGB_IDLE;
uint8_t rgbprog_idx = 0;
void idle_cpu()
{
SLPCTRL.CTRLA = SLPCTRL_SMODE_IDLE_gc | SLPCTRL_SEN_bm;
__asm("sleep");
}
void sleep_cpu()
{
SLPCTRL.CTRLA = SLPCTRL_SMODE_STDBY_gc | SLPCTRL_SEN_bm;
__asm("sleep");
}
// mcu init
void setup() {
// configure PA2 as falling edge interrupt for button
// note: only PA2 and PA6 support async wakeup.
// since we're using PA2, we're good to wakeup from a
// falling edge (button pushed) event only.
PORTA.DIRCLR = PIN2_bm;
PORTA.PIN2CTRL = PORT_PULLUPEN_bm | PORT_ISC_FALLING_gc;
// configure other hardware pins as appropriate
pinMode(PIN_PA3, INPUT_PULLUP); // unused, spare pad on board
pinMode(PIN_PA7, INPUT_PULLUP); // unused, voltage passthru
digitalWrite(PIN_PA6, LOW);
pinMode(PIN_PA6, OUTPUT); // LED boost regulator enable
digitalWrite(PIN_PA1, LOW);
pinMode(PIN_PA1, 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();
}
// mcu program loop
void loop() {
switch (run_rgbprog) {
case RGB_INIT: { // just started running a program
digitalWrite(PIN_LED_PWRENA, HIGH); // enable LED power supply,
delay(20); // wait a moment for LEDs to stabilize,
rgbprog_idx++; // select the next program in sequence,
if (rgbprog_idx >= PROG_COUNT) {
rgbprog_idx = 0;
}
rgb_program[rgbprog_idx](PROG_INIT);// initialize the program,
run_rgbprog++; // 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_RUNNING: { // continuing to run a program
rgb.show(); // send updates to the led
// then process the next program frame
if (!rgb_program[rgbprog_idx](PROG_RUN)) {
run_rgbprog = RGB_IDLE; // until the program says it's done
break;
}
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_LED_PWRENA, LOW); // disable LED power supply,
run_rgbprog = RGB_IDLE; // and clear run_rgbprog.
sleep_cpu(); // finally, go to sleep in standby mode
break;
}
}
}