├── .gitignore ├── .npmignore ├── .travis.yml ├── .yotta_setup.sh ├── DOXYGEN_FRONTPAGE.md ├── LICENSE ├── apache-2.0.txt ├── coverage.json ├── minar-internal-headers └── CallbackNode.h ├── minar ├── minar.h └── trace.h ├── module.json ├── readme.md ├── source └── minar.cpp └── test ├── complex_dispatch.cpp └── dispatchtest.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | yotta_modules/* 3 | yotta_targets/* 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | build/* 2 | yotta_modules/* 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | compiler: 3 | - clang 4 | 5 | before_install: 6 | - /bin/bash ./.yotta_setup.sh 7 | 8 | install: 9 | # !!! TODO ... install toolchain 10 | - yotta target x86-linux-native 11 | 12 | script: 13 | - yotta install 14 | - yotta build 15 | -------------------------------------------------------------------------------- /.yotta_setup.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | wget https://github.com/ARMmbed/yotta/tarball/master 4 | tar -xf master 5 | pip install ./ARMmbed-yotta-* 6 | 7 | -------------------------------------------------------------------------------- /DOXYGEN_FRONTPAGE.md: -------------------------------------------------------------------------------- 1 | # MINAR API 2 | 3 | This is the API for MINAR. For more information about MINAR, please see the [mbed OS User Guide](https://docs.mbed.com/docs/getting-started-mbed-os/en/latest/Full_Guide/MINAR/) or the [MINAR GitHub repository](https://github.com/ARMmbed/minar). 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Unless specifically indicated otherwise in a file, files are licensed 2 | under the Apache 2.0 license, as can be found in: apache-2.0.txt 3 | 4 | 5 | -------------------------------------------------------------------------------- /apache-2.0.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 ARM Limited 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /coverage.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "debug":{ 4 | "options" : { 5 | "coverage" : { 6 | "modules" : { 7 | "minar" : true 8 | } 9 | } 10 | } 11 | }, 12 | "mbed-os": { 13 | "stdio": { 14 | "default-baud": 115200 15 | } 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /minar-internal-headers/CallbackNode.h: -------------------------------------------------------------------------------- 1 | /* 2 | * PackageLicenseDeclared: Apache-2.0 3 | * Copyright (c) 2015 ARM Limited 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef __MINAR_CALLBACKNODE_H__ 19 | #define __MINAR_CALLBACKNODE_H__ 20 | 21 | #include "minar/minar.h" 22 | #include "core-util/ExtendablePoolAllocator.h" 23 | #include "core-util/assert.h" 24 | #include "minar/trace.h" 25 | 26 | /** 27 | * Parameters to control the initial size and growth increments for the pool of 28 | * CallbackNodes. The default values are expected to come from root level target 29 | * descriptions; but may be overridden by platform or application specific 30 | * configurations. 31 | * 32 | * @note: The values below take effect only if config definitions in the target 33 | * hierarchy don't include defaults. Refer to the output of 'yotta config' for 34 | * available defaults. 35 | * 36 | * TODO: these default values need some serious profiling. 37 | */ 38 | #ifndef YOTTA_CFG_MINAR_INITIAL_EVENT_POOL_SIZE 39 | #define YOTTA_CFG_MINAR_INITIAL_EVENT_POOL_SIZE 50 40 | #endif 41 | #ifndef YOTTA_CFG_MINAR_ADDITIONAL_EVENT_POOLS_SIZE 42 | #define YOTTA_CFG_MINAR_ADDITIONAL_EVENT_POOLS_SIZE 100 43 | #endif 44 | 45 | namespace minar{ 46 | /// Callbacks are stored as a sorted tree of these, currently just ordered by 47 | /// 'call_before', which enables a very simple form of coalescing. To do much 48 | /// better we need to estimate or learn how long each call will take, and use 49 | /// something like a proper interval tree. 50 | struct CallbackNode { 51 | CallbackNode() 52 | : cb(), call_before(0), tolerance(0), 53 | interval(0){ 54 | } 55 | CallbackNode( 56 | minar::callback_t cb, 57 | minar::tick_t call_before, 58 | minar::tick_t tolerance, 59 | minar::tick_t interval 60 | ) : cb(cb), call_before(call_before), tolerance(tolerance), 61 | interval(interval){ 62 | } 63 | static void* operator new(std::size_t size){ 64 | ytTraceMem("CallbackNode alloc %u\n", size); 65 | (void)size; 66 | void *p = get_allocator()->alloc(); 67 | if (NULL == p) { 68 | CORE_UTIL_RUNTIME_ERROR("Unable to allocate CallbackNode"); 69 | } 70 | return p; 71 | } 72 | 73 | static void operator delete(void *p){ 74 | ytTraceMem("CallbackNode free %u\n", sizeof(CallbackNode)); 75 | get_allocator()->free(p); 76 | } 77 | 78 | /// The callback pointer 79 | minar::callback_t cb; 80 | 81 | /// The scheduler will try quite hard to call the function at (or up to 82 | /// 'tolerance' before) 'call_before'. In the event that there is more to 83 | /// do than time to do it then it may still be called later. 84 | minar::tick_t call_before; 85 | minar::tick_t tolerance; 86 | 87 | /// For more-efficient repeating callbacks, store the interval here and 88 | /// re-schedule as soon as execution is completed, without another free & 89 | /// alloc. 90 | /// 91 | /// 0 means do not repeat 92 | minar::tick_t interval; 93 | 94 | static mbed::util::ExtendablePoolAllocator *get_allocator() { 95 | static mbed::util::ExtendablePoolAllocator *allocator = NULL; 96 | 97 | if (NULL == allocator) { 98 | UAllocTraits_t traits; 99 | traits.flags = UALLOC_TRAITS_NEVER_FREE; // allocate in the never-free heap 100 | allocator = new mbed::util::ExtendablePoolAllocator; 101 | if (allocator == NULL) { 102 | CORE_UTIL_RUNTIME_ERROR("Unable to create allocator for CallbackNode"); 103 | } 104 | if (!allocator->init(YOTTA_CFG_MINAR_INITIAL_EVENT_POOL_SIZE, YOTTA_CFG_MINAR_ADDITIONAL_EVENT_POOLS_SIZE, sizeof(CallbackNode), traits)) { 105 | CORE_UTIL_RUNTIME_ERROR("Unable to initialize allocator for CallbackNode"); 106 | } 107 | } 108 | return allocator; 109 | } 110 | }; // struct CallbackNode 111 | 112 | } // namespace minar 113 | 114 | #endif // #ifndef __MINAR_CALLBACKNODE_H__ 115 | 116 | -------------------------------------------------------------------------------- /minar/minar.h: -------------------------------------------------------------------------------- 1 | /* 2 | * PackageLicenseDeclared: Apache-2.0 3 | * Copyright (c) 2015 ARM Limited 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef __MINAR_MINAR_H__ 19 | #define __MINAR_MINAR_H__ 20 | 21 | #include "compiler-polyfill/attributes.h" 22 | #include "minar-platform/minar_platform.h" 23 | //#include "mbed.h" 24 | // [TODO] change this 25 | #include "core-util/Event.h" 26 | #include "core-util/FunctionPointer.h" 27 | 28 | namespace minar{ 29 | 30 | /// @name Types 31 | enum Constants{ 32 | /// Number of callbacks to look ahead when trying to find the optimal thing 33 | /// to execute 34 | Optimise_Lookahead = 5, 35 | // warn if callbacks take longer than this to execute 36 | Warn_Duration_Milliseconds = 10, 37 | // warn if a callback cannot be executed this long after it should have 38 | // been because necessary resources are not available 39 | Warn_Late_Milliseconds = 100, 40 | // warn if the event loop is lagging (all callbacks are being executed late 41 | // because there is too much to do) by more than this 42 | Warn_Lag_Milliseconds = 500, 43 | }; 44 | 45 | /// Basic callback type 46 | typedef mbed::util::Event callback_t; 47 | 48 | /// Internal time type 49 | typedef platform::tick_t tick_t; 50 | 51 | /// Handle onto scheduled callbacks 52 | typedef void* callback_handle_t; 53 | 54 | class SchedulerData; 55 | 56 | class Scheduler{ 57 | private: 58 | class CallbackAdder{ 59 | friend class Scheduler; 60 | public: 61 | CallbackAdder& delay(tick_t delay); 62 | CallbackAdder& tolerance(tick_t tolerance); 63 | CallbackAdder& period(tick_t tolerance); 64 | 65 | callback_handle_t getHandle(); 66 | 67 | ~CallbackAdder(); 68 | 69 | private: 70 | CallbackAdder(Scheduler& sched, callback_t cb); 71 | 72 | Scheduler& m_sched; 73 | callback_t m_cb; 74 | tick_t m_tolerance; 75 | tick_t m_delay; 76 | tick_t m_period; 77 | bool m_posted; 78 | }; 79 | public: 80 | // get the global scheduler instance 81 | // The scheduler will be automatically initialised the first time it is 82 | // referenced. 83 | // It is not currently possible to de-initialize the scheduler after it 84 | // has been initialised (it will exist for the lifetime of the 85 | // program). 86 | static Scheduler* instance(); 87 | 88 | /// start the scheduler, never returns in normal operation. The return type is 89 | /// (int) so that the end of a program's main function can be: 90 | /// ... 91 | /// return sched.start(); 92 | /// } 93 | static int start(); 94 | 95 | /// stop the scheduler, (even if there is still work to do), returns the number 96 | /// of items in the scheduling queue. This function should not normally be 97 | /// used, and is only provided as a convenience for writing tests. 98 | static int stop(); 99 | 100 | // Function for posting callback with bound argument(s) 101 | // usage: postCallback([&]{ ... // }).withDelay(...).requiring(...).releasing(...); 102 | static CallbackAdder postCallback(callback_t const& cb); 103 | 104 | 105 | // Function for posting callbacks using FunctionPointer objects without arguments 106 | static CallbackAdder postCallback(mbed::util::FunctionPointer& callback) 107 | { 108 | return postCallback(callback.bind()); 109 | } 110 | 111 | // Functions for posting callbacks to direct function pointers 112 | // and objects/member pointers without arguments 113 | static CallbackAdder postCallback(void (*callback)(void)) 114 | { 115 | return postCallback(mbed::util::FunctionPointer(callback).bind()); 116 | } 117 | 118 | template 119 | static CallbackAdder postCallback(T *object, void (T::*member)()) 120 | { 121 | return postCallback(mbed::util::FunctionPointer(object, member).bind()); 122 | } 123 | 124 | static int cancelCallback(callback_handle_t handle); 125 | 126 | static tick_t getTime(); 127 | 128 | private: 129 | 130 | 131 | Scheduler(); 132 | 133 | // [FPTR] this was a unique_ptr, what's the consequence of making it a simple pointer? 134 | SchedulerData* data; 135 | }; 136 | 137 | /// @name Time 138 | 139 | /// convert milliseconds into the internal "ticks" time representation 140 | tick_t milliseconds(uint32_t miliseconds); 141 | 142 | /// convert ticks to milliseconds 143 | uint32_t ticks(tick_t ticks); 144 | 145 | 146 | /// Return the scheduled execution time of the current callback. This lags 147 | /// behind the wall clock time if the system is busy. 148 | /// 149 | /// Note that this time is NOT monotonic. If callbacks are executed in an order 150 | /// different to their scheduled order because of the resources they need, then 151 | /// this time will jump backwards. 152 | tick_t getTime(); 153 | 154 | 155 | } // namespace minar 156 | 157 | #endif // ndef __MINAR_MINAR_H__ 158 | -------------------------------------------------------------------------------- /minar/trace.h: -------------------------------------------------------------------------------- 1 | /* 2 | * PackageLicenseDeclared: Apache-2.0 3 | * Copyright (c) 2015 ARM Limited 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef __MINAR_TRACE_H__ 19 | #define __MINAR_TRACE_H__ 20 | 21 | //#define __MINAR_TRACE_MEMORY__ 22 | //#define __MINAR_TRACE_DISPATCH__ 23 | 24 | #ifdef __MINAR_TRACE_MEMORY__ 25 | extern "C" { 26 | #include 27 | } 28 | #define ytTraceMem(...) printf(__VA_ARGS__) 29 | #else 30 | #define ytTraceMem(...) do{}while(0) 31 | #endif 32 | 33 | #ifdef __MINAR_TRACE_DISPATCH__ 34 | extern "C" { 35 | #include 36 | } 37 | #define ytTraceDispatch(...) printf(__VA_ARGS__) 38 | #else 39 | #define ytTraceDispatch(...) do{}while(0) 40 | #endif 41 | 42 | /* Run time warnings are turned on by default only in debug builds 43 | * The user can override this behaviour by setting MINAR_NO_RUNTIME_WARNINGS 44 | * in yotta config. Example: yt build -d --config '{"MINAR_NO_RUNTIME_WARNINGS":1}' */ 45 | #ifndef YOTTA_CFG_MINAR_NO_RUNTIME_WARNINGS 46 | #define YOTTA_CFG_MINAR_NO_RUNTIME_WARNINGS NDEBUG 47 | #endif 48 | 49 | #if YOTTA_CFG_MINAR_NO_RUNTIME_WARNINGS 50 | #define ytWarning(...) do{}while(0) 51 | #else 52 | #include 53 | #define ytWarning(...) printf(__VA_ARGS__) 54 | #endif 55 | 56 | #endif // #ifndef __MINAR_TRACE_H__ 57 | 58 | -------------------------------------------------------------------------------- /module.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "minar", 3 | "version": "1.3.0", 4 | "description": "The mbed OS scheduler.", 5 | "repository": { 6 | "type": "git", 7 | "url": "git@github.com:ARMmbed/minar.git" 8 | }, 9 | "homepage": "https://github.com/ARMmbed/minar", 10 | "bugs": { 11 | "url": "http://github.com/ARMmbed/minar/issues", 12 | "email": "project@hostname.com" 13 | }, 14 | "author": "James Crosby ", 15 | "licenses": [ 16 | { 17 | "url": "https://spdx.org/licenses/Apache-2.0", 18 | "type": "Apache-2.0" 19 | } 20 | ], 21 | "dependencies": { 22 | "compiler-polyfill": "^1.1.0", 23 | "minar-platform": "^1.0.0", 24 | "core-util": "^1.0.0" 25 | }, 26 | "testDependencies": { 27 | "greentea-client": ">=0.1.4,<2.0.0", 28 | "unity": "^2.0.0" 29 | }, 30 | "scripts": { 31 | "testReporter": [ 32 | "mbedgt", 33 | "--digest", 34 | "stdin", 35 | "-v", 36 | "-V" 37 | ] 38 | }, 39 | "keywords": [ 40 | "mbed", 41 | "mbed-official" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # MINAR scheduler 2 | 3 | MINAR is an *event scheduler*. Applications instruct MINAR to execute blocks of code called *events* (either immediately, or in the future), and MINAR decides when to run those blocks based on their scheduled execution time. When there's nothing to execute, MINAR puts the MCU to sleep. 4 | 5 | A (very) simplified view of MINAR's main loop can be found below (we'll improve it later): 6 | 7 | ``` 8 | while (true) { 9 | while (event_available_to_schedule()) 10 | schedule_event(); 11 | sleep(); 12 | } 13 | ``` 14 | 15 | ## Events 16 | 17 | In MINAR, an *event* is a pointer to a function, plus a specific binding of the function's arguments. The event is created from a [FunctionPointer](https://github.com/ARMmbed/core-util/blob/master/core-util/FunctionPointer.h) by calling its `bind` method. Although the `FunctionPointer` implementation was largely rewritten in mbed OS, it serves the same purpose as it did in mbed Classic: it keeps a pointer to a function or a method. Usage example: 18 | 19 | ``` 20 | #include "mbed.h" 21 | 22 | void f0(void) { 23 | } 24 | 25 | int f1(const char *arg) { 26 | } 27 | 28 | class A { 29 | public: 30 | void m(int a) { 31 | } 32 | } 33 | 34 | void test() { 35 | A a; 36 | FunctionPointer0 ptr_to_f0(f0); 37 | FunctionPointer1 ptr_to_f1(f1); 38 | FunctionPointer1 ptr_to_m(&a, &A::m); 39 | 40 | ptr_to_f0.call(); 41 | ptr_to_f1("test"); // you can also call directly, without 'call' 42 | ptr_to_m.call(0); // or simply 'ptr_to_m(0);' 43 | } 44 | ``` 45 | 46 | At the moment, there are implementations of `FunctionPointer` for functions without an argument (`FunctionPointer0`), as well as functions with one argument (`FunctionPointer1`), two arguments (`FunctionPointer2`) and three arguments (`FunctionPointer3`). 47 | 48 | In order to create an event from a function pointer, its `bind` method needs to be called. `bind` takes a set of fixed values for the function's arguments (if the function has arguments) and creates a [FunctionPointerBind](https://github.com/ARMmbed/core-util/blob/master/core-util/FunctionPointerBind.h) instance. `FunctionPointerBind` keeps a copy of those fixed values and allows us to call the function later with those fixed arguments without having to specify them again. This is best explained by an example. Building on top of the code above: 49 | 50 | ``` 51 | // FunctionPointerBind is templated on the return type of the bound function 52 | FunctionPointerBind bind_of_f0(ptr_to_f0.bind()); // f0 doesn't take any arguments 53 | FunctionPointerBind bind_of_f1(ptr_to_f1.bind("test")); // bind the argument "test" to this FunctionPointerBind 54 | FunctionPointerBind bind1_of_m(ptr_to_m.bind(0)); // bind 0 to this FunctionPointerBind 55 | FunctionPointerBind bind2_of_m(ptr_to_m.bind(10)); // bind 10 to this FunctionPointerBind 56 | 57 | bind_of_f0.call(); // equivalent to ptr_to_f0.call() 58 | bind_of_f1(); // equivalent to ptr_to_f1.call("test") 59 | bind1_of_m(); // equivalent to ptr_to_m.call(0) 60 | bind2_of_m(); // equivalent to ptr_to_m.call(10) 61 | ``` 62 | (Many more examples involving `FunctionPointer` and `Event` can be found [here](https://github.com/ARMmbed/core-util/blob/master/test/EventHandler/main.cpp)) 63 | 64 | The size of storage for the argument's values in `FunctionPointerBind` is fixed, which means that all `FunctionPointerBind` instances have the same size in memory. If the combined size of the arguments of `bind` is larger than the size of storage in `FunctionPointerBind`, you'll get a compiler error. 65 | 66 | A MINAR [Event](https://github.com/ARMmbed/core-util/blob/master/core-util/Event.h) is simply a `FunctionPointerBind` for functions that don't return any arguments: 67 | 68 | ``` 69 | typedef FunctionPointerBind Event; 70 | ``` 71 | 72 | In conclusion, using the mechanisms shown above, you can schedule any kind of function with various argument(s) by instantiating the proper `FunctionPointer` class with that function and then calling `bind` on the `FunctionPointer` instance. This will work so long as the function doesn't return anything and the total storage space required for its arguments is less than the fixed storage size in `FunctionPointerBind`. 73 | 74 | ## Using events 75 | 76 | To actually schedule an event, you call the `postCallback` function in MINAR. Building on the code above: 77 | 78 | ``` 79 | minar::Scheduler::postCallback(bind_of_f0); 80 | // note that f1 above can't be an event, since it returns something 81 | ``` 82 | 83 | `postCallback` adds `bind_of_f0` into the MINAR event queue, scheduling it to be executed as soon as possible. When calling `postCallback` you can specify a few more attributes for the event: 84 | 85 | - `period`: the event will run periodically, with the specified interval. 86 | - `delay`: the event will be executed after the specified delay. 87 | - `tolerance`: the tolerance for the event's execution time. 88 | 89 | Periods, delays and tolerances are expressed in _ticks_. Ticks are an internal MINAR type and the actual duration of a tick depends on the platform on which MINAR is running, so using ticks directly is not recommended. You can convert from ticks to milliseconds by calling `minar::milliseconds`. 90 | 91 | A tolerance is necessary for the efficient scheduling of callbacks. By providing a tolerance, it permits minar to group callbacks together if they have overlapping execution schedules and tolerances. This permits minar to schedule several callbacks in a single wakeup even if there is time between their desired execution times. It's important to provide minar with realistic tolerances, since large tolerances will improve power efficiency. For example, network code can accept significant delays without reduction in performance. The default value of `tolerance` is 50 milliseconds. 92 | 93 | Period, delay and tolerance can be specified in any order. Some examples: 94 | 95 | ``` 96 | void f() { 97 | } 98 | Event e(FunctionPointer0(f).bind()); 99 | 100 | // Schedule to execute as soon as possible 101 | minar::Scheduler::postCallback(e); 102 | // Schedule to execute after 100ms 103 | minar::Scheduler::postCallback(e).delay(minar::milliseconds(100)); 104 | // Schedule to execute each 500ms with an initial delay of 10ms 105 | minar::Scheduler::postCallback(e).period(minar::milliseconds(500)).delay(minar::milliseconds(10)) 106 | // Schedule to execute each 100ms, with a tolerance of 2ms 107 | minar::Scheduler::postCallback(e).tolerance(minar::milliseconds(2)).period(minar::milliseconds(100)); 108 | ``` 109 | 110 | With this in mind, we can now construct a better (but still simplified) pseudo-code representation of MINAR's event loop: 111 | 112 | ``` 113 | while(true) { 114 | // Look at the next event in the queue: 115 | // The next event to execute (sorted by execution time) is always 116 | // located at the top of the queue 117 | next = peekNext(); 118 | now_plus_tolerance = now + next->tolerance; // consider scheduling tolerance 119 | 120 | if(timeIsInPeriod(last_dispatch, next->call_before, now_plus_tolerance)) { 121 | // The next event in the queue is due, execute it now 122 | next->call(); // actual event execution 123 | 124 | if(!next->period) { 125 | pop(next); // we're done with this event 126 | } else { 127 | reschedule(next); // periodic event, re-schedule it 128 | } 129 | } else { 130 | // Nothing to do for now, so go to sleep until the next event 131 | // in the queue in due 132 | sleepFromUntil(now, peekNext()->call_before); 133 | } 134 | } 135 | ``` 136 | 137 | The above pseudo-code sequence should be enough to explain a very important feature of MINAR: **MINAR is not a pre-emptive scheduler**. The user events execute uninterrupted (`next->call()` above); control goes back to MINAR's event loop when the event exits. No pre-emption means that you won't have to worry about complex, hard to understand synchronization issues between different parts of your application, like you'd have to do in a traditional RTOS. However, it also means that **MINAR is not a real time scheduler**. If your callbacks take a lot of time to execute, some other callbacks in the system might start later than expected. Consider a simple example: 138 | 139 | ``` 140 | void f(void) { 141 | } 142 | 143 | void function_that_takes_100ms(void) { 144 | } 145 | 146 | Event e(FunctionPointer0(f).bind()); 147 | Event long_e(FunctionPointer0(function_that_takes_100ms).bind()); 148 | 149 | minar::Scheduler::postCallback(long_e).delay(minar::milliseconds(5)); 150 | minar::Scheduler::postCallback(e).delay(minar::milliseconds(10)); 151 | 152 | // MINAR will execute 'long_e' first, because it has the shortest delay. 153 | // 'long_e' will execute for 100ms 154 | // 'e' would then execute after 105ms instead of 10ms as originally intended 155 | ``` 156 | 157 | To avoid this kind of situation, remember to **keep the code for your events as short as possible**. This will give other events a chance to execute in time. 158 | 159 | This also means that **you can't use infinite loops in your application code any more**. In mbed Classic (and traditional embedded programming in general) the following pattern is quite common: 160 | 161 | ``` 162 | // WARNING: don't do this in mbed OS. 163 | int main() { 164 | while(1) { 165 | // Your application logic goes here 166 | } 167 | } 168 | ``` 169 | 170 | In mbed OS, the only infinite loop in the system exists in the MINAR scheduler (see the pseudo-code above). Since events must return on their own to give control back to the scheduler, an infinite loop in an event will prevent the scheduler from running, which in turn prevents other events from being executed. 171 | 172 | ## Event details 173 | 174 | An important thing to keep in mind is that **events are passed to MINAR by value**. When MINAR receives an event, it will make a copy of the event in an internal storage area, so even if the original event object gets out of scope, MINAR will still be able to call the corresponding function with its correct arguments later. This means that you don't have to worry if the event object goes out of scope after you call `postCallback` (so it's safe to use temporary objects): 175 | 176 | ``` 177 | void f(int a) { 178 | } 179 | 180 | minar::Scheduler::postCallback(FunctionPointer0(f).bind(10)); 181 | ``` 182 | 183 | Be careful though: MINAR only keeps a copy of the `Event` instance itself and nothing else outside that. If the event is bound to an object that goes out of scope before the event is scheduled, your program will likely not behave as expected (and might even crash): 184 | 185 | ``` 186 | class A { 187 | public: 188 | A(int i): _i(i) { 189 | } 190 | 191 | int f() { 192 | printf("i = %d", _i); 193 | } 194 | 195 | private: 196 | int _i; 197 | }; 198 | 199 | void test() { 200 | A a(10); 201 | // The intention is to call a.f() after 100ms 202 | minar::Scheduler::postCallback(FunctionPointer0(&a, &A::f).bind()). 203 | delay(minar::milliseconds(100)); 204 | // 'test' will exit immediately after `postCallback` above finishes 205 | // and 'a' will go out of scope. 100ms later, MINAR will try to call 206 | // 'A::f' on an instance that does not exist anymore ('a'), which leads 207 | // to undefined behaviour. 208 | } 209 | ``` 210 | 211 | ## Impact 212 | 213 | MINAR is the event scheduler of mbed OS, so it's important to understand how to use it properly. The first thing you're likely to notice is that mbed OS applications don't have a `main` function anymore, they use `app_start` instead: 214 | 215 | ``` 216 | // mbed OS application 217 | void app_start(int argc, char *argv[]) { 218 | // Application code starts here 219 | } 220 | ``` 221 | 222 | The difference between `main` and `app_start` is that `app_start` is an event scheduled with MINAR (using `postCallback`). `main` is now common for all applications: 223 | 224 | ``` 225 | extern "C" int main(void) { 226 | minar::Scheduler::postCallback( 227 | FunctionPointer2(&app_start).bind(0, NULL) 228 | ); 229 | // The 'start' function below doesn't actually return, since it runs 230 | // the MINAR's infinite event scheduler loop 231 | return minar::Scheduler::start(); 232 | } 233 | ``` 234 | 235 | MINAR encourages an asynchronous programming style in which functions that are expected to take a lot of time return immediately and post an event when they're done. This is again different from mbed Classic, where lots of functions (for example functions related to I/O operations) are blocking. Some of these blocking functions are still present in mbed OS, but their use is discouraged. Support for non-blocking operations in mbed OS is already in place for some modules and will be enhanced in the future. 236 | 237 | "Reasonable" blocking behaviour is still fine. You don't need to use asynchronous calls for everything; if you need to wait "about" a microsecond for something to happen (using, for example, an empty **for** loop), that's fine in most cases. The definition of "reasonable" depends on the requirements of your particular application. 238 | 239 | ## Runtime Warnings 240 | 241 | Warnings are printed to the serial port in the following situations: 242 | 243 | 1. if callbacks take longer than `Warn_Duration_Milliseconds` (10ms) to execute. 244 | 1. if the event loop is lagging (all callbacks are being executed late because there is too much to do) by more than `Warn_Lag_Milliseconds` (500ms). 245 | 246 | Warnings can be disabled by using [`yotta config`](http://yottadocs.mbed.com/reference/config.html). Just add the following to `config.json`. 247 | 248 | ``` 249 | { 250 | "MINAR_NO_RUNTIME_WARNINGS" : true 251 | } 252 | ``` 253 | 254 | # Recap 255 | 256 | - MINAR is an event scheduler, always enabled in mbed OS. 257 | - You can schedule events with MINAR. Events are regular C/C++ functions. 258 | - MINAR is not a pre-emptive scheduler. Control gets back to MINAR when the currently scheduled event finishes execution. 259 | - Events shouldn't take too much time to execute. 260 | - Event functions should never block in an infinite loop. 261 | - Your applications start with `app_start` now, not with `main`. 262 | - You are encouraged to use the new non-blocking APIs in mbed OS as much as possible. 263 | 264 | -------------------------------------------------------------------------------- /source/minar.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * PackageLicenseDeclared: Apache-2.0 3 | * Copyright (c) 2015 ARM Limited 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include "minar/minar.h" 19 | 20 | #include 21 | #include 22 | 23 | #include "minar-platform/minar_platform.h" 24 | 25 | #include "core-util/CriticalSectionLock.h" 26 | #include "core-util/BinaryHeap.h" 27 | #include "core-util/assert.h" 28 | #include "minar-internal-headers/CallbackNode.h" 29 | #include "minar/trace.h" 30 | 31 | using mbed::util::CriticalSectionLock; 32 | using mbed::util::BinaryHeap; 33 | using mbed::util::MinCompare; 34 | 35 | /// - Private Types 36 | 37 | namespace minar{ 38 | struct YTScopeTimer{ 39 | YTScopeTimer(minar::tick_t threshold, const char* msg, const void* ptr) 40 | : start(minar::platform::getTime()), thr(threshold), msg(msg), ptr(ptr){ 41 | } 42 | ~YTScopeTimer(){ 43 | const minar::tick_t dur = (minar::platform::Time_Mask & (minar::platform::getTime() - start)); 44 | if(dur > thr) 45 | ytWarning("WARNING: %s %p took %lums\n", msg, ptr, dur / minar::milliseconds(1)); 46 | } 47 | 48 | minar::tick_t start; 49 | minar::tick_t const thr; 50 | const char* const msg; 51 | const void* const ptr; 52 | }; 53 | 54 | 55 | class SchedulerData{ 56 | public: 57 | typedef CallbackNode* heap_node_t; // the binary heap (below) holds pointers to CallbackNode instances 58 | struct CallbackNodeCompare{ 59 | CallbackNodeCompare(SchedulerData const& sched) 60 | : sched(sched){ 61 | } 62 | // This function defines how the binary heap is ordered 63 | bool operator ()(const heap_node_t &a, const heap_node_t &b) const; 64 | 65 | SchedulerData const& sched; 66 | }; 67 | typedef BinaryHeap dispatch_tree_t; 68 | 69 | SchedulerData(); 70 | 71 | minar::callback_handle_t postGeneric( 72 | // [FPTR] cb below used to be a move ref, is there a better alternative to copy? 73 | minar::callback_t cb, 74 | minar::tick_t at, 75 | minar::tick_t interval, 76 | minar::tick_t double_sided_tolerance 77 | ); 78 | 79 | int cancel(callback_handle_t callback); 80 | 81 | int start(); 82 | 83 | // The dispatch queue is sorted by the latest possible evaluation time 84 | // of each callback (i.e. callbacks later in the queue may be possible 85 | // to evaluate sooner than those earlier) 86 | dispatch_tree_t dispatch_tree; 87 | 88 | minar::tick_t last_dispatch; 89 | minar::tick_t current_dispatch; 90 | bool stop_dispatch; 91 | }; 92 | 93 | /// - Private Function Declarations 94 | static minar::tick_t wrapTime(minar::tick_t time); 95 | static minar::tick_t smallestTimeIncrement(minar::tick_t from, minar::tick_t to_a, minar::tick_t or_b); 96 | static void* addressForFunction(minar::callback_t fn); 97 | static bool timeIsInPeriod(minar::tick_t start, minar::tick_t time, minar::tick_t end); 98 | 99 | /// - Pointer to instance 100 | static minar::Scheduler* staticScheduler = NULL; 101 | 102 | } // namespace minar 103 | 104 | 105 | /// - Implementation of minar class 106 | 107 | minar::Scheduler::CallbackAdder& minar::Scheduler::CallbackAdder::delay( 108 | minar::tick_t delay 109 | ){ 110 | m_delay = delay; 111 | return *this; 112 | } 113 | 114 | minar::Scheduler::CallbackAdder& minar::Scheduler::CallbackAdder::tolerance( 115 | minar::tick_t tolerance 116 | ){ 117 | m_tolerance = tolerance; 118 | return *this; 119 | } 120 | 121 | minar::Scheduler::CallbackAdder& minar::Scheduler::CallbackAdder::period( 122 | minar::tick_t period 123 | ){ 124 | m_period = period; 125 | return *this; 126 | } 127 | 128 | minar::callback_handle_t minar::Scheduler::CallbackAdder::getHandle(){ 129 | if(m_cb && !m_posted){ 130 | minar::callback_handle_t temp = m_sched.data->postGeneric( 131 | // [FPTR] std::move was used below, is there a better way to do this? 132 | m_cb, 133 | minar::platform::getTime() + m_delay, 134 | m_period, 135 | m_tolerance 136 | ); 137 | m_posted = true; 138 | return temp; 139 | } 140 | return NULL; 141 | } 142 | 143 | minar::Scheduler::CallbackAdder::~CallbackAdder(){ 144 | getHandle(); 145 | } 146 | 147 | minar::Scheduler::CallbackAdder::CallbackAdder(Scheduler& sched, callback_t cb) 148 | : m_sched(sched), 149 | m_cb(cb), 150 | m_tolerance(minar::milliseconds(50)), 151 | m_delay(minar::milliseconds(0)), 152 | m_period(minar::milliseconds(0)), 153 | m_posted(false){ 154 | } 155 | 156 | minar::Scheduler* minar::Scheduler::instance(){ 157 | if(!staticScheduler){ 158 | staticScheduler = new minar::Scheduler(); 159 | 160 | minar::platform::init(); 161 | 162 | CORE_UTIL_ASSERT(staticScheduler->data->dispatch_tree.get_num_elements() == 0 && "State not clean: cannot init."); 163 | 164 | staticScheduler->data->last_dispatch = minar::platform::getTime(); 165 | staticScheduler->data->current_dispatch = staticScheduler->data->last_dispatch; 166 | } 167 | return staticScheduler; 168 | } 169 | 170 | minar::Scheduler::Scheduler() 171 | // !!! FIXME: make_unique is C++14 172 | //: data(std::make_unique()){ 173 | : data(new minar::SchedulerData()){ 174 | } 175 | 176 | int minar::Scheduler::start(){ 177 | instance(); 178 | return staticScheduler->data->start(); 179 | } 180 | 181 | int minar::Scheduler::stop(){ 182 | instance(); 183 | staticScheduler->data->stop_dispatch = true; 184 | return staticScheduler->data->dispatch_tree.get_num_elements(); 185 | } 186 | 187 | minar::Scheduler::CallbackAdder minar::Scheduler::postCallback( 188 | minar::callback_t const& cb 189 | ){ 190 | instance(); 191 | return CallbackAdder(*staticScheduler, cb); 192 | } 193 | 194 | int minar::Scheduler::cancelCallback(minar::callback_handle_t handle){ 195 | instance(); 196 | return staticScheduler->data->cancel(handle); 197 | } 198 | 199 | minar::tick_t minar::Scheduler::getTime() { 200 | instance(); 201 | return staticScheduler->data->current_dispatch; 202 | } 203 | 204 | /// - SchedulerData Implementation 205 | 206 | minar::SchedulerData::SchedulerData() 207 | : dispatch_tree(CallbackNodeCompare(*this)), 208 | last_dispatch(0), 209 | current_dispatch(0), 210 | stop_dispatch(false){ 211 | UAllocTraits_t traits; 212 | 213 | traits.flags = UALLOC_TRAITS_NEVER_FREE; 214 | if (!dispatch_tree.init(YOTTA_CFG_MINAR_INITIAL_EVENT_POOL_SIZE, YOTTA_CFG_MINAR_ADDITIONAL_EVENT_POOLS_SIZE, traits)) { 215 | CORE_UTIL_RUNTIME_ERROR("Unable to initialize binary heap for SchedulerData"); 216 | } 217 | } 218 | 219 | bool minar::SchedulerData::CallbackNodeCompare::operator ()(const heap_node_t &a, const heap_node_t &b) const { 220 | // FIXME!!!! double-check that this works for the case where multiple 221 | // callbacks have the same dispatch time, and we pop one, set Dispatch equal 222 | // to that time, then re-sort 223 | if((a->call_before - sched.last_dispatch) < (b->call_before - sched.last_dispatch)) 224 | return true; 225 | else 226 | return false; 227 | return true; 228 | } 229 | 230 | int minar::SchedulerData::start(){ 231 | const static minar::tick_t Warn_Duration_Ticks = minar::milliseconds(minar::Warn_Duration_Milliseconds); 232 | const static minar::tick_t Warn_Lag_Ticks = minar::milliseconds(minar::Warn_Lag_Milliseconds); 233 | 234 | stop_dispatch = false; 235 | CallbackNode* next = NULL; 236 | minar::tick_t now = 0; 237 | minar::tick_t now_plus_tolerance = 0; 238 | bool something_to_do = false; 239 | 240 | while(!stop_dispatch){ 241 | now = minar::platform::getTime(); 242 | 243 | // look at the next callback, checking to see if we can execute it 244 | // because of the sort order, we will naturally execute the 245 | // must-execute-first callbacks first 246 | 247 | something_to_do = false; 248 | { 249 | CriticalSectionLock lock; 250 | CallbackNode *best = NULL; 251 | 252 | if(dispatch_tree.get_num_elements() > 0) { 253 | CallbackNode *root = dispatch_tree.get_root(); 254 | now_plus_tolerance = wrapTime(now + root->tolerance); 255 | if (timeIsInPeriod(last_dispatch, root->call_before, now_plus_tolerance)) { 256 | best = root; 257 | } 258 | } 259 | if (best != NULL) { 260 | next = best; 261 | dispatch_tree.remove_root(); 262 | something_to_do = true; 263 | 264 | // the last dispatch time must not be updated past the time of 265 | // the next thing in must-execute-by order, otherwise we will 266 | // break the sorting of our tree, and skip the execution of 267 | // things. If we haven't yet reached that time we shouldn't 268 | // update last_dispatch to be in the future though, (because if 269 | // we do that it might also go backwards) 270 | // 271 | // We have to perform this update with interrupts disabled 272 | // because we use last_dispatch for sorting dispatch_tree 273 | // 274 | // next is guaranteed to be the next item in must-execute-by 275 | // order, since the callback list is sorted in must-execute-by 276 | // order. 277 | last_dispatch = smallestTimeIncrement(last_dispatch, now, next->call_before); 278 | 279 | const minar::tick_t lag = wrapTime(now - last_dispatch); 280 | if(lag > Warn_Lag_Ticks) 281 | ytWarning("WARNING: event loop lag %lums\n", lag / minar::milliseconds(1)); 282 | } 283 | else 284 | { 285 | // nothing we can do right now... so go to sleep 286 | ytTraceDispatch("-_-\n"); 287 | 288 | // The platform_sleepFromUntil function must work 289 | // even with interrupts disabled (which is the case if the 290 | // WFE/WFI instructions are used) 291 | // 292 | // note that here we sleep for as *long* as possible (until the 293 | // latest possible evaluation time of the next callback (the 294 | // latest time is what the queue is sorted by)), to enable 295 | // simple coalescing 296 | 297 | // update last_dispatch to be the last time we checked, rather than 298 | // the last actual dispatch (which may be vanishing far into 299 | // the past if we have nothing to do) 300 | 301 | // if there's stuff to do then sleep until the next 302 | // must-execute-by time. If an interrupt changes this we will 303 | // wake up and unconditionally re-evaluate. 304 | 305 | // Find the next must-execute-by time that is not in the past 306 | // and sleep until then. 307 | // If none are found sleep unconditionally 308 | if (dispatch_tree.get_num_elements() > 0) { 309 | CallbackNode *root = dispatch_tree.get_root(); 310 | last_dispatch = smallestTimeIncrement(last_dispatch, now, root->call_before); 311 | minar::platform::sleepFromUntil(now, root->call_before); 312 | } else { 313 | last_dispatch = now; 314 | minar::platform::sleep(); 315 | } 316 | 317 | // before taking re-enabling interrupts (and taking any 318 | // interrupt handlers), make sure the time used for the basis 319 | // of any callbacks scheduled from the interrupt handlers is 320 | // up-to-date. 321 | current_dispatch = minar::platform::getTime(); 322 | } 323 | // after we wake from sleep (caused by an interrupt), 324 | // interrupts are re-enabled, we take any interrupt handlers, 325 | // then return here 326 | } 327 | 328 | // this is skipped when we return from sleep 329 | // because something_to_do will be false 330 | if(something_to_do){ 331 | // therefore "next" is valid 332 | ytTraceDispatch("[picked first, ahead / %d]\r\n", dispatch_tree.get_num_elements()); 333 | 334 | // current_dispatch is provided through the ytGetTime API call so 335 | // that functions can schedule future execution based on the 336 | // intended execution time of the callback, rather than the time it 337 | // actually executed. 338 | // 339 | // note that current_dispatch is always in the future (or equal) 340 | // compared to last_dispatch 341 | current_dispatch = wrapTime(next->call_before - next->tolerance/2); 342 | 343 | if(next->interval){ 344 | // recycle the callback for next time: do that here so that the 345 | // callback can cancel itself 346 | next->call_before = wrapTime(next->call_before + next->interval); 347 | dispatch_tree.insert(next); 348 | } 349 | 350 | // dispatch! 351 | if(next->cb){ 352 | ytTraceDispatch("[dispatch: now=%lx func=%p]\r\n", now, addressForFunction(next->cb)); 353 | YTScopeTimer t(Warn_Duration_Ticks, "callback", addressForFunction(next->cb)); 354 | next->cb(); 355 | } 356 | 357 | if(!next->interval){ 358 | // release any reference-counted callback as early as possible 359 | delete next; 360 | next = NULL; 361 | } 362 | } 363 | } // loop while(!stop_dispatch) 364 | 365 | return dispatch_tree.get_num_elements(); 366 | } 367 | 368 | minar::callback_handle_t minar::SchedulerData::postGeneric( 369 | // [FPTR] cb below used to be a move ref, is there a better alternative to copy? 370 | minar::callback_t cb, 371 | minar::tick_t at, 372 | minar::tick_t interval, 373 | minar::tick_t double_sided_tolerance 374 | ){ 375 | CORE_UTIL_ASSERT(double_sided_tolerance < (minar::platform::Time_Mask/2) + 1);//, "Callback tolerance greater than time wrap-around."); 376 | 377 | ytTraceDispatch("[post %lx %lx %p]\n", minar::platform::getTime(), at, addressForFunction(cb)); 378 | 379 | CallbackNode* n = new CallbackNode( 380 | cb, 381 | wrapTime(at + interval), 382 | 2 * double_sided_tolerance, 383 | interval 384 | ); 385 | dispatch_tree.insert(n); 386 | return n; 387 | } 388 | 389 | int minar::SchedulerData::cancel(minar::callback_handle_t handle) { 390 | CallbackNode *node = (CallbackNode*)handle; 391 | if (dispatch_tree.remove(node)) { 392 | delete node; 393 | return 1; 394 | } else { 395 | return 0; 396 | } 397 | } 398 | 399 | /// - Public Function Definitions 400 | 401 | /// @name Time 402 | 403 | /// convert milliseconds into the internal "ticks" time representation 404 | minar::tick_t minar::milliseconds(uint32_t milliseconds){ 405 | const uint64_t ticks = (((uint64_t) milliseconds) * ((uint64_t) minar::platform::Time_Base)) / 1000; 406 | CORE_UTIL_ASSERT(ticks < minar::platform::Time_Mask);//, @"Callback delay greater than time wrap-around."); 407 | return (minar::tick_t) (minar::platform::Time_Mask & ticks); 408 | } 409 | 410 | /// convert ticks into the milliseconds time representation 411 | uint32_t minar::ticks(minar::platform::tick_t ticks){ 412 | uint64_t milliseconds = ((uint64_t)ticks * 1000U) / minar::platform::Time_Base; 413 | assert(milliseconds <= 0xFFFFFFFF); 414 | return (uint32_t)milliseconds; 415 | } 416 | 417 | /// Return the scheduled execution time of the current callback. This lags 418 | /// behind the wall clock time if the system is busy. 419 | /// 420 | /// Note that this time is NOT monotonic. If callbacks are executed in an order 421 | /// different to their scheduled order because of the resources they need, then 422 | /// this time will jump backwards. 423 | minar::tick_t minar::getTime(){ 424 | return Scheduler::instance()->getTime(); 425 | } 426 | 427 | static minar::tick_t minar::wrapTime(minar::tick_t time){ 428 | return time & minar::platform::Time_Mask; 429 | } 430 | 431 | static minar::tick_t minar::smallestTimeIncrement(minar::tick_t from, minar::tick_t to_a, minar::tick_t or_b){ 432 | if((to_a >= from && or_b >= from) || (to_a < from && or_b < from)) 433 | return (to_a < or_b)? to_a : or_b; 434 | if(to_a > from && or_b < from) 435 | return to_a; 436 | //if(to_a < from && or_b > from) 437 | CORE_UTIL_ASSERT(to_a < from && or_b >= from);//, @" "); 438 | return or_b; 439 | } 440 | 441 | static void* minar::addressForFunction(minar::callback_t){ 442 | // !!! FIXME: need to poke into std::function to get an address that's 443 | // useful when debugging 444 | return NULL; 445 | } 446 | 447 | static bool minar::timeIsInPeriod(minar::tick_t start, minar::tick_t time, minar::tick_t end){ 448 | // Taking care to handle wrapping: (M = now + Minumum_Sleep) 449 | // Case (A.1) 450 | // S T E 451 | // 0 ---------------|----|---|-- 0xf 452 | // 453 | // Case (A.2): this case also allows S==T==E 454 | // E S T 455 | // 0 -|-----------------|----|-- 0xf 456 | // 457 | // Case (B) 458 | // T E S 459 | // 0 -|---|-----------------|--- 0xf 460 | // 461 | if((time >= start && ( time < end || // (A.1) 462 | start >= end)) || // (A.2) 463 | (time < start && end < start && end > time)){ // (B) 464 | return true; 465 | } 466 | return false; 467 | } 468 | 469 | -------------------------------------------------------------------------------- /test/complex_dispatch.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * PackageLicenseDeclared: Apache-2.0 3 | * Copyright (c) 2015 ARM Limited 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include "minar/minar.h" 20 | #include "mbed-drivers/mbed.h" 21 | #include "greentea-client/test_env.h" 22 | #include "unity/unity.h" 23 | #include "core-util/FunctionPointer.h" 24 | 25 | using mbed::util::FunctionPointer0; 26 | using mbed::util::FunctionPointer1; 27 | 28 | // Minimum and maximum allowed values of "cnt", computed in accordance with the 29 | // various periods of tolerances of events in main(). TODO: actually check these 30 | // values by running on different platforms 31 | #define MIN_ALLOWED_CNT 46 32 | #define MAX_ALLOWED_CNT 50 33 | 34 | #define EXPECTED_CALLBACK_COUNT 1 35 | 36 | static int cnt; 37 | 38 | class LED { 39 | public: 40 | LED(const char* name, PinName led): _name(name), _led(led) {} 41 | 42 | void toggle(void) { 43 | _led = !_led; 44 | } 45 | 46 | void callback_no_increment(void) { 47 | printf("%s callback tick... \r\n", _name); 48 | toggle(); 49 | } 50 | 51 | void callback_and_increment(void) { 52 | printf("%s callback tick and increment... %d\r\n", _name, cnt++); 53 | toggle(); 54 | } 55 | 56 | private: 57 | const char *_name; 58 | DigitalOut _led; 59 | }; 60 | 61 | static void cb_msg_and_increment(const char *msg) { 62 | printf("%s...%d\r\n", msg, cnt++); 63 | } 64 | 65 | static void stop_scheduler() { 66 | printf("Stopping scheduler...\r\n"); 67 | minar::Scheduler::stop(); 68 | } 69 | 70 | void app_start(int, char*[]) { 71 | GREENTEA_SETUP(35, "default"); 72 | 73 | LED led1("led1", LED1); 74 | LED led2("led2", LED2); 75 | 76 | led1.toggle(); 77 | 78 | // The next callback will run once 79 | minar::Scheduler::postCallback(FunctionPointer0(&led1, &LED::callback_no_increment).bind()) 80 | .delay(minar::milliseconds(500)) 81 | .tolerance(minar::milliseconds(100)); 82 | 83 | // The next callback will be the only periodic one 84 | minar::Scheduler::postCallback(FunctionPointer0(&led2, &LED::callback_and_increment).bind()) 85 | .period(minar::milliseconds(650)) 86 | .tolerance(minar::milliseconds(100)); 87 | 88 | FunctionPointer1 fp(cb_msg_and_increment); 89 | // Schedule this one to run after a while 90 | minar::Scheduler::postCallback(fp.bind("postCallbackWithDelay...")) 91 | .delay(minar::milliseconds(5000)) 92 | .tolerance(minar::milliseconds(200)); 93 | 94 | // Schedule this one to run immediately 95 | minar::Scheduler::postCallback(fp.bind("postImmediate")) 96 | .tolerance(minar::milliseconds(200)); 97 | 98 | // Stop the scheduler after enough time has passed 99 | minar::Scheduler::postCallback(stop_scheduler) 100 | .delay(minar::milliseconds(30000)) 101 | .tolerance(minar::milliseconds(3000)); 102 | 103 | int cb_cnt = minar::Scheduler::start(); // this will return after stop_scheduler above is executed 104 | 105 | // After returning, there should be only one event in the queue (the periodic one) 106 | 107 | bool cnt_ok = (cnt >= MIN_ALLOWED_CNT) && (cnt <= MAX_ALLOWED_CNT); 108 | printf("Final counter value: %d\r\n", cnt); 109 | TEST_ASSERT_TRUE_MESSAGE(cnt_ok, "Counter value is out of range"); 110 | TEST_ASSERT_EQUAL_MESSAGE(EXPECTED_CALLBACK_COUNT, cb_cnt, "Wrong call back count!"); 111 | 112 | GREENTEA_TESTSUITE_RESULT(cnt_ok && (cb_cnt == EXPECTED_CALLBACK_COUNT)); 113 | } 114 | 115 | -------------------------------------------------------------------------------- /test/dispatchtest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * PackageLicenseDeclared: Apache-2.0 3 | * Copyright (c) 2015 ARM Limited 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | // This is a purely visual test of MINAR, it blinks two LEDs connected 19 | // on LED1 (each 500ms) and LED2 (each 1000ms) 20 | 21 | #include 22 | 23 | #include "mbed-drivers/mbed.h" 24 | #include "minar/minar.h" 25 | #include "core-util/FunctionPointer.h" 26 | #include "greentea-client/test_env.h" 27 | 28 | using mbed::util::FunctionPointer0; 29 | 30 | static DigitalOut led1(LED1); 31 | static DigitalOut led2(LED2); 32 | 33 | static void toggleLED1() 34 | { 35 | led1 = !led1; 36 | } 37 | 38 | static void toggleLED2() 39 | { 40 | led2 = !led2; 41 | } 42 | 43 | static void testComplete() 44 | { 45 | GREENTEA_TESTSUITE_RESULT(true); 46 | } 47 | 48 | void app_start(int, char*[]) 49 | { 50 | GREENTEA_SETUP(20, "default"); 51 | 52 | minar::Scheduler::postCallback(FunctionPointer0(toggleLED1).bind()) 53 | .period(minar::milliseconds(500)) 54 | .tolerance(minar::milliseconds(10)); 55 | 56 | minar::Scheduler::postCallback(FunctionPointer0(toggleLED2).bind()) 57 | .period(minar::milliseconds(250)) 58 | .tolerance(minar::milliseconds(10)); 59 | 60 | minar::Scheduler::postCallback(FunctionPointer0(testComplete).bind()) 61 | .delay(minar::milliseconds(15000)) 62 | .tolerance(minar::milliseconds(100)); 63 | } 64 | 65 | --------------------------------------------------------------------------------