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