arduino uses TCA timer to do its things. it doesn't need to do anything while the MCU is in low power sleep. disable TCA whenever entering sleep from user code. note that the prior "working" demo commented using the rgb_run_next sleep loop in the program. was still diagnosing issues with TCB0 interrupt. if there's still issues waking from idle with TCB interrupt, then things will still need to be addressed... this fix is untested.
186 lines
5.2 KiB
C++
186 lines
5.2 KiB
C++
/*
|
|
hackspacecon wand firmware
|
|
|
|
initially written by true
|
|
rgb programs and further hacking by 000000widow
|
|
|
|
----
|
|
|
|
this version was hacked to work with AS7.
|
|
|
|
the resulting binary at time of porting takes an additional
|
|
531 bytes of flash and 8 bytes of SRAM. I do not know why.
|
|
but I suspect it is due to the inability to get -flto working
|
|
properly on libraries. oh well; can figure it out later.
|
|
|
|
|
|
|
|
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 is pushed (pin goes low), MCU will wake up and process the
|
|
button interrupt. 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 directly, 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 program returns 0.
|
|
|
|
- every time the rgb program is run, the rgb data to send can be 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,
|
|
RGB_STOPPING
|
|
};
|
|
|
|
enum {
|
|
PROG_RUN = 0,
|
|
PROG_INIT = 1
|
|
};
|
|
|
|
uint8_t run_rgbprog = RGB_IDLE;
|
|
uint8_t rgbprog_idx = 0;
|
|
|
|
|
|
|
|
void sleep_cpu(uint8_t sleep_type)
|
|
{
|
|
// TCA interrupt is used by arduino timing routines
|
|
// needs to be disabled while asleep to save power
|
|
TCA0.SPLIT.CTRLA &= ~TCA_SPLIT_ENABLE_bm;
|
|
|
|
// configure for idle mode, then idle
|
|
SLPCTRL.CTRLA = sleep_type | 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 should already be enabled?)
|
|
sei();
|
|
}
|
|
|
|
// mcu program loop
|
|
void loop() {
|
|
// if something other than TCB0 or PA2 going low wakes us, we don't care
|
|
// sleep with prior sleep settings until we're ready to run next rgb prog step
|
|
while (!rgb_run_next) {
|
|
__asm("sleep");
|
|
}
|
|
|
|
// re-enable TCA0 since arduino core uses it
|
|
TCA0.SPLIT.CTRLA |= TCA_SPLIT_ENABLE_bm;
|
|
|
|
// clear run flag for next go
|
|
rgb_run_next = 0;
|
|
|
|
switch (run_rgbprog) {
|
|
case RGB_INIT: { // just started running a program
|
|
digitalWrite(PIN_LED_PWRENA, HIGH); // enable LED power supply,
|
|
delay(15); // 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.
|
|
|
|
sleep_cpu(SLPCTRL_SMODE_IDLE_gc); // 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_STOPPING; // until the program says it's done
|
|
break;
|
|
}
|
|
|
|
sleep_cpu(SLPCTRL_SMODE_IDLE_gc); // we can idle CPU after running the program
|
|
|
|
break;
|
|
}
|
|
|
|
case RGB_STOPPING: {
|
|
rgb.show(); // send final updates to the led
|
|
run_rgbprog = RGB_IDLE;
|
|
|
|
sleep_cpu(SLPCTRL_SMODE_IDLE_gc); // 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(SLPCTRL_SMODE_STDBY_gc); // finally, go to sleep in standby mode
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|