I'm trying to make a remotely controlled servo motor controller on ESP8266 which is controlled by a server.
The problem I'm facing is how to make an asynchronous timer, like tmr.alarm()
, but in microseconds. tmr.delay()
doesn't work so well, because it stops everything else and is not so accurate.
You can get this to work on Arduino, but how to implement this in Lua?
2 Answers
I think you might struggle to get a microsecond delay that is both accurate and non-blocking with the ESP8266.
According to the NodeMCU documentation:
If you look at the
app/modules/tmr.c
code for this function, then you will see that it executes a low level ets_delay_us(delay). This function isn't part of the NodeMCU code or the SDK; it's actually part of thextensa-lx106
boot ROM, and is a simple timing loop which polls against the internal CPU clock. It does this with interrupts disabled, because if they are enabled then there is no guarantee that the delay will be as requested.
tmr.delay()
is really intended to be used where you need to have more precise timing control on an external hardware I/O (e.g. lifting a GPIO pin high for 20 μSec). It will achieve no functional purpose in pretty much every other usecase, as any other system code-based activity will be blocked from execution; at worst it will break your application and create hard-to-diagnose timeout errors.
It seems that interrupts have to be disabled in this case simply because if an interrupt did occur mid-delay with a short interval (on the order of a few microseconds), the interrupt handler would take up far more time than the whole delay was supposed to be.
Let's say you wanted a timer for 20 microseconds, and an interrupt occurred at about 10 μs. If the handler takes more than 10 μs, you will have already passed the 20 μs delay that you intended.
So, we can rule out tmr.delay()
if you do need interrupts working.
I did a bit more digging, and apparently the ESP8266 does support microsecond timers through ets_timer_arm_new()
where the last parameter is zero. NodeMCU, however, sets this value to 1 which uses the millisecond precision. This post seems to support that idea:
If you need to get the interval between two gpio interrupt, use the system api system_get_time() to calculate the relative time.(us) If you want to use a os_timer api to arrange a us timer event, use system_timer_reinit at the beginning of user_init and call os_timer_arm_us.
If you're willing to try editing and rebuilding the firmware, it might be worth a shot. Although, there was a feature request for this, which was declined as:
So I tested nanosecond timers, and cannot establish intervals less than 1000us (with compiled and stripped code and in 160MHz CPU mode I got something like 800us). Is this a case to provide new (mostly unusable) functionality?
- djphoenixNot feasible ATM -> closing.
- marcelstoer
I have managed to recompile the NodeMCU firmware with us timer enabled:
Install docker build environment of Marcel Stör: https://hub.docker.com/r/marcelstoer/nodemcu-build/
change firmware files in your firmware directory (e.g.
./user/nodemcu-firmware
)./app/user/user_main.c
void user_init(void) {
add right here the line: system_timer_reinit();
./sdk-overrides/osapi.h
add above the line#include_next "osapi.h": #define USE_US_TIMER
./app/modules/tmr.c
->static int tmr_start(lua_State* L){
change:os_timer_arm
->os_timer_arm_us
./app/modules/tmr.c
->static int tmr_interval(lua_State* L){
change:os_timer_arm
->os_timer_arm_us
./app/modules/tmr.c:
leaveos_timer_arm
inint luaopen_tmr( lua_State *L ){
as is, otherwise you will get a watchdog reset upon start-up- recompile firmware, and flash your ESP8266
With CPU running at 160MHz, I have managed to sample ADC with 8.3kHz (timer delay of 125uS). If I go faster, the watchdog kicks in.
Code:
local mytimer2 = tmr.create()
local count = 0
local count2 = 0
local adc_read = adc.read
mytimer2:register(125, 1, function (t2)
count = count + 1; count2 = count2 + 1
local adc_v = adc_read(0)
if (count2 == 500) then
count2 = 0
end
if count == 100000 then
mytimer2:stop()
print("Time at end: "..tmr.time())
print("Counter: "..count)
end
end)
print("Time at start: "..tmr.time())
mytimer2:start()
Output:
Time at start: 1
Time at end: 13
Counter: 100000
100.000 reads in 12 sec.