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