As the title says... Chat on...

User avatar
By TerryE
#16989 I keep coming across topics where the original poster says: my code doesn't work. Can anyone tell me why? And when I look at their code, I see that they are using a tmr.delay() call to wait for some asynchronous activity to complete. This just won't work! So why? If you look at the code for tmr.delay() it is in app/modules/tmr.c and it executes an os_delay_us(delay).

This function isn't part of the nodeMCU code, nor the Expressif SDK. It is part of the xtensa-lx106 boot ROM and looking at a disassembly of this ROM entry, what this function does is to do a low level machine code loop which will delay the specified number of μSec with interrupts disabled. As the nodeMCU documentation states (translating Chinese English in to English):
tmr.delay() will make the CPU work in non-interrupt mode, so other instructions and interrupts will be blocked. Take care in using this function.

Q: why does it disable interrupts? A: because if they are enabled then there is no guarantee that the delay will be as requested.

Q: when does it make sense to use a tmr.delay()? A: when you want to have exact timing control on an external hardware I/O (e.g. lifting a GPIO pin high for 20 μSec)

Q: when does it make sense not to use a tmr.delay()? A: pretty much every other circumstance. A good indication is if the delay if for more than a few mSec, then the programmer is doing something wrong, and at best the tmr.delay() will be having no functional purpose; at worst it will break the code. (The ESP kernel is non-preemptive, so a lot of things don't actually get scheduled until the Lua execution is idle and waiting for an event to happen.)

So don't use it. nodeMCU is event driven so find the correct even an use it. This could be a timer.alarm() or even another event, such as an sk:on("sent", sendMore) where sendMore() might be:
Code: Select allfunction sendMore(sk)
  local recs =  toBeSent it a global  rather than an upvalue !!
  if type(recs) = 'array' and #recs>0 then
    sk:send(recs[1])
    toBeSent = {unpack(recs, 2)}  -- shift the array to pop the sent record
  else
    sk:close()
 end
end
User avatar
By TerryE
#20209 Just posting back too this in the hope of creating an open discussion on this issue. What the library code does is to call this routine in the boot ROM:
Code: Select all    2ec8                .long     003fffc7  ;? HW register address
00002ecc <ets_delay_us>:
    2ecc:  f0c112       addi      a1, a1, -16
    2ecf:  0261d2       s32i      a13, a1, 8
    2ed2:  11c9         s32i.n    a12, a1, 4
    2ed4:  0109         s32i.n    a0, a1, 0
    2ed6:  02cd         mov.n     a12, a2
    2ed8:  0ae5c5       call0     dd38 <xthal_get_ccount>
    2edb:  fffb01       l32r      a0, 2ec8 <ets_delay_us-0x04>
    2ede:  0008         l32i.n    a0, a0, 0
    2ee0:  02dd         mov.n     a13, a2
    2ee2:  82c0c0       mull      a12, a0, a12
    2ee5:  0ae505       call0     dd38 <xthal_get_ccount>
    2ee8:  c032d0       sub       a3, a2, a13
    2eeb:  08b3c7       bgeu      a3, a12, 2ef7 <ets_delay_us+0x2b>
    2eee:  0ae485       call0     dd38 <xthal_get_ccount>
    2ef1:  c042d0       sub       a4, a2, a13
    2ef4:  f634c7       bltu      a4, a12, 2eee <ets_delay_us+0x22>
    2ef7:  11c8         l32i.n    a12, a1, 4
    2ef9:  21d8         l32i.n    a13, a1, 8
    2efb:  0108         l32i.n    a0, a1, 0
    2efd:  10c112       addi      a1, a1, 16
    2f00:  f00d         ret.n


Cal has access to the instruction set manual for the ESP8266 CPU but as far as I can see the main loop is the 3 lines starting at 0x2EEE which is just a while lop count down against a hardware clock. (This doesn't seem to be program for the counter wrap around BTW). What is somewhat more controversial is whether interrupts are disabled or not during this count down, and if they aren't what interrupt-driven code exists within the SDK.

I also note that the SDK 1.1.1 limits the argument to this function to a uint16. It also expands the intro to the SDK as follows. (Note that Espressif also ship an RTOS SDK, but this stripped down variant is called the non-OS SDK).
  • When using non-OS SDK which is single-threaded, any task should not occupy CPU too long;
    • If a task occupies the CPU for a long time, the ESP8266 can't feed the dog, will cause a watchdog reset;
    • Task should not occupy CPU more than 10 mS, otherwise may cause Wi-Fi connection break.
  • We suggest that you use a timer to check periodically.
  • Using non-OS SDK, please don’t call any function defined with ICACHE_FLASH_ATTR in interrupt handler.
  • We suggest to use RTOS SDK, RTOS can schedule different task

There you have it folks: no Lua callback function should run for more than 10 mSec if you don't want the TCP stack to break.
Note that all of the nodeMCU firmware has the ICACHE_FLASH_ATTR declaration.
User avatar
By cal
#20290 This is a raw take on the code:

Code: Select allvoid ets_delay_us(int delay) {
   int count0 = xthal_get_ccount();
   int delayCount = delay * cpu_frequency;
   while (xthal_get_ccount() - count0 < delayCount);
}


I don't have seen lua code called from interrupts.
Interrupt code is in platform directory and should not have flash attribute.

Cal