Start of developer docs for RTOS/ESP32.
This commit is contained in:
parent
51fabc7439
commit
ddeb26c458
|
@ -2,95 +2,85 @@
|
|||
|
||||
**# # # Work in Progress # # #**
|
||||
|
||||
## How does the non-OS SDK structure execution
|
||||
### NodeMCU task paradigm
|
||||
|
||||
Details of the execution model for the **non-OS SDK** is not well documented by
|
||||
Espressif. This section summarises the project's understanding of how this execution
|
||||
model works based on the Espressif-supplied examples and SDK documentation, plus
|
||||
various posts on the Espressif BBS and other forums, and an examination of the
|
||||
BootROM code.
|
||||
NodeMCU uses a task based approach for scheduling work to run within
|
||||
the Lua VM. This interface is defined in `task/task.h`, and comprises
|
||||
three aspects:
|
||||
- Task registration (via `task_getid()`)
|
||||
- Task posting (via `task_post()` and associated macros)
|
||||
- Task processing (via `task_pump_messages()`)
|
||||
|
||||
The ESP8266 boot ROM contains a set of primitive tasking and dispatch functions
|
||||
which are also used by the SDK. In this model, execution units are either:
|
||||
This NodeMCU task API is designed to complement the Lua library model, so
|
||||
that a library can declare one or more task handlers and that both ISRs
|
||||
and library functions can then post a message for delivery to a task handler.
|
||||
|
||||
- **INTERRUPT SERVICE ROUTINES (ISRs)** which are declared and controlled
|
||||
through the `ets_isr_attach()` and other `ets_isr_*` and `ets_intr_*`
|
||||
functions. ISRs can be defined on a range of priorities, where a higher
|
||||
priority ISR is able to interrupt a lower priority one. ISRs are time
|
||||
critical and should complete in no more than 50 µSec.
|
||||
Note that NodeMCU tasks must not be confused with the FreeRTOS tasks.
|
||||
A FreeRTOS task is fully preemptible thread of execution managed by the
|
||||
OS, while a NodeMCU task is a non-preemptive\* a callback invoked by the
|
||||
Lua FreeRTOS task. It effectively implements cooperative multitasking.
|
||||
To reduce confusion, from here on FreeRTOS tasks will be referred to as
|
||||
threads, and NodeMCU tasks simply as tasks. Most NodeMCU developers
|
||||
will not need to concern themselves with threads unless they're doing
|
||||
low-level driver development.
|
||||
|
||||
ISR code and data constants should be run out of RAM or ROM, for two reasons:
|
||||
if an ISR interrupts a flash I/O operation (which must disable the Flash
|
||||
instruction cache) and a cache miss occurs, then the ISR will trigger a
|
||||
fatal exception; secondly, the
|
||||
execution time for Flash memory (that is located in the `irom0` load section)
|
||||
is indeterminate: whilst cache-hits can run at full memory bandwidth, any
|
||||
cache-misses require the code to be read from Flash; and even though
|
||||
H/W-based, this is at roughly 26x slower than memory bandwidth (for DIO
|
||||
flash); this will cause ISR execution to fall outside the require time
|
||||
guidelines. (Note that any time critical code within normal execution and that
|
||||
is bracketed by interrupt lock / unlock guards should also follow this 50
|
||||
µSec guideline.)<br/><br/>
|
||||
The Lua runtime is NOT reentrant, and hence any code which calls any
|
||||
Lua API must run within the Lua task context. _ISRs, other threads and API
|
||||
callbacks must not access the Lua API or Lua resources._ This typically
|
||||
means that messages need to be posted using the NodeMCU task API for
|
||||
handling within the correct thread context. Depending on the scenario,
|
||||
data may need to be put on a FreeRTOS queue to transfer ownership safely
|
||||
between the threads.
|
||||
|
||||
- **TASKS**. A task is a normal execution unit running at a non-interrupt priority.
|
||||
Tasks can be executed from Flash memory. An executing task can be interrupted
|
||||
by one or more ISRs being delivered, but it won't be preempted by another
|
||||
queued task. The Espressif guideline is that no individual task should run for
|
||||
more than 15 mSec, before returning control to the SDK.
|
||||
|
||||
The ROM will queue up to 32 pending tasks at priorities 0..31 and will
|
||||
execute the highest priority queued task next (or wait on interrupt if none
|
||||
is runnable). The SDK tasking system is layered on this ROM dispatcher and
|
||||
it reserves 29 of these task priorities for its own use, including the
|
||||
implementation of the various SDK timer, WiFi and other callback mechanisms
|
||||
such as the software WDT.
|
||||
|
||||
Three of these task priorities are allocated for and exposed directly at an
|
||||
application level. The application can declare a single task handler for each
|
||||
level, and associate a task queue with the level. Tasks can be posted to this
|
||||
queue. (The post will fail is the queue is full). Tasks are then delivered
|
||||
FIFO within task priority.
|
||||
|
||||
How the three user task priorities USER0 .. USER2 are positioned relative to
|
||||
the SDK task priorities is undocumented, but some SDK tasks definitely run at
|
||||
a lower priority than USER0. As a result if you always have a USER task queued
|
||||
for execution, then you can starve SDK housekeeping tasks and you will start
|
||||
to get WiFi and other failures. Espressif therefore recommends that you don't
|
||||
stay computable for more than 500 mSec to avoid such timeouts.
|
||||
|
||||
Note that the 50µS, 15mSec and 500mSec limits are guidelines
|
||||
and not hard constraints -- that is if you break them (slightly) then your code
|
||||
may (usually) work, but you can get very difficult to diagnose and intermittent
|
||||
failures. Also running ISRs from Flash may work until there is a collision with
|
||||
SPIFFS I/O which will then a cause CPU exception.
|
||||
|
||||
Also note that the SDK API function `system_os_post()`, and the `task_post_*()`
|
||||
macros which generate this can be safely called from an ISR.
|
||||
|
||||
The Lua runtime is NOT reentrant, and hence any code which calls any Lua API
|
||||
must run within a task context. Any such task is what we call a _Lua-Land Task_
|
||||
(or **LLT**). _ISRs must not access the Lua API or Lua resources._ LLTs can be
|
||||
executed as SDK API callbacks or OS tasks. They can also, of course, call the
|
||||
Lua execution system to execute Lua code (e.g. `luaL_dofile()` and related
|
||||
calls).
|
||||
|
||||
Also since the application has no control over the relative time ordering of
|
||||
tasks and SDK API callbacks, LLTs can't make any assumptions about whether a
|
||||
The application has no control over the relative time ordering of
|
||||
tasks and API callbacks, and no assumptions can be made about whether a
|
||||
task and any posted successors will run consecutively.
|
||||
|
||||
This API is designed to complement the Lua library model, so that a library can
|
||||
declare one or more task handlers and that both ISPs and LLTs can then post a
|
||||
message for delivery to a task handler. Each task handler has a unique message
|
||||
associated with it, and may bind a single uint32 parameter. How this parameter
|
||||
is interpreted is left to the task poster and task handler to coordinate.
|
||||
\*) Non-preemptive in the sense that other NodeMCU tasks will not preempt it.
|
||||
The RTOS may still preempt the task to run ISRs or other threads.
|
||||
|
||||
The interface is exposed through `#include "task/task.h"` and involves two API
|
||||
calls. Any task handlers are declared, typically in the module_init function by
|
||||
assigning `task_get_id(some_task_callback)` to a (typically globally) accessible
|
||||
handle variable, say `XXX_callback_handle`. This can then be used in an ISR or
|
||||
normal LLT to execute a `task_post_YYY(XXX_callback_handle,param)` where YYY is
|
||||
one of `low`, `medium`, `high`. The callback will then be executed when the SDK
|
||||
delivers the task.
|
||||
#### Task registration
|
||||
Each module which wishes to receive events and process those within the
|
||||
context of the Lua thread need to register their callback. This is done
|
||||
by calling `task_get_id(module_callback_fn)`. A non-zero return value
|
||||
indicates successful registration, and the returned handle can be used
|
||||
to post events to this task. The task registration is typically done in
|
||||
the module\_init function.
|
||||
|
||||
_Note_: `task_post_YYY` can fail with a false return if the task Q is full.
|
||||
#### Task posting
|
||||
To signal a task, a message is posted to it using
|
||||
`task_post(prio, handle, param)` or the helper macros `task_post_low()/task_post_medium()/task_post_high()`. Each message carries a single parameter value
|
||||
of type `intptr_t` and may be used to carry either pointers or raw values.
|
||||
Each task defines its own schema depending on its needs.
|
||||
|
||||
Note that the message queues can theoretically fill up, and this is reported
|
||||
by a `false` return value from the `task_post()` call. In this case no
|
||||
message was posted. In most cases nothing much can be done, and the best
|
||||
approach may be to simply ignore the failure and drop the data. Be careful
|
||||
not to accidentally leak memory in this circumstance however! A good
|
||||
habit would be to do something like this:
|
||||
```
|
||||
char *buf = malloc (len);
|
||||
// do something with buf...
|
||||
if (!task_post_medium(handle, buf))
|
||||
free (buf);
|
||||
```
|
||||
|
||||
The `task_post*()` function and macros can be safely called from any ISR or
|
||||
thread.
|
||||
|
||||
#### Task processing and priorities
|
||||
The Lua runtime executes in a single thread, and at its root level runs
|
||||
the task message processing loop. Task messages arrive on three queues,
|
||||
one for each priority level. The queues are services in order of their
|
||||
priority, so while a higher-priority queue contains messages, no
|
||||
lower-priority messages will be delivered. It is thus quite possible to
|
||||
jam up the Lua runtime by continually posting high-priority messages.
|
||||
Don't do that.
|
||||
|
||||
Unless there are particular reasons not to, messages should be posted
|
||||
with medium priority. This is the friendly, cooperative level. If something
|
||||
is time sensitive (e.g. audio buffer running low), high priority may be
|
||||
warranted. The low priority level is intended for background processing
|
||||
during otherwise idle time.
|
||||
|
||||
|
|
Loading…
Reference in New Issue