diff --git a/app/include/rom.h b/app/include/rom.h index de031179..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 @@ -38,6 +41,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); @@ -94,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 new file mode 100644 index 00000000..adcb4c46 --- /dev/null +++ b/app/include/rtc/rtcaccess.h @@ -0,0 +1,57 @@ +#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/rtc/rtcfifo.h b/app/include/rtc/rtcfifo.h new file mode 100644 index 00000000..a9d6e7ef --- /dev/null +++ b/app/include/rtc/rtcfifo.h @@ -0,0 +1,490 @@ +/* + * 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 10 +#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)) + +#ifndef RTCTIME_SLEEP_ALIGNED +# define RTCTIME_SLEEP_ALIGNED rtc_time_deep_sleep_until_aligned +#endif + +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 & 0xffff) + ((deltat & 0x1ff) <<16) + + ((decimals & 0x7)<<25) + ((tagindex & 0xf)<<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 align=rtc_mem_read(RTC_ALIGNMENT_POS); + 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) +{ + 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/rtc/rtctime.h b/app/include/rtc/rtctime.h new file mode 100644 index 00000000..574b5013 --- /dev/null +++ b/app/include/rtc/rtctime.h @@ -0,0 +1,59 @@ +/* + * 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 + */ +#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 + +#ifndef _RTCTIME_INTERNAL_H_ +struct rtc_timeval +{ + uint32_t tv_sec; + uint32_t tv_usec; +}; +#endif + +void TEXT_SECTION_ATTR rtctime_early_startup (void); +void rtctime_late_startup (void); +void rtctime_gettimeofday (struct rtc_timeval *tv); +void rtctime_settimeofday (const struct rtc_timeval *tv); +bool rtctime_have_time (void); +void rtctime_deep_sleep_us (uint32_t us); +void rtctime_deep_sleep_until_aligned_us (uint32_t align_us, uint32_t min_us); + +#endif diff --git a/app/include/rtc/rtctime_internal.h b/app/include/rtc/rtctime_internal.h new file mode 100644 index 00000000..0c9a3a5a --- /dev/null +++ b/app/include/rtc/rtctime_internal.h @@ -0,0 +1,737 @@ +/* + * Copyright 2015 Dius Computing Pty Ltd. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * - Neither the name of the copyright holders nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Bernd Meyer + * @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/include/user_modules.h b/app/include/user_modules.h index 875a017f..688c7bcd 100644 --- a/app/include/user_modules.h +++ b/app/include/user_modules.h @@ -35,6 +35,10 @@ #define LUA_USE_MODULES_CRYPTO #define LUA_USE_MODULES_RC #define LUA_USE_MODULES_DHT +#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 ad0c4cfa..1bb26fb5 100644 --- a/app/modules/modules.h +++ b/app/modules/modules.h @@ -181,6 +181,38 @@ #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 + +#if defined(LUA_USE_MODULES_RTCTIME) +#define MODULES_RTCTIME "rtctime" +#define ROM_MODULES_RTCTIME \ + _ROM(MODULES_RTCTIME, luaopen_rtctime, rtctime_map) +#else +#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 + +#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 \ @@ -203,6 +235,10 @@ ROM_MODULES_CJSON \ ROM_MODULES_CRYPTO \ ROM_MODULES_RC \ - ROM_MODULES_DHT + ROM_MODULES_DHT \ + ROM_MODULES_RTCMEM \ + ROM_MODULES_RTCTIME \ + ROM_MODULES_RTCFIFO \ + ROM_MODULES_SNTP \ #endif diff --git a/app/modules/rtcfifo.c b/app/modules/rtcfifo.c new file mode 100644 index 00000000..20492ffa --- /dev/null +++ b/app/modules/rtcfifo.c @@ -0,0 +1,192 @@ +// Module for RTC sample FIFO storage + +#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}]) +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); + s.value = luaL_checknumber (L, 2); + s.decimals = luaL_checknumber (L, 3); + 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); + lua_pushnumber (L, s->value); + 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); // 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/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 +} diff --git a/app/modules/rtctime.c b/app/modules/rtctime.c new file mode 100644 index 00000000..2ee298dd --- /dev/null +++ b/app/modules/rtctime.c @@ -0,0 +1,137 @@ +// Module for RTC time keeping + +#include "lauxlib.h" + +#include "rtc/rtctime_internal.h" +#include "rtc/rtctime.h" + + +// ******* 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 (); + + uint32_t sec = luaL_checknumber (L, 1); + uint32_t usec = 0; + if (lua_isnumber (L, 2)) + usec = lua_tonumber (L, 2); + + struct rtc_timeval tv = { sec, usec }; + rtctime_settimeofday (&tv); + return 0; +} + + +// sec, usec = rtctime.get () +static int rtctime_get (lua_State *L) +{ + struct rtc_timeval tv; + rtctime_gettimeofday (&tv); + lua_pushnumber (L, tv.tv_sec); + lua_pushnumber (L, tv.tv_usec); + return 2; +} + +static void do_sleep_opt (lua_State *L, int idx) +{ + if (lua_isnumber (L, idx)) + { + uint32_t opt = lua_tonumber (L, idx); + if (opt < 0 || opt > 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); + rtctime_deep_sleep_us (us); // does not return + return 0; +} + + +// rtctime.dsleep_aligned (aligned_usec, min_usec, option) +static int rtctime_dsleep_aligned (lua_State *L) +{ + 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); + rtctime_deep_sleep_until_aligned_us (align_us, min_us); // does not return + return 0; +} + + +// Module function map +#define MIN_OPT_LEVEL 2 +#include "lrodefs.h" +const LUA_REG_TYPE rtctime_map[] = +{ + { LSTRKEY("set"), LFUNCVAL(rtctime_set) }, + { LSTRKEY("get"), LFUNCVAL(rtctime_get) }, + { 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/modules/sntp.c b/app/modules/sntp.c new file mode 100644 index 00000000..f2162878 --- /dev/null +++ b/app/modules/sntp.c @@ -0,0 +1,369 @@ +/* + * 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; + rtctime_gettimeofday (&tv); + 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; + + rtctime_gettimeofday (&tv); + 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; + rtctime_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 +} diff --git a/app/user/user_main.c b/app/user/user_main.c index c24e0f00..c965a424 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; @@ -31,12 +36,16 @@ 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 + // Note: Keep this as close to call_user_start() as possible, since it + // is where the cpu clock actually gets bumped to 80MHz. + rtctime_early_startup (); +#endif call_user_start (); } @@ -141,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());