-
Notifications
You must be signed in to change notification settings - Fork 0
Error Tracker Usage Guide
🚨 Attention: The Error Tracker has been removed since PR #35.
Welcome to the Error Tracker Usage Guide. This guide will walk you through how to use the Error Tracker with examples.
The Error Tracker fulfills a very valuable function: it keeps track of errors during the runtime of your program.
The main mechanic of the Error Tracker is to append an error code to the end of a small buffer. This is what occurs when you invoke PUT_ERROR. As you do this, the number of error codes stored grows; that is, until either you clear it or the buffer overflows.
The Error Tracker can manage one or multiple buffers at one time. Making use of multiple buffers can help isolate errors to certain areas of your code, and becomes critical in situations where the flow of your code can be interrupted by a service routine.
To initialise the Error Tracker module, call ErrorTracker_Init with a buffer of your choice. The buffer you select will be designated as the default buffer. That is, if no other buffer is made active, the default buffer will be the place where errors are kept. This will normally be defined in the global or file-level scope.
#include "tuk/error_tracker.h"
static ErrorBuffer s_default_error_buffer;
int main()
{
ErrorTracker_Init(&s_default_error_buffer);
}To save an error to the current buffer, make a call to PUT_ERROR with the error code of your choice (See Error Codes). Optionally, you may include additional diagnostic information; but be wary, there are caveats to this as described in Pitfalls!
#include "tuk/error_tracker.h"
int main()
{
ErrorTracker_Init(&s_default_error_buffer);
// current buffer: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
PUT_ERROR(ERR_CAN_WRAPPER_INIT);
// current buffer: [0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
PUT_ERROR(ERR_UNKNOWN_COMMAND, 0x0A);
// current buffer: [0x02, 0x01, 0x0A, 0x00, 0x00, 0x00, 0x00]
}As mentioned in Overview, the Error Tracker can manage multiple buffers at once. Using multiple buffers will be necessary so as to not mangle your error data in cases where service routines can interrupt the flow of your program and create errors of their own.
Buffers are managed in a stack-based system. At the bottom is the default error buffer. Additional buffers can be pushed and popped off the stack, and only the top buffer is treated as the active buffer.
Use the ErrorTracker_Push_Buffer and ErrorTracker_Pop_Buffer functions to do this.
ErrorTracker_Init(&s_default_error_buffer);
// current stack: [ default >
ErrorBuffer local_buffer;
ErrorTracker_Push_Buffer(&local_buffer);
// current stack: [ default, local >
PUT_ERROR(ERR_UNKNOWN_COMMAND); // gets placed into local_buffer
ErrorTracker_Pop_Buffer();
// current stack: [ default >The error_tracker/errors/ folder contains the full list of error codes categorized by subsystem.
You may add custom error codes for your subsystem in the corresponding file. If the error is common enough, such as if it corresponds to a common HAL function, you may add it to the common.txt file. Keep in mind however, you should insert the new error on a new line at the end of the file. This is to minimize fluctuation in error code values.
To make the most effective use of the Error Tracker, follow these best practices:
-
Initialize Early: The Error Tracker should be one of the first things you initialise (after HAL's initialisation segment). This will guarantee that any errors occurring during initialization are captured.
-
Regularly Check and Clear Buffers: Periodically check and clear error buffers. This practice helps in keeping the buffer sizes manageable and ensures that new errors are not missed due to buffer overflows.
-
Document Custom Error Codes: When adding custom error codes, document their purpose in the error list file. This helps other programmers understand and use these codes correctly.
-
Consistent Error Handling: Follow a consistent pattern for error handling throughout your codebase. This includes the use of
PUT_ERRORand any additional diagnostic information. -
Avoid Overuse: Store error data judiciously. Overuse can lead to overflown buffers and some information may be lost.
Interrupt service routines (ISRs) can disrupt the flow of your program. Any ISR that calls PUT_ERROR must use a dedicated error buffer. This ensures that the main program's error buffer remains intact and prevents data corruption.
This is a very subtle edge case that you must be aware of.
Below is an example of good practice with service routines and error buffers:
#include "tuk/error_tracker.h"
static ErrorBuffer s_tim2_error_buffer;
// HAL callback for timers. This is called by an interrupt service routine.
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim == &htim2)
{
ErrorTracker_Push_Buffer(&s_tim2_error_buffer);
// Your code here...
// Oops!
PUT_ERROR(ERR_ADC_GET_VALUE)
ErrorTracker_Pop_Buffer();
}
}
// called in your main loop somewhere...
void Report_Errors()
{
if (ErrorBuffer_Has_Error(&s_tim2_error_buffer))
{
CANMessage error_report;
error_report.cmd = CMD_CDH_PROCESS_ERROR;
SET_ARG(error_report, 0, s_tim2_error_buffer); // syntax: SET_ARG(msg, byte #, input var)
CANWrapper_Transmit(NODE_CDH, &error_report);
}
}Consistency is key when recording errors. Ensure that all PUT_ERROR calls follow a consistent format when including any additional diagnostic information. Inconsistent formatting can lead to confusion and make debugging more difficult. There are no hard rules for how you should format errors, just ensure that each error code is being used consistently.
An error buffer only has capacity for 7 bytes of individual items. This is due to CAN only supporting 8 bytes of data being sent at once. Therefore, you have to be judicious with what you choose to store.
If this limitation proves too restrictive, I may consider implementing an automated reporting system of some kind to deal with overflows.