Start of developer docs for RTOS/ESP32.
This commit is contained in:
parent
51fabc7439
commit
ddeb26c458
|
@ -2,95 +2,85 @@
|
||||||
|
|
||||||
**# # # Work in Progress # # #**
|
**# # # 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
|
NodeMCU uses a task based approach for scheduling work to run within
|
||||||
Espressif. This section summarises the project's understanding of how this execution
|
the Lua VM. This interface is defined in `task/task.h`, and comprises
|
||||||
model works based on the Espressif-supplied examples and SDK documentation, plus
|
three aspects:
|
||||||
various posts on the Espressif BBS and other forums, and an examination of the
|
- Task registration (via `task_getid()`)
|
||||||
BootROM code.
|
- 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
|
This NodeMCU task API is designed to complement the Lua library model, so
|
||||||
which are also used by the SDK. In this model, execution units are either:
|
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
|
Note that NodeMCU tasks must not be confused with the FreeRTOS tasks.
|
||||||
through the `ets_isr_attach()` and other `ets_isr_*` and `ets_intr_*`
|
A FreeRTOS task is fully preemptible thread of execution managed by the
|
||||||
functions. ISRs can be defined on a range of priorities, where a higher
|
OS, while a NodeMCU task is a non-preemptive\* a callback invoked by the
|
||||||
priority ISR is able to interrupt a lower priority one. ISRs are time
|
Lua FreeRTOS task. It effectively implements cooperative multitasking.
|
||||||
critical and should complete in no more than 50 µSec.
|
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:
|
The Lua runtime is NOT reentrant, and hence any code which calls any
|
||||||
if an ISR interrupts a flash I/O operation (which must disable the Flash
|
Lua API must run within the Lua task context. _ISRs, other threads and API
|
||||||
instruction cache) and a cache miss occurs, then the ISR will trigger a
|
callbacks must not access the Lua API or Lua resources._ This typically
|
||||||
fatal exception; secondly, the
|
means that messages need to be posted using the NodeMCU task API for
|
||||||
execution time for Flash memory (that is located in the `irom0` load section)
|
handling within the correct thread context. Depending on the scenario,
|
||||||
is indeterminate: whilst cache-hits can run at full memory bandwidth, any
|
data may need to be put on a FreeRTOS queue to transfer ownership safely
|
||||||
cache-misses require the code to be read from Flash; and even though
|
between the threads.
|
||||||
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/>
|
|
||||||
|
|
||||||
- **TASKS**. A task is a normal execution unit running at a non-interrupt priority.
|
The application has no control over the relative time ordering of
|
||||||
Tasks can be executed from Flash memory. An executing task can be interrupted
|
tasks and API callbacks, and no assumptions can be made about whether a
|
||||||
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
|
|
||||||
task and any posted successors will run consecutively.
|
task and any posted successors will run consecutively.
|
||||||
|
|
||||||
This API is designed to complement the Lua library model, so that a library can
|
\*) Non-preemptive in the sense that other NodeMCU tasks will not preempt it.
|
||||||
declare one or more task handlers and that both ISPs and LLTs can then post a
|
The RTOS may still preempt the task to run ISRs or other threads.
|
||||||
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.
|
|
||||||
|
|
||||||
The interface is exposed through `#include "task/task.h"` and involves two API
|
#### Task registration
|
||||||
calls. Any task handlers are declared, typically in the module_init function by
|
Each module which wishes to receive events and process those within the
|
||||||
assigning `task_get_id(some_task_callback)` to a (typically globally) accessible
|
context of the Lua thread need to register their callback. This is done
|
||||||
handle variable, say `XXX_callback_handle`. This can then be used in an ISR or
|
by calling `task_get_id(module_callback_fn)`. A non-zero return value
|
||||||
normal LLT to execute a `task_post_YYY(XXX_callback_handle,param)` where YYY is
|
indicates successful registration, and the returned handle can be used
|
||||||
one of `low`, `medium`, `high`. The callback will then be executed when the SDK
|
to post events to this task. The task registration is typically done in
|
||||||
delivers the task.
|
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