diff --git a/src/Scout.cpp b/src/Scout.cpp index 3fa0b83..3feb162 100644 --- a/src/Scout.cpp +++ b/src/Scout.cpp @@ -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(); @@ -180,7 +179,8 @@ void PinoccioScout::loop() { handler.loop(); ModuleHandler::loop(); Backpacks::loop(); - + SleepHandler::loop(); + if(showStatus) { Led.setRedValue(Led.getRedValue()); @@ -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); + } } } @@ -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 diff --git a/src/Shell.cpp b/src/Shell.cpp index 15bfc27..744bcda 100644 --- a/src/Shell.cpp +++ b/src/Shell.cpp @@ -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); @@ -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 * \****************************/ @@ -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 * \****************************/ @@ -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); @@ -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); diff --git a/src/SleepHandler.cpp b/src/SleepHandler.cpp index 4314138..d0527de 100644 --- a/src/SleepHandler.cpp +++ b/src/SleepHandler.cpp @@ -2,16 +2,51 @@ #include #include #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; } @@ -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; @@ -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 @@ -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. @@ -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; diff --git a/src/SleepHandler.h b/src/SleepHandler.h index 476d056..af9bbbd 100644 --- a/src/SleepHandler.h +++ b/src/SleepHandler.h @@ -3,12 +3,34 @@ #include "backpack-bus/Pbbe.h" #include "util/Duration.h" +#include "lwm/sys/sysTimer.h" +#include "lwm/nwk/nwk.h" +#include "util/radio_state_t.h" +ISR(SCNT_CMP2_vect); ISR(SCNT_OVFL_vect); class SleepHandler { public: + + static void setOffset(Duration d); + static void setOffsetInFuture(bool future); + + static const Duration& getOffset(); + static const Duration& meshsleeptime(); + + static bool getMatch(); static void setup(); + static void loop(); + + static void sleepRadio(); + static void wakeRadio(); + + static void scheduleSleepRadio(Duration future); + static void scheduleWakeRadio(Duration future); + + static void setRadioPeriod(uint32_t sleepms, uint32_t wakems); + static int getRadioState(); // Schedule a sleep until the given number of ms from now. The sleep // actually starts when doSleep is called, but any delay between @@ -21,6 +43,7 @@ class SleepHandler { // How many ticks are left before the end time is reached? static uint32_t scheduledTicksLeft(); + static uint32_t scheduledTicksLeft2(); // Sleep until the previously scheduled time. If interruptible is // true, this can return earlier if we are woken from sleep by @@ -76,7 +99,29 @@ class SleepHandler { return uptime() - totalSleep; } + // Returns the local authority's time + static Duration meshtime() { + if(offsetInFuture) + return uptime() + meshOffset; + else + return uptime() - meshOffset; + } + protected: + + // The mesh offset + static Duration meshOffset; + // The offset direction, forward is true, backward is false + static bool offsetInFuture; + + static radio_state_t radioState; + static uint32_t sleepPeriod; + static uint32_t wakePeriod; + + // The total time radio spent sleeping since startup + static Duration meshSleep; + static uint32_t meshSleepStart; + // The time from startup to the most recent overflow static Duration lastOverflow; // The total time spent sleeping since startup @@ -87,11 +132,15 @@ class SleepHandler { // Allow the symbol counter overflow ISR to access our protected members friend void SCNT_OVFL_vect(); + friend void SCNT_CMP2_vect(); static bool sleepUntilMatch(bool interruptible); static uint32_t read_sccnt(); static void write_scocr3(uint32_t val); + static void write_scocr2(uint32_t val); static uint32_t read_scocr3(); + static uint32_t read_scocr2(); + static void setTimer2(uint32_t ms); // The symbol counter always runs at 62.5kHz (period 16μs). When running // off the 16Mhz crystal, this is just a prescaler. When running of the diff --git a/src/util/Duration.h b/src/util/Duration.h index 8526f59..1374954 100644 --- a/src/util/Duration.h +++ b/src/util/Duration.h @@ -40,6 +40,18 @@ class Duration { return result; } + + Duration operator +(Duration d) { + Duration result(*this); + result.seconds += d.seconds; + result.us += d.us; + if (result.us > 1000000) { + result.us -= 1000000; + result.seconds++; + } + + return result; + } }; #endif // LIB_PINOCCIO_DURATION_H diff --git a/src/util/radio_state_t.h b/src/util/radio_state_t.h new file mode 100644 index 0000000..22b67c7 --- /dev/null +++ b/src/util/radio_state_t.h @@ -0,0 +1,6 @@ +#ifndef LIB_PINOCCIO_RADIO_H +#define LIB_PINOCCIO_RADIO_H + +enum radio_state_t { RF_AWAKE = 0, RF_WILL_SLEEP, RF_SHOULD_SLEEP, RF_SLEEPING, RF_WILL_WAKE, RF_SHOULD_WAKE }; + +#endif // LIB_PINOCCIO_RADIO_H