diff --git a/Sming/Arch/Esp8266/Components/esp8266/include/esp_systemapi.h b/Sming/Arch/Esp8266/Components/esp8266/include/esp_systemapi.h index 41a483a43c..c5d8545581 100644 --- a/Sming/Arch/Esp8266/Components/esp8266/include/esp_systemapi.h +++ b/Sming/Arch/Esp8266/Components/esp8266/include/esp_systemapi.h @@ -116,6 +116,7 @@ extern void ets_isr_unmask(unsigned intr); #include "xtensa/xtruntime.h" /** @brief Disable interrupts + * @retval Current interrupt level * @note Hardware timer is unaffected if operating in non-maskable mode */ #define noInterrupts() XTOS_SET_INTLEVEL(15) @@ -124,6 +125,10 @@ extern void ets_isr_unmask(unsigned intr); */ #define interrupts() XTOS_SET_INTLEVEL(0) +/** @brief Restore interrupts to level saved from previous noInterrupts() call + */ +#define restoreInterrupts(level) XTOS_RESTORE_INTLEVEL(level) + #ifdef __cplusplus } #endif diff --git a/Sming/Arch/Esp8266/Components/gdbstub/gdbstub.cpp b/Sming/Arch/Esp8266/Components/gdbstub/gdbstub.cpp index 845074dd61..35a94e7ecc 100644 --- a/Sming/Arch/Esp8266/Components/gdbstub/gdbstub.cpp +++ b/Sming/Arch/Esp8266/Components/gdbstub/gdbstub.cpp @@ -753,7 +753,7 @@ void ATTR_GDBEXTERNFN commandLoop(bool waitForStart, bool allowDetach) } if(gdb_state.attached != initiallyAttached) { - System.queueCallback(TaskCallback(gdb_on_attach), gdb_state.attached); + System.queueCallback(TaskCallback32(gdb_on_attach), gdb_state.attached); } debug_i("<< LEAVE CMDLOOP"); diff --git a/Sming/Arch/Esp8266/Components/gdbstub/gdbsyscall.cpp b/Sming/Arch/Esp8266/Components/gdbstub/gdbsyscall.cpp index c8deff9ba9..76f76a0535 100644 --- a/Sming/Arch/Esp8266/Components/gdbstub/gdbsyscall.cpp +++ b/Sming/Arch/Esp8266/Components/gdbstub/gdbsyscall.cpp @@ -253,7 +253,7 @@ bool ATTR_GDBEXTERNFN gdb_syscall_complete(const char* data) } if(syscall_info.callback != nullptr) { - System.queueCallback(TaskCallback(syscall_info.callback), uint32_t(&syscall_info)); + System.queueCallback(TaskCallback(syscall_info.callback), &syscall_info); } gdb_state.syscall = syscall_ready; diff --git a/Sming/Arch/Esp8266/Components/gdbstub/gdbuart.cpp b/Sming/Arch/Esp8266/Components/gdbstub/gdbuart.cpp index de684c9f69..dd51af172f 100644 --- a/Sming/Arch/Esp8266/Components/gdbstub/gdbuart.cpp +++ b/Sming/Arch/Esp8266/Components/gdbstub/gdbuart.cpp @@ -203,7 +203,7 @@ void ATTR_GDBEXTERNFN gdbFlushUserData() #if GDBSTUB_ENABLE_UART2 -static void sendUserDataTask(uint32_t) +static void sendUserDataTask() { sendUserDataQueued = false; @@ -321,7 +321,7 @@ static void IRAM_ATTR gdb_uart_callback(uart_t* uart, uint32_t status) if(breakCheck && c == '\x03') { if(break_requests++ == 0) { // First attempt, break within a task callback - System.queueCallback(TaskCallback(doCtrlBreak)); + System.queueCallback(doCtrlBreak); } else if(break_requests == 3) { // Application failed to stop, break immediately doCtrlBreak(); diff --git a/Sming/Arch/Host/Components/esp_hal/include/esp_systemapi.h b/Sming/Arch/Host/Components/esp_hal/include/esp_systemapi.h index dfd62200c1..ea292b0f2b 100644 --- a/Sming/Arch/Host/Components/esp_hal/include/esp_systemapi.h +++ b/Sming/Arch/Host/Components/esp_hal/include/esp_systemapi.h @@ -44,13 +44,28 @@ struct ip_addr { #include #include "debug_progmem.h" +#define debugf debug_i #define SYSTEM_ERROR(fmt, ...) hostmsg("ERROR: " fmt "\r\n", ##__VA_ARGS__) -#define noInterrupts() -#define interrupts() +__forceinline unsigned noInterrupts() +{ + ets_intr_lock(); + return 1; +} + +__forceinline void interrupts() +{ + ets_intr_unlock(); +} + +__forceinline void restoreInterrupts(unsigned level) +{ + (void)level; + interrupts(); +} -#define BIT(nr) (1UL << (nr)) +#define BIT(nr) (1UL << (nr)) #ifdef __cplusplus } diff --git a/Sming/Core/HardwareSerial.cpp b/Sming/Core/HardwareSerial.cpp index 2426027000..ca2ba52ef7 100644 --- a/Sming/Core/HardwareSerial.cpp +++ b/Sming/Core/HardwareSerial.cpp @@ -159,9 +159,9 @@ unsigned HardwareSerial::getStatus() /* * Called via task queue */ -void HardwareSerial::staticOnStatusChange(uint32_t param) +void HardwareSerial::staticOnStatusChange(void* param) { - auto serial = reinterpret_cast(param); + auto serial = static_cast(param); if(serial != nullptr) { serial->invokeCallbacks(); } @@ -181,7 +181,7 @@ void HardwareSerial::staticCallbackHandler(uart_t* uart, uint32_t status) // If required, queue a callback if((status & serial->statusMask) != 0 && !serial->callbackQueued) { - System.queueCallback(staticOnStatusChange, uint32_t(serial)); + System.queueCallback(staticOnStatusChange, serial); serial->callbackQueued = true; } } diff --git a/Sming/Core/HardwareSerial.h b/Sming/Core/HardwareSerial.h index 61e7926803..83e216d067 100644 --- a/Sming/Core/HardwareSerial.h +++ b/Sming/Core/HardwareSerial.h @@ -461,7 +461,7 @@ class HardwareSerial : public ReadWriteStream * @param status UART status flags indicating cause(s) of interrupt */ static void IRAM_ATTR staticCallbackHandler(uart_t* uart, uint32_t status); - static void staticOnStatusChange(uint32_t param); + static void staticOnStatusChange(void* param); void invokeCallbacks(); /** diff --git a/Sming/Platform/System.cpp b/Sming/Platform/System.cpp index 8ce2a403d2..7dff514713 100644 --- a/Sming/Platform/System.cpp +++ b/Sming/Platform/System.cpp @@ -11,86 +11,111 @@ #include "Platform/System.h" #include "Timer.h" +#ifndef TASK_QUEUE_LENGTH +/** @brief default number of tasks in global queue + * @note tasks are usually short-lived and executed very promptly, so a large queue is + * normally un-necessry. If queue overrun is suspected, check `SystemClass::getMaxTaskCount()`. + */ +#define TASK_QUEUE_LENGTH 10 +#endif + SystemClass System; SystemState SystemClass::state = eSS_None; os_event_t SystemClass::taskQueue[TASK_QUEUE_LENGTH]; + +#ifdef ENABLE_TASK_COUNT volatile uint8_t SystemClass::taskCount; volatile uint8_t SystemClass::maxTaskCount; +#endif /** @brief OS calls this function which invokes user-defined callback - * @note callback function pointer is placed in event->sig, with parameter - * in event->par. + * @note callback function pointer is placed in event->sig, with parameter in event->par. */ void SystemClass::taskHandler(os_event_t* event) { - auto callback = reinterpret_cast(event->sig); - if(callback) { - // If we get interrupt during adjustment of the counter, do it again - uint8_t oldCount = taskCount; - --taskCount; - if(taskCount != oldCount - 1) - --taskCount; +#ifdef ENABLE_TASK_COUNT + auto level = noInterrupts(); + --taskCount; + restoreInterrupts(level); +#endif + auto callback = reinterpret_cast(event->sig); + if(callback != nullptr) { callback(event->par); } } bool SystemClass::initialize() { - if(state != eSS_None) + if(state != eSS_None) { return false; + } state = eSS_Intializing; // Initialise the global task queue - return system_os_task(taskHandler, USER_TASK_PRIO_1, taskQueue, TASK_QUEUE_LENGTH); + if(!system_os_task(taskHandler, USER_TASK_PRIO_1, taskQueue, TASK_QUEUE_LENGTH)) { + return false; + } + +#ifdef ARCH_ESP8266 + system_init_done_cb([]() { state = eSS_Ready; }); +#else + state = eSS_Ready; +#endif + + return true; } -bool SystemClass::queueCallback(TaskCallback callback, uint32_t param) +bool SystemClass::queueCallback(TaskCallback32 callback, uint32_t param) { if(callback == nullptr) { return false; } - if(++taskCount > maxTaskCount) { +#ifdef ENABLE_TASK_COUNT + auto level = noInterrupts(); + ++taskCount; + if(taskCount > maxTaskCount) { maxTaskCount = taskCount; } + restoreInterrupts(level); +#endif return system_os_post(USER_TASK_PRIO_1, reinterpret_cast(callback), param); } -void SystemClass::onReady(SystemReadyDelegate readyHandler) +bool SystemClass::queueCallback(TaskDelegate callback) { - if(readyHandler) { - auto handler = new SystemReadyDelegate(readyHandler); - queueCallback( - [](uint32_t param) { - SystemClass::state = eSS_Ready; - auto handler = reinterpret_cast(param); - (*handler)(); - delete handler; - }, - reinterpret_cast(handler)); + if(!callback) { + return false; } -} -void SystemClass::onReady(ISystemReadyHandler* readyHandler) -{ - if(readyHandler) { - queueCallback( - [](uint32_t param) { - SystemClass::state = eSS_Ready; - auto handler = reinterpret_cast(param); - handler->onSystemReady(); - }, - reinterpret_cast(readyHandler)); + // @todo consider failing immediately if called from interrupt context + + auto delegate = new TaskDelegate(callback); + if(delegate == nullptr) { + return false; + } + + auto delegateHandler = [](void* param) { + auto delegate = static_cast(param); + (*delegate)(); + delete delegate; + }; + + if(!queueCallback(delegateHandler, delegate)) { + delete delegate; + return false; } + + return true; } void SystemClass::restart(unsigned deferMillis) { if(deferMillis == 0) { - queueCallback([](uint32_t) { system_restart(); }); + queueCallback(system_restart); } else { auto timer = new AutoDeleteTimer; timer->initializeMs(deferMillis, system_restart).startOnce(); diff --git a/Sming/Platform/System.h b/Sming/Platform/System.h index 36f78e6d01..a49ad5ee32 100644 --- a/Sming/Platform/System.h +++ b/Sming/Platform/System.h @@ -24,24 +24,28 @@ #pragma once #include +#include +#include -/** @brief default number of tasks in global queue - * @note tasks are usually short-lived and executed very promptly. If necessary this - * value can be overridden in makefile or user_config.h. +/** @brief Task callback function type, uint32_t parameter + * @ingroup event_handlers + * @note Callback code does not need to be in IRAM */ -#ifndef TASK_QUEUE_LENGTH -#define TASK_QUEUE_LENGTH 10 -#endif +typedef void (*TaskCallback32)(uint32_t param); -/** @brief Task callback function type +/** @brief Task callback function type, void* parameter * @ingroup event_handlers * @note Callback code does not need to be in IRAM - * @todo Integrate delegation into callbacks */ -typedef void (*TaskCallback)(uint32_t param); +typedef void (*TaskCallback)(void* param); + +/** @brief Task Delegate callback type + * @ingroup event_handlers + */ +typedef Delegate TaskDelegate; /// @ingroup event_handlers -typedef Delegate SystemReadyDelegate; ///< Handler function for system ready +typedef TaskDelegate SystemReadyDelegate; ///< Handler function for system ready class ISystemReadyHandler { @@ -142,32 +146,70 @@ class SystemClass * @param readyHandler Function to handle event * @note if system is ready, callback is executed immediately without deferral */ - void onReady(SystemReadyDelegate readyHandler); + void onReady(SystemReadyDelegate readyHandler) + { + queueCallback(readyHandler); + } /** @brief Set handler for system ready event * @param readyHandler Function to handle event * @note if system is ready, callback is executed immediately without deferral */ - void onReady(ISystemReadyHandler* readyHandler); + void onReady(ISystemReadyHandler* readyHandler) + { + if(readyHandler != nullptr) { + queueCallback([](void* param) { static_cast(param)->onSystemReady(); }, readyHandler); + } + } /** * @brief Queue a deferred callback. * @param callback The function to be called - * @param param Parameter passed to the callback + * @param param Parameter passed to the callback (optional) * @retval bool false if callback could not be queued * @note It is important to check the return value to avoid memory leaks and other issues, * for example if memory is allocated and relies on the callback to free it again. * Note also that this method is typically called from interrupt context so must avoid things * like heap allocation, etc. */ - static bool IRAM_ATTR queueCallback(TaskCallback callback, uint32_t param = 0); + static bool IRAM_ATTR queueCallback(TaskCallback32 callback, uint32_t param = 0); + + /** + * @brief Queue a deferred callback, with optional void* parameter + */ + __forceinline static bool IRAM_ATTR queueCallback(TaskCallback callback, void* param = nullptr) + { + return queueCallback(reinterpret_cast(callback), reinterpret_cast(param)); + } + + /** + * @brief Queue a deferred callback with no callback parameter + */ + __forceinline static bool IRAM_ATTR queueCallback(InterruptCallback callback) + { + return queueCallback(reinterpret_cast(callback)); + } + + /** + * @brief Queue a deferred Delegate callback + * @param callback The Delegate to be called + * @retval bool false if callback could not be queued + * @note Provides flexibility and ease of use for using capturing lambdas, etc. + * but requires heap allocation and not as fast as a function callback. + * DO NOT use from interrupt context, use a Task/Interrupt callback. + */ + static bool queueCallback(TaskDelegate callback); /** @brief Get number of tasks currently on queue * @retval unsigned */ static unsigned getTaskCount() { +#ifdef ENABLE_TASK_COUNT return taskCount; +#else + return 255; +#endif } /** @brief Get maximum number of tasks seen on queue at any one time @@ -177,7 +219,11 @@ class SystemClass */ static unsigned getMaxTaskCount() { +#ifdef ENABLE_TASK_COUNT return maxTaskCount; +#else + return 255; +#endif } private: @@ -185,9 +231,11 @@ class SystemClass private: static SystemState state; - static os_event_t taskQueue[]; ///< OS task queue + static os_event_t taskQueue[]; ///< OS task queue +#ifdef ENABLE_TASK_COUNT static volatile uint8_t taskCount; ///< Number of tasks on queue static volatile uint8_t maxTaskCount; ///< Profiling to establish appropriate queue size +#endif }; /** @brief Global instance of system object diff --git a/Sming/README.rst b/Sming/README.rst index f239a3ed29..001b987ca7 100644 --- a/Sming/README.rst +++ b/Sming/README.rst @@ -58,6 +58,30 @@ Change it like this: make DEBUG_VERBOSE_LEVEL=3 +Task Queue +----------- + +The task queue is used for *System.queueCallback()* calls. + +.. envvar:: TASK_QUEUE_LENGTH + + Maximum number of entries in the task queue (default 16). Must be a power of 2. + + +.. envvar:: ENABLE_TASK_COUNT + + If problems are suspected with task queuing, it may be getting flooded. + For this reason you should check the return value from `queueCallback()`. + + You can enable this option to keep track of the number of active tasks, + *System::getTaskCount()*, and the maximum, *System::getMaxTaskCount()*. + + By default this is disabled and both methods will return 255. + This is because interrupts must be disabled to ensure an accurate count, + which may not be desirable. + + + Release builds -------------- diff --git a/Sming/component.mk b/Sming/component.mk index e052e81826..f58fd15375 100644 --- a/Sming/component.mk +++ b/Sming/component.mk @@ -120,3 +120,13 @@ CONFIG_VARS += COM_SPEED_SERIAL COM_SPEED_SERIAL ?= $(COM_SPEED) APP_CFLAGS += -DCOM_SPEED_SERIAL=$(COM_SPEED_SERIAL) +# Task queue counter to check for overflows +COMPONENT_VARS += ENABLE_TASK_COUNT +ifeq ($(ENABLE_TASK_COUNT),1) + GLOBAL_CFLAGS += -DENABLE_TASK_COUNT=1 +endif + +# Task queue length +COMPONENT_VARS += TASK_QUEUE_LENGTH +TASK_QUEUE_LENGTH ?= 10 +COMPONENT_CFLAGS += -DTASK_QUEUE_LENGTH=$(TASK_QUEUE_LENGTH) diff --git a/docs/Doxyfile b/docs/Doxyfile index 7a9547320c..e92a833f91 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -101,6 +101,7 @@ STRIP_FROM_PATH = $(SMING_HOME) INPUT = api.dox sdk.dox \ ../Sming/Core \ + ../Sming/Arch/Host/Core \ ../Sming/Platform \ ../Sming/Services \ ../Sming/Wiring \ @@ -383,5 +384,6 @@ PREDEFINED = \ ENABLE_SSL=1 \ ENABLE_CMD_EXECUTOR=1 \ ENABLE_CUSTOM_PWM=1 \ - SMING_DEPRECATED= - + SMING_DEPRECATED= \ + SMING_ARCH=Host \ + ARCH_HOST=1 diff --git a/docs/source/arch/host/host-emulator.rst b/docs/source/arch/host/host-emulator.rst index 85e9c4d4f9..d6683ee2de 100644 --- a/docs/source/arch/host/host-emulator.rst +++ b/docs/source/arch/host/host-emulator.rst @@ -10,7 +10,7 @@ If you want to try it we have an that can be run directly from your browser. Requirements (Linux) -------------------------- +-------------------- Modern Linux distribution ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -48,8 +48,8 @@ execute the commands below: Requirements (Windows) ---------------------- -For Windows, make sure your `MinGW` distro is up to date. If you run -`gcc --version` you should get `gcc (MinGW.org GCC-6.3.0-1) 6.3.0` or +For Windows, make sure your ``MinGW`` distro is up to date. If you run +``gcc --version`` you should get ``gcc (MinGW.org GCC-6.3.0-1) 6.3.0`` or later. If it's older, execute these commands: :: diff --git a/docs/source/information/flash.rst b/docs/source/information/flash.rst new file mode 100644 index 0000000000..e490a1843d --- /dev/null +++ b/docs/source/information/flash.rst @@ -0,0 +1,36 @@ +Flash memory +============ + +Introduction +------------ + +Size varies from 512Kbytes on the ESP-01 up to 4Mbytes on the ESP12F. Up to 16MBytes are supported for custom designs. + +You can find general details for the ESP8266 memory layout in the `Wiki `__. + +This is the layout for Sming with a 4MByte flash device: + +======= =============== ==== ========================= =================================================== +Address Config variable Size Source filename Description +(hex) (if any) (KB) (if applicable) +======= =============== ==== ========================= =================================================== +000000 1 rboot.bin Boot loader +001000 4 rBoot configuration +002000 ROM_0_ADDR rom0.bin First ROM image +100000 RBOOT_SPIFFS_0 +202000 ROM_1_ADDR rom1.bin Second ROM image +300000 RBOOT_SPIFFS_1 +3FB000 4 blank.bin RF Calibration data (Initialised to FFh) +3FC000 4 esp_init_data_default.bin PHY configuration data +3FD000 12 blank.bin System parameter area +======= =============== ==== ========================= =================================================== + + +Partition Tables +---------------- + +.. todo:: + + Whilst SDK version 3 requires a partition table, previous versions do not but this can be added so that we + can use it as a common reference for all the above locations. + diff --git a/docs/source/information/index.rst b/docs/source/information/index.rst index 1cc0653922..d95b29cf4e 100644 --- a/docs/source/information/index.rst +++ b/docs/source/information/index.rst @@ -5,9 +5,13 @@ Information :maxdepth: 1 /_inc/Sming/building + multitasking events + memory + flash interrupts timers + tasks debugging rboot-ota command-handler diff --git a/docs/source/information/memory.rst b/docs/source/information/memory.rst new file mode 100644 index 0000000000..650227840c --- /dev/null +++ b/docs/source/information/memory.rst @@ -0,0 +1,77 @@ +Memory +====== + +Introduction +------------ + +You can find a map for the ESP8266 memory layout in the `Wiki `__. + +The ESP8266 has several types of memory, and it is important to have a basic apprecation of what they +are and how they're used. + +DRAM + Data RAM where variables, etc. are stored. Contains the stack (which starts at the top of RAM) + and the heap (which starts near the bottom, after any static data). + +IRAM + Instruction RAM. All code executes from here, but there's not much of it so + so only parts of your application are loaded at any one time. This *caching* happens + in the background by mapping certain memory address ranges into physical flash + locations. + + If a location is accessed which is already in the cache (a 'hit') then the access + runs at full RAM speed. If it's not in the cache (a 'miss') then an interrupt (exception) + is generated internally which performs the flash read operation. + + This is why interrupt service routines must not access PROGMEM directly, and must + be marked using *IRAM_ATTR* to ensure it's *always* available. + + You *may* get a performance improvement using *IRAM_ATTR* but + means that commonly-used code does not necessarily run faster if explicitly + stored in IRAM. + + +Flash + Main storage for your application, libraries, the Espressif SDK code, etc. + Flash memory is accessed via external serial bus and is relatively slow. + For the ESP8266, it's approximately 12x slower, though this only applies + on cache misses. See also :doc:`flash`. + +ROM + Fixed code stored in the Esp8266 itself containing very low-level support code which + is factory-programmed and cannot be changed. + + +Initialisation +-------------- + +At startup, only the non-volatile *Flash* and *ROM* memories contain anything useful, +whilst *DRAM* and *IRAM* will probably just contain garbage. The Arduino platform +was initially designed to work with much simpler hardware, where the program was executed +directly from Flash memory on hardware reset. + +BIOS + The ESP8266 and ESP32 are far more complex, and most of the low-level initialisation + happens in the ROM code. The ROM essentially contains the systems BIOS, with various + low-level routines which may be used instead of accessing hardware directly. It is + also responsible for setting up memory caching. + +Runtime libraries + Control is passed to runtime libraries provided by Espressif, stored in Flash memory. + Both ROM and runtime code are closed-source and not generally available for inspection, + though disassemblies do exist. + +Boot loader + The first point we really see what's going on is in the bootloader (rBoot). + The bootloader identifies the correct program image (as there can be more than one), + loads the starting portion into IRAM and jumps there. It also configures the caching + mechanism so that the correct program image is loaded. + You can find more details about this in the :component:`rboot` documentation. + +Memory initialisation + Code is copied from flash into IRAM, and *const* data copied into DRAM. + Also static and global variable values are initialised from tables stored in flash. + Static and global variables without an initialised value are initialised to 0. + +Sming initialisation + ... diff --git a/docs/source/information/multitasking.rst b/docs/source/information/multitasking.rst new file mode 100644 index 0000000000..b7c5bcc68d --- /dev/null +++ b/docs/source/information/multitasking.rst @@ -0,0 +1,60 @@ +Multitasking +============ + +Pre-emptive Multitasking +------------------------ + +Modern computers have evolved so that you can write a program and it will (mostly) run +without interfering with anything else that is also running. + +With multiple CPUs tasks can actually run at the same time (concurrently), but as there +are always many more threads (tasks) these have to be shared. This is especially true on +older systems which only have a single CPU. + +The OS does this using a mechanism called *Pre-emptive Multitasking*. As far as your program +is concerned, it just runs without any apparent interruptions, but in reality it only gets +a little 'slice' of time before the operating system forceably switches to a different program and +lets it run for another 'slice' of time. (Hence the term, *time slicing*.) + +With pre-emptive multitasking the operating system has to maintain state for every single +task that is active. This requires additional RAM. + +There is also the overhead of switching between tasks. Each task also requires its own +stack space so there is usually more restriction on how it is used. + +FreeRTOS is perhaps the most well-known example of a pre-emptive embedded OS. + +Co-operative Multitasking +------------------------- + +By constrast, *Co-operative Multitasking*, requires applications to 'play fair' and not hog the CPU. +This means that whenever you get called to do some work, you must release control back to the system +in a timely manner. + +This technique is well-suited to resource-limited systems such as the Esp8266. + +The Esp8266 has only a single CPU, and relatively limited RAM; about 50KBytes is available +for application use. The heap grows from the bottom of RAM, and the stack starts at the top. + +Sming uses the ESP8266 Non-OS SDK, which manages much of the low-level system control including +the WiFi stack. It contains the main control loop, which in pseudo-code goes something like this:: + + for(;;) { + ServiceWifi(); + ServiceTimers(); + ServiceTasks(); + FeedWatchdog(); + } + +Application code gets called from there in response to a network request, or a timer that you've set up, +and you must deal with the request and return promptly to allow things to continue. + +If you don't do this, the system will very likely behave erratically and suffer from dropped WiFi +connections and poor responsiveness. In extreme cases, the system will reset as a self-defence mechanism +via *Watchdog Timer*; If it didn't do this, the device would remain unresponsive until physically reset, +which is generally a bad thing for an embedded device! + +.. attention:: + + Although there are functions available to manually reset watchdog timers, you should endeavour to avoid + doing this from application code unless absolutely necessary. diff --git a/docs/source/information/tasks.rst b/docs/source/information/tasks.rst new file mode 100644 index 0000000000..42fdbef6f1 --- /dev/null +++ b/docs/source/information/tasks.rst @@ -0,0 +1,59 @@ +Tasks +===== + +Introduction +------------ + +If you need to perform any kind of background processing *task*, then you will need +to consider how to break it up so it executes in small slices to allow other +system functions to continue normally. + +You can use callback timers to schedule periodic tasks, but if you simply need to +run the task as soon as possible you should use the task queue. + +The task queue +-------------- + +Sming has a *task queue* which applications can use by calling `System.queueCallback()`: + +.. doxygenclass:: SystemClass + :members: queueCallback + +Callbacks are executed as soon as possible, and allow other higher priority tasks +(such as servicing the WiFi stack) to be handled in a timely manner. + +.. Note:: + + The task queue size is fixed, so the call to *queueCallback* will fail if there + is no room. + + If you wish to check the actual queue usage set :envvar:`ENABLE_TASK_COUNT` + and use `System::getMaxTaskCount()` and `System::getTaskCount()` methods. + + If necessary, the task queue size can be changed using :envvar:`TASK_QUEUE_LENGTH`. + +You must not spend too much time in the callback. How much time depends on the +nature of your application, but tasks consuming more than 100ms will probably affect +responsiveness and should be broken into smaller chunks. You might do this by +wrapping such tasks in a class together with some state information. At the end of +the initial callback if there is further work to be done simply make another call +to *queueCallback*. + +Task Schedulers +--------------- + +At present Sming doesn't provide any structured (class-based) support for task scheduling, +however there are various scheduling libraries available for +`Arduino `__. + +These are quite simple and generic: + +- `A very simple Arduino task manager `__ +- `TaskScheduler `__ + +This one is rather more complex: + +- `Task Scheduler `__ + +Using a scheduler is a powerful technique which allows the programmer to focus on the task +at hand, rather than how to get it to run. diff --git a/samples/Basic_Delegates/app/application.cpp b/samples/Basic_Delegates/app/application.cpp index 85eb802809..701c7d1822 100644 --- a/samples/Basic_Delegates/app/application.cpp +++ b/samples/Basic_Delegates/app/application.cpp @@ -1,21 +1,24 @@ #include -void evaluateSpeed(); +extern void evaluateSpeed(); -void plainOldOrdinaryFunction() +static void plainOldOrdinaryFunction() { debugf("plainOldOrdinaryFunction"); } -void functionWithMoreComplicatedSignature(int a, String b) +static void functionWithMoreComplicatedSignature(int a, const String& b) { - debugf("functionWithMoreComlicatedSignature %d %s", a, b.c_str()); + debugf("functionWithMoreComplicatedSignature %d %s", a, b.c_str()); } class Task { public: - Task(){}; + Task() + { + } + bool setTimer(int reqInterval) { if(reqInterval <= 0) { @@ -29,8 +32,6 @@ class Task void callPlainOldOrdinaryFunction() { taskTimer.initializeMs(taskInterval, plainOldOrdinaryFunction).start(); - // or just - // taskTimer.initializeMs(taskInterval, plainOldOrdinaryFunction).start(); } // This example shows how to use std::bind to make us of a function that has more parameters than our signature has @@ -74,23 +75,38 @@ class Task debugf("callMemberFunction"); } + void delegateCallback() + { + debugf("Delegate callback invoked, taskInterval = %u", taskInterval); + } + + // Shows how to use a capturing lambda with the task queue + void queueDelegateCallback() + { + System.queueCallback([this]() { delegateCallback(); }); + } + private: Timer taskTimer; - int taskInterval = 1000; + unsigned taskInterval = 1000; }; -Task task1; -Task task2; -Task task3; -Task task4; -Task task5; +static Task task1; +static Task task2; +static Task task3; +static Task task4; +static Task task5; +static Task task6; void init() { Serial.begin(COM_SPEED_SERIAL); + Serial.systemDebugOutput(true); evaluateSpeed(); + task1.queueDelegateCallback(); + task2.setTimer(1600); task2.callPlainOldOrdinaryFunction(); diff --git a/samples/Basic_Delegates/component.mk b/samples/Basic_Delegates/component.mk index 973dfe71cf..2e02dba4b6 100644 --- a/samples/Basic_Delegates/component.mk +++ b/samples/Basic_Delegates/component.mk @@ -1 +1,2 @@ DISABLE_SPIFFS = 1 +DEBUG_VERBOSE_LEVEL = 3 diff --git a/samples/Basic_Interrupts/app/application.cpp b/samples/Basic_Interrupts/app/application.cpp index 02777dd986..a08602cd69 100644 --- a/samples/Basic_Interrupts/app/application.cpp +++ b/samples/Basic_Interrupts/app/application.cpp @@ -86,28 +86,7 @@ void interruptDelegate() /* OK, so you probably got a number which hit 255 pretty quickly! It stays there to indicate the task queue * overflowed, which happens because we're getting way more interrupts than we can process in a timely manner, so - * lots of them get dropped. - * - * So what's happening? - * - * In our example, if you use a jumper wire to ground the input pin then it will bounce around as contact is made - * and then broken; this is known as 'contact bounce'. This means you'll get multiple outputs instead of a clean - * signal. If your circuit uses a push switch, reed switch, etc. then you'll need a 'debounce circuit'. - * - * If you want to try a very simple debounce circuit to see what happens, try this: - * - * 3v3 - * _|_ - * ___ 100nF - * | - * | - * INPUT >--------------> GPIO PIN - * - * You'll need to hit the reset button to get the task counter back to 0. Now try toggling the input again... - * I'll leave it to you to work out why this helps. Hint: We've enabled pullups on the input pins, so there - * will be another resistor in the circuit. - * - * There is plenty of information available on this subject. Just search the internet for 'debounce circuit'. + * lots of them get dropped. This is because of 'contact bounce'. */ } diff --git a/samples/LiveDebug/app/application.cpp b/samples/LiveDebug/app/application.cpp index 60ded5f0b8..a60f0186f6 100644 --- a/samples/LiveDebug/app/application.cpp +++ b/samples/LiveDebug/app/application.cpp @@ -382,7 +382,7 @@ COMMAND_HANDLER(queueBreak) { Serial.println(_F("Queuing a call to gdb_do_break()\r\n" "This differs from `break` in that a console read will be in progress when the break is called")); - System.queueCallback(TaskCallback(handleCommand_break)); + System.queueCallback(handleCommand_break); return true; } @@ -619,7 +619,7 @@ void onConsoleReadCompleted(const GdbSyscallInfo& info) void readConsole() { consoleOffRequested = false; - System.queueCallback([](uint32_t) { + System.queueCallback(InterruptCallback([]() { showPrompt(); if(gdb_present() == eGDB_Attached) { // Issue the syscall @@ -641,7 +641,7 @@ void readConsole() * GDB is either detached or not present, serial callback will process input */ } - }); + })); } extern "C" void gdb_on_attach(bool attached) diff --git a/tests/HostTests/app/test-timers.cpp b/tests/HostTests/app/test-timers.cpp index 72f6ae3f11..cc680807c5 100644 --- a/tests/HostTests/app/test-timers.cpp +++ b/tests/HostTests/app/test-timers.cpp @@ -28,14 +28,14 @@ class CallbackTimerTest : public TestGroup static void IRAM_ATTR timer1Callback(void* arg) { System.queueCallback( - [](uint32_t param) { + [](void* param) { debugf("timer1 expired"); - auto tmr = reinterpret_cast(param); + auto tmr = static_cast(param); if(++tmr->count == 5) { tmr->stop(); } }, - uint32_t(arg)); + arg); } void execute() override