343 lines
10 KiB
C
Executable File
343 lines
10 KiB
C
Executable File
/*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
|
|
|
|
/*-------------------------------------
|
|
NEW TIMER API
|
|
---------------------------------------
|
|
|
|
tmr.wdclr() -- not changed
|
|
tmr.now() -- not changed
|
|
tmr.time() -- not changed
|
|
tmr.delay() -- not changed
|
|
tmr.alarm() -- not changed
|
|
tmr.stop() -- changed, see below. use tmr.unregister for old functionality
|
|
|
|
tmr.register(id, interval, mode, function)
|
|
bind function with timer and set the interval in ms
|
|
the mode can be:
|
|
tmr.ALARM_SINGLE for a single run alarm
|
|
tmr.ALARM_SEMI for a multiple single run alarm
|
|
tmr.ALARM_AUTO for a repating alarm
|
|
tmr.register does NOT start the timer
|
|
tmr.alarm is a tmr.register & tmr.start macro
|
|
tmr.unregister(id)
|
|
stop alarm, unbind function and clean up memory
|
|
not needed for ALARM_SINGLE, as it unregisters itself
|
|
tmr.start(id)
|
|
ret: bool
|
|
start a alarm, returns true on success
|
|
tmr.stop(id)
|
|
ret: bool
|
|
stops a alarm, returns true on success
|
|
this call dose not free any memory, to do so use tmr.unregister
|
|
stopped alarms can be started with start
|
|
tmr.interval(id, interval)
|
|
set alarm interval, running alarm will be restarted
|
|
tmr.state(id)
|
|
ret: (bool, int) or nil
|
|
returns alarm status (true=started/false=stopped) and mode
|
|
nil if timer is unregistered
|
|
tmr.softwd(int)
|
|
set a negative value to stop the timer
|
|
any other value starts the timer, when the
|
|
countdown reaches zero, the device restarts
|
|
the timer units are seconds
|
|
*/
|
|
|
|
#include "module.h"
|
|
#include "lauxlib.h"
|
|
#include "platform.h"
|
|
#include "c_types.h"
|
|
#include "user_interface.h"
|
|
|
|
#define TIMER_MODE_OFF 3
|
|
#define TIMER_MODE_SINGLE 0
|
|
#define TIMER_MODE_SEMI 2
|
|
#define TIMER_MODE_AUTO 1
|
|
#define TIMER_IDLE_FLAG (1<<7)
|
|
|
|
typedef struct{
|
|
os_timer_t os;
|
|
sint32_t lua_ref;
|
|
uint32_t interval;
|
|
uint8_t mode;
|
|
}timer_struct_t;
|
|
typedef timer_struct_t* timer_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 timer_struct_t alarm_timers[NUM_TMR];
|
|
static os_timer_t rtc_timer;
|
|
|
|
static void alarm_timer_common(void* arg){
|
|
timer_t tmr = &alarm_timers[(uint32_t)arg];
|
|
lua_State* L = lua_getstate();
|
|
if(tmr->lua_ref == LUA_NOREF)
|
|
return;
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, tmr->lua_ref);
|
|
//if the timer was set to single run we clean up after it
|
|
if(tmr->mode == TIMER_MODE_SINGLE){
|
|
luaL_unref(L, LUA_REGISTRYINDEX, tmr->lua_ref);
|
|
tmr->lua_ref = LUA_NOREF;
|
|
tmr->mode = TIMER_MODE_OFF;
|
|
}else if(tmr->mode == TIMER_MODE_SEMI){
|
|
tmr->mode |= TIMER_IDLE_FLAG;
|
|
}
|
|
lua_call(L, 0, 0);
|
|
}
|
|
|
|
// Lua: tmr.delay( us )
|
|
static int tmr_delay( lua_State* L ){
|
|
sint32_t us = luaL_checkinteger(L, 1);
|
|
if(us <= 0)
|
|
return luaL_error(L, "wrong arg range");
|
|
while(us >= 1000000){
|
|
us -= 1000000;
|
|
os_delay_us(1000000);
|
|
system_soft_wdt_feed ();
|
|
}
|
|
if(us>0){
|
|
os_delay_us(us);
|
|
system_soft_wdt_feed ();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Lua: tmr.now() , return system timer in us
|
|
static int tmr_now(lua_State* L){
|
|
uint32_t now = 0x7FFFFFFF & system_get_time();
|
|
lua_pushinteger(L, now);
|
|
return 1;
|
|
}
|
|
|
|
// Lua: tmr.register( id, interval, mode, function )
|
|
static int tmr_register(lua_State* L){
|
|
uint32_t id = luaL_checkinteger(L, 1);
|
|
MOD_CHECK_ID(tmr, id);
|
|
sint32_t interval = luaL_checkinteger(L, 2);
|
|
uint8_t mode = luaL_checkinteger(L, 3);
|
|
//validate arguments
|
|
const int32_t MAX_TIMEOUT = 0xC49BA5; // assuming system_timer_reinit() has *not* been called
|
|
uint8_t args_invalid = (interval <= 0 || interval > MAX_TIMEOUT)
|
|
|| (mode != TIMER_MODE_SINGLE && mode != TIMER_MODE_SEMI && mode != TIMER_MODE_AUTO)
|
|
|| (lua_type(L, 4) != LUA_TFUNCTION && lua_type(L, 4) != LUA_TLIGHTFUNCTION);
|
|
if(args_invalid)
|
|
return luaL_error(L, "wrong arg range");
|
|
//get the lua function reference
|
|
lua_pushvalue(L, 4);
|
|
sint32_t ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
timer_t tmr = &alarm_timers[id];
|
|
if(!(tmr->mode & TIMER_IDLE_FLAG) && tmr->mode != TIMER_MODE_OFF)
|
|
ets_timer_disarm(&tmr->os);
|
|
//there was a bug in this part, the second part of the following condition was missing
|
|
if(tmr->lua_ref != LUA_NOREF && tmr->lua_ref != ref)
|
|
luaL_unref(L, LUA_REGISTRYINDEX, tmr->lua_ref);
|
|
tmr->lua_ref = ref;
|
|
tmr->mode = mode|TIMER_IDLE_FLAG;
|
|
tmr->interval = interval;
|
|
ets_timer_setfn(&tmr->os, alarm_timer_common, (void*)id);
|
|
return 0;
|
|
}
|
|
|
|
// Lua: tmr.start( id )
|
|
static int tmr_start(lua_State* L){
|
|
uint8_t id = luaL_checkinteger(L, 1);
|
|
MOD_CHECK_ID(tmr,id);
|
|
timer_t tmr = &alarm_timers[id];
|
|
//we return false if the timer is not idle
|
|
if(!(tmr->mode&TIMER_IDLE_FLAG)){
|
|
lua_pushboolean(L, 0);
|
|
}else{
|
|
tmr->mode &= ~TIMER_IDLE_FLAG;
|
|
ets_timer_arm_new(&tmr->os, tmr->interval, tmr->mode==TIMER_MODE_AUTO, 1);
|
|
lua_pushboolean(L, 1);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// Lua: tmr.alarm( id, interval, repeat, function )
|
|
static int tmr_alarm(lua_State* L){
|
|
tmr_register(L);
|
|
return tmr_start(L);
|
|
}
|
|
|
|
// Lua: tmr.stop( id )
|
|
static int tmr_stop(lua_State* L){
|
|
uint8_t id = luaL_checkinteger(L, 1);
|
|
MOD_CHECK_ID(tmr,id);
|
|
timer_t tmr = &alarm_timers[id];
|
|
//we return false if the timer is idle (of not registered)
|
|
if(!(tmr->mode & TIMER_IDLE_FLAG) && tmr->mode != TIMER_MODE_OFF){
|
|
tmr->mode |= TIMER_IDLE_FLAG;
|
|
ets_timer_disarm(&tmr->os);
|
|
lua_pushboolean(L, 1);
|
|
}else{
|
|
lua_pushboolean(L, 0);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// Lua: tmr.unregister( id )
|
|
static int tmr_unregister(lua_State* L){
|
|
uint8_t id = luaL_checkinteger(L, 1);
|
|
MOD_CHECK_ID(tmr,id);
|
|
timer_t tmr = &alarm_timers[id];
|
|
if(!(tmr->mode & TIMER_IDLE_FLAG) && tmr->mode != TIMER_MODE_OFF)
|
|
ets_timer_disarm(&tmr->os);
|
|
if(tmr->lua_ref != LUA_NOREF)
|
|
luaL_unref(L, LUA_REGISTRYINDEX, tmr->lua_ref);
|
|
tmr->lua_ref = LUA_NOREF;
|
|
tmr->mode = TIMER_MODE_OFF;
|
|
return 0;
|
|
}
|
|
|
|
// Lua: tmr.interval( id, interval )
|
|
static int tmr_interval(lua_State* L){
|
|
uint8_t id = luaL_checkinteger(L, 1);
|
|
MOD_CHECK_ID(tmr,id);
|
|
timer_t tmr = &alarm_timers[id];
|
|
sint32_t interval = luaL_checkinteger(L, 2);
|
|
if(interval <= 0)
|
|
return luaL_error(L, "wrong arg range");
|
|
if(tmr->mode != TIMER_MODE_OFF){
|
|
tmr->interval = interval;
|
|
if(!(tmr->mode&TIMER_IDLE_FLAG)){
|
|
ets_timer_disarm(&tmr->os);
|
|
ets_timer_arm_new(&tmr->os, tmr->interval, tmr->mode==TIMER_MODE_AUTO, 1);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Lua: tmr.state( id )
|
|
static int tmr_state(lua_State* L){
|
|
uint8_t id = luaL_checkinteger(L, 1);
|
|
MOD_CHECK_ID(tmr,id);
|
|
timer_t tmr = &alarm_timers[id];
|
|
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;
|
|
}
|
|
|
|
/*I left the led comments 'couse I don't know
|
|
why they are here*/
|
|
|
|
// extern void update_key_led();
|
|
// Lua: tmr.wdclr()
|
|
static int tmr_wdclr( lua_State* L ){
|
|
system_soft_wdt_feed ();
|
|
// update_key_led();
|
|
return 0;
|
|
}
|
|
|
|
//system_rtc_clock_cali_proc() returns
|
|
//a fixed point value (12 bit fraction part)
|
|
//it tells how many rtc clock ticks represent 1us.
|
|
//the high 64 bits of the uint64_t multiplication
|
|
//are unnedded (I did the math)
|
|
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 ){
|
|
soft_watchdog = luaL_checkinteger(L, 1);
|
|
return 0;
|
|
}
|
|
|
|
// Module function map
|
|
|
|
static const LUA_REG_TYPE tmr_map[] = {
|
|
{ LSTRKEY( "delay" ), LFUNCVAL( tmr_delay ) },
|
|
{ LSTRKEY( "now" ), LFUNCVAL( tmr_now ) },
|
|
{ LSTRKEY( "wdclr" ), LFUNCVAL( tmr_wdclr ) },
|
|
{ LSTRKEY( "softwd" ), LFUNCVAL( tmr_softwd ) },
|
|
{ LSTRKEY( "time" ), LFUNCVAL( tmr_time ) },
|
|
{ LSTRKEY( "register" ), LFUNCVAL( tmr_register ) },
|
|
{ LSTRKEY( "alarm" ), LFUNCVAL( tmr_alarm ) },
|
|
{ LSTRKEY( "start" ), LFUNCVAL( tmr_start ) },
|
|
{ LSTRKEY( "stop" ), LFUNCVAL( tmr_stop ) },
|
|
{ LSTRKEY( "unregister" ), LFUNCVAL( tmr_unregister ) },
|
|
{ LSTRKEY( "state" ), LFUNCVAL( tmr_state ) },
|
|
{ LSTRKEY( "interval" ), LFUNCVAL( tmr_interval) },
|
|
{ LSTRKEY( "ALARM_SINGLE" ), LNUMVAL( TIMER_MODE_SINGLE ) },
|
|
{ LSTRKEY( "ALARM_SEMI" ), LNUMVAL( TIMER_MODE_SEMI ) },
|
|
{ LSTRKEY( "ALARM_AUTO" ), LNUMVAL( TIMER_MODE_AUTO ) },
|
|
{ LNILKEY, LNILVAL }
|
|
};
|
|
|
|
int luaopen_tmr( lua_State *L ){
|
|
int i;
|
|
for(i=0; i<NUM_TMR; i++){
|
|
alarm_timers[i].lua_ref = LUA_NOREF;
|
|
alarm_timers[i].mode = TIMER_MODE_OFF;
|
|
ets_timer_disarm(&alarm_timers[i].os);
|
|
}
|
|
last_rtc_time=system_get_rtc_time(); // Right now is time 0
|
|
last_rtc_time_us=0;
|
|
|
|
ets_timer_disarm(&rtc_timer);
|
|
ets_timer_setfn(&rtc_timer, rtc_callback, NULL);
|
|
ets_timer_arm_new(&rtc_timer, 1000, 1, 1);
|
|
return 0;
|
|
}
|
|
|
|
NODEMCU_MODULE(TMR, "tmr", tmr_map, luaopen_tmr);
|