0

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.

asked Oct 11, 2023 at 12:24
3
  • 1
    I 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. Commented Oct 11, 2023 at 13:45
  • 1
    Do 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)? Commented 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. Commented Oct 11, 2023 at 18:41

1 Answer 1

2

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.

answered Oct 11, 2023 at 19:22
1
  • Interesting, I'll look into it. Thanks for your reply! Commented Oct 11, 2023 at 19:27

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.