Handling interrupts efficiently is key for responsive real-time embedded systems. For C++ projects on ARM Cortex-M devices, it can be very useful to directly call C++ object member functions from the interrupt handler. This allows object-oriented encapsulation of interrupt handling logic while still providing low latency and quick response to events. With proper configuration, member functions of C++ objects can be safely called directly from the interrupt vector, eliminating the need for a wrapper function or queueing of callbacks.
Background on ARM Cortex-M Interrupts
The ARM Cortex-M processors have a Nested Vectored Interrupt Controller (NVIC) that handles prioritized hardware interrupts. When an interrupt occurs, the processor will finish executing the current instruction, then jump to the corresponding vector in the vector table. This table contains the memory addresses for each interrupt handler routine.
The NVIC allows flexibility in configuring interrupt priority levels and managing nested interrupts. Higher priority interrupts can preempt lower priority ones. The processor automatically stacks context to allow returning to the original code once the interrupt handler has finished.
These microcontroller interrupts provide very low latency signaling of external or internal events. Quickly executing the interrupt handler is key for real-time performance. The processor supports low-latency exception entry and exit to minimize overhead.
Challenges with C++ Objects in Interrupt Handlers
When using C++ classes in an embedded project, it can be very convenient to invoke class member functions from the interrupt handler code. This allows encapsulating device drivers, protocol stacks, and other logic into C++ objects. However, care must be taken when calling C++ code from an interrupt context:
- The C++ compiler may generate longer exception handling code that is unsuitable for interrupts.
- Objects may assume they are not re-entered, but interrupts can preempt ongoing member functions.
- The compiler may insert calls to library code for global constructors, memory management, etc. which is unsafe in interrupts.
- Member functions may attempt to acquire mutexes or other locks, causing deadlock.
To safely call C++ object member functions from interrupt handlers requires configuring the compiler, selecting appropriate compiler flags, using suitable coding discipline, and testing interrupt handling scenarios thoroughly. With the proper precautions, C++ object methods can be used without overhead or risk compared to C code.
Compiler Configuration for C++ in Interrupts
Here are some key compiler and linker options to enable efficient C++ object usage from interrupt handlers on ARM Cortex-M devices:
- Disable exceptions – exception handling code is unsuitable for interrupt handlers, so exceptions should be completely disabled.
- Avoid RTTI – Run-time type information features like dynamic_cast and typeid should not be used.
- Omit unused code – Enable linker optimizations to remove unused C++ features.
- No external linkage – Avoid globals and functions with external linkage.
- Tune optimization – Higher optimization levels further reduce overhead.
For example, with the GCC toolchain, compiling with -fno-exceptions -fno-rtti -Os
helps produce efficient code for interrupt usage. The linker can remove unused code with --gc-sections
. Other compilers have similar options to restrict code size.
Guidelines for C++ Objects in Interrupts
In addition to compiler options, following these software disciplines allows safely calling C++ object member functions directly from interrupt handlers:
- Use static objects rather than dynamic allocation in interrupt handlers.
- Avoid virtual functions if possible, use direct calls.
- Design objects to be reentrant for concurrency.
- Disable interrupts around lock acquisitions.
- Minimize required static constructors and destructors.
- Use volatile and atomic accesses for shared data.
With simple designs focused on the interrupt handling requirements, C++ classes can provide clean encapsulation without any overhead problems compared to C code.
Calling Object Methods from the Interrupt Vector
Here is an example of invoking a C++ object member function directly from the interrupt vector table on an ARM Cortex-M processor: // C++ member function class EthDriver { public: void onFrameRx() { // handle frame received } }; // Global driver instance EthDriver eth; // Interrupt vector table extern “C” { void __attribute__ ((interrupt)) ETH_IRQHandler() { eth.onFrameRx(); // Directly call member } }
The C++ class EthDriver encapsulates the Ethernet device driver functionality. A global instance eth is declared so that the interrupt handler can access it. The interrupt vector ETH_IRQHandler directly calls the onFrameRx() member function to handle receiving a packet.
This avoids the overhead of a wrapper function to dispatch to a C++ method. It also prevents any delays from queuing and deferring work to another context. The frame can be processed immediately by the object instance.
Validating Correct Interrupt Handling
To ensure C++ objects work correctly in interrupt handlers, thoroughly test concurrency scenarios. Verify the system design by injecting interrupts at various points and ensuring the objects remain consistent. Consider these strategies:
- Toggle I/O pins on interrupt entry/exit to visualize with a logic analyzer.
- Force interrupts with test points or debugger commands.
- Seed and check incrementing counters in objects modified in interrupts.
- Run high priority interrupts that preempt lower priority code.
- Check with concurrency analysis tools like Thread Sanitizer.
Testing interrupts systematically helps catch any missed cases in the C++ object design. This ensures reliable behavior in the field under real interrupt loads.
Conclusion
The Cortex-M NVIC and interrupt handling model enables very fast dispatching and execution of interrupt handler code. With appropriate configuration and object-oriented software design, C++ member functions can be invoked directly from interrupt vectors without overhead or risk compared to C code.
Eliminating wrappers and queues allows immediate interrupt response while still benefiting from C++ encapsulation advantages. By disabling language features unsuitable for interrupts, and thoroughly concurrency testing the software, C++ object methods can be safely used for low-latency real-time embedded interrupt handling.