From 1a613effebef76530e895222ef6d7bee4dfecc7b Mon Sep 17 00:00:00 2001 From: Johny Mattsson Date: Thu, 25 Jun 2015 12:26:54 +1000 Subject: [PATCH 1/5] Imported RTC access from a DiUS internal project. Added Lua access module named "rtcmem" for read/write of RTC user module. --- app/include/rtc/rtcaccess.h | 58 +++++++++++++++++++++++++++++++++++ app/include/user_modules.h | 1 + app/modules/modules.h | 11 ++++++- app/modules/rtcmem.c | 60 +++++++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 app/include/rtc/rtcaccess.h create mode 100644 app/modules/rtcmem.c diff --git a/app/include/rtc/rtcaccess.h b/app/include/rtc/rtcaccess.h new file mode 100644 index 00000000..12ed811a --- /dev/null +++ b/app/include/rtc/rtcaccess.h @@ -0,0 +1,58 @@ +#ifndef RTC_ACCESS_H +#define RTC_ACCESS_H + +#include + +#define RTC_MMIO_BASE 0x60000700 +#define RTC_USER_MEM_BASE 0x60001200 +#define RTC_USER_MEM_NUM_DWORDS 128 +#define RTC_TARGET_ADDR 0x04 +#define RTC_COUNTER_ADDR 0x1c + + +static inline uint32_t rtc_mem_read(uint32_t addr) +{ + return ((uint32_t*)RTC_USER_MEM_BASE)[addr]; +} + +static inline void rtc_mem_write(uint32_t addr, uint32_t val) +{ + ((uint32_t*)RTC_USER_MEM_BASE)[addr]=val; +} + +static inline uint64_t rtc_make64(uint32_t high, uint32_t low) +{ + return (((uint64_t)high)<<32)|low; +} + +static inline uint64_t rtc_mem_read64(uint32_t addr) +{ + return rtc_make64(rtc_mem_read(addr+1),rtc_mem_read(addr)); +} + +static inline void rtc_mem_write64(uint32_t addr, uint64_t val) +{ + rtc_mem_write(addr+1,val>>32); + rtc_mem_write(addr,val&0xffffffff); +} + +static inline void rtc_memw(void) +{ + asm volatile ("memw"); +} + +static inline void rtc_reg_write(uint32_t addr, uint32_t val) +{ + rtc_memw(); + addr+=RTC_MMIO_BASE; + *((volatile uint32_t*)addr)=val; + rtc_memw(); +} + +static inline uint32_t rtc_reg_read(uint32_t addr) +{ + addr+=RTC_MMIO_BASE; + rtc_memw(); + return *((volatile uint32_t*)addr); +} +#endif diff --git a/app/include/user_modules.h b/app/include/user_modules.h index 875a017f..7e8eedff 100644 --- a/app/include/user_modules.h +++ b/app/include/user_modules.h @@ -35,6 +35,7 @@ #define LUA_USE_MODULES_CRYPTO #define LUA_USE_MODULES_RC #define LUA_USE_MODULES_DHT +#define LUA_USE_MODULES_RTCMEM #endif /* LUA_USE_MODULES */ diff --git a/app/modules/modules.h b/app/modules/modules.h index ad0c4cfa..206e02ac 100644 --- a/app/modules/modules.h +++ b/app/modules/modules.h @@ -181,6 +181,14 @@ #define ROM_MODULES_DHT #endif +#if defined(LUA_USE_MODULES_RTCMEM) +#define MODULES_RTCMEM "rtcmem" +#define ROM_MODULES_RTCMEM \ + _ROM(MODULES_RTCMEM, luaopen_rtcmem, rtcmem_map) +#else +#define ROM_MODULES_RTCMEM +#endif + #define LUA_MODULES_ROM \ ROM_MODULES_GPIO \ ROM_MODULES_PWM \ @@ -203,6 +211,7 @@ ROM_MODULES_CJSON \ ROM_MODULES_CRYPTO \ ROM_MODULES_RC \ - ROM_MODULES_DHT + ROM_MODULES_DHT \ + ROM_MODULES_RTCMEM \ #endif diff --git a/app/modules/rtcmem.c b/app/modules/rtcmem.c new file mode 100644 index 00000000..4baa9c68 --- /dev/null +++ b/app/modules/rtcmem.c @@ -0,0 +1,60 @@ +// Module for RTC user memory access + +#include "lauxlib.h" +#include "rtc/rtcaccess.h" + +static int rtcmem_read32 (lua_State *L) +{ + int idx = luaL_checknumber (L, 1); + int n = 1; + if (lua_isnumber (L, 2)) + n = lua_tonumber (L, 2); + + if (!lua_checkstack (L, n)) + return 0; + + int ret = 0; + while (n > 0 && idx >= 0 && idx < RTC_USER_MEM_NUM_DWORDS) + { + lua_pushinteger (L, rtc_mem_read (idx++)); + --n; + ++ret; + } + return ret; +} + + +static int rtcmem_write32 (lua_State *L) +{ + int idx = luaL_checknumber (L, 1); + int n = lua_gettop (L) - 1; + luaL_argcheck ( + L, idx + n <= RTC_USER_MEM_NUM_DWORDS, 1, "RTC mem would overrun"); + int src = 2; + while (n-- > 0) + { + rtc_mem_write (idx++, lua_tonumber (L, src++)); + } + return 0; +} + + +// Module function map +#define MIN_OPT_LEVEL 2 +#include "lrodefs.h" +const LUA_REG_TYPE rtcmem_map[] = +{ + { LSTRKEY("read32"), LFUNCVAL(rtcmem_read32) }, + { LSTRKEY("write32"), LFUNCVAL(rtcmem_write32) }, + { LNILKEY, LNILVAL } +}; + +LUALIB_API int luaopen_rtcmem (lua_State *L) +{ +#if LUA_OPTIMIZE_MEMORY > 0 + return 0; +#else + luaL_register (L, AUXLIB_RTCMEM, rtcmem_map); + return 1; +#endif +} From 2187424928d553d0f94b6553a9fb8fc14369a63a Mon Sep 17 00:00:00 2001 From: Johny Mattsson Date: Thu, 25 Jun 2015 15:32:21 +1000 Subject: [PATCH 2/5] Imported RTC+sleep timekeeping from an internal DiUS project. Added Lua module rtctime to interface with it. This keeps as accurate time as is possible on the ESP8266, including across deep sleeps (provided rtctime.dsleep() is used rather than node.dsleep()). Intended to be used together with NTP for high accuracy time keeping. The API is via rtctime.{get,set}timeofday(), working from Unix epoch. Note that 160MHz CPU clock is not currently supported by the rtctime code, as it is only aware of the 52MHz boot clock and the regular 80Mhz default clock. See rtctime.h for detailed info on how this all works. --- app/include/rom.h | 9 + app/include/rtc/rtcaccess.h | 5 +- app/include/rtc/rtctime.h | 512 ++++++++++++++++++++++++++++++++++++ app/include/user_modules.h | 1 + app/modules/modules.h | 9 + app/modules/rtctime.c | 90 +++++++ app/user/user_main.c | 12 + 7 files changed, 636 insertions(+), 2 deletions(-) create mode 100644 app/include/rtc/rtctime.h create mode 100644 app/modules/rtctime.c diff --git a/app/include/rom.h b/app/include/rom.h index de031179..f9dfb7e7 100644 --- a/app/include/rom.h +++ b/app/include/rom.h @@ -38,6 +38,15 @@ extern unsigned char * base64_decode(const unsigned char *src, size_t len, size_ extern void mem_init(void * start_addr); +// Interrupt Service Routine functions +typedef void (*ets_isr_fn) (void *arg, uint32_t sp); +extern int ets_isr_attach (unsigned int interrupt, ets_isr_fn, void *arg); +extern void ets_isr_mask (unsigned intr); +extern void ets_isr_unmask (unsigned intr); + +// Cycle-counter +extern unsigned int xthal_get_ccount (void); +extern int xthal_set_ccompare (unsigned int timer_number, unsigned int compare_value); // 2, 3 = reset (module dependent?), 4 = wdt int rtc_get_reset_reason (void); diff --git a/app/include/rtc/rtcaccess.h b/app/include/rtc/rtcaccess.h index 12ed811a..b4010ec8 100644 --- a/app/include/rtc/rtcaccess.h +++ b/app/include/rtc/rtcaccess.h @@ -9,13 +9,14 @@ #define RTC_TARGET_ADDR 0x04 #define RTC_COUNTER_ADDR 0x1c +#define EARLY_ENTRY_ATTR __attribute__((section(".text"))) -static inline uint32_t rtc_mem_read(uint32_t addr) +static inline uint32_t EARLY_ENTRY_ATTR rtc_mem_read(uint32_t addr) { return ((uint32_t*)RTC_USER_MEM_BASE)[addr]; } -static inline void rtc_mem_write(uint32_t addr, uint32_t val) +static inline void EARLY_ENTRY_ATTR rtc_mem_write(uint32_t addr, uint32_t val) { ((uint32_t*)RTC_USER_MEM_BASE)[addr]=val; } diff --git a/app/include/rtc/rtctime.h b/app/include/rtc/rtctime.h new file mode 100644 index 00000000..88c21ec5 --- /dev/null +++ b/app/include/rtc/rtctime.h @@ -0,0 +1,512 @@ +/* + * 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 + * @author Johny Mattsson + */ + +#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) + +static inline uint64_t rtc_time_get_now_us_adjusted(uint32_t mhz); + +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=(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 (before0 && 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 4) + luaL_error (L, "unknown sleep option"); + deep_sleep_set_option (opt); + } +} + +// rtctime.dsleep (usec, option) +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); + return 0; +} + + +// rtctime.dsleep_aligned (aligned_usec, min_usec, option) +static int rtctime_dsleep_aligned (lua_State *L) +{ + if (!rtc_time_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); + return 0; +} + + +// Module function map +#define MIN_OPT_LEVEL 2 +#include "lrodefs.h" +const LUA_REG_TYPE rtctime_map[] = +{ + { LSTRKEY("settimeofday"), LFUNCVAL(rtctime_settimeofday) }, + { LSTRKEY("gettimeofday"), LFUNCVAL(rtctime_gettimeofday) }, + { LSTRKEY("dsleep"), LFUNCVAL(rtctime_dsleep) }, + { LSTRKEY("dsleep_aligned"), LFUNCVAL(rtctime_dsleep_aligned) }, + { LNILKEY, LNILVAL } +}; + +LUALIB_API int luaopen_rtctime (lua_State *L) +{ +#if LUA_OPTIMIZE_MEMORY > 0 + return 0; +#else + luaL_register (L, AUXLIB_RTCTIME, rtctime_map); + return 1; +#endif +} diff --git a/app/user/user_main.c b/app/user/user_main.c index c24e0f00..4492ffdb 100644 --- a/app/user/user_main.c +++ b/app/user/user_main.c @@ -17,11 +17,16 @@ #include "flash_fs.h" #include "user_interface.h" #include "user_exceptions.h" +#include "user_modules.h" #include "ets_sys.h" #include "driver/uart.h" #include "mem.h" +#ifdef LUA_USE_MODULES_RTCTIME +#include "rtc/rtctime.h" +#endif + #define SIG_LUA 0 #define TASK_QUEUE_LEN 4 os_event_t *taskQueue; @@ -37,6 +42,13 @@ void 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 (); +#endif call_user_start (); } From 67e72c45df41eb01f7bfb5c311ec6f6a185738b5 Mon Sep 17 00:00:00 2001 From: Johny Mattsson Date: Fri, 26 Jun 2015 16:36:39 +1000 Subject: [PATCH 3/5] Imported RTC FIFO component + added Lua interface. The rtcfifo module uses RTC memory to store sensor samples across deep-sleeps, making it possible to batch up samples for less frequent uploads. This component uses 9 RTC memory slots for control, and a variable number of slots for sample storage (see rtcfifo.prepare() on how to control the size/location of the latter). When used together with the rtctime module, it also exposes the convenience function rtcfifo.dsleep_until_sample() which can be used to easily take readings on a regular basis without having to manually take into account time spent awake to get an accurate sleep time. The format used for storing samples is quite dense, and allows for 16 bits of data in a fixed point format (per sample). --- app/include/rtc/rtcfifo.h | 485 +++++++++++++++++++++++++++++++++++++ app/include/user_modules.h | 1 + app/modules/modules.h | 9 + app/modules/rtcfifo.c | 198 +++++++++++++++ app/modules/rtctime.c | 2 +- 5 files changed, 694 insertions(+), 1 deletion(-) create mode 100644 app/include/rtc/rtcfifo.h create mode 100644 app/modules/rtcfifo.c diff --git a/app/include/rtc/rtcfifo.h b/app/include/rtc/rtcfifo.h new file mode 100644 index 00000000..b2000fd4 --- /dev/null +++ b/app/include/rtc/rtcfifo.h @@ -0,0 +1,485 @@ +/* + * 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 + */ +#ifndef _RTCFIFO_H_ +#define _RTCFIFO_H_ +#include "rtcaccess.h" +#include "rtctime.h" + +// 1: measurement alignment, in microseconds +// 2: timestamp for next sample (seconds). For sensors which sense during the sleep phase. Set to +// 0 to indicate no sample waiting. Simply do not use for sensors which deliver values prior to +// deep sleep. + +// 3: Number of samples to take before doing a "real" boot. Decremented as samples are obtained +// 4: Reload value for (10). Needs to be applied by the firmware in the real boot (rtc_restart_samples_to_take()) +// +// 5: FIFO location. First FIFO address in bits 0:7, first non-FIFO address in bits 8:15. +// Number of tag spaces in bits 16:23 +// 6: Number of samples in FIFO. +// 7: FIFO tail (where next sample will be written. Increments by 1 for each sample) +// 8: FIFO head (where next sample will be read. Increments by 1 for each sample) +// 9: FIFO head timestamp. Used and maintained when pulling things off the FIFO. This is the timestamp of the +// most recent sample pulled off; I.e. the head samples timestamp is this plus that sample's delta_t +// 10: FIFO tail timestamp. Used and maintained when adding things to the FIFO. This is the timestamp of the +// most recent sample to have been added. I.e. a new sample's delta-t is calculated relative to this +// (9/10) are meaningless when (3) is zero +// + +#define RTC_FIFO_BASE 8 +#define RTC_FIFO_MAGIC 0x44695553 + +// RTCFIFO storage +#define RTC_FIFO_MAGIC_POS (RTC_FIFO_BASE+0) +#define RTC_ALIGNMENT_POS (RTC_FIFO_BASE+1) +#define RTC_TIMESTAMP_POS (RTC_FIFO_BASE+2) + +#define RTC_SAMPLESTOTAKE_POS (RTC_FIFO_BASE+3) +#define RTC_SAMPLESPERBOOT_POS (RTC_FIFO_BASE+4) + +#define RTC_FIFOLOC_POS (RTC_FIFO_BASE+5) +#define RTC_FIFOCOUNT_POS (RTC_FIFO_BASE+6) +#define RTC_FIFOTAIL_POS (RTC_FIFO_BASE+7) +#define RTC_FIFOHEAD_POS (RTC_FIFO_BASE+8) +#define RTC_FIFOTAIL_T_POS (RTC_FIFO_BASE+9) +#define RTC_FIFOHEAD_T_POS (RTC_FIFO_BASE+10) + +// 32-127: FIFO space. Consisting of a number of tag spaces (see 4), followed by data entries. +// Data entries consist of: +// Bits 28:31 -> tag index. 0-15 +// Bits 25:27 -> decimals +// Bits 16:24 -> delta-t in seconds from previous entry +// Bits 0:15 -> sample value + +#define RTC_DEFAULT_FIFO_START 32 +#define RTC_DEFAULT_FIFO_END 128 +#define RTC_DEFAULT_TAGCOUNT 5 +#define RTC_DEFAULT_FIFO_LOC (RTC_DEFAULT_FIFO_START + (RTC_DEFAULT_FIFO_END<<8) + (RTC_DEFAULT_TAGCOUNT<<16)) + +typedef struct +{ + uint32_t timestamp; + uint32_t value; + uint32_t decimals; + uint32_t tag; +} sample_t; + +static inline void rtc_fifo_clear_content(void); + +static inline uint32_t rtc_fifo_get_tail(void) +{ + return rtc_mem_read(RTC_FIFOTAIL_POS); +} + +static inline void rtc_fifo_put_tail(uint32_t val) +{ + rtc_mem_write(RTC_FIFOTAIL_POS,val); +} + +static inline uint32_t rtc_fifo_get_head(void) +{ + return rtc_mem_read(RTC_FIFOHEAD_POS); +} + +static inline void rtc_fifo_put_head(uint32_t val) +{ + rtc_mem_write(RTC_FIFOHEAD_POS,val); +} + + +static inline uint32_t rtc_fifo_get_tail_t(void) +{ + return rtc_mem_read(RTC_FIFOTAIL_T_POS); +} + +static inline void rtc_fifo_put_tail_t(uint32_t val) +{ + rtc_mem_write(RTC_FIFOTAIL_T_POS,val); +} + +static inline uint32_t rtc_fifo_get_head_t(void) +{ + return rtc_mem_read(RTC_FIFOHEAD_T_POS); +} + +static inline void rtc_fifo_put_head_t(uint32_t val) +{ + rtc_mem_write(RTC_FIFOHEAD_T_POS,val); +} + + +static inline uint32_t rtc_fifo_get_count(void) +{ + return rtc_mem_read(RTC_FIFOCOUNT_POS); +} + +static inline void rtc_fifo_put_count(uint32_t val) +{ + rtc_mem_write(RTC_FIFOCOUNT_POS,val); +} + +static inline uint32_t rtc_fifo_get_tagcount(void) +{ + return (rtc_mem_read(RTC_FIFOLOC_POS)>>16)&0xff; +} + +static inline uint32_t rtc_fifo_get_tagpos(void) +{ + return (rtc_mem_read(RTC_FIFOLOC_POS)>>0)&0xff; +} + +static inline uint32_t rtc_fifo_get_last(void) +{ + return (rtc_mem_read(RTC_FIFOLOC_POS)>>8)&0xff; +} + +static inline uint32_t rtc_fifo_get_first(void) +{ + return rtc_fifo_get_tagpos()+rtc_fifo_get_tagcount(); +} + +static inline void rtc_fifo_put_loc(uint32_t first, uint32_t last, uint32_t tagcount) +{ + rtc_mem_write(RTC_FIFOLOC_POS,first+(last<<8)+(tagcount<<16)); +} + +static inline uint32_t rtc_fifo_normalise_index(uint32_t index) +{ + if (index>=rtc_fifo_get_last()) + index=rtc_fifo_get_first(); + return index; +} + +static inline void rtc_fifo_increment_count(void) +{ + rtc_fifo_put_count(rtc_fifo_get_count()+1); +} + +static inline void rtc_fifo_decrement_count(void) +{ + rtc_fifo_put_count(rtc_fifo_get_count()-1); +} + +static inline uint32_t rtc_get_samples_to_take(void) +{ + return rtc_mem_read(RTC_SAMPLESTOTAKE_POS); +} + +static inline void rtc_put_samples_to_take(uint32_t val) +{ + rtc_mem_write(RTC_SAMPLESTOTAKE_POS,val); +} + +static inline void rtc_decrement_samples_to_take(void) +{ + uint32_t stt=rtc_get_samples_to_take(); + if (stt) + rtc_put_samples_to_take(stt-1); +} + +static inline void rtc_restart_samples_to_take(void) +{ + rtc_put_samples_to_take(rtc_mem_read(RTC_SAMPLESPERBOOT_POS)); +} + +static inline uint32_t rtc_fifo_get_value(uint32_t entry) +{ + return entry&0xffff; +} + +static inline uint32_t rtc_fifo_get_decimals(uint32_t entry) +{ + return (entry>>25)&0x07; +} + +static inline uint32_t rtc_fifo_get_deltat(uint32_t entry) +{ + return (entry>>16)&0x1ff; +} + +static inline uint32_t rtc_fifo_get_tagindex(uint32_t entry) +{ + return (entry>>28)&0x0f; +} + +static inline uint32_t rtc_fifo_get_tag_from_entry(uint32_t entry) +{ + uint32_t index=rtc_fifo_get_tagindex(entry); + uint32_t tags_at=rtc_fifo_get_tagpos(); + return rtc_mem_read(tags_at+index); +} + +static inline void rtc_fifo_fill_sample(sample_t* dst, uint32_t entry, uint32_t timestamp) +{ + dst->timestamp=timestamp; + dst->value=rtc_fifo_get_value(entry); + dst->decimals=rtc_fifo_get_decimals(entry); + dst->tag=rtc_fifo_get_tag_from_entry(entry); +} + + +// returns 1 if sample popped, 0 if not +static inline int8_t rtc_fifo_pop_sample(sample_t* dst) +{ + uint32_t count=rtc_fifo_get_count(); + + if (count==0) + return 0; + uint32_t head=rtc_fifo_get_head(); + uint32_t timestamp=rtc_fifo_get_head_t(); + uint32_t entry=rtc_mem_read(head); + timestamp+=rtc_fifo_get_deltat(entry); + rtc_fifo_fill_sample(dst,entry,timestamp); + + head=rtc_fifo_normalise_index(head+1); + + rtc_fifo_put_head(head); + rtc_fifo_put_head_t(timestamp); + rtc_fifo_decrement_count(); + return 1; +} + +// returns 1 if sample is available, 0 if not +static inline int8_t rtc_fifo_peek_sample(sample_t* dst, uint32_t from_top) +{ + if (rtc_fifo_get_count()<=from_top) + return 0; + uint32_t head=rtc_fifo_get_head(); + uint32_t entry=rtc_mem_read(head); + uint32_t timestamp=rtc_fifo_get_head_t(); + timestamp+=rtc_fifo_get_deltat(entry); + + while (from_top--) + { + head=rtc_fifo_normalise_index(head+1); + entry=rtc_mem_read(head); + timestamp+=rtc_fifo_get_deltat(entry); + } + + rtc_fifo_fill_sample(dst,entry,timestamp); + return 1; +} + +static inline void rtc_fifo_drop_samples(uint32_t from_top) +{ + uint32_t count=rtc_fifo_get_count(); + + if (count<=from_top) + from_top=count; + uint32_t head=rtc_fifo_get_head(); + uint32_t head_t=rtc_fifo_get_head_t(); + + while (from_top--) + { + uint32_t entry=rtc_mem_read(head); + head_t+=rtc_fifo_get_deltat(entry); + head=rtc_fifo_normalise_index(head+1); + rtc_fifo_decrement_count(); + } + rtc_fifo_put_head(head); + rtc_fifo_put_head_t(head_t); +} + +static inline int rtc_fifo_find_tag_index(uint32_t tag) +{ + uint32_t tags_at=rtc_fifo_get_tagpos(); + uint32_t count=rtc_fifo_get_tagcount(); + uint32_t i; + + for (i=0;i0x1ff) + return -1; + return delta; +} + +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); +} + +static inline void rtc_fifo_store_sample(const sample_t* s) +{ + uint32_t head=rtc_fifo_get_head(); + uint32_t tail=rtc_fifo_get_tail(); + uint32_t count=rtc_fifo_get_count(); + int32_t tagindex=rtc_fifo_find_tag_index(s->tag); + + if (count==0) + { + rtc_fifo_put_head_t(s->timestamp); + rtc_fifo_put_tail_t(s->timestamp); + } + uint32_t tail_t=rtc_fifo_get_tail_t(); + int32_t deltat=rtc_fifo_delta_t(s->timestamp,tail_t); + + if (tagindex<0 || deltat<0) + { // We got something that doesn't fit into the scheme. Might be a long delay, might + // be some sort of dynamic change. In order to go on, we need to start over.... + // ets_printf("deltat is %d, tagindex is %d\n",deltat,tagindex); + + rtc_fifo_clear_content(); + rtc_fifo_put_head_t(s->timestamp); + rtc_fifo_put_tail_t(s->timestamp); + head=rtc_fifo_get_head(); + tail=rtc_fifo_get_tail(); + count=rtc_fifo_get_count(); + tagindex=rtc_fifo_find_tag_index(s->tag); // This should work now + if (tagindex<0) + return; // Uh-oh! This should never happen + } + + if (head==tail && count>0) + { // Full! Need to remove a sample + sample_t dummy; + rtc_fifo_pop_sample(&dummy); + } + + rtc_mem_write(tail++,rtc_fifo_construct_entry(s->value,tagindex,s->decimals,deltat)); + rtc_fifo_put_tail(rtc_fifo_normalise_index(tail)); + rtc_fifo_put_tail_t(s->timestamp); + rtc_fifo_increment_count(); +} + +static uint32_t rtc_fifo_make_tag(const uint8_t* s) +{ + uint32_t tag=0; + int i; + for (i=0;i<4;i++) + { + if (!s[i]) + break; + tag+=((uint32_t)(s[i]&0xff))<<(i*8); + } + return tag; +} + +static void rtc_fifo_tag_to_string(uint32_t tag, uint8_t s[5]) +{ + int i; + s[4]=0; + for (i=0;i<4;i++) + s[i]=(tag>>(8*i))&0xff; +} + +static inline uint32_t rtc_fifo_get_divisor(const sample_t* s) +{ + uint8_t decimals=s->decimals; + uint32_t div=1; + while (decimals--) + div*=10; + return div; +} + +static inline void rtc_fifo_clear_tags(void) +{ + uint32_t tags_at=rtc_fifo_get_tagpos(); + uint32_t count=rtc_fifo_get_tagcount(); + while (count--) + rtc_mem_write(tags_at++,0); +} + +static inline void rtc_fifo_clear_content(void) +{ + uint32_t first=rtc_fifo_get_first(); + rtc_fifo_put_tail(first); + rtc_fifo_put_head(first); + rtc_fifo_put_count(0); + rtc_fifo_put_tail_t(0); + rtc_fifo_put_head_t(0); + rtc_fifo_clear_tags(); +} + +static inline void rtc_fifo_init(uint32_t first, uint32_t last, uint32_t tagcount) +{ + rtc_fifo_put_loc(first,last,tagcount); + rtc_fifo_clear_content(); +} + +static inline void rtc_fifo_init_default(uint32_t tagcount) +{ + if (tagcount==0) + tagcount=RTC_DEFAULT_TAGCOUNT; + + rtc_fifo_init(RTC_DEFAULT_FIFO_START,RTC_DEFAULT_FIFO_END,tagcount); +} + + +static inline uint8_t rtc_fifo_check_magic(void) +{ + if (rtc_mem_read(RTC_FIFO_MAGIC_POS)==RTC_FIFO_MAGIC) + return 1; + return 0; +} + +static inline void rtc_fifo_set_magic(void) +{ + rtc_mem_write(RTC_FIFO_MAGIC_POS,RTC_FIFO_MAGIC); +} + +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) +{ + uint32_t align=rtc_mem_read(RTC_ALIGNMENT_POS); + rtc_time_deep_sleep_until_aligned(align,min_sleep_us,mhz); +} + +static inline void rtc_fifo_prepare(uint32_t samples_per_boot, uint32_t us_per_sample, uint32_t tagcount) +{ + rtc_mem_write(RTC_SAMPLESPERBOOT_POS,samples_per_boot); + rtc_mem_write(RTC_ALIGNMENT_POS,us_per_sample); + + rtc_put_samples_to_take(0); + rtc_fifo_init_default(tagcount); + rtc_fifo_set_magic(); +} +#endif diff --git a/app/include/user_modules.h b/app/include/user_modules.h index 652643fe..d6115724 100644 --- a/app/include/user_modules.h +++ b/app/include/user_modules.h @@ -37,6 +37,7 @@ #define LUA_USE_MODULES_DHT #define LUA_USE_MODULES_RTCMEM #define LUA_USE_MODULES_RTCTIME +#define LUA_USE_MODULES_RTCFIFO #endif /* LUA_USE_MODULES */ diff --git a/app/modules/modules.h b/app/modules/modules.h index c67bd605..5c270c36 100644 --- a/app/modules/modules.h +++ b/app/modules/modules.h @@ -197,6 +197,14 @@ #define ROM_MODULES_RTCTIME #endif +#if defined(LUA_USE_MODULES_RTCFIFO) +#define MODULES_RTCFIFO "rtcfifo" +#define ROM_MODULES_RTCFIFO \ + _ROM(MODULES_RTCFIFO, luaopen_rtcfifo, rtcfifo_map) +#else +#define ROM_MODULES_RTCFIFO +#endif + #define LUA_MODULES_ROM \ ROM_MODULES_GPIO \ ROM_MODULES_PWM \ @@ -222,5 +230,6 @@ ROM_MODULES_DHT \ ROM_MODULES_RTCMEM \ ROM_MODULES_RTCTIME \ + ROM_MODULES_RTCFIFO \ #endif diff --git a/app/modules/rtcfifo.c b/app/modules/rtcfifo.c new file mode 100644 index 00000000..5cb29bd2 --- /dev/null +++ b/app/modules/rtcfifo.c @@ -0,0 +1,198 @@ +// Module for RTC sample FIFO storage + +#include "lauxlib.h" +#include "user_modules.h" +#include "rtc/rtcfifo.h" + +// rtcfifo.prepare ([{sensor_count=n, interval_us=m, storage_begin=x, storage_end=y}]) +static int rtcfifo_prepare (lua_State *L) +{ + uint32_t sensor_count = RTC_DEFAULT_TAGCOUNT; + uint32_t interval_us = 0; + int first = -1, last = -1; + + if (lua_istable (L, 1)) + { +#ifdef LUA_USE_MODULES_RTCTIME + lua_getfield (L, 1, "interval_us"); + if (lua_isnumber (L, -1)) + interval_us = lua_tonumber (L, -1); + lua_pop (L, 1); +#endif + + lua_getfield (L, 1, "sensor_count"); + if (lua_isnumber (L, -1)) + sensor_count = lua_tonumber (L, -1); + lua_pop (L, 1); + + lua_getfield (L, 1, "storage_begin"); + if (lua_isnumber (L, -1)) + first = lua_tonumber (L, -1); + lua_pop (L, 1); + lua_getfield (L, 1, "storage_end"); + if (lua_isnumber (L, -1)) + last = lua_tonumber (L, -1); + lua_pop (L, 1); + } + else if (!lua_isnone (L, 1)) + return luaL_error (L, "expected table as arg #1"); + + rtc_fifo_prepare (0, interval_us, sensor_count); + + if (first != -1 && last != -1) + rtc_fifo_put_loc (first, last, sensor_count); + + return 0; +} + + +// ready = rtcfifo.ready () +static int rtcfifo_ready (lua_State *L) +{ + lua_pushnumber (L, rtc_fifo_check_magic ()); + return 1; +} + +static void check_fifo_magic (lua_State *L) +{ + if (!rtc_fifo_check_magic ()) + luaL_error (L, "rtcfifo not prepared!"); +} + + +// rtcfifo.put (timestamp, value, decimals, sensor_name) +static int rtcfifo_put (lua_State *L) +{ + check_fifo_magic (L); + + sample_t s; + s.timestamp = luaL_checknumber (L, 1); + double val = 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 { + uint32_t u; + char s[4]; + } conv = { 0 }; + strncpy (conv.s, str, len > 4 ? 4 : len); + s.tag = conv.u; + + rtc_fifo_store_sample (&s); + return 0; +} + + +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->decimals); + union { + uint32_t u; + char s[4]; + } conv = { s->tag }; + if (conv.s[3] == 0) + lua_pushstring (L, conv.s); + else + lua_pushlstring (L, conv.s, 4); + return 4; +} + + +// timestamp, value, decimals, sensor_name = rtcfifo.pop () +static int rtcfifo_pop (lua_State *L) +{ + check_fifo_magic (L); + + sample_t s; + if (!rtc_fifo_pop_sample (&s)) + return 0; + else + return extract_sample (L, &s); +} + + +// timestamp, value, decimals, sensor_name = rtcfifo.peek ([offset]) +static int rtcfifo_peek (lua_State *L) +{ + check_fifo_magic (L); + + sample_t s; + uint32_t offs = 0; + if (lua_isnumber (L, 1)) + offs = lua_tonumber (L, 1); + if (!rtc_fifo_peek_sample (&s, offs)) + return 0; + else + return extract_sample (L, &s); +} + + +// rtcfifo.drop (num) +static int rtcfifo_drop (lua_State *L) +{ + check_fifo_magic (L); + + rtc_fifo_drop_samples (luaL_checknumber (L, 1)); + return 0; +} + + +// num = rtcfifo.count () +static int rtcfifo_count (lua_State *L) +{ + check_fifo_magic (L); + + lua_pushnumber (L, rtc_fifo_get_count ()); + return 1; +} + + +#ifdef LUA_USE_MODULES_RTCTIME +// rtcfifo.dsleep_until_sample (min_sleep_us) +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 + return 0; +} +#endif + +// Module function map +#define MIN_OPT_LEVEL 2 +#include "lrodefs.h" +const LUA_REG_TYPE rtcfifo_map[] = +{ + { LSTRKEY("prepare"), LFUNCVAL(rtcfifo_prepare) }, + { LSTRKEY("ready"), LFUNCVAL(rtcfifo_ready) }, + { LSTRKEY("put"), LFUNCVAL(rtcfifo_put) }, + { LSTRKEY("pop"), LFUNCVAL(rtcfifo_pop) }, + { LSTRKEY("peek"), LFUNCVAL(rtcfifo_peek) }, + { LSTRKEY("drop"), LFUNCVAL(rtcfifo_drop) }, + { LSTRKEY("count"), LFUNCVAL(rtcfifo_count) }, +#ifdef LUA_USE_MODULES_RTCTIME + { LSTRKEY("dsleep_until_sample"), LFUNCVAL(rtcfifo_dsleep_until_sample) }, +#endif + { LNILKEY, LNILVAL } +}; + +LUALIB_API int luaopen_rtcfifo (lua_State *L) +{ +#if LUA_OPTIMIZE_MEMORY > 0 + return 0; +#else + luaL_register (L, AUXLIB_RTCFIFO, rtcfifo_map); + return 1; +#endif +} diff --git a/app/modules/rtctime.c b/app/modules/rtctime.c index bf19a2ff..d32a6e51 100644 --- a/app/modules/rtctime.c +++ b/app/modules/rtctime.c @@ -1,4 +1,4 @@ -// Module for RTC user memory access +// Module for RTC time keeping #include "lauxlib.h" #include "rtc/rtctime.h" From 09410d55c6da0b40df00e29b2be0a79577ab0a57 Mon Sep 17 00:00:00 2001 From: Johny Mattsson Date: Tue, 30 Jun 2015 11:51:33 +1000 Subject: [PATCH 4/5] Added SNTP module, for easy time sync. Integrates with the rtctime component if it's available. --- app/include/user_modules.h | 1 + app/modules/modules.h | 9 + app/modules/sntp.c | 370 +++++++++++++++++++++++++++++++++++++ 3 files changed, 380 insertions(+) create mode 100644 app/modules/sntp.c diff --git a/app/include/user_modules.h b/app/include/user_modules.h index d6115724..688c7bcd 100644 --- a/app/include/user_modules.h +++ b/app/include/user_modules.h @@ -38,6 +38,7 @@ #define LUA_USE_MODULES_RTCMEM #define LUA_USE_MODULES_RTCTIME #define LUA_USE_MODULES_RTCFIFO +#define LUA_USE_MODULES_SNTP #endif /* LUA_USE_MODULES */ diff --git a/app/modules/modules.h b/app/modules/modules.h index 5c270c36..1bb26fb5 100644 --- a/app/modules/modules.h +++ b/app/modules/modules.h @@ -205,6 +205,14 @@ #define ROM_MODULES_RTCFIFO #endif +#if defined(LUA_USE_MODULES_SNTP) +#define MODULES_SNTP "sntp" +#define ROM_MODULES_SNTP \ + _ROM(MODULES_SNTP, luaopen_sntp, sntp_map) +#else +#define ROM_MODULES_SNTP +#endif + #define LUA_MODULES_ROM \ ROM_MODULES_GPIO \ ROM_MODULES_PWM \ @@ -231,5 +239,6 @@ ROM_MODULES_RTCMEM \ ROM_MODULES_RTCTIME \ ROM_MODULES_RTCFIFO \ + ROM_MODULES_SNTP \ #endif diff --git a/app/modules/sntp.c b/app/modules/sntp.c new file mode 100644 index 00000000..bb0deda3 --- /dev/null +++ b/app/modules/sntp.c @@ -0,0 +1,370 @@ +/* + * 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 Johny Mattsson + */ + +// Module for Simple Network Time Protocol (SNTP) + +#include "lauxlib.h" +#include "os_type.h" +#include "osapi.h" +#include "lwip/udp.h" +#include "c_stdlib.h" +#include "user_modules.h" + +#ifdef LUA_USE_MODULES_RTCTIME +#include "rtc/rtctime.h" +#endif + +#define NTP_PORT 123 +#define NTP_ANYCAST_ADDR(dst) IP4_ADDR(dst, 224, 0, 1, 1) + +#define MAX_ATTEMPTS 5 + +#if 0 +# define sntp_dbg(...) c_printf(__VA_ARGS__) +#else +# define sntp_dbg(...) +#endif + +typedef struct +{ + uint32_t sec; + uint32_t frac; +} ntp_timestamp_t; + +typedef struct +{ + uint8_t mode : 3; + uint8_t ver : 3; + uint8_t LI : 2; + uint8_t stratum; + uint8_t poll; + uint8_t precision; + uint32_t root_delay; + uint32_t root_dispersion; + uint32_t refid; + ntp_timestamp_t ref; + ntp_timestamp_t origin; + ntp_timestamp_t recv; + ntp_timestamp_t xmit; +} ntp_frame_t; + +typedef struct +{ + struct udp_pcb *pcb; + ntp_timestamp_t cookie; + os_timer_t timer; + int sync_cb_ref; + int err_cb_ref; + uint8_t attempts; +} sntp_state_t; + +static sntp_state_t *state; +static ip_addr_t server; + +static void cleanup (lua_State *L) +{ + os_timer_disarm (&state->timer); + udp_remove (state->pcb); + luaL_unref (L, LUA_REGISTRYINDEX, state->sync_cb_ref); + luaL_unref (L, LUA_REGISTRYINDEX, state->err_cb_ref); + os_free (state); + state = 0; +} + + +static void handle_error (lua_State *L) +{ + sntp_dbg("sntp: handle_error\n"); + if (state->err_cb_ref != LUA_NOREF) + { + lua_rawgeti (L, LUA_REGISTRYINDEX, state->err_cb_ref); + cleanup (L); + lua_call (L, 0, 0); + } + else + cleanup (L); +} + + +static void sntp_dosend (lua_State *L) +{ + ++state->attempts; + sntp_dbg("sntp: attempt %d\n", state->attempts); + + struct pbuf *p = pbuf_alloc (PBUF_TRANSPORT, sizeof (ntp_frame_t), PBUF_RAM); + if (!p) + handle_error (L); + + ntp_frame_t req; + os_memset (&req, 0, sizeof (req)); + req.ver = 4; + req.mode = 3; // client +#ifdef LUA_USE_MODULES_RTCTIME + struct rtc_timeval tv; + rtc_time_gettimeofday (&tv, CPU_DEFAULT_MHZ); + req.xmit.sec = htonl (tv.tv_sec); + req.xmit.frac = htonl (tv.tv_usec); +#else + req.xmit.frac = htonl (system_get_time ()); +#endif + state->cookie = req.xmit; + + os_memcpy (p->payload, &req, sizeof (req)); + int ret = udp_sendto (state->pcb, p, &server, NTP_PORT); + sntp_dbg("sntp: send: %d\n", ret); + pbuf_free (p); + if (ret != ERR_OK) + handle_error (L); +} + + +static void on_timeout (void *arg) +{ + sntp_dbg("sntp: timer\n"); + lua_State *L = arg; + if (state->attempts >= MAX_ATTEMPTS) + handle_error (L); + else + sntp_dosend (L); +} + + +static void on_recv (void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr, uint16_t port) +{ + (void)port; + sntp_dbg("sntp: on_recv\n"); + + lua_State *L = arg; + + if (!state || state->pcb != pcb) + { + // "impossible", but don't leak if it did happen somehow... + udp_remove (pcb); + pbuf_free (p); + return; + } + + if (!p) + return; + + if (p->len < sizeof (ntp_frame_t)) + { + pbuf_free (p); + return; // not an ntp frame, ignore + } + + // make sure we have an aligned copy to work from + ntp_frame_t ntp; + os_memcpy (&ntp, p->payload, sizeof (ntp)); + pbuf_free (p); + sntp_dbg("sntp: transmit timestamp: %u, %u\n", ntp.xmit.sec, ntp.xmit.frac); + + // sanity checks before we touch our clocks + ip_addr_t anycast; + NTP_ANYCAST_ADDR(&anycast); + if (server.addr != anycast.addr && server.addr != addr->addr) + return; // unknown sender, ignore + + if (ntp.origin.sec != state->cookie.sec || + ntp.origin.frac != state->cookie.frac) + return; // unsolicited message, ignore + + if (ntp.LI == 3) + return; // server clock not synchronized (why did it even respond?!) + + server.addr = addr->addr; + ntp.origin.sec = ntohl (ntp.origin.sec); + ntp.origin.frac = ntohl (ntp.origin.frac); + ntp.recv.sec = ntohl (ntp.recv.sec); + ntp.recv.frac = ntohl (ntp.recv.frac); + ntp.xmit.sec = ntohl (ntp.xmit.sec); + ntp.xmit.frac = ntohl (ntp.xmit.frac); + + const uint32_t UINT32_MAXI = (uint32_t)-1; + const uint64_t MICROSECONDS = 1000000ull; + const uint32_t NTP_TO_UNIX_EPOCH = 2208988800ul; + + bool have_cb = (state->sync_cb_ref != LUA_NOREF); + + // if we have rtctime, do higher resolution delta calc, else just use + // the transmit timestamp +#ifdef LUA_USE_MODULES_RTCTIME + struct rtc_timeval tv; + + rtc_time_gettimeofday (&tv, CPU_DEFAULT_MHZ); + ntp_timestamp_t dest; + dest.sec = tv.tv_sec; + dest.frac = (MICROSECONDS * tv.tv_usec) / UINT32_MAXI; + + // Compensation as per RFC2030 + int64_t delta_s = (((int64_t)ntp.recv.sec - ntp.origin.sec) + + ((int64_t)ntp.xmit.sec - dest.sec)) / 2; + + int64_t delta_f = (((int64_t)ntp.recv.frac - ntp.origin.frac) + + ((int64_t)ntp.xmit.frac - dest.frac)) / 2; + + dest.sec += delta_s; + if (delta_f + dest.frac < 0) + { + delta_f += UINT32_MAXI; + --dest.sec; + } + else if (delta_f + dest.frac > UINT32_MAXI) + { + delta_f -= UINT32_MAXI; + ++dest.sec; + } + dest.frac += delta_f; + + 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); + + if (have_cb) + { + lua_rawgeti (L, LUA_REGISTRYINDEX, state->sync_cb_ref); + lua_pushnumber (L, tv.tv_sec); + lua_pushnumber (L, tv.tv_usec); + } +#else + if (have_cb) + { + lua_rawgeti (L, LUA_REGISTRYINDEX, state->sync_cb_ref); + lua_pushnumber (L, ntp.xmit.sec - NTP_TO_UNIX_EPOCH); + lua_pushnumber (L, (MICROSECONDS * ntp.xmit.frac) / UINT32_MAXI); + } +#endif + + cleanup (L); + + if (have_cb) + { + lua_pushstring (L, ipaddr_ntoa (&server)); + lua_call (L, 3, 0); + } +} + + +// sntp.sync (server or nil, syncfn or nil, errfn or nil) +static int sntp_sync (lua_State *L) +{ + // default to anycast address, then allow last server to stick + if (server.addr == IPADDR_ANY) + NTP_ANYCAST_ADDR(&server); + + const char *errmsg = 0; + #define sync_err(x) do { errmsg = x; goto error; } while (0) + + if (state) + return luaL_error (L, "sync in progress"); + + state = (sntp_state_t *)c_malloc (sizeof (sntp_state_t)); + if (!state) + sync_err ("out of memory"); + + memset (state, 0, sizeof (sntp_state_t)); + + state->sync_cb_ref = LUA_NOREF; + state->err_cb_ref = LUA_NOREF; + + state->pcb = udp_new (); + if (!state->pcb) + sync_err ("out of memory"); + + if (udp_bind (state->pcb, IP_ADDR_ANY, NTP_PORT) != ERR_OK) + sync_err ("ntp port in use"); + + udp_recv (state->pcb, on_recv, L); + + // use last server, unless new one specified + if (!lua_isnoneornil (L, 1)) + { + if (!ipaddr_aton (luaL_checkstring (L, 1), &server)) + sync_err ("bad IP address"); + } + + if (!lua_isnoneornil (L, 2)) + { + lua_pushvalue (L, 2); + state->sync_cb_ref = luaL_ref (L, LUA_REGISTRYINDEX); + } + else + state->sync_cb_ref = LUA_NOREF; + + if (!lua_isnoneornil (L, 3)) + { + lua_pushvalue (L, 3); + state->err_cb_ref = luaL_ref (L, LUA_REGISTRYINDEX); + } + else + state->err_cb_ref = LUA_NOREF; + + os_timer_disarm (&state->timer); + os_timer_setfn (&state->timer, on_timeout, L); + os_timer_arm (&state->timer, 1000, 1); + + state->attempts = 0; + sntp_dosend (L); + return 0; + +error: + if (state) + { + if (state->pcb) + udp_remove (state->pcb); + c_free (state); + state = 0; + } + return luaL_error (L, errmsg); +} + + +// Module function map +#define MIN_OPT_LEVEL 2 +#include "lrodefs.h" +const LUA_REG_TYPE sntp_map[] = +{ + { LSTRKEY("sync"), LFUNCVAL(sntp_sync) }, + { LNILKEY, LNILVAL } +}; + +LUALIB_API int luaopen_sntp (lua_State *L) +{ +#if LUA_OPTIMIZE_MEMORY > 0 + return 0; +#else + luaL_register (L, AUXLIB_SNTP, sntp_map); + return 1; +#endif +} From dd38a0a0e6138994772a79cf4d556fd75d2ebff4 Mon Sep 17 00:00:00 2001 From: Johny Mattsson Date: Mon, 6 Jul 2015 13:45:35 +1000 Subject: [PATCH 5/5] Imported reworked rtctime support. --- app/include/rom.h | 4 + app/include/rtc/rtcaccess.h | 6 +- app/include/rtc/rtcfifo.h | 13 +- app/include/rtc/rtctime.h | 483 +------------------ app/include/rtc/rtctime_internal.h | 737 +++++++++++++++++++++++++++++ app/modules/rtcfifo.c | 16 +- app/modules/rtctime.c | 73 ++- app/modules/sntp.c | 7 +- app/user/user_main.c | 10 +- 9 files changed, 840 insertions(+), 509 deletions(-) create mode 100644 app/include/rtc/rtctime_internal.h diff --git a/app/include/rom.h b/app/include/rom.h index f9dfb7e7..ca6227fe 100644 --- a/app/include/rom.h +++ b/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 diff --git a/app/include/rtc/rtcaccess.h b/app/include/rtc/rtcaccess.h index b4010ec8..adcb4c46 100644 --- a/app/include/rtc/rtcaccess.h +++ b/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; } diff --git a/app/include/rtc/rtcfifo.h b/app/include/rtc/rtcfifo.h index b2000fd4..a9d6e7ef 100644 --- a/app/include/rtc/rtcfifo.h +++ b/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) diff --git a/app/include/rtc/rtctime.h b/app/include/rtc/rtctime.h index 88c21ec5..574b5013 100644 --- a/app/include/rtc/rtctime.h +++ b/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 * @author Johny Mattsson */ +#ifndef _RTCTIME_H_ +#define _RTCTIME_H_ -#ifndef RTCTIME_H -#define RTCTIME_H +/* 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. + */ -#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) - -static inline uint64_t rtc_time_get_now_us_adjusted(uint32_t mhz); +#include +#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=(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 (before0 && 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 + * @author Johny Mattsson + */ + +#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 +#include +#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=(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_afterafter); + + 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<>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 diff --git a/app/modules/rtcfifo.c b/app/modules/rtcfifo.c index 5cb29bd2..20492ffa 100644 --- a/app/modules/rtcfifo.c +++ b/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 diff --git a/app/modules/rtctime.c b/app/modules/rtctime.c index d32a6e51..2ee298dd 100644 --- a/app/modules/rtctime.c +++ b/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 } diff --git a/app/modules/sntp.c b/app/modules/sntp.c index bb0deda3..f2162878 100644 --- a/app/modules/sntp.c +++ b/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) { diff --git a/app/user/user_main.c b/app/user/user_main.c index 4492ffdb..c965a424 100644 --- a/app/user/user_main.c +++ b/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());