Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions src/Scout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,7 @@ void PinoccioScout::setup(const char *sketchName, const char *sketchRevision, in
void PinoccioScout::loop() {
now = SleepHandler::uptime().seconds;

bool canSleep = true;
// TODO: Let other loop functions return some "cansleep" status as well
// TODO: Let other loop functions return some "cansleep" status

PinoccioClass::loop();

Expand Down Expand Up @@ -180,7 +179,8 @@ void PinoccioScout::loop() {
handler.loop();
ModuleHandler::loop();
Backpacks::loop();

SleepHandler::loop();

if(showStatus)
{
Led.setRedValue(Led.getRedValue());
Expand All @@ -189,14 +189,14 @@ void PinoccioScout::loop() {
}

if (sleepPending) {
canSleep = canSleep && !NWK_Busy();

// if remaining <= 0, we won't actually sleep anymore, but still
// call doSleep to run the callback and clean up
if (SleepHandler::scheduledTicksLeft() == 0)
if (SleepHandler::scheduledTicksLeft() == 0){
doSleep(true);
else if (canSleep)
}
else{
doSleep(false);
}
}
}

Expand Down Expand Up @@ -664,12 +664,8 @@ void PinoccioScout::doSleep(bool pastEnd) {
sleepPending = false;

if (!pastEnd) {
NWK_SleepReq();

// TODO: suspend more stuff? Wait for UART byte completion?

SleepHandler::doSleep(true);
NWK_WakeupReq();
}

// TODO: Allow ^C to stop running callbacks like this one
Expand Down
99 changes: 97 additions & 2 deletions src/Shell.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,20 +216,23 @@ static numvar getLastResetCause(void) {
}

static StringBuffer uptimeReportHQ(void) {
StringBuffer report(100);
StringBuffer report(125);
int freeMem = getFreeMemory();

char reset[20];
strncpy_P(reset, Scout.getLastResetCause(), sizeof(reset));
reset[sizeof(reset) - 1] = 0; // ensure termination, strncpy is weird

report.appendSprintf("[%d,[%d,%d,%d,%d],[%ld,%ld,%d,",keyMap("uptime",0),
report.appendSprintf("[%d,[%d,%d,%d,%d,%d],[%ld,%ld,%ld,%d,",
keyMap("uptime",0),
keyMap("total", 0),
keyMap("sleep", 0),
keyMap("meshsleep", 0),
keyMap("random", 0),
keyMap("reset", 0),
SleepHandler::uptime().seconds,
SleepHandler::sleeptime().seconds,
SleepHandler::meshsleeptime().seconds,
(int)random());

report.appendJsonString(reset, true);
Expand Down Expand Up @@ -291,9 +294,48 @@ static numvar uptimeStatus(void) {
appendTime(out, SleepHandler::sleeptime());
speol(out.c_str());

out = F("Global: ");
appendTime(out, SleepHandler::meshtime());
speol(out.c_str());
return true;
}

static numvar uptimeSetOffset(void) {
if (!checkArgs(3, F("usage: uptime.setoffset(seconds, micros, inFuture)"))) {
return 0;
}

uint32_t seconds = getarg(1);
uint32_t us = getarg(2);

Duration d;
d.seconds = seconds;
d.us = us;

bool future = getarg(3);

SleepHandler::setOffsetInFuture(future);
SleepHandler::setOffset(d);

return 1;
}

static numvar uptimeMeshOffsetMicros(void) {
return SleepHandler::getOffset().us;
}

static numvar uptimeMeshOffsetSeconds(void) {
return SleepHandler::getOffset().seconds;
}

static numvar uptimeMeshSleepingMicros(void) {
return SleepHandler::meshsleeptime().us;
}

static numvar uptimeMeshSleepingSeconds(void) {
return SleepHandler::meshsleeptime().seconds;
}

/****************************\
* KEY HANDLERS *
\****************************/
Expand Down Expand Up @@ -477,6 +519,47 @@ static numvar powerWakeupPin(void) {
return 1;
}

static numvar powerGetRadioState(void) {
return SleepHandler::getRadioState();
}

static numvar powerSleepRadio(void) {
SleepHandler::sleepRadio();
return 1;
}

static numvar powerWakeRadio(void) {
SleepHandler::wakeRadio();
return 1;
}

static numvar powerScheduleSleepRadio(void) {
if (!checkArgs(2, F("usage: power.schedulesleepradio(seconds, micros)"))) {
return 0;
}
uint32_t seconds = getarg(1);
uint32_t us = getarg(2);

Duration d;
d.seconds = seconds;
d.us = us;

SleepHandler::scheduleSleepRadio(d);

return 1;
}

static numvar powerSetRadioPeriod(void) {
if (!checkArgs(2, F("usage: power.setradioperiod(sleepms, wakems)"))) {
return 0;
}
uint32_t sleepms = getarg(1);
uint32_t wakems = getarg(2);

SleepHandler::setRadioPeriod(sleepms, wakems);
return 1;
}

/****************************\
* RGB LED HANDLERS *
\****************************/
Expand Down Expand Up @@ -2396,6 +2479,11 @@ void PinoccioShell::setup() {
addFunction("power.sleep", powerSleep);
addFunction("power.report", powerReport);
addFunction("power.wakeup.pin", powerWakeupPin);
addFunction("power.sleepradio", powerSleepRadio);
addFunction("power.schedulesleepradio", powerScheduleSleepRadio);
addFunction("power.setradioperiod", powerSetRadioPeriod);
addFunction("power.getradiostate", powerGetRadioState);
addFunction("power.wakeradio", powerWakeRadio);

addFunction("mesh.config", meshConfig);
addFunction("mesh.setchannel", meshSetChannel);
Expand Down Expand Up @@ -2450,6 +2538,13 @@ void PinoccioShell::setup() {
addFunction("uptime.getlastreset", getLastResetCause);
addFunction("uptime.status", uptimeStatus);
addFunction("uptime", uptimeStatus);
addFunction("uptime.setoffset", uptimeSetOffset);

addFunction("uptime.meshsleeping.micros", uptimeMeshSleepingMicros);
addFunction("uptime.meshsleeping.seconds", uptimeMeshSleepingSeconds);

addFunction("uptime.meshoffset.micros", uptimeMeshOffsetMicros);
addFunction("uptime.meshoffset.seconds", uptimeMeshOffsetSeconds);

addFunction("led.on", ledTorch); // alias
addFunction("led.off", ledOff);
Expand Down
150 changes: 150 additions & 0 deletions src/SleepHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,51 @@
#include <avr/sleep.h>
#include <util/atomic.h>
#include "SleepHandler.h"
#include "util/radio_state_t.h"

static volatile bool timer_match;

Duration SleepHandler::lastOverflow = {0, 0};
Duration SleepHandler::totalSleep = {0, 0};
Duration SleepHandler::meshSleep = {0, 0};
uint32_t SleepHandler::meshSleepStart = 0;
radio_state_t SleepHandler::radioState = RF_AWAKE;
uint32_t SleepHandler::sleepPeriod = 100;
uint32_t SleepHandler::wakePeriod = 100;
Duration SleepHandler::meshOffset = {0, 0};
boolean SleepHandler::offsetInFuture = true;

// Returns the time mesh slept since startup
const Duration& SleepHandler::meshsleeptime() {
return meshSleep;
}

const Duration& SleepHandler::getOffset(){
return meshOffset;
}

void SleepHandler::setOffset(Duration d){
meshOffset = d;
}

void SleepHandler::setOffsetInFuture(bool future){
offsetInFuture = future;
}

Pbbe::LogicalPin::mask_t SleepHandler::pinWakeups = 0;

ISR(SCNT_CMP3_vect) {
timer_match = true;
}

ISR(SCNT_CMP2_vect) {
if (RF_WILL_SLEEP == SleepHandler::radioState) {
SleepHandler::radioState = RF_SHOULD_SLEEP;
} else if (RF_WILL_WAKE == SleepHandler::radioState) {
SleepHandler::radioState = RF_SHOULD_WAKE;
}
}

ISR(SCNT_OVFL_vect) {
SleepHandler::lastOverflow += (1LL << 32) * SleepHandler::US_PER_TICK;
}
Expand Down Expand Up @@ -40,6 +75,15 @@ uint32_t SleepHandler::read_scocr3() {
return sccnt;
}

uint32_t SleepHandler::read_scocr2() {
// Read LL first, that will freeze the other registers for reading
uint32_t sccnt = SCOCR2LL;
sccnt |= (uint32_t)SCOCR2LH << 8;
sccnt |= (uint32_t)SCOCR2HL << 16;
sccnt |= (uint32_t)SCOCR2HH << 24;
return sccnt;
}

void SleepHandler::write_scocr3(uint32_t val) {
// Write LL last, that will update the entire register atomically
SCOCR3HH = val >> 24;
Expand All @@ -48,7 +92,16 @@ void SleepHandler::write_scocr3(uint32_t val) {
SCOCR3LL = val;
}

void SleepHandler::write_scocr2(uint32_t val) {
// Write LL last, that will update the entire register atomically
SCOCR2HH = val >> 24;
SCOCR2HL = val >> 16;
SCOCR2LH = val >> 8;
SCOCR2LL = val;
}

void SleepHandler::setup() {

// Enable asynchronous mode for timer2. This is required to start the
// 32kiHz crystal at all, so we can use it for the symbol counter. See
// http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=142962
Expand All @@ -73,6 +126,22 @@ void SleepHandler::setup() {
PCICR |= (1 << PCIE0);
}

void SleepHandler::loop() {
switch (radioState) {
case RF_SHOULD_SLEEP:
sleepRadio();
scheduleWakeRadio(meshtime() + (uint64_t)(sleepPeriod * 1000));
break;
case RF_SHOULD_WAKE:
wakeRadio();
scheduleSleepRadio(meshtime() + (uint64_t)(wakePeriod * 1000));
break;
default:
// RF_WILL_SLEEP, RF_WILL_WAKE, RF_SLEEPING, RF_AWAKE
break;
}
}

// Sleep until the timer match interrupt fired. If interruptible is
// true, this can return before if some other interrupt wakes us up
// from sleep. If this happens, true is returned.
Expand Down Expand Up @@ -147,6 +216,87 @@ uint32_t SleepHandler::scheduledTicksLeft() {
return left;
}

uint32_t SleepHandler::scheduledTicksLeft2() {
uint32_t left = read_scocr2() - read_sccnt();

// If a compare match has occured, we're past the end of sleep already.
// We check this _after_ grabbing the counter value above, to prevent
// a race condition where the counter goes past the compare value
// after checking for the timer_match flag. We check both the
// interrupt flag and the timer_match flag, to handle both before
// and after sleeping (since before sleeping, the IRQ is disabled, but
// during sleep the wakeup clears the flag).
if ((SCIRQS & (1 << IRQSCP2)) || radioState != RF_WILL_SLEEP || radioState != RF_WILL_WAKE)
return 0;
return left;
}

int SleepHandler::getRadioState() {
return radioState;
}

void SleepHandler::setRadioPeriod(uint32_t sleepms, uint32_t wakems){
sleepPeriod = sleepms;
wakePeriod = wakems;
}

void SleepHandler::scheduleSleepRadio(Duration future) {
// todo check its in the future
// todo handle microseconds too
// todo calculate off of an uptime + offset
radioState = RF_WILL_SLEEP;
setTimer2((future.seconds - meshtime().seconds) * 1000);
}

void SleepHandler::scheduleWakeRadio(Duration future) {
// todo check its in the future
// todo handle microseconds too
// todo calculate off of an uptime + offset
radioState = RF_WILL_WAKE;
setTimer2((future.seconds - meshtime().seconds) * 1000);
}

// TODO take a few ticks off the schedule as it takes us some amount of
// time to service the interrupt, mark the flag, get around to the loop
// and wake the radio
void SleepHandler::sleepRadio() {

meshSleepStart = read_sccnt();

while (NWK_Busy()) {}

radioState = RF_SLEEPING;
NWK_SleepReq();
}

void SleepHandler::wakeRadio() {
uint32_t after = read_sccnt();
meshSleep += (uint64_t)(after - meshSleepStart) * US_PER_TICK;
NWK_WakeupReq();
radioState = RF_AWAKE;
}

void SleepHandler::setTimer2(uint32_t ms) {
uint32_t ticks = msToTicks(ms);

// Make sure we cannot "miss" the compare match if a low timeout is
// passed (really only ms = 0, which is forbidden, but handle it
// anyway).
if (ticks < 2) ticks = 2;
// Disable interrupts to prevent the counter passing the target before
// we clear the IRQSCP3 flag (due to other interrupts happening)
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
// Schedule SCNT_CMP2 when the given counter is reached
write_scocr2(read_sccnt() + ticks);

// Clear any previously pending interrupt
SCIRQS = (1 << IRQSCP2);

// Enable the SCNT_CMP2 interrupt to wake us from sleep
SCIRQM |= (1 << IRQMCP2);
}
}

void SleepHandler::doSleep(bool interruptible) {
// Disable Analag comparator
uint8_t acsr = ACSR;
Expand Down
Loading