Mostly functional ATTINY monitor
This commit is contained in:
1
firmware/lowpower_mcu/.gitignore
vendored
Normal file
1
firmware/lowpower_mcu/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.pio
|
||||
39
firmware/lowpower_mcu/include/README
Normal file
39
firmware/lowpower_mcu/include/README
Normal file
@@ -0,0 +1,39 @@
|
||||
|
||||
This directory is intended for project header files.
|
||||
|
||||
A header file is a file containing C declarations and macro definitions
|
||||
to be shared between several project source files. You request the use of a
|
||||
header file in your project source file (C, C++, etc) located in `src` folder
|
||||
by including it, with the C preprocessing directive `#include'.
|
||||
|
||||
```src/main.c
|
||||
|
||||
#include "header.h"
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Including a header file produces the same results as copying the header file
|
||||
into each source file that needs it. Such copying would be time-consuming
|
||||
and error-prone. With a header file, the related declarations appear
|
||||
in only one place. If they need to be changed, they can be changed in one
|
||||
place, and programs that include the header file will automatically use the
|
||||
new version when next recompiled. The header file eliminates the labor of
|
||||
finding and changing all the copies as well as the risk that a failure to
|
||||
find one copy will result in inconsistencies within a program.
|
||||
|
||||
In C, the usual convention is to give header files names that end with `.h'.
|
||||
It is most portable to use only letters, digits, dashes, and underscores in
|
||||
header file names, and at most one dot.
|
||||
|
||||
Read more about using header files in official GCC documentation:
|
||||
|
||||
* Include Syntax
|
||||
* Include Operation
|
||||
* Once-Only Headers
|
||||
* Computed Includes
|
||||
|
||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||
46
firmware/lowpower_mcu/lib/README
Normal file
46
firmware/lowpower_mcu/lib/README
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
This directory is intended for project specific (private) libraries.
|
||||
PlatformIO will compile them to static libraries and link into executable file.
|
||||
|
||||
The source code of each library should be placed in a an own separate directory
|
||||
("lib/your_library_name/[here are source files]").
|
||||
|
||||
For example, see a structure of the following two libraries `Foo` and `Bar`:
|
||||
|
||||
|--lib
|
||||
| |
|
||||
| |--Bar
|
||||
| | |--docs
|
||||
| | |--examples
|
||||
| | |--src
|
||||
| | |- Bar.c
|
||||
| | |- Bar.h
|
||||
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||
| |
|
||||
| |--Foo
|
||||
| | |- Foo.c
|
||||
| | |- Foo.h
|
||||
| |
|
||||
| |- README --> THIS FILE
|
||||
|
|
||||
|- platformio.ini
|
||||
|--src
|
||||
|- main.c
|
||||
|
||||
and a contents of `src/main.c`:
|
||||
```
|
||||
#include <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
PlatformIO Library Dependency Finder will find automatically dependent
|
||||
libraries scanning project source files.
|
||||
|
||||
More information about PlatformIO Library Dependency Finder
|
||||
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||
53
firmware/lowpower_mcu/platformio.ini
Normal file
53
firmware/lowpower_mcu/platformio.ini
Normal file
@@ -0,0 +1,53 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[platformio]
|
||||
default_envs = main
|
||||
|
||||
[env]
|
||||
platform = atmelavr
|
||||
framework = arduino
|
||||
board = attiny85
|
||||
upload_protocol = custom
|
||||
upload_port = /dev/ttyACM1
|
||||
upload_speed = 19200
|
||||
upload_flags =
|
||||
-C
|
||||
$PROJECT_PACKAGES_DIR/tool-avrdude/avrdude.conf
|
||||
-p
|
||||
$BOARD_MCU
|
||||
-P
|
||||
$UPLOAD_PORT
|
||||
-b
|
||||
$UPLOAD_SPEED
|
||||
-c
|
||||
stk500v1
|
||||
upload_command = avrdude $UPLOAD_FLAGS -U flash:w:$SOURCE:i
|
||||
|
||||
[common]
|
||||
;; This is the defaullt src_filter
|
||||
src_filter =
|
||||
+<*>
|
||||
-<.git/>
|
||||
-<.svn/>
|
||||
-<example/>
|
||||
-<examples/>
|
||||
-<test/>
|
||||
-<tests/>
|
||||
|
||||
[env:main]
|
||||
src_filter =
|
||||
${common.src_filter}
|
||||
-<test*>
|
||||
|
||||
[env:test]
|
||||
src_filter =
|
||||
${common.src_filter}
|
||||
-<main*>
|
||||
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;
|
||||
}
|
||||
}
|
||||
11
firmware/lowpower_mcu/test/README
Normal file
11
firmware/lowpower_mcu/test/README
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
This directory is intended for PlatformIO Unit Testing and project tests.
|
||||
|
||||
Unit Testing is a software testing method by which individual units of
|
||||
source code, sets of one or more MCU program modules together with associated
|
||||
control data, usage procedures, and operating procedures, are tested to
|
||||
determine whether they are fit for use. Unit testing finds problems early
|
||||
in the development cycle.
|
||||
|
||||
More information about PlatformIO Unit Testing:
|
||||
- https://docs.platformio.org/page/plus/unit-testing.html
|
||||
Reference in New Issue
Block a user