forked from nodemcu/nodemcu-firmware
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtmr.c
352 lines (300 loc) · 10.8 KB
/
tmr.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
/*guys, srsly, turn on warnings in the makefile*/
#if defined(__GNUC__)
#pragma GCC diagnostic warning "-Wall"
#pragma GCC diagnostic warning "-Wextra"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#endif
/* See docs/modules/tmr.md for documentaiton o current API */
#include "module.h"
#include "lauxlib.h"
#include "platform.h"
#include <stdint.h>
#include "user_interface.h"
#include "pm/swtimer.h"
#define TIMER_MODE_SINGLE 0
#define TIMER_MODE_AUTO 1
#define TIMER_MODE_SEMI 2
#define TIMER_MODE_OFF 3
#define TIMER_IDLE_FLAG (1<<7)
#define STRINGIFY_VAL(x) #x
#define STRINGIFY(x) STRINGIFY_VAL(x)
// assuming system_timer_reinit() has *not* been called
#define MAX_TIMEOUT_DEF 0x68D7A3 // SDK specfied limit
static const uint32 MAX_TIMEOUT=MAX_TIMEOUT_DEF;
static const char* MAX_TIMEOUT_ERR_STR = "Range: 1-"STRINGIFY(MAX_TIMEOUT_DEF);
typedef struct{
os_timer_t os;
sint32_t lua_ref; /* Reference to registered callback function */
sint32_t self_ref; /* Reference to UD registered slot */
uint32_t interval;
uint8_t mode;
} tmr_t;
// The previous implementation extended the rtc counter to 64 bits, and then
// applied rtc2sec with the current calibration value to that 64 bit value.
// This means that *ALL* clock ticks since bootup are counted with the
// *current* clock period. In extreme cases (long uptime, sudden temperature
// change), this could result in tmr.time() going backwards....
//
// This implementation instead applies rtc2usec to short time intervals only
// (the longest being around 1 second), and then accumulates the resulting
// microseconds in a 64 bit counter. That's guaranteed to be monotonic, and
// should be a lot closer to representing an actual uptime.
static uint32_t rtc_time_cali=0;
static uint32_t last_rtc_time=0;
static uint64_t last_rtc_time_us=0;
static sint32_t soft_watchdog = -1;
static os_timer_t rtc_timer;
static void alarm_timer_common(void* arg){
tmr_t *tmr = (tmr_t *) arg;
if(tmr->lua_ref > 0) {
lua_State* L = lua_getstate();
lua_rawgeti(L, LUA_REGISTRYINDEX, tmr->lua_ref);
lua_rawgeti(L, LUA_REGISTRYINDEX, tmr->self_ref);
if (tmr->mode != TIMER_MODE_AUTO) {
if(tmr->mode == TIMER_MODE_SINGLE) {
luaL_unref2(L, LUA_REGISTRYINDEX, tmr->lua_ref);
luaL_unref2(L, LUA_REGISTRYINDEX, tmr->self_ref);
tmr->mode = TIMER_MODE_OFF;
} else if (tmr->mode == TIMER_MODE_SEMI) {
tmr->mode |= TIMER_IDLE_FLAG;
luaL_unref2(L, LUA_REGISTRYINDEX, tmr->self_ref);
}
}
luaL_pcallx(L, 1, 0);
}
}
// Lua: tmr.delay( us )
static int tmr_delay( lua_State* L ){
sint32_t us = luaL_checkinteger(L, 1);
luaL_argcheck(L, us>0, 1, "wrong arg range");
while(us > 0){
os_delay_us(us >= 1000000 ? 1000000 : us);
system_soft_wdt_feed ();
us -= 1000000;
}
return 0;
}
// Lua: tmr.now() , return system timer in us
static int tmr_now(lua_State* L){
lua_pushinteger(L, (uint32_t) (0x7FFFFFFF & system_get_time()));
return 1;
}
// Lua: tmr.ccount() , returns CCOUNT register
static int tmr_ccount(lua_State* L){
lua_pushinteger(L, CCOUNT_REG);
return 1;
}
/*
** Health warning: this is also called DIRECTLY from alarm() which assumes that the Lua
** stack is preserved for the following start(), so the stack MUST be balanced here.
*/
// Lua: t:register( interval, mode, function )
static int tmr_register(lua_State* L) {
tmr_t *tmr = (tmr_t *) luaL_checkudata(L, 1, "tmr.timer");
uint32_t interval = luaL_checkinteger(L, 2);
uint8_t mode = luaL_checkinteger(L, 3);
luaL_argcheck(L, (interval > 0 && interval <= MAX_TIMEOUT), 2, MAX_TIMEOUT_ERR_STR);
luaL_argcheck(L, (mode == TIMER_MODE_SINGLE || mode == TIMER_MODE_SEMI || mode == TIMER_MODE_AUTO), 3, "Invalid mode");
luaL_argcheck(L, lua_isfunction(L, 4), 4, "Must be function");
//get the lua function reference
lua_pushvalue(L, 4);
if(!(tmr->mode & TIMER_IDLE_FLAG) && tmr->mode != TIMER_MODE_OFF)
os_timer_disarm(&tmr->os);
luaL_reref(L, LUA_REGISTRYINDEX, &tmr->lua_ref);
tmr->mode = mode|TIMER_IDLE_FLAG;
tmr->interval = interval;
os_timer_setfn(&tmr->os, alarm_timer_common, tmr);
return 0;
}
// Lua: t:start( [restart] )
static int tmr_start(lua_State* L){
tmr_t *tmr = (tmr_t *) luaL_checkudata(L, 1, "tmr.timer");
lua_settop(L, 2);
luaL_argcheck(L, lua_isboolean(L, 2) || lua_isnil(L, 2), 2, "boolean expected");
int restart = lua_toboolean(L, 2);
lua_settop(L, 1); /* we need to have userdata on top of the stack */
if (tmr->self_ref == LUA_NOREF)
tmr->self_ref = luaL_ref(L, LUA_REGISTRYINDEX);
//we return false if the timer is not idle and is not to be restarted
int idle = tmr->mode&TIMER_IDLE_FLAG;
if(!(idle || restart)){
lua_pushboolean(L, false);
}else{
if (!idle) {os_timer_disarm(&tmr->os);}
tmr->mode &= ~TIMER_IDLE_FLAG;
os_timer_arm(&tmr->os, tmr->interval, tmr->mode==TIMER_MODE_AUTO);
lua_pushboolean(L, true);
}
return 1;
}
// Lua: t:alarm( interval, repeat, function )
static int tmr_alarm(lua_State* L){
tmr_register(L);
/* remove tmr.alarm's other then the 1st UD parameters from Lua stack.
tmr.start expects UD and optional restart parameter. */
lua_settop(L, 1);
return tmr_start(L);
}
// Lua: t:stop()
static int tmr_stop(lua_State* L){
tmr_t *tmr = (tmr_t *) luaL_checkudata(L, 1, "tmr.timer");
int idle = tmr->mode == TIMER_MODE_OFF || (tmr->mode & TIMER_IDLE_FLAG);
luaL_unref2(L, LUA_REGISTRYINDEX, tmr->self_ref);
if(!idle)
os_timer_disarm(&tmr->os);
tmr->mode |= TIMER_IDLE_FLAG;
lua_pushboolean(L, !idle); /* return false if the timer is idle (or not registered) */
return 1;
}
#ifdef TIMER_SUSPEND_ENABLE
#define TMR_SUSPEND_REMOVED_MSG "This feature has been removed, we apologize for any inconvenience this may have caused."
#define tmr_suspend tmr_suspend_removed
#define tmr_resume tmr_suspend_removed
#define tmr_suspend_all tmr_suspend_removed
#define tmr_resume_all tmr_suspend_removed
static int tmr_suspend_removed(lua_State* L){
return luaL_error(L, TMR_SUSPEND_REMOVED_MSG);
}
#endif
// Lua: t:unregister()
static int tmr_unregister(lua_State* L){
tmr_t *tmr = (tmr_t *) luaL_checkudata(L, 1, "tmr.timer");
luaL_unref2(L, LUA_REGISTRYINDEX, tmr->self_ref);
luaL_unref2(L, LUA_REGISTRYINDEX, tmr->lua_ref);
if(!(tmr->mode & TIMER_IDLE_FLAG) && tmr->mode != TIMER_MODE_OFF)
os_timer_disarm(&tmr->os);
tmr->mode = TIMER_MODE_OFF;
return 0;
}
// Lua: t:interval( interval )
static int tmr_interval(lua_State* L){
tmr_t *tmr = (tmr_t *) luaL_checkudata(L, 1, "tmr.timer");
uint32_t interval = luaL_checkinteger(L, 2);
luaL_argcheck(L, (interval > 0 && interval <= MAX_TIMEOUT), 2, MAX_TIMEOUT_ERR_STR);
if(tmr->mode != TIMER_MODE_OFF){
tmr->interval = interval;
if(!(tmr->mode&TIMER_IDLE_FLAG)){
os_timer_disarm(&tmr->os);
os_timer_arm(&tmr->os, tmr->interval, tmr->mode==TIMER_MODE_AUTO);
}
}
return 0;
}
// Lua: t:state()
static int tmr_state(lua_State* L){
tmr_t *tmr = (tmr_t *) luaL_checkudata(L, 1, "tmr.timer");
if(tmr->mode == TIMER_MODE_OFF){
lua_pushnil(L);
return 1;
}
lua_pushboolean(L, (tmr->mode & TIMER_IDLE_FLAG) == 0);
lua_pushinteger(L, tmr->mode & (~TIMER_IDLE_FLAG));
return 2;
}
// Lua: tmr.wdclr()
static int tmr_wdclr( lua_State* L ){
system_soft_wdt_feed ();
return 0;
}
// The on ESP8266 system_rtc_clock_cali_proc() returns a fixed point value
// (12 bit fraction part), giving how many rtc clock ticks represent 1us.
// The high 64 bits of the uint64_t multiplication are not needed)
static uint32_t rtc2usec(uint64_t rtc){
return (rtc*rtc_time_cali)>>12;
}
// This returns the number of microseconds uptime. Note that it relies on
// the rtc clock, which is notoriously temperature dependent
inline static uint64_t rtc_timer_update(bool do_calibration){
if (do_calibration || rtc_time_cali==0)
rtc_time_cali=system_rtc_clock_cali_proc();
uint32_t current = system_get_rtc_time();
uint32_t since_last=current-last_rtc_time; // This will transparently deal with wraparound
uint32_t us_since_last=rtc2usec(since_last);
uint64_t now=last_rtc_time_us+us_since_last;
// Only update if at least 100ms has passed since we last updated.
// This prevents the rounding errors in rtc2usec from accumulating
if (us_since_last>=100000){
last_rtc_time=current;
last_rtc_time_us=now;
}
return now;
}
void rtc_callback(void *arg){
rtc_timer_update(true);
if(soft_watchdog > 0){
soft_watchdog--;
if(soft_watchdog == 0)
system_restart();
}
}
// Lua: tmr.time() , return rtc time in second
static int tmr_time( lua_State* L ){
uint64_t us=rtc_timer_update(false);
lua_pushinteger(L, us/1000000);
return 1;
}
// Lua: tmr.softwd( value )
static int tmr_softwd( lua_State* L ){
int t = luaL_checkinteger(L, 1);
luaL_argcheck(L, t>0 , 2, "invalid time");
soft_watchdog = t;
return 0;
}
// Lua: tmr.create()
static int tmr_create( lua_State *L ) {
tmr_t *ud = (tmr_t *)lua_newuserdata(L, sizeof(*ud));
luaL_getmetatable(L, "tmr.timer");
lua_setmetatable(L, -2);
*ud = (tmr_t) {{0}, LUA_NOREF, LUA_NOREF, 0, TIMER_MODE_OFF};
return 1;
}
// Module function map
LROT_BEGIN(tmr_dyn, NULL, LROT_MASK_GC_INDEX)
LROT_FUNCENTRY( __gc, tmr_unregister )
LROT_TABENTRY( __index, tmr_dyn )
LROT_FUNCENTRY( register, tmr_register )
LROT_FUNCENTRY( alarm, tmr_alarm )
LROT_FUNCENTRY( start, tmr_start )
LROT_FUNCENTRY( stop, tmr_stop )
LROT_FUNCENTRY( unregister, tmr_unregister )
LROT_FUNCENTRY( state, tmr_state )
LROT_FUNCENTRY( interval, tmr_interval )
#ifdef TIMER_SUSPEND_ENABLE
LROT_FUNCENTRY( suspend, tmr_suspend )
LROT_FUNCENTRY( resume, tmr_resume )
#endif
LROT_END(tmr_dyn, NULL, LROT_MASK_GC_INDEX)
LROT_BEGIN(tmr, NULL, 0)
LROT_FUNCENTRY( delay, tmr_delay )
LROT_FUNCENTRY( now, tmr_now )
LROT_FUNCENTRY( wdclr, tmr_wdclr )
LROT_FUNCENTRY( softwd, tmr_softwd )
LROT_FUNCENTRY( time, tmr_time )
LROT_FUNCENTRY( ccount, tmr_ccount )
#ifdef TIMER_SUSPEND_ENABLE
LROT_FUNCENTRY( suspend_all, tmr_suspend_all )
LROT_FUNCENTRY( resume_all, tmr_resume_all )
#endif
LROT_FUNCENTRY( create, tmr_create )
LROT_NUMENTRY( ALARM_SINGLE, TIMER_MODE_SINGLE )
LROT_NUMENTRY( ALARM_SEMI, TIMER_MODE_SEMI )
LROT_NUMENTRY( ALARM_AUTO, TIMER_MODE_AUTO )
LROT_END(tmr, NULL, 0)
#include "pm/swtimer.h"
int luaopen_tmr( lua_State *L ){
luaL_rometatable(L, "tmr.timer", LROT_TABLEREF(tmr_dyn));
last_rtc_time=system_get_rtc_time(); // Right now is time 0
last_rtc_time_us=0;
os_timer_disarm(&rtc_timer);
os_timer_setfn(&rtc_timer, rtc_callback, NULL);
os_timer_arm(&rtc_timer, 1000, 1);
// The function rtc_callback calls the a function that calibrates the SoftRTC
// for drift in the esp8266's clock. My guess: after the duration of light_sleep
// there is bound to be some drift in the clock, so a calibration is due.
SWTIMER_REG_CB(rtc_callback, SWTIMER_RESUME);
// The function alarm_timer_common handles timers created by the developer via
// tmr.create(). No reason not to resume the timers, so resume em'.
SWTIMER_REG_CB(alarm_timer_common, SWTIMER_RESUME);
return 0;
}
NODEMCU_MODULE(TMR, "tmr", tmr, luaopen_tmr);