Mostly functional ATTINY monitor
This commit is contained in:
45
firmware/lowpower_mcu/src/AttinyDebounce.cpp
Normal file
45
firmware/lowpower_mcu/src/AttinyDebounce.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "AttinyDebounce.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
AttinyDebounce::AttinyDebounce(unsigned int index,
|
||||
unsigned long debounceMillis,
|
||||
void (* const callback)(int),
|
||||
bool assertValue) :
|
||||
index(index),
|
||||
debounceMillis(debounceMillis),
|
||||
callback(callback),
|
||||
assertValue(assertValue),
|
||||
startAssertingTime(0),
|
||||
state(IDLE)
|
||||
{}
|
||||
|
||||
void AttinyDebounce::update(unsigned long millisNow) {
|
||||
bool assertedNow = ((PINB >> index) & 1) == assertValue;
|
||||
switch (state) {
|
||||
case IDLE:
|
||||
if (assertedNow) {
|
||||
state = DEBOUNCING;
|
||||
startAssertingTime = millisNow;
|
||||
}
|
||||
break;
|
||||
case DEBOUNCING:
|
||||
if (!assertedNow) {
|
||||
state = IDLE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (millisNow > startAssertingTime + debounceMillis) {
|
||||
state = TRIGGERED;
|
||||
callback(index);
|
||||
}
|
||||
|
||||
break;
|
||||
case TRIGGERED:
|
||||
// TODO: Debounce this part too
|
||||
if (!assertedNow) {
|
||||
state = IDLE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
35
firmware/lowpower_mcu/src/AttinyDebounce.h
Normal file
35
firmware/lowpower_mcu/src/AttinyDebounce.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef __ATTINYDEBOUNCE_H_
|
||||
#define __ATTINYDEBOUNCE_H_
|
||||
|
||||
#pragma once
|
||||
|
||||
class AttinyDebounce {
|
||||
|
||||
public:
|
||||
AttinyDebounce(unsigned int index,
|
||||
unsigned long debounceMillis,
|
||||
void (* const callback)(int index),
|
||||
bool assertValue);
|
||||
|
||||
void update(unsigned long millisNow);
|
||||
|
||||
|
||||
private:
|
||||
enum State {
|
||||
IDLE,
|
||||
DEBOUNCING,
|
||||
TRIGGERED,
|
||||
};
|
||||
|
||||
/* Maybe abstract out the HW specifcs (index, PORTB reads) if it seems useful */
|
||||
const unsigned int index;
|
||||
const unsigned long debounceMillis;
|
||||
void (* const callback)(int index);
|
||||
const bool assertValue;
|
||||
|
||||
unsigned long startAssertingTime;
|
||||
enum State state;
|
||||
};
|
||||
|
||||
|
||||
#endif // __ATTINYDEBOUNCE_H_
|
||||
209
firmware/lowpower_mcu/src/main.cpp
Normal file
209
firmware/lowpower_mcu/src/main.cpp
Normal file
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* This code enables an ATTINY to act as a low-power monitor for the ESP8266
|
||||
* "main" MCU. It sleeps the ATTINY while waiting for a state change on some
|
||||
* input pins, wakes the ESP8266 with a reset pulse, and becomes an I2C device
|
||||
* which can return the source of the wakeup signal.
|
||||
*
|
||||
* Specifically, this is used to monitor for:
|
||||
* - Short button press wakeups
|
||||
* - Long button press wakeups
|
||||
* - RTC wakeups (sourced from the ESP8266, which lacks state to know they occured)
|
||||
* */
|
||||
|
||||
#include <Wire.h>
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <avr/interrupt.h>
|
||||
#include <avr/sleep.h>
|
||||
|
||||
#include "AttinyDebounce.h"
|
||||
|
||||
|
||||
// Output pins
|
||||
constexpr uint8_t MAIN_MCU_NRESET_PIN = 3;
|
||||
constexpr uint8_t SDA_PIN = 0;
|
||||
constexpr uint8_t SCL_PIN = 2;
|
||||
|
||||
// Wake source pins
|
||||
constexpr uint8_t BUTTON_PIN = 1; // Button press
|
||||
constexpr uint8_t RTC_PIN = 4; // RTC alarm (currently, from the ESP8266 RTC)
|
||||
constexpr uint8_t AUX_PIN = 5; // Another wakeup source. May be used for other sensors in the future.
|
||||
|
||||
constexpr uint8_t DEBUG_LED = RTC_PIN;
|
||||
|
||||
constexpr unsigned long CMD_TIMEOUT_MILLIS = 1000; // After this period of with no commands, the device will sleep
|
||||
constexpr unsigned long WAKE_PULSE_MILLIS = 100; // Length of pulse sent to wake the main MCU
|
||||
constexpr unsigned long LONG_PRESS_MILLIS = 5000;
|
||||
constexpr unsigned long I2C_WATCHDOG_PERIOD_MILLIS = 2000; // If we don't get a request from the Main MCU in this time, just go to sleep.
|
||||
constexpr unsigned long DEBOUNCE_MILLIS = 100;
|
||||
|
||||
constexpr uint8_t I2C_DEVICE_ADDR = 0x4F; // Chosen arbitrarily
|
||||
|
||||
static void buttonCallback(int);
|
||||
|
||||
AttinyDebounce ButtonDebounce(BUTTON_PIN, DEBOUNCE_MILLIS, buttonCallback, LOW);
|
||||
|
||||
// Commands sent from the main MCU
|
||||
enum class I2cReq : uint8_t {
|
||||
// Request for info
|
||||
READ_WAKE_SRC = 0x01, // Request the source of the wakeup
|
||||
|
||||
// Commands
|
||||
CMD_SLEEP = 0x81,
|
||||
};
|
||||
|
||||
enum class WakeSource : uint8_t {
|
||||
BUTTON_SHORT = 0,
|
||||
BUTTON_LONG = 1,
|
||||
// BUTTON_DOUBLE = 2,
|
||||
// BUTTON_TRIPLE = 3,
|
||||
// BUTTON_QUADRUPLE = 4,
|
||||
// BUTTON_MANY = 5,
|
||||
RTC = 10,
|
||||
AUX = 11,
|
||||
UNRESOLVED = 0xFE,
|
||||
NONE = 0xFF
|
||||
};
|
||||
|
||||
enum class AppState : uint8_t {
|
||||
SLEEP,
|
||||
WATCHING_INPUT,
|
||||
START_WAKE_MAIN_MCU,
|
||||
WAKING_MAIN_MCU,
|
||||
WAITING_FOR_I2C,
|
||||
};
|
||||
|
||||
static volatile AppState appState;
|
||||
static unsigned long startWakeMillis, i2cWatchDogTime;
|
||||
static I2cReq i2c_cmd_byte;
|
||||
static volatile WakeSource wakeSource;
|
||||
static volatile unsigned long endButtonMillis;
|
||||
|
||||
static void i2cReceiveHook(int numBytes) {
|
||||
while (numBytes > 0) {
|
||||
i2c_cmd_byte = static_cast<I2cReq>(Wire.read());
|
||||
}
|
||||
}
|
||||
|
||||
static void i2cRequestHook() {
|
||||
switch (i2c_cmd_byte) {
|
||||
case I2cReq::READ_WAKE_SRC:
|
||||
Wire.write(static_cast<uint8_t>(wakeSource));
|
||||
break;
|
||||
case I2cReq::CMD_SLEEP:
|
||||
appState = AppState::SLEEP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void enablePinChangeInterrupt() {
|
||||
GIMSK |= _BV(PCIE);
|
||||
PCMSK |= _BV(BUTTON_PIN);
|
||||
sei();
|
||||
}
|
||||
|
||||
|
||||
static void sleepUntilPinChange() {
|
||||
enablePinChangeInterrupt();
|
||||
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
||||
sleep_enable();
|
||||
sleep_cpu();
|
||||
sleep_disable();
|
||||
}
|
||||
|
||||
static void resetState() {
|
||||
wakeSource = WakeSource::NONE;
|
||||
startWakeMillis = 0;
|
||||
endButtonMillis = 0;
|
||||
i2cWatchDogTime = 0;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
// Init pin states
|
||||
pinMode(MAIN_MCU_NRESET_PIN, OUTPUT);
|
||||
pinMode(BUTTON_PIN, INPUT);
|
||||
pinMode(RTC_PIN, INPUT);
|
||||
pinMode(AUX_PIN, INPUT);
|
||||
pinMode(DEBUG_LED, OUTPUT);
|
||||
|
||||
// On first run, reset the main MCU to get in sync.
|
||||
digitalWrite(MAIN_MCU_NRESET_PIN, HIGH);
|
||||
delay(WAKE_PULSE_MILLIS);
|
||||
digitalWrite(MAIN_MCU_NRESET_PIN, LOW);
|
||||
|
||||
appState = AppState::SLEEP;
|
||||
|
||||
// Init I2C
|
||||
Wire.begin(I2C_DEVICE_ADDR);
|
||||
Wire.onReceive(i2cReceiveHook);
|
||||
Wire.onRequest(i2cRequestHook);
|
||||
|
||||
enablePinChangeInterrupt();
|
||||
|
||||
ADCSRA &= ~_BV(ADEN); // Disable ADC, save some power
|
||||
|
||||
}
|
||||
|
||||
static void buttonCallback(int index) {
|
||||
wakeSource = WakeSource::BUTTON_SHORT;
|
||||
}
|
||||
|
||||
ISR(PCINT0_vect) {
|
||||
unsigned long millisNow = millis();
|
||||
ButtonDebounce.update(millisNow);
|
||||
}
|
||||
|
||||
|
||||
void loop() {
|
||||
unsigned long millisNow = millis();
|
||||
ButtonDebounce.update(millisNow);
|
||||
|
||||
switch (appState) {
|
||||
case AppState::SLEEP:
|
||||
resetState();
|
||||
//digitalWrite(DEBUG_LED, HIGH);
|
||||
sleepUntilPinChange();
|
||||
//digitalWrite(DEBUG_LED, LOW);
|
||||
appState = AppState::WATCHING_INPUT;
|
||||
break;
|
||||
case AppState::WATCHING_INPUT:
|
||||
if (wakeSource == WakeSource::UNRESOLVED) {
|
||||
// Keep waiting. For example, waiting to see if long or short button press.
|
||||
// TODO: Not yet implemented
|
||||
} else if (wakeSource == WakeSource::NONE) {
|
||||
// appState = AppState::SLEEP;
|
||||
} else {
|
||||
// We know the wakeup source. Time to inform the main MCU.
|
||||
appState = AppState::START_WAKE_MAIN_MCU;
|
||||
}
|
||||
break;
|
||||
case AppState::START_WAKE_MAIN_MCU:
|
||||
digitalWrite(MAIN_MCU_NRESET_PIN, HIGH);
|
||||
startWakeMillis = millis();
|
||||
appState = AppState::WAKING_MAIN_MCU;
|
||||
break;
|
||||
case AppState::WAKING_MAIN_MCU:
|
||||
if (millis() > startWakeMillis + WAKE_PULSE_MILLIS) {
|
||||
appState = AppState::WAITING_FOR_I2C;
|
||||
digitalWrite(MAIN_MCU_NRESET_PIN, LOW);
|
||||
}
|
||||
break;
|
||||
case AppState::WAITING_FOR_I2C:
|
||||
if (i2cWatchDogTime == 0) {
|
||||
i2cWatchDogTime = millis();
|
||||
}
|
||||
|
||||
if (i2cWatchDogTime + I2C_WATCHDOG_PERIOD_MILLIS < millis()) {
|
||||
appState = AppState::SLEEP;
|
||||
break;
|
||||
}
|
||||
|
||||
if (i2c_cmd_byte == I2cReq::CMD_SLEEP) {
|
||||
appState = AppState::SLEEP;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
54
firmware/lowpower_mcu/src/test.cpp
Normal file
54
firmware/lowpower_mcu/src/test.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include <Arduino.h>
|
||||
#include <avr/interrupt.h>
|
||||
#include <avr/sleep.h>
|
||||
|
||||
#define LED 4
|
||||
#define BUTTON 0
|
||||
|
||||
void setup() {
|
||||
pinMode(LED, OUTPUT);
|
||||
digitalWrite(LED, HIGH);
|
||||
pinMode(BUTTON, INPUT);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
digitalWrite(LED, HIGH);
|
||||
delay(200);
|
||||
digitalWrite(LED, LOW);
|
||||
delay(200);
|
||||
}
|
||||
|
||||
// Enable pin change interrupt
|
||||
GIMSK |= _BV(PCIE);
|
||||
PCMSK |= _BV(PCINT0);
|
||||
sei();
|
||||
|
||||
}
|
||||
|
||||
static void doSleep() {
|
||||
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
||||
sleep_enable();
|
||||
sleep_cpu();
|
||||
sleep_disable();
|
||||
}
|
||||
|
||||
volatile bool isrFlag = false;
|
||||
ISR(PCINT0_vect) {
|
||||
isrFlag = true;
|
||||
}
|
||||
|
||||
|
||||
volatile bool val;
|
||||
|
||||
void loop() {
|
||||
static unsigned long ts = millis();
|
||||
if (millis() > ts + 500) {
|
||||
ts = millis();
|
||||
PORTB ^= _BV(LED);
|
||||
}
|
||||
|
||||
if (isrFlag) {
|
||||
PORTB &= _BV(LED);
|
||||
doSleep();
|
||||
isrFlag = false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user