10

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 2

7

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 the xtensa-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?
- djphoenix

Not feasible ATM -> closing.
- marcelstoer

5

I have managed to recompile the NodeMCU firmware with us timer enabled:

add right here the line: system_timer_reinit();

  1. ./sdk-overrides/osapi.h add above the line #include_next "osapi.h": #define USE_US_TIMER

  2. ./app/modules/tmr.c -> static int tmr_start(lua_State* L){ change: os_timer_arm -> os_timer_arm_us

  3. ./app/modules/tmr.c -> static int tmr_interval(lua_State* L){ change: os_timer_arm -> os_timer_arm_us

  4. ./app/modules/tmr.c: leave os_timer_arm in int 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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.