nodemcu-firmware: Hardware timer (platform_hw_timer_*) causing freeze

I am building a module to radio control Somfy blinds via 433Mhz RF transmitter. A sequence of 0 and 1 with different delays needs to be transmitted as illustrated on the chart: image

There are implementations for Arduino using delay functions but on ESP8266 I need to go asynchronous. That is why I have decided to use the hw_timer to achieve precise timing in the order of microseconds.

Actual behavior

I am indeed able to control blinds but after sending few commands the ESP8266 freezes and then goes restart triggered by hw watchdog (rst cause: 4, boot mode:(3,0)).

Test code

The minimum code to reproduce the issue is

modules\somfy.c

#include "os_type.h"
#include "osapi.h"

#include "module.h"
#include "lauxlib.h"
#include "platform.h"
#include "hw_timer.h"
#include "user_interface.h"

#define SYMBOL 640 // symbol width in microseconds

#define TIMER_OWNER ((os_param_t) 's')

static uint32_t   delay[10] = {9415, 89565, 4*SYMBOL, 4*SYMBOL, 4*SYMBOL, 4550, SYMBOL, SYMBOL, SYMBOL, 30415}; // 143 ms total
static uint8_t    signalindex;

static void ICACHE_RAM_ATTR sendCommand(os_param_t p) {
    (void) p;
    //NODE_DBG("%d\n", signalindex);
    signalindex++;
    if (signalindex<10) {
        platform_hw_timer_arm_us(TIMER_OWNER, delay[signalindex-1]);
    } else {
        platform_hw_timer_close(TIMER_OWNER);
    }
}

static int somfy_lua_sendcommand(lua_State* L) {
    if (!platform_hw_timer_init(TIMER_OWNER, NMI_SOURCE, TRUE)) {
        // Failed to init the timer
        luaL_error(L, "Unable to initialize timer");
    }
    NODE_DBG("Triggering hw timer (%d)\n",  system_get_time());
    platform_hw_timer_set_func(TIMER_OWNER, sendCommand, 0);
    signalindex=0;
    sendCommand(0);
    return 0;
}

static const LUA_REG_TYPE somfy_map[] = {
    { LSTRKEY( "sendcommand" ), LFUNCVAL(somfy_lua_sendcommand)},
    { LNILKEY, LNILVAL}
};

NODEMCU_MODULE(SOMFY, "somfy", somfy_map, NULL);

and the Lua code to trigger it

tmr.alarm(0, 250, tmr.ALARM_AUTO, function() somfy.sendcommand() end)

The Lua code freezes and crashes in less than 1 minute. The sendcommand calls platform_hw_timer_close() in less than 150ms so there should be no issue that the function is called when the timer is still running.

Am I doing something which is not compliant with specifications?

NodeMCU version

I am using dev branch, version up to 56b4a3e commit. Modules included: file, gpio, net, node, tmr, uart, wifi.

Hardware

ESP-01. No need to connect the RF transmitter to reproduce the issue.

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 19 (13 by maintainers)

Most upvoted comments

I have merged gpio.serout and gpio.seroutasync as suggested. I like the idea. It’s backwards compatible and does not increase the code size much while both versions are still available. I have added one feature - if callback parameter is numeric the gpio goes async but no callback is triggered.

@oyooyo small modification and gpio.serout behaves the way you describe. The modification is that the last time ISR is called the GPIO state is not inverted.

@devsaurus I have fixed also the way the callback was triggered. The way it was implemented was funny and kind of a workaround. I did not know about this straightforward way.

Latest commit.

@vsky279 regarding your question / implementation of the finished callback: I never saw your approach before to arm a timer from ISR for callbacks. Can’t assess whether it’s ok in interrupt context. For the pcm module I used task_post_*() as mentioned in the Extension Developer FAQ. Examples can be found in gpio and pcm.

@oyooyo I’m not sure whether we should drop the synchronous gpio.serout() completely. The hw_timer based asynchronous solution comes with constraints regarding the step time. IIRC Espressif specifies a minimum reload time of ~50 µs. We might end up with two flavors targeted for different use cases:

  • synchronous operation with sub-50 µs resolution, restricted to max. overall duration
  • asynchronous operation with less granularity but virtually unrestricted duration

Am I missing something here?

Both flavors could be further collapsed into

gpio.serout(pin, start_level, delay_times[, [repeat_num][, callback])

Setting or omitting a callback would switch between the asynchronous and synchronous variant respectively. (repeat_num could be made a mandatory parameter for simplicity)

What do others think?

With this backgound your approach could be regarded as an ISR-based variant of gpio.serout().

I thought the same when I saw this issue. While I have no personal interest in a somfy module, I’m very interested in a proper, asynchronous version of gpio.serout() using interrupts instead of busy-waiting. So I would very much welcome if the code made its way into a function like gpio.seroutasync() or so that could then be used by multiple one-way communication protocols (for example using 433MHz or IR transmitters).

Lukas, kudos for sticking to our new issue template 😃