浏览代码

Imported reworked rtctime support.

Johny Mattsson 9 年之前
父节点
当前提交
dd38a0a0e6

+ 4 - 0
app/include/rom.h

@@ -1,5 +1,8 @@
 // Headers to the various functions in the rom (as we discover them)
 
+#ifndef _ROM_H_
+#define _ROM_H_
+
 // SHA1 is assumed to match the netbsd sha1.h headers
 #define SHA1_DIGEST_LENGTH		20
 #define SHA1_DIGEST_STRING_LENGTH	41
@@ -103,3 +106,4 @@ typedef void (*exception_handler_fn) (struct exception_frame *ef, uint32_t cause
  */
 exception_handler_fn _xtos_set_exception_handler (uint32_t cause, exception_handler_fn handler);
 
+#endif

+ 2 - 4
app/include/rtc/rtcaccess.h

@@ -9,14 +9,12 @@
 #define RTC_TARGET_ADDR 0x04
 #define RTC_COUNTER_ADDR 0x1c
 
-#define EARLY_ENTRY_ATTR __attribute__((section(".text")))
-
-static inline uint32_t EARLY_ENTRY_ATTR rtc_mem_read(uint32_t addr)
+static inline uint32_t rtc_mem_read(uint32_t addr)
 {
   return ((uint32_t*)RTC_USER_MEM_BASE)[addr];
 }
 
-static inline void EARLY_ENTRY_ATTR rtc_mem_write(uint32_t addr, uint32_t val)
+static inline void rtc_mem_write(uint32_t addr, uint32_t val)
 {
   ((uint32_t*)RTC_USER_MEM_BASE)[addr]=val;
 }

+ 9 - 4
app/include/rtc/rtcfifo.h

@@ -55,7 +55,7 @@
 // (9/10) are meaningless when (3) is zero
 //
 
-#define RTC_FIFO_BASE          8
+#define RTC_FIFO_BASE          10
 #define RTC_FIFO_MAGIC         0x44695553
 
 // RTCFIFO storage
@@ -85,6 +85,10 @@
 #define RTC_DEFAULT_TAGCOUNT    5
 #define RTC_DEFAULT_FIFO_LOC (RTC_DEFAULT_FIFO_START + (RTC_DEFAULT_FIFO_END<<8) + (RTC_DEFAULT_TAGCOUNT<<16))
 
+#ifndef RTCTIME_SLEEP_ALIGNED
+# define RTCTIME_SLEEP_ALIGNED rtc_time_deep_sleep_until_aligned
+#endif
+
 typedef struct
 {
   uint32_t timestamp;
@@ -340,7 +344,8 @@ static int32_t rtc_fifo_delta_t(uint32_t t, uint32_t ref_t)
 
 static uint32_t rtc_fifo_construct_entry(uint32_t val, uint32_t tagindex, uint32_t decimals, uint32_t deltat)
 {
-  return val+(deltat<<16)+(decimals<<25)+(tagindex<<28);
+  return (val & 0xffff) + ((deltat & 0x1ff) <<16) +
+         ((decimals & 0x7)<<25) + ((tagindex & 0xf)<<28);
 }
 
 static inline void rtc_fifo_store_sample(const sample_t* s)
@@ -467,10 +472,10 @@ static inline void rtc_fifo_unset_magic(void)
   rtc_mem_write(RTC_FIFO_MAGIC_POS,0);
 }
 
-static inline void rtc_fifo_deep_sleep_until_sample(uint32_t min_sleep_us, uint32_t mhz)
+static inline void rtc_fifo_deep_sleep_until_sample(uint32_t min_sleep_us)
 {
   uint32_t align=rtc_mem_read(RTC_ALIGNMENT_POS);
-  rtc_time_deep_sleep_until_aligned(align,min_sleep_us,mhz);
+  RTCTIME_SLEEP_ALIGNED(align,min_sleep_us);
 }
 
 static inline void rtc_fifo_prepare(uint32_t samples_per_boot, uint32_t us_per_sample, uint32_t tagcount)

+ 15 - 468
app/include/rtc/rtctime.h

@@ -28,485 +28,32 @@
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
  * OF THE POSSIBILITY OF SUCH DAMAGE.
  *
- * @author Bernd Meyer <bmeyer@dius.com.au>
  * @author Johny Mattsson <jmattsson@dius.com.au>
  */
+#ifndef _RTCTIME_H_
+#define _RTCTIME_H_
 
-#ifndef RTCTIME_H
-#define RTCTIME_H
-
-#include "rtcaccess.h"
-
-// Layout of the RTC storage space:
-//
-// 0: Magic. If set to RTC_TIME_MAGIC, the rest is valid. If not, continue to proper boot
-//
-// 1: cycle counter offset, lower 32 bit
-// 2: cycle counter offset, upper 32 bit
-//
-// 3: cached result of sleep clock calibration. Has the format of system_rtc_clock_cali_proc(),
-//    or 0 if not available (see 4/5 below)
-// 4: Number of microseconds we tried to sleep, or 0 if we didn't sleep since last calibration, ffffffff if invalid
-// 5: Number of RTC cycles we decided to sleep, or 0 if we didn't sleep since last calibration, ffffffff if invalid
-
-// 6: Number of microseconds which we add to (1/2) to avoid time going backwards
-// 7: microsecond value returned in the last gettimeofday() to "user space".
-//
-// (1:2) set to 0 if no time information is available.
-
-//     Entries 4-7 are needed because the RTC cycles/second appears quite temperature dependent,
-//     and thus is heavily influenced by what else the chip is doing. As such, any calibration against
-//     the crystal-provided clock (which necessarily would have to happen while the chip is active and
-//     burning a few milliwatts) will be significantly different from the actual frequency during deep
-//     sleep.
-//     Thus, in order to calibrate for deep sleep conditions, we keep track of total sleep microseconds
-//     and total sleep clock cycles between settimeofday() calls (which presumably are NTP driven), and
-//     adjust the calibration accordingly on each settimeofday(). This will also track frequency changes
-//     due to ambient temperature changes.
-//     6/7 get used when a settimeofday() would result in turning back time. As that can cause all sorts
-//     of ugly issues, we *do* adjust (1/2), but compensate by making the same adjustment to (6). Then each
-//     time gettimeofday() is called, we inspect (7) and determine how much time has passed since the last
-//     call (yes, this gets it wrong if more than a second has passed, but not in a way that causes issues)
-//     and try to take up to 6% of that time away from (6) until (6) reaches 0. Also, whenever we go to
-//     deep sleep, we add (6) to the sleep time, thus catching up all in one go.
-//     Note that for calculating the next sample-aligned wakeup, we need to use the post-adjustment
-//     timeofday(), but for calculating actual sleep time, we use the pre-adjustment one, thus bringing
-//     things back into line.
-//
-
-#define RTC_TIME_BASE  0  // Where the RTC timekeeping block starts in RTC user memory slots
-#define RTC_TIME_MAGIC 0x44695573
-#define RTC_TIME_MAGIC_SLEEP 0x64697573
-
-// What rate we run the CPU at most of the time, and thus the rate at which we keep our time data
-#define CPU_DEFAULT_MHZ 80
-#define CPU_BOOTUP_MHZ  52
-#define RTC_TIME_CCOMPARE_INT 6 // Interrupt cause for CCOMPARE0 match
-
-// RTCTIME storage
-#define RTC_TIME_MAGIC_POS       (RTC_TIME_BASE+0)
-#define RTC_CYCLEOFFSETL_POS     (RTC_TIME_BASE+1)
-#define RTC_CYCLEOFFSETH_POS     (RTC_TIME_BASE+2)
-
-#define RTC_SLEEPTOTALUS_POS     (RTC_TIME_BASE+3)
-#define RTC_SLEEPTOTALCYCLES_POS (RTC_TIME_BASE+4)
-#define RTC_TODOFFSETUS_POS      (RTC_TIME_BASE+5)
-#define RTC_LASTTODUS_POS        (RTC_TIME_BASE+6)
-
-#define RTC_CALIBRATION_POS      (RTC_TIME_BASE+7)
+/* We don't want to expose the raw rtctime interface as it is heavily
+ * 'static inline' and used by a few things, so instead we wrap the
+ * relevant functions and expose these instead, through the rtctime.c module.
+ */
 
-static inline uint64_t rtc_time_get_now_us_adjusted(uint32_t mhz);
+#include <c_types.h>
 
+#ifndef _RTCTIME_INTERNAL_H_
 struct rtc_timeval
 {
   uint32_t tv_sec;
   uint32_t tv_usec;
 };
-
-
-static inline bool EARLY_ENTRY_ATTR rtc_time_check_sleep_magic(void)
-{
-  if (rtc_mem_read(RTC_TIME_MAGIC_POS)==RTC_TIME_MAGIC_SLEEP)
-    return 1;
-  return 0;
-}
-
-static inline bool EARLY_ENTRY_ATTR rtc_time_check_wake_magic(void)
-{
-  if (rtc_mem_read(RTC_TIME_MAGIC_POS)==RTC_TIME_MAGIC)
-    return 1;
-  return 0;
-}
-
-static inline bool EARLY_ENTRY_ATTR rtc_time_check_magic(void)
-{
-  return rtc_time_check_wake_magic() || rtc_time_check_sleep_magic();
-}
-
-static inline void EARLY_ENTRY_ATTR rtc_time_set_wake_magic(void)
-{
-  rtc_mem_write(RTC_TIME_MAGIC_POS,RTC_TIME_MAGIC);
-}
-
-static inline void rtc_time_set_sleep_magic(void)
-{
-  rtc_mem_write(RTC_TIME_MAGIC_POS,RTC_TIME_MAGIC_SLEEP);
-}
-
-static inline void rtc_time_unset_magic(void)
-{
-  rtc_mem_write(RTC_TIME_MAGIC_POS,0);
-}
-
-static inline uint32_t rtc_time_read_raw(void)
-{
-  return rtc_reg_read(RTC_COUNTER_ADDR);
-}
-
-static inline uint32_t rtc_time_read_raw_ccount(void)
-{
-  return xthal_get_ccount();
-}
-
-static inline uint64_t rtc_time_unix_ccount(uint32_t mhz)
-{
-  // Do *not* cache this, as it can change before the read(s) inside the loop
-  if (!rtc_mem_read64(RTC_CYCLEOFFSETL_POS))
-    return 0;
-
-  uint64_t result=0;
-  // Need to be careful here of race conditions
-  while (!result)
-  {
-    uint32_t before=rtc_time_read_raw_ccount();
-    uint64_t base=rtc_mem_read64(RTC_CYCLEOFFSETL_POS);
-    uint32_t after=rtc_time_read_raw_ccount();
-
-    if (before<after)
-    {
-      uint64_t ccount80;
-      if (mhz==CPU_DEFAULT_MHZ)
-        ccount80=((uint64_t)before+after)/2;
-      else
-        ccount80=((uint64_t)before+after)*CPU_DEFAULT_MHZ/(2*mhz);
-      result=base+ccount80;
-    }
-  }
-  return result;
-}
-
-static inline uint64_t rtc_time_unix_us(uint32_t mhz)
-{
-  return rtc_time_unix_ccount(mhz)/CPU_DEFAULT_MHZ;
-}
-
-static inline void rtc_time_register_time_reached(uint32_t s, uint32_t us)
-{
-  rtc_mem_write(RTC_LASTTODUS_POS,us);
-}
-
-static inline uint32_t rtc_time_us_since_time_reached(uint32_t s, uint32_t us)
-{
-  uint32_t lastus=rtc_mem_read(RTC_LASTTODUS_POS);
-  if (us<lastus)
-    us+=1000000;
-  return us-lastus;
-}
-
-// A small sanity check so sleep times go completely nuts if someone
-// has provided wrong timestamps to gettimeofday.
-static inline bool rtc_time_calibration_is_sane(uint32_t cali)
-{
-  return (cali>=(4<<12)) && (cali<=(10<<12));
-}
-
-static inline void rtc_time_settimeofday(const struct rtc_timeval* tv)
-{
-  if (!rtc_time_check_magic())
-    return;
-
-
-  uint32_t sleep_us=rtc_mem_read(RTC_SLEEPTOTALUS_POS);
-  uint32_t sleep_cycles=rtc_mem_read(RTC_SLEEPTOTALCYCLES_POS);
-  // At this point, the CPU clock will definitely be at the default rate (nodemcu fully booted)
-  uint64_t now_esp_us=rtc_time_get_now_us_adjusted(CPU_DEFAULT_MHZ);
-  uint64_t now_ntp_us=((uint64_t)tv->tv_sec)*1000000+tv->tv_usec;
-  int64_t  diff_us=now_esp_us-now_ntp_us;
-
-  // Store the *actual* time.
-  uint64_t target_ccount=now_ntp_us*CPU_DEFAULT_MHZ;
-  // again, be mindful of race conditions
-  while (1)
-  {
-    uint32_t before=rtc_time_read_raw_ccount();
-    rtc_mem_write64(RTC_CYCLEOFFSETL_POS,target_ccount-before);
-    uint32_t after=rtc_time_read_raw_ccount();
-    if (before<after)
-      break;
-  }
-
-  // calibrate sleep period based on difference between expected time and actual time
-  if (sleep_us>0 && sleep_us<0xffffffff &&
-      sleep_cycles>0 && sleep_cycles<0xffffffff)
-  {
-    uint64_t actual_sleep_us=sleep_us-diff_us;
-    uint32_t cali=(actual_sleep_us<<12)/sleep_cycles;
-    if (rtc_time_calibration_is_sane(cali))
-      rtc_mem_write(RTC_CALIBRATION_POS,cali);
-  }
-
-  rtc_mem_write(RTC_SLEEPTOTALUS_POS,0);
-  rtc_mem_write(RTC_SLEEPTOTALCYCLES_POS,0);
-
-  // Deal with time adjustment if necessary
-  if (diff_us>0) // Time went backwards. Avoid that....
-  {
-    if (diff_us>0xffffffffULL)
-      diff_us=0xffffffffULL;
-    now_ntp_us+=diff_us;
-  }
-  else
-    diff_us=0;
-  rtc_mem_write(RTC_TODOFFSETUS_POS,diff_us);
-
-  uint32_t now_s=now_ntp_us/1000000;
-  uint32_t now_us=now_ntp_us%1000000;
-  rtc_time_register_time_reached(now_s,now_us);
-}
-
-static inline uint32_t rtc_time_get_calibration(void)
-{
-  uint32_t cal=rtc_time_check_magic()?rtc_mem_read(RTC_CALIBRATION_POS):0;
-  if (!cal)
-  {
-    // Make a first guess, most likely to be rather bad, but better then nothing.
-#ifndef BOOTLOADER_CODE // This will pull in way too much of the system for the bootloader to handle.
-    ets_delay_us(200);
-    cal=system_rtc_clock_cali_proc();
-    rtc_mem_write(RTC_CALIBRATION_POS,cal);
-#else
-    cal=6<<12;
 #endif
-  }
-  return cal;
-}
-
-static inline void rtc_time_invalidate_calibration(void)
-{
-  rtc_mem_write(RTC_CALIBRATION_POS,0);
-}
 
-static inline uint64_t rtc_time_us_to_ticks(uint64_t us)
-{
-  uint32_t cal=rtc_time_get_calibration();
-
-  return (us<<12)/cal;
-}
-
-static inline uint64_t rtc_time_get_now_us_raw(uint32_t mhz)
-{
-  if (!rtc_time_check_magic())
-    return 0;
-
-  return rtc_time_unix_us(mhz);
-}
-
-static inline uint64_t rtc_time_get_now_us_adjusted(uint32_t mhz)
-{
-  uint64_t raw=rtc_time_get_now_us_raw(mhz);
-  if (!raw)
-    return 0;
-  return raw+rtc_mem_read(RTC_TODOFFSETUS_POS);
-}
-
-static inline void rtc_time_gettimeofday(struct rtc_timeval* tv, uint32_t mhz)
-{
-  uint64_t now=rtc_time_get_now_us_adjusted(mhz);
-  uint32_t sec=now/1000000;
-  uint32_t usec=now%1000000;
-  uint32_t to_adjust=rtc_mem_read(RTC_TODOFFSETUS_POS);
-  if (to_adjust)
-  {
-    uint32_t us_passed=rtc_time_us_since_time_reached(sec,usec);
-    uint32_t adjust=us_passed>>4;
-    if (adjust)
-    {
-      if (adjust>to_adjust)
-        adjust=to_adjust;
-      to_adjust-=adjust;
-      now-=adjust;
-      now/1000000;
-      now%1000000;
-      rtc_mem_write(RTC_TODOFFSETUS_POS,to_adjust);
-    }
-  }
-  tv->tv_sec=sec;
-  tv->tv_usec=usec;
-  rtc_time_register_time_reached(sec,usec);
-}
-
-static inline void rtc_time_add_sleep_tracking(uint32_t us, uint32_t cycles)
-{
-  if (rtc_time_check_magic())
-  {
-    // us is the one that will grow faster...
-    uint32_t us_before=rtc_mem_read(RTC_SLEEPTOTALUS_POS);
-    uint32_t us_after=us_before+us;
-    uint32_t cycles_after=rtc_mem_read(RTC_SLEEPTOTALCYCLES_POS)+cycles;
-
-    if (us_after<us_before) // Give up if it would cause an overflow
-    {
-      us_after=cycles_after=0xffffffff;
-    }
-    rtc_mem_write(RTC_SLEEPTOTALUS_POS,    us_after);
-    rtc_mem_write(RTC_SLEEPTOTALCYCLES_POS,cycles_after);
-  }
-}
-
-static void rtc_time_enter_deep_sleep_us(uint32_t us)
-{
-  if (rtc_time_check_wake_magic())
-    rtc_time_set_sleep_magic();
-
-  rtc_reg_write(0,0);
-  rtc_reg_write(0,rtc_reg_read(0)&0xffffbfff);
-  rtc_reg_write(0,rtc_reg_read(0)|0x30);
-
-  rtc_reg_write(0x44,4);
-  rtc_reg_write(0x0c,0x00010010);
-
-  rtc_reg_write(0x48,(rtc_reg_read(0x48)&0xffff01ff)|0x0000fc00);
-  rtc_reg_write(0x48,(rtc_reg_read(0x48)&0xfffffe00)|0x00000080);
-
-  rtc_reg_write(RTC_TARGET_ADDR,rtc_time_read_raw()+136);
-  rtc_reg_write(0x18,8);
-  rtc_reg_write(0x08,0x00100010);
-
-  ets_delay_us(20);
-
-  rtc_reg_write(0x9c,17);
-  rtc_reg_write(0xa0,3);
-
-  rtc_reg_write(0x0c,0x640c8);
-  rtc_reg_write(0,rtc_reg_read(0)&0xffffffcf);
-
-  uint32_t cycles=rtc_time_us_to_ticks(us);
-  rtc_time_add_sleep_tracking(us,cycles);
-
-  rtc_reg_write(RTC_TARGET_ADDR,rtc_time_read_raw()+cycles);
-  rtc_reg_write(0x9c,17);
-  rtc_reg_write(0xa0,3);
-
-  // Clear bit 0 of DPORT 0x04. Doesn't seem to be necessary
-  // wm(0x3fff0004,bitrm(0x3fff0004),0xfffffffe));
-  rtc_reg_write(0x40,-1);
-  rtc_reg_write(0x44,32);
-  rtc_reg_write(0x10,0);
-
-  rtc_reg_write(0x18,8);
-  rtc_reg_write(0x08,0x00100000); //  go to sleep
-}
-
-static inline void rtc_time_deep_sleep_us(uint32_t us, uint32_t mhz)
-{
-  if (rtc_time_check_magic())
-  {
-    uint32_t to_adjust=rtc_mem_read(RTC_TODOFFSETUS_POS);
-    if (to_adjust)
-    {
-      us+=to_adjust;
-      rtc_mem_write(RTC_TODOFFSETUS_POS,0);
-    }
-    uint64_t now=rtc_time_get_now_us_raw(mhz); // Now the same as _adjusted()
-    if (now)
-    { // Need to maintain the clock first. When we wake up, counter will be 0
-      uint64_t wakeup=now+us;
-      uint64_t wakeup_cycles=wakeup*CPU_DEFAULT_MHZ;
-      rtc_mem_write64(RTC_CYCLEOFFSETL_POS,wakeup_cycles);
-    }
-  }
-  rtc_time_enter_deep_sleep_us(us);
-}
-
-static inline void rtc_time_deep_sleep_until_aligned(uint32_t align, uint32_t min_sleep_us, uint32_t mhz)
-{
-  uint64_t now=rtc_time_get_now_us_adjusted(mhz);
-  uint64_t then=now+min_sleep_us;
-
-  if (align)
-  {
-    then+=align-1;
-    then-=(then%align);
-  }
-  rtc_time_deep_sleep_us(then-now,mhz);
-}
-
-static inline void EARLY_ENTRY_ATTR rtc_time_reset(bool clear_cali)
-{
-  rtc_mem_write64(RTC_CYCLEOFFSETL_POS,0);
-  rtc_mem_write(RTC_SLEEPTOTALUS_POS,0);
-  rtc_mem_write(RTC_SLEEPTOTALCYCLES_POS,0);
-  rtc_mem_write(RTC_TODOFFSETUS_POS,0);
-  rtc_mem_write(RTC_LASTTODUS_POS,0);
-  if (clear_cali)
-    rtc_mem_write(RTC_CALIBRATION_POS,0);
-}
-
-static inline void EARLY_ENTRY_ATTR rtc_time_register_bootup(void)
-{
-  uint32_t reset_reason=rtc_get_reset_reason();
-#ifndef BOOTLOADER_CODE
-  static const bool erase_calibration=true;
-#else
-  // In the boot loader, any leftover calibration is going to be better than anything we can
-  // come up with....
-  static const bool erase_calibration=false;
-#endif
-
-  if (rtc_time_check_sleep_magic())
-  {
-    if (reset_reason!=2)  // This was *not* a proper wakeup from a deep sleep. All our time keeping is f*cked!
-      rtc_time_reset(erase_calibration); // Possibly keep the calibration, it should still be good
-    rtc_time_set_wake_magic();
-    return;
-  }
-
-  if (rtc_time_check_wake_magic())
-  {
-    // This was *not* a proper wakeup from rtc-time initiated deep sleep. All our time keeping is f*cked!
-    rtc_time_reset(erase_calibration); // Possibly keep the calibration, it should still be good
-  }
-}
-
-static inline void EARLY_ENTRY_ATTR rtc_time_switch_to_default_clock(uint32_t mhz)
-{
-  if (rtc_time_check_magic())
-  {
-    uint64_t cycles=rtc_time_read_raw_ccount();
-    uint64_t missing_cycles=cycles*(CPU_DEFAULT_MHZ-mhz)/mhz;
-    uint64_t offset=rtc_mem_read64(RTC_CYCLEOFFSETL_POS);
-    if (offset)
-    {
-      rtc_mem_write64(RTC_CYCLEOFFSETL_POS,offset+missing_cycles);
-    }
-  }
-}
-
-static inline void rtc_time_ccount_wrap_handler(void* dst_v, uint32_t sp)
-{
-  uint32_t off_h=rtc_mem_read(RTC_CYCLEOFFSETH_POS);
-  if (rtc_time_check_magic() && off_h)
-  {
-    rtc_mem_write(RTC_CYCLEOFFSETH_POS,off_h+1);
-  }
-  xthal_set_ccompare(0,0); // This resets the interrupt condition
-}
-
-static inline void EARLY_ENTRY_ATTR rtc_time_install_wrap_handler(void)
-{
-  xthal_set_ccompare(0,0); // Recognise a ccounter wraparound
-  ets_isr_attach(RTC_TIME_CCOMPARE_INT,rtc_time_ccount_wrap_handler,NULL);
-  ets_isr_unmask(1<<RTC_TIME_CCOMPARE_INT);
-}
-
-
-// Call this from the nodemcu entry point, i.e. just before we switch from 52MHz to 80MHz
-static inline void EARLY_ENTRY_ATTR rtc_time_switch_clocks(void)
-{
-  rtc_time_install_wrap_handler();
-  rtc_time_switch_to_default_clock(CPU_BOOTUP_MHZ);
-}
-
-
-static inline bool rtc_time_have_time(void)
-{
-  return (rtc_time_check_magic() && rtc_mem_read64(RTC_CYCLEOFFSETL_POS)!=0);
-}
-
-static inline void rtc_time_prepare(void)
-{
-  rtc_time_reset(true);
-  rtc_time_set_wake_magic();
-}
+void TEXT_SECTION_ATTR rtctime_early_startup (void);
+void rtctime_late_startup (void);
+void rtctime_gettimeofday (struct rtc_timeval *tv);
+void rtctime_settimeofday (const struct rtc_timeval *tv);
+bool rtctime_have_time (void);
+void rtctime_deep_sleep_us (uint32_t us);
+void rtctime_deep_sleep_until_aligned_us (uint32_t align_us, uint32_t min_us);
 
 #endif

+ 737 - 0
app/include/rtc/rtctime_internal.h

@@ -0,0 +1,737 @@
+/*
+ * Copyright 2015 Dius Computing Pty Ltd. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the
+ *   distribution.
+ * - Neither the name of the copyright holders nor the names of
+ *   its contributors may be used to endorse or promote products derived
+ *   from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @author Bernd Meyer <bmeyer@dius.com.au>
+ * @author Johny Mattsson <jmattsson@dius.com.au>
+ */
+
+#ifndef _RTCTIME_INTERNAL_H_
+#define _RTCTIME_INTERNAL_H_
+
+/*
+ * The ESP8266 has four distinct power states:
+ *
+ * 1) Active ---      CPU and modem are powered and running
+ * 2) Modem Sleep --- CPU is active, but the RF section is powered down
+ * 3) Light Sleep --- CPU is halted, RF section is powered down. CPU gets reactivated by interrupt
+ * 4) Deep Sleep ---  CPU and RF section are powered down, restart requires a full reset
+ *
+ * There are also three (relevant) sources of time information
+ *
+ * A) CPU Cycle Counter --- this is incremented at the CPU frequency in modes (1) and (2), but is
+ *                    halted in state (3), and gets reset in state (4). Highly precise 32 bit counter
+ *                    which overflows roughly every minute. Starts counting as soon as the CPU becomes
+ *                    active after a reset. Can cause an interrupt when it hits a particular value;
+ *                    This interrupt (and the register that determines the comparison value) are not
+ *                    used by the system software, and are available for user code to use.
+ *
+ * B) Free Running Counter 2 --- This is a peripheral which gets configured to run at 1/256th of the
+ *                    CPU frequency. It is also active in states (1) and (2), and is halted in state (3).
+ *                    However, the ESP system code will adjust its value across periods of Light Sleep
+ *                    that it initiates, so *in effect*, this counter kind-of remains active in (3).
+ *                    While in states (1) and (2), it is as precise as the CPU Cycle. While in state (3),
+ *                    however, it is only as precise as the system's knowledge of how long the sleep
+ *                    period was. This knowledge is limited (it is based on (C), see below).
+ *                    The Free Running Counter 2 is a 32 bit counter which overflows roughly every
+ *                    4 hours, and typically has a resolution of 3.2us. It starts counting as soon as
+ *                    it gets configured, which is considerably *after* the time of reset, and in fact
+ *                    is not done by the ESP boot loader, but rather by the loaded-from-SPI-flash system
+ *                    code. This means it is not yet running when the boot loader calls the configured
+ *                    entry point, and the time between reset and the counter starting to run depends on
+ *                    the size of code/data to be copied into RAM from the flash.
+ *                    The FRC2 is also used by the system software for its internal time keeping, i.e. for
+ *                    dealing with any registered ETS_Timers (and derived-from-them timer functionality).
+ *
+ * C) "Real Time Clock" --- This peripheral runs from an internal low power RC oscillator, at a frequency
+ *                    somewhere in the 120-200kHz range. It keeps running in all power states, and is in
+ *                    fact the time source responsible for generating an interrupt (state (3)) or reset
+ *                    (state (4)) to end Light and Deep Sleep periods. However, it *does* get reset to
+ *                    zero after a reset, even one it caused itself.
+ *                    The major issue with the RTC is that it is not using a crystal (support for an
+ *                    external 32.768kHz crystal was planned at one point, but was removed from the
+ *                    final ESP8266 design), and thus the frequency of the oscillator is dependent on
+ *                    a number of parameters, including the chip temperature. The ESP's system software
+ *                    contains code to "calibrate" exactly how long one cycle of the oscillator is, and
+ *                    uses that calibration to work out how many cycles to sleep for modes (3) and (4).
+ *                    However, once the chip has entered a low power state, it quickly cools down, which
+ *                    results in the oscillator running faster than during calibration, leading to early
+ *                    wakeups. This effect is small (even in relative terms) for short sleep periods (because
+ *                    the temperature does not change much over a few hundred milliseconds), but can get
+ *                    quite large for extended sleeps.
+ *
+ * For added fun, a typical ESP8266 module starts up running the CPU (and thus the cycle counter) at 52MHz,
+ * but usually this will be switched to 80MHz on application startup, and can later be switched to 160MHz
+ * under user control. Meanwhile, the FRC2 is usually kept running at 80MHz/256, regardless of the CPU
+ * clock.
+ *
+ *
+ *
+ * The code in this file implements a best-effort time keeping solution for the ESP. It keeps track of time
+ * by switching between various time sources. All state is kept in RAM associated with the RTC, which is
+ * maintained across Deep Sleep periods.
+ *
+ * Internally, time is managed in units of cycles of a (hypothetical) 2080MHz clock, e.g. in units
+ * of 0.4807692307ns. The reason for this choice is that this covers both the FRC2 and the cycle
+ * counter periods, while running at 52MHz, 80MHz or 160MHz.
+ *
+ * At any given time, the time status indicates whether the FRC2 or the Cycle Counter is the current time
+ * source, how many unit cycles each LSB of the chosen time source "is worth", and what the unix time in
+ * unit cycles was when the time source was at 0.
+ * Given that either time source overflows its 32 bit counter in a relatively short time, the code also
+ * maintains a "last read 32 bit value" for the selected time source, and on each subsequent read will
+ * check for overflow and, if necessary, adjust the unix-time-at-time-source-being-zero appropriately.
+ * In order to avoid missing overflows, a timer gets installed which requests time every 40 seconds.
+ *
+ * To avoid race conditions, *none* of the code here must be called from an interrupt context unless
+ * the user can absolutely guarantee that there will never be a clock source rollover (which can be the
+ * case for sensor applications that only stay awake for a few seconds). And even then, do so at your
+ * own risk.
+ *
+ *
+ * Deep sleep is handled by moving the time offset forward *before* the sleep to the scheduled wakeup
+ * time. Due to the nature of the RTC, the actual wakeup time may be a little bit different, but
+ * it's the best that can be done. The code attempts to come up with a better calibration value if
+ * authoritative time is available both before and after a sleep; This works reasonably well, but of
+ * course is still merely a guess, which may well be somewhat wrong.
+ *
+ */
+#include <osapi.h>
+#include <ets_sys.h>
+#include "rom.h"
+#include "rtcaccess.h"
+
+// Layout of the RTC storage space:
+//
+// 0: Magic, and time source. Meaningful values are
+//     * RTC_TIME_MAGIC_SLEEP: Indicates that the device went to sleep under RTCTIME control.
+//       This is the magic expected on deep sleep wakeup; Any other status means we lost track
+//       of time, and whatever time offset is stored in state is invalid and must be cleared.
+//     * RTC_TIME_MAGIC_CCOUNT: Time offset is relative to the Cycle Counter.
+//     * RTC_TIME_MAGIC_FRC2: Time offset is relative to the Free Running Counter.
+//    Any values other than these indicate that RTCTIME is not in use and no state is available, nor should
+//    RTCTIME make any changes to any of the RTC memory space.
+//
+// 1/2: UNIX time in Unit Cycles when time source had value 0 (64 bit, lower 32 bit in 1, upper in 2).
+//      If 0, then time is unknown.
+// 3: Last used value of time source (32 bit unsigned). If current time source is less, then a rollover happened
+// 4: Length of a time source cycle in Unit Cycles.
+// 5: cached result of sleep clock calibration. Has the format of system_rtc_clock_cali_proc(),
+//    or 0 if not available (see 6/7 below)
+// 6: Number of microseconds we tried to sleep, or 0 if we didn't sleep since last calibration, ffffffff if invalid
+// 7: Number of RTC cycles we decided to sleep, or 0 if we didn't sleep since last calibration, ffffffff if invalid
+
+// 8: Number of microseconds which we add to (1/2) to avoid time going backwards
+// 9: microsecond value returned in the last gettimeofday() to "user space".
+//
+//     Entries 6-9 are needed because the RTC cycles/second appears quite temperature dependent,
+//     and thus is heavily influenced by what else the chip is doing. As such, any calibration against
+//     the crystal-provided clock (which necessarily would have to happen while the chip is active and
+//     burning a few milliwatts) will be significantly different from the actual frequency during deep
+//     sleep.
+//     Thus, in order to calibrate for deep sleep conditions, we keep track of total sleep microseconds
+//     and total sleep clock cycles between settimeofday() calls (which presumably are NTP driven), and
+//     adjust the calibration accordingly on each settimeofday(). This will also track frequency changes
+//     due to ambient temperature changes.
+//     8/9 get used when a settimeofday() would result in turning back time. As that can cause all sorts
+//     of ugly issues, we *do* adjust (1/2), but compensate by making the same adjustment to (8). Then each
+//     time gettimeofday() is called, we inspect (9) and determine how much time has passed since the last
+//     call (yes, this gets it wrong if more than a second has passed, but not in a way that causes issues)
+//     and try to take up to 6% of that time away from (8) until (8) reaches 0. Also, whenever we go to
+//     deep sleep, we add (8) to the sleep time, thus catching up all in one go.
+//     Note that for calculating the next sample-aligned wakeup, we need to use the post-adjustment
+//     timeofday(), but for calculating actual sleep time, we use the pre-adjustment one, thus bringing
+//     things back into line.
+//
+
+#define RTC_TIME_BASE  0  // Where the RTC timekeeping block starts in RTC user memory slots
+#define RTC_TIME_MAGIC_CCOUNT 0x44695573
+#define RTC_TIME_MAGIC_FRC2   (RTC_TIME_MAGIC_CCOUNT+1)
+#define RTC_TIME_MAGIC_SLEEP  (RTC_TIME_MAGIC_CCOUNT+2)
+
+#define UNITCYCLE_MHZ     2080
+#define CPU_OVERCLOCK_MHZ 160
+#define CPU_DEFAULT_MHZ   80
+#define CPU_BOOTUP_MHZ    52
+
+// RTCTIME storage
+#define RTC_TIME_MAGIC_POS       (RTC_TIME_BASE+0)
+#define RTC_CYCLEOFFSETL_POS     (RTC_TIME_BASE+1)
+#define RTC_CYCLEOFFSETH_POS     (RTC_TIME_BASE+2)
+#define RTC_LASTSOURCEVAL_POS    (RTC_TIME_BASE+3)
+#define RTC_SOURCECYCLEUNITS_POS (RTC_TIME_BASE+4)
+#define RTC_CALIBRATION_POS      (RTC_TIME_BASE+5)
+#define RTC_SLEEPTOTALUS_POS     (RTC_TIME_BASE+6)
+#define RTC_SLEEPTOTALCYCLES_POS (RTC_TIME_BASE+7)
+#define RTC_TODOFFSETUS_POS      (RTC_TIME_BASE+8)
+#define RTC_LASTTODUS_POS        (RTC_TIME_BASE+9)
+
+
+struct rtc_timeval
+{
+  uint32_t tv_sec;
+  uint32_t tv_usec;
+};
+
+static inline uint64_t rtc_time_get_now_us_adjusted();
+
+static inline uint32_t rtc_time_get_magic(void)
+{
+  return rtc_mem_read(RTC_TIME_MAGIC_POS);
+}
+
+static inline bool rtc_time_check_sleep_magic(void)
+{
+  uint32_t magic=rtc_time_get_magic();
+  return (magic==RTC_TIME_MAGIC_SLEEP);
+}
+
+static inline bool rtc_time_check_wake_magic(void)
+{
+  uint32_t magic=rtc_time_get_magic();
+  return (magic==RTC_TIME_MAGIC_FRC2 || magic==RTC_TIME_MAGIC_CCOUNT);
+}
+
+static inline bool rtc_time_check_magic(void)
+{
+  uint32_t magic=rtc_time_get_magic();
+  return (magic==RTC_TIME_MAGIC_FRC2 || magic==RTC_TIME_MAGIC_CCOUNT || magic==RTC_TIME_MAGIC_SLEEP);
+}
+
+static inline void rtc_time_set_magic(uint32_t new_magic)
+{
+  rtc_mem_write(RTC_TIME_MAGIC_POS,new_magic);
+}
+
+static inline void rtc_time_set_sleep_magic(void)
+{
+  rtc_time_set_magic(RTC_TIME_MAGIC_SLEEP);
+}
+
+static inline void rtc_time_set_ccount_magic(void)
+{
+  rtc_time_set_magic(RTC_TIME_MAGIC_CCOUNT);
+}
+
+static inline void rtc_time_set_frc2_magic(void)
+{
+  rtc_time_set_magic(RTC_TIME_MAGIC_FRC2);
+}
+
+
+
+static inline void rtc_time_unset_magic(void)
+{
+  rtc_mem_write(RTC_TIME_MAGIC_POS,0);
+}
+
+static inline uint32_t rtc_time_read_raw(void)
+{
+  return rtc_reg_read(RTC_COUNTER_ADDR);
+}
+
+static inline uint32_t rtc_time_read_raw_ccount(void)
+{
+  return xthal_get_ccount();
+}
+
+static inline uint32_t rtc_time_read_raw_frc2(void)
+{
+  return NOW();
+}
+
+// Get us the number of Unit Cycles that have elapsed since the source was 0.
+// Note: This may in fact adjust the stored cycles-when-source-was-0 entry, so
+// we need to make sure we call this before reading that entry
+static inline uint64_t rtc_time_source_offset(void)
+{
+  uint32_t magic=rtc_time_get_magic();
+  uint32_t raw=0;
+
+  switch (magic)
+  {
+  case RTC_TIME_MAGIC_CCOUNT: raw=rtc_time_read_raw_ccount(); break;
+  case RTC_TIME_MAGIC_FRC2:   raw=rtc_time_read_raw_frc2();   break;
+  default: return 0; // We are not in a position to offer time
+  }
+  uint32_t multiplier=rtc_mem_read(RTC_SOURCECYCLEUNITS_POS);
+  uint32_t previous=rtc_mem_read(RTC_LASTSOURCEVAL_POS);
+  if (raw<previous)
+  { // We had a rollover.
+    uint64_t to_add=(1ULL<<32)*multiplier;
+    uint64_t base=rtc_mem_read64(RTC_CYCLEOFFSETL_POS);
+    if (base)
+      rtc_mem_write64(RTC_CYCLEOFFSETL_POS,base+to_add);
+  }
+  rtc_mem_write(RTC_LASTSOURCEVAL_POS,raw);
+  return ((uint64_t)raw)*multiplier;
+}
+
+static inline uint64_t rtc_time_unix_unitcycles(void)
+{
+  // Note: The order of these two must be maintained, as the first call might change the outcome of the second
+  uint64_t offset=rtc_time_source_offset();
+  uint64_t base=rtc_mem_read64(RTC_CYCLEOFFSETL_POS);
+
+  if (!base)
+    return 0; // No known time
+
+  return base+offset;
+}
+
+static inline uint64_t rtc_time_unix_us(void)
+{
+  return rtc_time_unix_unitcycles()/UNITCYCLE_MHZ;
+}
+
+static inline void rtc_time_register_time_reached(uint32_t s, uint32_t us)
+{
+  rtc_mem_write(RTC_LASTTODUS_POS,us);
+}
+
+static inline uint32_t rtc_time_us_since_time_reached(uint32_t s, uint32_t us)
+{
+  uint32_t lastus=rtc_mem_read(RTC_LASTTODUS_POS);
+  if (us<lastus)
+    us+=1000000;
+  return us-lastus;
+}
+
+// A small sanity check so sleep times go completely nuts if someone
+// has provided wrong timestamps to gettimeofday.
+static inline bool rtc_time_calibration_is_sane(uint32_t cali)
+{
+  return (cali>=(4<<12)) && (cali<=(10<<12));
+}
+
+
+static inline uint32_t rtc_time_get_calibration(void)
+{
+  uint32_t cal=rtc_time_check_magic()?rtc_mem_read(RTC_CALIBRATION_POS):0;
+  if (!cal)
+  {
+    // Make a first guess, most likely to be rather bad, but better then nothing.
+#ifndef BOOTLOADER_CODE // This will pull in way too much of the system for the bootloader to handle.
+    ets_delay_us(200);
+    cal=system_rtc_clock_cali_proc();
+    rtc_mem_write(RTC_CALIBRATION_POS,cal);
+#else
+    cal=6<<12;
+#endif
+  }
+  return cal;
+}
+
+static inline void rtc_time_invalidate_calibration(void)
+{
+  rtc_mem_write(RTC_CALIBRATION_POS,0);
+}
+
+static inline uint64_t rtc_time_us_to_ticks(uint64_t us)
+{
+  uint32_t cal=rtc_time_get_calibration();
+
+  return (us<<12)/cal;
+}
+
+static inline uint64_t rtc_time_get_now_us_raw(void)
+{
+  if (!rtc_time_check_magic())
+    return 0;
+
+  return rtc_time_unix_us();
+}
+
+static inline uint64_t rtc_time_get_now_us_adjusted(void)
+{
+  uint64_t raw=rtc_time_get_now_us_raw();
+  if (!raw)
+    return 0;
+  return raw+rtc_mem_read(RTC_TODOFFSETUS_POS);
+}
+
+
+static inline void rtc_time_add_sleep_tracking(uint32_t us, uint32_t cycles)
+{
+  if (rtc_time_check_magic())
+  {
+    // us is the one that will grow faster...
+    uint32_t us_before=rtc_mem_read(RTC_SLEEPTOTALUS_POS);
+    uint32_t us_after=us_before+us;
+    uint32_t cycles_after=rtc_mem_read(RTC_SLEEPTOTALCYCLES_POS)+cycles;
+
+    if (us_after<us_before) // Give up if it would cause an overflow
+    {
+      us_after=cycles_after=0xffffffff;
+    }
+    rtc_mem_write(RTC_SLEEPTOTALUS_POS,    us_after);
+    rtc_mem_write(RTC_SLEEPTOTALCYCLES_POS,cycles_after);
+  }
+}
+
+static void rtc_time_enter_deep_sleep_us(uint32_t us)
+{
+  if (rtc_time_check_wake_magic())
+    rtc_time_set_sleep_magic();
+
+  rtc_reg_write(0,0);
+  rtc_reg_write(0,rtc_reg_read(0)&0xffffbfff);
+  rtc_reg_write(0,rtc_reg_read(0)|0x30);
+
+  rtc_reg_write(0x44,4);
+  rtc_reg_write(0x0c,0x00010010);
+
+  rtc_reg_write(0x48,(rtc_reg_read(0x48)&0xffff01ff)|0x0000fc00);
+  rtc_reg_write(0x48,(rtc_reg_read(0x48)&0xfffffe00)|0x00000080);
+
+  rtc_reg_write(RTC_TARGET_ADDR,rtc_time_read_raw()+136);
+  rtc_reg_write(0x18,8);
+  rtc_reg_write(0x08,0x00100010);
+
+  ets_delay_us(20);
+
+  rtc_reg_write(0x9c,17);
+  rtc_reg_write(0xa0,3);
+
+  rtc_reg_write(0x0c,0x640c8);
+  rtc_reg_write(0,rtc_reg_read(0)&0xffffffcf);
+
+  uint32_t cycles=rtc_time_us_to_ticks(us);
+  rtc_time_add_sleep_tracking(us,cycles);
+
+  rtc_reg_write(RTC_TARGET_ADDR,rtc_time_read_raw()+cycles);
+  rtc_reg_write(0x9c,17);
+  rtc_reg_write(0xa0,3);
+
+  // Clear bit 0 of DPORT 0x04. Doesn't seem to be necessary
+  // wm(0x3fff0004,bitrm(0x3fff0004),0xfffffffe));
+  rtc_reg_write(0x40,-1);
+  rtc_reg_write(0x44,32);
+  rtc_reg_write(0x10,0);
+
+  rtc_reg_write(0x18,8);
+  rtc_reg_write(0x08,0x00100000); //  go to sleep
+}
+
+static inline void rtc_time_deep_sleep_us(uint32_t us)
+{
+  if (rtc_time_check_magic())
+  {
+    uint32_t to_adjust=rtc_mem_read(RTC_TODOFFSETUS_POS);
+    if (to_adjust)
+    {
+      us+=to_adjust;
+      rtc_mem_write(RTC_TODOFFSETUS_POS,0);
+    }
+    uint64_t now=rtc_time_get_now_us_raw(); // Now the same as _adjusted()
+    if (now)
+    { // Need to maintain the clock first. When we wake up, counter will be 0
+      uint64_t wakeup=now+us;
+      uint64_t wakeup_cycles=wakeup*UNITCYCLE_MHZ;
+      rtc_mem_write64(RTC_CYCLEOFFSETL_POS,wakeup_cycles);
+    }
+  }
+  rtc_time_enter_deep_sleep_us(us);
+}
+
+static inline void rtc_time_deep_sleep_until_aligned(uint32_t align, uint32_t min_sleep_us)
+{
+  uint64_t now=rtc_time_get_now_us_adjusted();
+  uint64_t then=now+min_sleep_us;
+
+  if (align)
+  {
+    then+=align-1;
+    then-=(then%align);
+  }
+  rtc_time_deep_sleep_us(then-now);
+}
+
+static inline void rtc_time_reset(bool clear_cali)
+{
+  rtc_mem_write64(RTC_CYCLEOFFSETL_POS,0);
+  rtc_mem_write(RTC_SLEEPTOTALUS_POS,0);
+  rtc_mem_write(RTC_SLEEPTOTALCYCLES_POS,0);
+  rtc_mem_write(RTC_TODOFFSETUS_POS,0);
+  rtc_mem_write(RTC_LASTTODUS_POS,0);
+  rtc_mem_write(RTC_SOURCECYCLEUNITS_POS,0);
+  rtc_mem_write(RTC_LASTSOURCEVAL_POS,0);
+
+  if (clear_cali)
+    rtc_mem_write(RTC_CALIBRATION_POS,0);
+}
+
+static inline bool rtc_time_have_time(void)
+{
+  return (rtc_time_check_magic() && rtc_mem_read64(RTC_CYCLEOFFSETL_POS)!=0);
+}
+
+
+static inline void rtc_time_select_frc2_source()
+{
+  // FRC2 always runs at 1/256th of the default 80MHz clock, even if the actual clock is different
+  uint32_t new_multiplier=(256*UNITCYCLE_MHZ+CPU_DEFAULT_MHZ/2)/CPU_DEFAULT_MHZ;
+
+  uint64_t now;
+  uint32_t before;
+  uint32_t after;
+
+  // Deal with race condition here...
+  do {
+    before=rtc_time_read_raw_frc2();
+    now=rtc_time_unix_unitcycles();
+    after=rtc_time_read_raw_frc2();
+  } while (before>after);
+
+  if (rtc_time_have_time())
+  {
+    uint64_t offset=(uint64_t)after*new_multiplier;
+    rtc_mem_write64(RTC_CYCLEOFFSETL_POS,now-offset);
+    rtc_mem_write(RTC_LASTSOURCEVAL_POS,after);
+  }
+  rtc_mem_write(RTC_SOURCECYCLEUNITS_POS,new_multiplier);
+  rtc_mem_write(RTC_TIME_MAGIC_POS,RTC_TIME_MAGIC_FRC2);
+}
+
+static inline void rtc_time_select_ccount_source(uint32_t mhz, bool first)
+{
+  uint32_t new_multiplier=(UNITCYCLE_MHZ+mhz/2)/mhz;
+
+  // Check that
+  if (new_multiplier*mhz!=UNITCYCLE_MHZ)
+    ets_printf("Trying to use unsuitable frequency: %dMHz\n",mhz);
+
+  if (first)
+  { // The ccounter has been running at this rate since startup, and the offset is set accordingly
+    rtc_mem_write(RTC_LASTSOURCEVAL_POS,0);
+    rtc_mem_write(RTC_SOURCECYCLEUNITS_POS,new_multiplier);
+    rtc_mem_write(RTC_TIME_MAGIC_POS,RTC_TIME_MAGIC_CCOUNT);
+    return;
+  }
+
+  uint64_t now;
+  uint32_t before;
+  uint32_t after;
+
+  // Deal with race condition here...
+  do {
+    before=rtc_time_read_raw_ccount();
+    now=rtc_time_unix_unitcycles();
+    after=rtc_time_read_raw_ccount();
+  } while (before>after);
+
+  if (rtc_time_have_time())
+  {
+    uint64_t offset=(uint64_t)after*new_multiplier;
+    rtc_mem_write64(RTC_CYCLEOFFSETL_POS,now-offset);
+    rtc_mem_write(RTC_LASTSOURCEVAL_POS,after);
+  }
+  rtc_mem_write(RTC_SOURCECYCLEUNITS_POS,new_multiplier);
+  rtc_mem_write(RTC_TIME_MAGIC_POS,RTC_TIME_MAGIC_CCOUNT);
+}
+
+
+
+static inline void rtc_time_switch_to_ccount_frequency(uint32_t mhz)
+{
+  if (rtc_time_check_magic())
+    rtc_time_select_ccount_source(mhz,false);
+}
+
+static inline void rtc_time_switch_to_system_clock(void)
+{
+  if (rtc_time_check_magic())
+    rtc_time_select_frc2_source();
+}
+
+static inline void rtc_time_tmrfn(void* arg)
+{
+  rtc_time_source_offset();
+}
+
+static inline void rtc_time_install_timer(void)
+{
+  static ETSTimer tmr;
+
+  os_timer_setfn(&tmr,rtc_time_tmrfn,NULL);
+  os_timer_arm(&tmr,10000,1);
+}
+
+#if 0 // Kept around for reference....
+static inline void rtc_time_ccount_wrap_handler(void* dst_v, uint32_t sp)
+{
+  uint32_t off_h=rtc_mem_read(RTC_CYCLEOFFSETH_POS);
+  if (rtc_time_check_magic() && off_h)
+  {
+    rtc_mem_write(RTC_CYCLEOFFSETH_POS,off_h+1);
+  }
+  xthal_set_ccompare(0,0); // This resets the interrupt condition
+}
+
+static inline void rtc_time_install_wrap_handler(void)
+{
+  xthal_set_ccompare(0,0); // Recognise a ccounter wraparound
+  ets_isr_attach(RTC_TIME_CCOMPARE_INT,rtc_time_ccount_wrap_handler,NULL);
+  ets_isr_unmask(1<<RTC_TIME_CCOMPARE_INT);
+}
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+// This switches from MAGIC_SLEEP to MAGIC_CCOUNT, with ccount running at bootup frequency (i.e. 52MHz).
+// To be called as early as possible, potententially as the first thing in an overridden entry point.
+static inline void rtc_time_register_bootup(void)
+{
+  uint32_t reset_reason=rtc_get_reset_reason();
+#ifndef BOOTLOADER_CODE
+  static const bool erase_calibration=true;
+#else
+  // In the boot loader, any leftover calibration is going to be better than anything we can
+  // come up with....
+  static const bool erase_calibration=false;
+#endif
+
+  if (rtc_time_check_sleep_magic())
+  {
+    if (reset_reason!=2)  // This was *not* a proper wakeup from a deep sleep. All our time keeping is f*cked!
+      rtc_time_reset(erase_calibration); // Possibly keep the calibration, it should still be good
+    rtc_time_select_ccount_source(CPU_BOOTUP_MHZ,true);
+    return;
+  }
+
+  if (rtc_time_check_magic())
+  {
+    // We did not go to sleep properly. All our time keeping is f*cked!
+    rtc_time_reset(erase_calibration); // Possibly keep the calibration, it should still be good
+  }
+}
+
+// Call this from the nodemcu entry point, i.e. just before we switch from 52MHz to 80MHz
+static inline void rtc_time_switch_clocks(void)
+{
+  rtc_time_switch_to_ccount_frequency(CPU_DEFAULT_MHZ);
+}
+
+// Call this exactly once, from user_init, i.e. once the operating system is up and running
+static inline void rtc_time_switch_system(void)
+{
+  rtc_time_install_timer();
+  rtc_time_switch_to_system_clock();
+}
+
+
+static inline void rtc_time_prepare(void)
+{
+  rtc_time_reset(true);
+  rtc_time_select_frc2_source();
+}
+
+static inline void rtc_time_gettimeofday(struct rtc_timeval* tv)
+{
+  uint64_t now=rtc_time_get_now_us_adjusted();
+  uint32_t sec=now/1000000;
+  uint32_t usec=now%1000000;
+  uint32_t to_adjust=rtc_mem_read(RTC_TODOFFSETUS_POS);
+  if (to_adjust)
+  {
+    uint32_t us_passed=rtc_time_us_since_time_reached(sec,usec);
+    uint32_t adjust=us_passed>>4;
+    if (adjust)
+    {
+      if (adjust>to_adjust)
+        adjust=to_adjust;
+      to_adjust-=adjust;
+      now-=adjust;
+      now/1000000;
+      now%1000000;
+      rtc_mem_write(RTC_TODOFFSETUS_POS,to_adjust);
+    }
+  }
+  tv->tv_sec=sec;
+  tv->tv_usec=usec;
+  rtc_time_register_time_reached(sec,usec);
+}
+
+static inline void rtc_time_settimeofday(const struct rtc_timeval* tv)
+{
+  if (!rtc_time_check_magic())
+    return;
+
+
+  uint32_t sleep_us=rtc_mem_read(RTC_SLEEPTOTALUS_POS);
+  uint32_t sleep_cycles=rtc_mem_read(RTC_SLEEPTOTALCYCLES_POS);
+  // At this point, the CPU clock will definitely be at the default rate (nodemcu fully booted)
+  uint64_t now_esp_us=rtc_time_get_now_us_adjusted();
+  uint64_t now_ntp_us=((uint64_t)tv->tv_sec)*1000000+tv->tv_usec;
+  int64_t  diff_us=now_esp_us-now_ntp_us;
+
+  // Store the *actual* time.
+  uint64_t target_unitcycles=now_ntp_us*UNITCYCLE_MHZ;
+  uint64_t sourcecycles=rtc_time_source_offset();
+  rtc_mem_write64(RTC_CYCLEOFFSETL_POS,target_unitcycles-sourcecycles);
+
+  // calibrate sleep period based on difference between expected time and actual time
+  if (sleep_us>0 && sleep_us<0xffffffff &&
+      sleep_cycles>0 && sleep_cycles<0xffffffff)
+  {
+    uint64_t actual_sleep_us=sleep_us-diff_us;
+    uint32_t cali=(actual_sleep_us<<12)/sleep_cycles;
+    if (rtc_time_calibration_is_sane(cali))
+      rtc_mem_write(RTC_CALIBRATION_POS,cali);
+  }
+
+  rtc_mem_write(RTC_SLEEPTOTALUS_POS,0);
+  rtc_mem_write(RTC_SLEEPTOTALCYCLES_POS,0);
+
+  // Deal with time adjustment if necessary
+  if (diff_us>0) // Time went backwards. Avoid that....
+  {
+    if (diff_us>0xffffffffULL)
+      diff_us=0xffffffffULL;
+    now_ntp_us+=diff_us;
+  }
+  else
+    diff_us=0;
+  rtc_mem_write(RTC_TODOFFSETUS_POS,diff_us);
+
+  uint32_t now_s=now_ntp_us/1000000;
+  uint32_t now_us=now_ntp_us%1000000;
+  rtc_time_register_time_reached(now_s,now_us);
+}
+
+#endif

+ 5 - 11
app/modules/rtcfifo.c

@@ -2,6 +2,8 @@
 
 #include "lauxlib.h"
 #include "user_modules.h"
+#include "rtc/rtctime.h"
+#define RTCTIME_SLEEP_ALIGNED rtctime_deep_sleep_until_aligned_us
 #include "rtc/rtcfifo.h"
 
 // rtcfifo.prepare ([{sensor_count=n, interval_us=m, storage_begin=x, storage_end=y}])
@@ -67,12 +69,8 @@ static int rtcfifo_put (lua_State *L)
 
   sample_t s;
   s.timestamp = luaL_checknumber (L, 1);
-  double val = luaL_checknumber (L, 2);
+  s.value = luaL_checknumber (L, 2);
   s.decimals = luaL_checknumber (L, 3);
-  uint32_t i = s.decimals;
-  while (i--)
-    val *= 10;
-  s.value = val;
   size_t len;
   const char *str = luaL_checklstring (L, 4, &len);
   union {
@@ -90,11 +88,7 @@ static int rtcfifo_put (lua_State *L)
 static int extract_sample (lua_State *L, const sample_t *s)
 {
   lua_pushnumber (L, s->timestamp);
-  double val = s->value;
-  int i;
-  for (i = 0; i < s->decimals; ++i)
-    val /= 10;
-  lua_pushnumber (L, val);
+  lua_pushnumber (L, s->value);
   lua_pushnumber (L, s->decimals);
   union {
     uint32_t u;
@@ -164,7 +158,7 @@ static int rtcfifo_dsleep_until_sample (lua_State *L)
   check_fifo_magic (L);
 
   uint32_t min_us = luaL_checknumber (L, 1);
-  rtc_fifo_deep_sleep_until_sample (min_us, CPU_DEFAULT_MHZ); // no return
+  rtc_fifo_deep_sleep_until_sample (min_us); // no return
   return 0;
 }
 #endif

+ 60 - 13
app/modules/rtctime.c

@@ -1,10 +1,59 @@
 // Module for RTC time keeping
 
 #include "lauxlib.h"
+
+#include "rtc/rtctime_internal.h"
 #include "rtc/rtctime.h"
 
-//  rtctime.settimeofday (sec, usec)
-static int rtctime_settimeofday (lua_State *L)
+
+// ******* C API functions *************
+
+void rtctime_early_startup (void)
+{
+  Cache_Read_Enable (0, 0, 1);
+  rtc_time_register_bootup ();
+  rtc_time_switch_clocks ();
+  Cache_Read_Disable ();
+}
+
+void rtctime_late_startup (void)
+{
+  rtc_time_switch_system ();
+}
+
+void rtctime_gettimeofday (struct rtc_timeval *tv)
+{
+  rtc_time_gettimeofday (tv);
+}
+
+void rtctime_settimeofday (const struct rtc_timeval *tv)
+{
+  if (!rtc_time_check_magic ())
+    rtc_time_prepare ();
+  rtc_time_settimeofday (tv);
+}
+
+bool rtctime_have_time (void)
+{
+  return rtc_time_have_time ();
+}
+
+void rtctime_deep_sleep_us (uint32_t us)
+{
+  rtc_time_deep_sleep_us (us);
+}
+
+void rtctime_deep_sleep_until_aligned_us (uint32_t align_us, uint32_t min_us)
+{
+  rtc_time_deep_sleep_until_aligned (align_us, min_us);
+}
+
+
+
+// ******* Lua API functions *************
+
+//  rtctime.set (sec, usec)
+static int rtctime_set (lua_State *L)
 {
   if (!rtc_time_check_magic ())
     rtc_time_prepare ();
@@ -15,16 +64,16 @@ static int rtctime_settimeofday (lua_State *L)
     usec = lua_tonumber (L, 2);
 
   struct rtc_timeval tv = { sec, usec };
-  rtc_time_settimeofday (&tv);
+  rtctime_settimeofday (&tv);
   return 0;
 }
 
 
-// sec, usec = rtctime.gettimeofday ()
-static int rtctime_gettimeofday (lua_State *L)
+// sec, usec = rtctime.get ()
+static int rtctime_get (lua_State *L)
 {
   struct rtc_timeval tv;
-  rtc_time_gettimeofday (&tv, CPU_DEFAULT_MHZ);
+  rtctime_gettimeofday (&tv);
   lua_pushnumber (L, tv.tv_sec);
   lua_pushnumber (L, tv.tv_usec);
   return 2;
@@ -46,8 +95,7 @@ static int rtctime_dsleep (lua_State *L)
 {
   uint32_t us = luaL_checknumber (L, 1);
   do_sleep_opt (L, 2);
-  // does not return
-  rtc_time_deep_sleep_us (us, CPU_DEFAULT_MHZ);
+  rtctime_deep_sleep_us (us); // does not return
   return 0;
 }
 
@@ -55,14 +103,13 @@ static int rtctime_dsleep (lua_State *L)
 // rtctime.dsleep_aligned (aligned_usec, min_usec, option)
 static int rtctime_dsleep_aligned (lua_State *L)
 {
-  if (!rtc_time_have_time ())
+  if (!rtctime_have_time ())
     return luaL_error (L, "time not available, unable to align");
 
   uint32_t align_us = luaL_checknumber (L, 1);
   uint32_t min_us = luaL_checknumber (L, 2);
   do_sleep_opt (L, 3);
-  // does not return
-  rtc_time_deep_sleep_until_aligned (align_us, min_us, CPU_DEFAULT_MHZ);
+  rtctime_deep_sleep_until_aligned_us (align_us, min_us); // does not return
   return 0;
 }
 
@@ -72,8 +119,8 @@ static int rtctime_dsleep_aligned (lua_State *L)
 #include "lrodefs.h"
 const LUA_REG_TYPE rtctime_map[] =
 {
-  { LSTRKEY("settimeofday"), LFUNCVAL(rtctime_settimeofday) },
-  { LSTRKEY("gettimeofday"), LFUNCVAL(rtctime_gettimeofday) },
+  { LSTRKEY("set"), LFUNCVAL(rtctime_set) },
+  { LSTRKEY("get"), LFUNCVAL(rtctime_get) },
   { LSTRKEY("dsleep"),  LFUNCVAL(rtctime_dsleep)  },
   { LSTRKEY("dsleep_aligned"), LFUNCVAL(rtctime_dsleep_aligned) },
   { LNILKEY, LNILVAL }

+ 3 - 4
app/modules/sntp.c

@@ -131,7 +131,7 @@ static void sntp_dosend (lua_State *L)
   req.mode = 3; // client
 #ifdef LUA_USE_MODULES_RTCTIME
   struct rtc_timeval tv;
-  rtc_time_gettimeofday (&tv, CPU_DEFAULT_MHZ);
+  rtctime_gettimeofday (&tv);
   req.xmit.sec = htonl (tv.tv_sec);
   req.xmit.frac = htonl (tv.tv_usec);
 #else
@@ -221,7 +221,7 @@ static void on_recv (void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_a
 #ifdef LUA_USE_MODULES_RTCTIME
   struct rtc_timeval tv;
 
-  rtc_time_gettimeofday (&tv, CPU_DEFAULT_MHZ);
+  rtctime_gettimeofday (&tv);
   ntp_timestamp_t dest;
   dest.sec = tv.tv_sec;
   dest.frac = (MICROSECONDS * tv.tv_usec) / UINT32_MAXI;
@@ -248,8 +248,7 @@ static void on_recv (void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_a
 
   tv.tv_sec = dest.sec - NTP_TO_UNIX_EPOCH;
   tv.tv_usec = (MICROSECONDS * dest.frac) / UINT32_MAXI;
-  rtc_time_set_wake_magic ();
-  rtc_time_settimeofday (&tv);
+  rtctime_settimeofday (&tv);
 
   if (have_cb)
   {

+ 5 - 5
app/user/user_main.c

@@ -36,18 +36,15 @@ os_event_t *taskQueue;
  * by the time it is invoked the irom has not yet been mapped. This naturally
  * also goes for anything the trampoline itself calls.
  */
-void user_start_trampoline (void) TEXT_SECTION_ATTR;
-void user_start_trampoline (void)
+void TEXT_SECTION_ATTR user_start_trampoline (void)
 {
    __real__xtos_set_exception_handler (
      EXCCAUSE_LOAD_STORE_ERROR, load_non_32_wide_handler);
 
 #ifdef LUA_USE_MODULES_RTCTIME
-  rtc_time_register_bootup ();
-
   // Note: Keep this as close to call_user_start() as possible, since it
   // is where the cpu clock actually gets bumped to 80MHz.
-  rtc_time_switch_clocks ();
+  rtctime_early_startup ();
 #endif
   call_user_start ();
 }
@@ -153,6 +150,9 @@ void nodemcu_init(void)
 *******************************************************************************/
 void user_init(void)
 {
+#ifdef LUA_USE_MODULES_RTCTIME
+    rtctime_late_startup ();
+#endif
     // NODE_DBG("SDK version:%s\n", system_get_sdk_version());
     // system_print_meminfo();
     // os_printf("Heap size::%d.\n",system_get_free_heap_size());