I am looking at refactoring my code by implementing callback functions in order to be able to reuse the abstract logic of other functions/classes.
However, I feel I need to learn more about the limits of callback functions on embedded systems like the teensy 4.0 I'm using. The reason I ask is because - if I'm correctly informed - std::function is adviced against on embedded systems due to memory overhead. Another reason for asking is that the functions I want to use for callback might run the risk of violating the principle of keeping callback functions short.
I've consulted the chapter devoted to embedded programming principles in Stroustrup's Programming: Principles and Practice Using C++. Unfortunately, he doesn't give advice on callback functions for embedded systems, specifically.
I understand I can simply rely on a trial and error methodology using function pointers, but figured I'd ask for your experience and advice before I rewrite a lot of code. In other words, I'm asking for good principles and guidelines.
-
1I frequently use callbacks in embedded systems, on much less capable MCUs than the Cortex (328p f/ex). The call overhead is minor - a call through a pointer instead of calling direct. This page has a guide to callbacks on the Teensy.JRobert– JRobert2023年10月11日 13:45:10 +00:00Commented Oct 11, 2023 at 13:45
-
1Do your callbacks need to carry state (i.e. be functor objects holding data), or would you just be using naked functions (or non-capturing lambdas)?Edgar Bonet– Edgar Bonet2023年10月11日 16:09:59 +00:00Commented Oct 11, 2023 at 16:09
-
@EdgarBonet Off the top of my head it'll primarily be naked functions, but It's possible I'll also need other types of use cases.Erik– Erik2023年10月11日 18:41:43 +00:00Commented Oct 11, 2023 at 18:41
1 Answer 1
I have been using callbacks quite intensively on an Arduino Mega 2560,
for managing RS-485 messages. This board has roughly 8 times less flash
and 128 times less RAM than your Teensy. However, as the AVR core does
not support std::function
, I used the good old C idiom for defining
callbacks:
void MyClass::register_callback(
void (*callback)(some_type some_data, void *callback_data),
void *callback_data);
It is up to the caller to nicely pack all the state needed by the
callback into a struct
, and pass a pointer to this struct
as the
last argument of register_callback()
. The class will record both the
function pointer and the void*
pointer. When the expected event
happens, the class calls the callback, giving it the saved void*
as
its last argument. The callback then casts this pointer to a pointer to
the expected struct
, and now it can access the state it needs.
Yes, this is a bit clumsy, but it is a tried and true way of handling callback state in plain C. And the cost is marginal, especially for your board that has orders of magnitude more resources than my Mega.
If you are afraid of the cost of std::function
, I suggest you
implement a simple callback example using this method, then using
std::function
, and you compare the memory usage. If the cost of
std::function
turns out to be significant, you can always go the C
way. I suspect std::function
will be just fine, and it really is more
convenient than handling the function pointer and the state as separate
arguments.
-
Interesting, I'll look into it. Thanks for your reply!Erik– Erik2023年10月11日 19:27:50 +00:00Commented Oct 11, 2023 at 19:27