EventQueue
EventQueue class hierarchy
The EventQueue class provides a flexible queue for scheduling events. You can use the EventQueue class for synchronization between multiple threads, or to move events out of interrupt context (deferred execution of time consuming or non-ISR safe operations).
The EventQueue class is thread and ISR safe.
You can use the dispatch_for
, dispatch_once
and dispatch_forever
APIs to execute pending events.
break_dispatch
is to terminate the execution of events in the specified EventQueue.
Shared event queues
Mbed OS provides two shared queues software can use. This can avoid the need to create private event dispatch threads and reduce the total amount of RAM used.
EventQueue class reference
Public Member Functions | |
EventQueue (unsigned size=(32 *(EQUEUE_EVENT_SIZE-2 *sizeof(void *)+sizeof(mbed::Callback< void()>))), unsigned char *buffer=NULL) | |
Create an EventQueue. More... | |
~EventQueue () | |
Destroy an EventQueue. More... | |
void | dispatch_for (duration ms) |
Dispatch events. More... | |
void | dispatch (int ms=-1) |
Dispatch events. More... | |
void | dispatch_forever () |
Dispatch events without a timeout. More... | |
void | dispatch_once () |
Dispatch currently queued events only and then terminate. More... | |
void | break_dispatch () |
Break out of a running event loop. More... | |
unsigned | tick () |
Millisecond counter. More... | |
bool | cancel (int id) |
Cancel an in-flight event. More... | |
template<typename F , typename A > | |
bool | cancel (UserAllocatedEvent< F, A > *event) |
Cancel an in-flight user allocated event. More... | |
int | time_left (int id) |
Query how much time is left for delayed event. More... | |
template<typename F , typename A > | |
int | time_left (UserAllocatedEvent< F, A > *event) |
Query how much time is left for delayed UserAllocatedEvent. More... | |
void | background (mbed::Callback< void(int)> update) |
Background an event queue onto a single-shot timer-interrupt. More... | |
int | chain (EventQueue *target) |
Chain an event queue onto another event queue. More... | |
template<typename F , typename... Args> | |
int | call (F f, Args...args) |
Calls an event on the queue. More... | |
template<typename T , typename R , typename... Args> | |
int | call (T *obj, R(T::*method)(Args...args), Args...args) |
Calls an event on the queue. More... | |
template<typename F , typename... ArgTs> | |
int | call_in (duration ms, F f, ArgTs...args) |
Calls an event on the queue after a specified delay. More... | |
template<typename T , typename R , typename... ArgTs> | |
int | call_in (duration ms, T *obj, R(T::*method)(ArgTs...args), ArgTs...args) |
Calls an event on the queue after a specified delay. More... | |
template<typename F , typename... ArgTs> | |
int | call_every (duration ms, F f, ArgTs...args) |
Calls an event on the queue periodically. More... | |
template<typename T , typename R , typename... ArgTs> | |
int | call_every (duration ms, T *obj, R(T::*method)(ArgTs...args), ArgTs...args) |
Calls an event on the queue periodically. More... | |
template<typename R , typename... BoundArgTs, typename... ContextArgTs, typename... ArgTs> | |
Event< void(ArgTs...)> | event (R(*func)(BoundArgTs..., ArgTs...), ContextArgTs...context_args) |
Creates an event bound to the event queue. More... | |
template<typename T , typename R , typename... BoundArgTs, typename... ContextArgTs, typename... ArgTs> | |
Event< void(ArgTs...)> | event (T *obj, R(T::*method)(BoundArgTs..., ArgTs...), ContextArgTs...context_args) |
Creates an event bound to the event queue. More... | |
template<typename R , typename... BoundArgTs, typename... ContextArgTs, typename... ArgTs> | |
Event< void(ArgTs...)> | event (mbed::Callback< R(BoundArgTs..., ArgTs...)> cb, ContextArgTs...context_args) |
Creates an event bound to the event queue. More... | |
template<typename F , typename... ArgTs> | |
UserAllocatedEvent< F, void(ArgTs...)> | make_user_allocated_event (F f, ArgTs...args) |
Creates an user allocated event bound to the event queue. More... | |
template<typename T , typename R , typename... ArgTs> | |
UserAllocatedEvent< mbed::Callback< void(ArgTs...)>, void(ArgTs...)> | make_user_allocated_event (T *obj, R(T::*method)(ArgTs...args), ArgTs...args) |
Creates an user allocated event bound to the event queue. More... |
Shared event queue reference
EventQueue example: deferring from interrupt context
The code executes two handler functions (rise_handler
and fall_handler
) in two different contexts:
- In interrupt context when a rising edge is detected on
SW2
(rise_handler
). - In the context of the event loop's thread function when a falling edge is detected on
SW2
(fall_handler
). You can use thefall_handler
function as an argument toqueue.event()
to specify thatfall_handler
runs in user context instead of interrupt context.
/*
* Copyright (c) 2020 Arm Limited and affiliates.
* SPDX-License-Identifier: Apache-2.0
*/
#include "mbed.h"
DigitalOut led1(LED1);
InterruptIn sw(SW2);
EventQueue queue(32 * EVENTS_EVENT_SIZE);
Thread t;
void rise_handler(void)
{
queue.call(printf, "rise_handler in context %p\n", ThisThread::get_id());
// Toggle LED
led1 = !led1;
}
void fall_handler(void)
{
printf("rise_handler in context %p\n", ThisThread::get_id());
// Toggle LED
led1 = !led1;
}
int main()
{
// Start the event queue
t.start(callback(&queue, &EventQueue::dispatch_forever));
printf("Starting in context %p\r\n", ThisThread::get_id());
// The 'rise' handler will execute in IRQ context
sw.rise(rise_handler);
// The 'fall' handler will execute in the context of thread 't'
sw.fall(queue.event(fall_handler));
}
Shared event example: deferring from interrupt context
Like the previous example, this defers from interrupt to an event queue thread. However, rather than creating its own thread, it uses the shared event queue – potentially sharing it with other system components and saving RAM.
As the event queue is shared, you should limit the execution time of your event functions to avoid delaying other users’ events excessively.
/*
* Copyright (c) 2020 Arm Limited and affiliates.
* SPDX-License-Identifier: Apache-2.0
*/
#include "mbed.h"
#include "mbed_events.h"
DigitalOut led1(LED1);
InterruptIn sw(SW2);
void rise_handler(void)
{
// Toggle LED
led1 = !led1;
}
void fall_handler(void)
{
printf("fall_handler in context %p\r\n", ThisThread::get_id());
// Toggle LED
led1 = !led1;
}
int main()
{
// Request the shared queue
EventQueue *queue = mbed_event_queue();
printf("Starting in context %p\r\n", ThisThread::get_id());
// The 'rise' handler will execute in IRQ context
sw.rise(rise_handler);
// The 'fall' handler will execute in the context of the shared queue thread
sw.fall(queue->event(fall_handler));
}
EventQueue example: posting events to the queue
The code below demonstrates queueing functions to be called after a delay and queueing functions to be called periodically.
/*
* Copyright (c) 2020 Arm Limited and affiliates.
* SPDX-License-Identifier: Apache-2.0
*/
#include "mbed_events.h"
#include <stdio.h>
using namespace std::chrono_literals;
// In the example below there are 3 types of event callbacks
// 1) Call the provided function immediately
// 2) Call the provided function once (after the specified period)
// 3) Call the provided function every specified period
//
// Expected output:
//
// called immediately
// called every 1 seconds
// called in 2 seconds
// called every 1 seconds
// called every 1 seconds
// ^ repeated forever
//
int main()
{
// creates a queue with the default size
EventQueue queue;
// events are simple callbacks
queue.call(printf, "called immediately\n");
queue.call_in(2000ms, printf, "called in 2 seconds\n");
queue.call_every(1000ms, printf, "called every 1 seconds\n");
// events are executed by the dispatch_forever method
queue.dispatch_forever();
}
EventQueue example: chaining events from more than one queue
Event queues easily align with module boundaries, where event dispatch can implicitly synchronize internal state. Multiple modules can use independent event queues but still be composed through the EventQueue::chain
function.
/*
* Copyright (c) 2020 Arm Limited and affiliates.
* SPDX-License-Identifier: Apache-2.0
*/
#include "mbed_events.h"
#include <stdio.h>
using namespace std::chrono_literals;
/**
Event queues easily align with module boundaries, where internal state can be
implicitly synchronized through event dispatch. Multiple modules can use
independent event queues, but still be composed through the EventQueue::chain function.
Note the call() methods in this example only dispatch once as no period is
set and thus defaults to dispatch_once(). The ordering of the chaining dictates
the order of the dispatching.
Expected output:
hello from a!
hello from c!
hello from b!
**/
int main()
{
// Create some event queues with pending events
EventQueue a;
a.call(printf, "hello from a!\n");
EventQueue b;
b.call(printf, "hello from b!\n");
EventQueue c;
c.call(printf, "hello from c!\n");
// Chain c and b onto a's event queue. Both c and b will be dispatched
// in the context of a's dispatch function.
c.chain(&a);
b.chain(&a);
// Dispatching a will in turn dispatch b and c, printing hello from
// all three queues
a.dispatch_forever();
}
Shared event example: running the shared queue from main
To further save RAM, if you have no other work to do in your main function after initialization, you can dispatch the global event queue from there, avoiding the need to create a separate dispatch thread.
To do this, set the mbed_app.json
configuration option events.shared-dispatch-from-application
to true, and add a dispatch call to main, as in this example. (The prints now show the same context for startup and fall_handler
).
/*
* Copyright (c) 2020 Arm Limited and affiliates.
* SPDX-License-Identifier: Apache-2.0
*/
#include "mbed.h"
#include "mbed_events.h"
DigitalOut led1(LED1);
InterruptIn sw(SW2);
void rise_handler(void)
{
// Toggle LED
led1 = !led1;
}
void fall_handler(void)
{
printf("fall_handler in context %p\r\n", ThisThread::get_id());
// Toggle LED
led1 = !led1;
}
int main()
{
// Request the shared queue
EventQueue *queue = mbed_event_queue();
printf("Starting in context %p\r\n", ThisThread::get_id());
// The 'rise' handler will execute in IRQ context
sw.rise(rise_handler);
// The 'fall' handler will execute in the context of the shared queue (actually the main thread)
sw.fall(queue->event(fall_handler));
// Setup complete, so we now dispatch the shared queue from main
queue->dispatch_forever();
}
Static EventQueue example: posting user allocated events to the static queue
Use static EventQueue to prevent your program from failing due to queue memory exhaustion or to prevent dynamic memory allocation:
/*
* Copyright (c) 2019 ARM Limited. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
* Licensed under the Apache License, Version 2.0 (the License); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an AS IS BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "mbed.h"
// Creates static event queue
static EventQueue queue(0);
void handler(int count);
// Creates events for later bound
auto event1 = make_user_allocated_event(handler, 1);
auto event2 = make_user_allocated_event(handler, 2);
// Creates event bound to the specified event queue
auto event3 = queue.make_user_allocated_event(handler, 3);
auto event4 = queue.make_user_allocated_event(handler, 4);
void handler(int count)
{
printf("UserAllocatedEvent = %d \n", count);
return;
}
void post_events(void)
{
// Single instance of user allocated event can be posted only once.
// Event can be posted again if the previous dispatch has finished or event has been canceled.
// bind & post
event1.call_on(&queue);
// event cannot be posted again until dispatched or canceled
bool post_succeed = event1.try_call();
assert(!post_succeed);
queue.cancel(&event1);
// try to post
post_succeed = event1.try_call();
assert(post_succeed);
// bind & post
post_succeed = event2.try_call_on(&queue);
assert(post_succeed);
// post
event3.call();
// post
event4();
}
int main()
{
printf("*** start ***\n");
Thread event_thread;
// The event can be manually configured for special timing requirements.
// Timings are specified in milliseconds.
// Starting delay - 100 msec
// Delay between each event - 200msec
event1.delay(100);
event1.period(200);
event2.delay(100);
event2.period(200);
event3.delay(100);
event3.period(200);
event4.delay(100);
event4.period(200);
event_thread.start(callback(post_events));
// Posted events are dispatched in the context of the queue's dispatch function
queue.dispatch_for(400ms); // Dispatch time - 400msec
// 400 msec - Only 2 set of events will be dispatched as period is 200 msec
event_thread.join();
}