Best Practices in Developing for DATAFLOW Runtime for ARM Cortex-M
This Guide Article has been written for Version 2.1 of the DATAFLOW Software. For Previous Releases use the version selection in the navigation bar at the top of this page.
This article contains tipps and tricks when developing and debugging applications for the DATAFLOW Runtime on an ARM Cortex-M Microprocessors.
Define Idle Callback
The Idle Callback is a callback method that is called whenever the DATAFLOW Runtime main loop is idle and has no work to execute.
In production, the device shall go into sleep mode to save power. This is done with the WFI (Wait for Interrupt) instructor on an ARM Core. Make sure that the processor pipeline is empty before this instruction with DSB (Data Synchronisation Barrier) and ISB (Instruction Synchronisation Barrier).
Implement Callback
The callback is implemented using a class that inherits the IdleCallbackIfc interface.
The DATAFLOW Runtime provides a GenericPowerSaveIdleCallback. This can be used as a basis for a custom callback implementation. It is part of the static runtime library, so an example is shown here:
#include <Imt.Base.Dff.Runtime/IdleCallbackIfc.h>
class CustomIdleCallback : public imt::base::dff::runtime::IdleCallbackIfc {
public:
void onIdle(void) {
// Attention: We are woken up periodically by the SysTick interrupt (1ms)
// So it's hard to determine that the WFI instruction is working.
// -> Verify with oscilloscope and debug pins
#ifndef WIN32
// in order to calculate the CPU load we need the busy idle
#ifndef RUNTIME_STATISTICS
// Data barrier
asm volatile ("DSB"); // place a DSB to ensure that all data is synchronized before entering "wait for interrupt"
asm volatile ("ISB"); // place a ISB to ensure that all instructions are synchronized before "wait for interrupt"
asm volatile ("WFI"); // Enter sleep state and request wait for interrupts
#endif //RUNTIME_STATISTICS
#endif //WIN32
}
};
Register Callback
The callback functions must be passed to the RuntimeCore::Init method in the application main() function:
CustomIdleCallback idleCallback;
RuntimeCore::init(&idleCallback, nullptr);
Note: The second argument is used for the Event Pool Capacity Callback, see Define Event Capacity Callback.
Define Event Pool Capacity Callback
The size of the Event Pool is a very important configuration decision that must be made when developing an application with the DATAFLOW runtime. When the buffer is too large, the RAM footprint of the Runtime may be too large and is blocking RAM needed for the intended functionality of the application. When it is to small, an ASSERT_EX will be triggered when a message shall be sent and the buffer contains no more free message slots.
To prevent an ASSERT_EX, the DATAFLOW Runtime provides a callback function that is called for every message when the event pool runs below 10% capacity. This allows the application to dump certain messages to help recover the situation.
Once the pool is above 20% capacity again, the callback will no longer be called when messages are sent.
Callback Implementation
The callback is implemented with a class that derives from the EventPoolCapacityIfc interface.
Capacity Critical
When the onEventPoolCapacityCritical method is called, the application can dump uncritical messages such as data from external interfaces. This may help the runtime to recover without running into an ASSERT_EX.
This handler can also be used to toggle an error LED or to log this event.
#include <Imt.Base.Dff.Runtime/EventPoolCapacityCallbackIfc.h>
class EventPoolCapacityCallback : public imt::base::dff::runtime::EventPoolCapacityCallbackIfc {
public:
bool onEventPoolCapacityCritical(const uint16_t protocolIdentifier) {
// TODO turn ON Warning LED
// TODO log event
// returning true indicates that the message has to be enqueued, false indicates that the message can be discarded
bool enqueued = false;
switch (protocolIdentifier) {
case ProtocolIdentifier::BYTE_DATA:
enqueued = false;
break;
default:
enqueued = true;
break;
}
// further optimizations could be to disable external interrupts
return enqueued;
}
// continued below ...
Recovery
Once the free event percentage is ..., the onEventPoolCapacityRecovered is called. This can be used to turn off any error indicators or log the event.
// ... continued from above
void onEventPoolCapacityRecovered(void) {
// TODO turn OFF Warning LED
// TODO log event
}
};
Register Callback
The callback functions must be passed to the RuntimeCore::Init method in the application main() function:
static EventPoolCapacityCallback eventPoolCapacityCallback;
RuntimeCore::init(null, eventPoolCapacityCallback);
Note: The first argument is used for the Idle Callback, see Define Idle Callback.
Register Assert Handlers
The DATAFLOW Runtime code uses 3 types of asserts:
ASSERT_COMPILER
ASSERT_DEBUG
ASSERT_EX
All 3 asserts can and should also be used in the application code.
Compiler asserts will prevent the code from being compiled and shall always be used if possible. For the other 2 types, a handler will be called during the application execution. Default handlers are registered, but as a developer, more control on the assert behavior may be required.
Implement Assert Handlers
An assert handler is a function that must have the signature shown in the examples below.
For the debug assert handler, a breakpoint shall be triggered.
static void assertDebugEventHandler(const AssertActionManager::AssertEvent::Id actionEvent, const char_t* const pMsg) {
// add break point here
#ifdef _DEBUG
#ifdef _WINDOWS
__debugbreak();
#endif // _WINDOWS
#endif // _DEBUG
}
In the implementation of the EX assert handler, the execution of the application must be halted. This can be done with an endless loop as shown below.
static void assertExEventHandler(const AssertActionManager::AssertEvent::Id actionEvent, const char_t* const pMsg) {
// declare a volatile variable, so that the compiler will never optimize away our default exception handler
volatile uint32_t defaultEndlessCounter = 0U;
for (;;) {
// the application hasn't registered an action handlers, but an assert has occurred (see call stack)
defaultEndlessCounter++;
}
}
Additional Code can be added to Assert Handlers based on the capabilities of the used IDE / Debugger. Possibilities are:
- printf to write message to attached console
- Send string over SWD or other serial device
- Toggle GPIO (Warning/Error LED)
Register Assert Handlers
The assert handlers can be registered using the AssertActionManager.
AssertActionManager::registerActionHandler(&assertDebugEventHandler, AssertActionManager::AssertEvent::ASSERT_DEBUG_EVENT);
AssertActionManager::registerActionHandler(&assertExEventHandler, AssertActionManager::AssertEvent::ASSERT_EX_EVENT);
Add Breakpoint to Debug Assert Handler
When the application is deployed using a debugger, it is recommended to always add a breakpoint in the Debug Assert Handler. If the compiler provides an instuction to trigger a debug breakpoint from code, this can also be used. In Visual Studio, this is the __debugbreak intrinsic function.
Also consider to indicate a debug assert with an LED.
Indicate Runtime Status with LEDs
When designing the Hardware for your System, it can be useful to add additional LEDs to the PCB. These LED can be used to show the runtime status and indicate asserts. This may help to detect a problem even if no debugger is attached (production board).
We recommend to provide 3 LED for this purpose if the PCB design allows:
Heartbeat (Green)
This LED shall be toggled from an active part with the lowest priority with a frequency that can be observed well (for example 1 Hz). When this LED blinks, the Runtime is not blocked by any active part with a higher priority that violates the run to completion principle.
When this LED is no longer blinking, one of the following problems may exits:
- The runtime may be blocked by an active part with higher priority
- There are a lot of interrupts with higher priority than the SYSTICK and the scheduler will therefore not be executed
- An ASSERT_EX was triggered (see Error LED below as well).
Warning (Yellow) or Status (Green)
This LED shall be initialized as off and turned on in the assert DEBUG handler. No code outside of the initialization code shall exist that can turn it off again.
When this LED is on, at least one assert debug has happened in the code.
When a Status LED is used, the logic shall be inverted (on: OK, off: problem).
Error (Red)
This LED shall be initialized as off and turned on in the assert EX handler. No code outside of the initialization code shall exist that can turn it off again.
When this LED is on, the application has triggered an ASSERT_EX and will no longer be executed.
Example with Status, Heartbeat and System Fail LED:
Add Hardware Revision to PCB
When developing an Application, it may be very useful to know the revision of the PCB board the software is running on. This allows to initialize peripherals differently when there has been a design change in the PCB for example. If done correctly, there is no need to create different firmware variants for different hardware revisions.
In order to implement this, the PCB designer shall consider a number of resistors connected to free GPIO pins. These can be used to code the hardware revision in a binary format by adding/removing resistors from the design for each revision.
On Initialization, the Revision GPIO pins shall be initialized first and the state shall be read. Once the revision is known, this can be used in the initialization of the remaining peripherals. If an unsupported revision is detected, an ASSERT_EX can be triggered to avoid any unsafe condition.
Example
Common Issues
This section collects common mistakes made when working with the DATAFLOW Runtime.
SYSTICK not enabled
When the Startup Code does not enable the Systick Interrupt, the Runtime Timers will not work, because the process Method is never called.
Solution: Set a priority for the SYSTICK Interrupt and enable it right before the RUNTIME endless loop is entered.
Timer not started
When a Timer (Periodic or One Shot) is added to an Active Part in a DATAFLOW Designer Diagram, it is not started by default. The Timer Handler will never be called.
Solution: Either set the Timer to 'Autostart' in the DATAFLOW Designer so that it is started automatically when the Runtime starts OR start it from the user code.
Required Module: DATAFLOW Runtime
This Article has been written based on V2.1.1 of the DATAFLOW software.
Latest update 2023-05-31 by WUM.
Comments
0 comments
Please sign in to leave a comment.