From dd38a0a0e6138994772a79cf4d556fd75d2ebff4 Mon Sep 17 00:00:00 2001 From: Johny Mattsson Date: Mon, 6 Jul 2015 13:45:35 +1000 Subject: [PATCH] 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());