AVR SD Safety Framework 1.0.0
Loading...
Searching...
No Matches
Tips and Tricks

This page provides additional guidance on using the Safety Framework APIs beyond the standard documentation. It highlights advanced usage options to enhance flexibility for specific applications.

Total Startup Schedule Duration Longer than FDTI

Collapse / Expand Tip

When creating schedules for TM_Startup, the total duration of all Tasks in the schedule is intended to be slightly less than the configured WDT timeout to ensure timely execution and clearing within the Fault Detection Time Interval (FDTI). However, if the schedule is longer than FDTI, the T_HandleWatchdog Task can be included in the schedule one or more times.

Note
During TM_Startup, while the device is in Safe State, the WDT timeout is configured using INIT_WDT_DURATION.
The WDT timeout in Mission Mode when using TM_Scheduler is configured using WWDT_CLOSED_WINDOW and WWDT_OPEN_WINDOW

The example below shows T_HandleWatchdog inserted between application Tasks in start-up and Mission Mode schedules.

Note
DUR_HANDLE_WATCHDOG is not defined by the Framework; see Measurements to see the actual WCET for T_HandleWatchdog and measure application-specific Tasks to set correct durations.
// The application startup Tasks below take longer in total than the remaining INIT_WDT_DURATION timeout.
static const scheduleEntry_t appStartupSchedule[LEN] = {
{ &T_HandleStartupApplication0, DUR_HANDLE_APP_STARTUP_0 }, // Application-specific startup Task
{ &T_HandleWatchdog, DUR_HANDLE_WATCHDOG }, // Framework Task: clears and re-arms the WDT
{ &T_HandleStartupApplication1, DUR_HANDLE_APP_STARTUP_1 } // Application-specific startup Task
};
const scheduleEntry_t appStartupSchedule[STARTUP_SCHEDULE_LEN]
Defines an example Application Startup Schedule with optional Tasks.
Definition main_example.h:150
void T_HandleWatchdog(void)
Clears the Watchdog Timer (WDT) while performing the WDT count diagnostic.
Definition tasks_shared.c:106
void TM_Startup(const scheduleEntry_t appStartupSchedule[], uint8_t scheduleLength)
Manages and synchronizes safety system and application startup Tasks.
Definition task_manager_startup.c:34
Defines a schedule entry containing a task function pointer and its duration.
Definition define_schedule_entry.h:58
Note
See Increase WDT Resolution to Match FDTI for how to achieve this while using TM_Scheduler, which comes with the added considerations needed since the WDT is in windowed mode while in Mission Mode.

Increase WDT Resolution to Match FDTI

Collapse / Expand Tip

The selectable WDT timeouts have low granularity, which can make it difficult to match the configured WWDT_CLOSED_WINDOW and WWDT_OPEN_WINDOW values to a specific Application Fault Detection Time Interval (FDTI). To effectively increase the resolution of the Watchdog timer, the WDT Timeout can instead be configured to half of or less than the FDTI, where the Application schedule used in TM_Scheduler includes one or more additional T_HandleWatchdog calls. Another option is calling the TM_Scheduler multiple times within FDTI.

With this approach, the values used in WWDT_CLOSED_WINDOW and WWDT_OPEN_WINDOW can be tuned and accumulated such that it is ensured that WDT will time out within an FDTI suitable for the Application.

Note
It is recommended to configure the timeouts to account for the worst case WDT clock source variations to ensure clearing the WDT inside the open window.

The example below targets an example Application FDTI of approximately 6 ms. Accumulating two WWDT periods with the given config satisfies the 6 ms FDTI while each individual timeout remains within the available WDT granularity.

// Here, one WWDT period (closed + open) ~= 3 ms, about a half of the 6 ms target FDTI.
#define WWDT_CLOSED_WINDOW WDT_TIMEOUT_CLK64 // 64 WDT clock cycles (~1.95 ms) closed window
#define WWDT_OPEN_WINDOW WDT_TIMEOUT_CLK32 // 32 WDT clock cycles (~0.98 ms) open window
static const scheduleEntry_t appSchedule[LEN] = {
{ &T_HandleApplication0, DUR_HANDLE_APP_0 }, // Duration set to e.g. ~2.5 ms, within the WDT open window
{ &T_HandleWatchdog, DUR_HANDLE_WATCHDOG }, // Clears the WWDT in its open window
{ &T_HandleApplication1, DUR_HANDLE_APP_1 }, // Duration set to e.g. ~2.5 ms, within the WDT open window
};
while (1)
{
}
#define DUR_UNTIL_NEXT_SCHEDULE
Configures the duration in system clock cycles until the first Task in the next example Application s...
Definition main_example.h:76
const scheduleEntry_t appSchedule[SCHEDULE_LEN]
Defines an example Application Schedule with optional Tasks.
Definition main_example.h:192
void TM_Scheduler(const scheduleEntry_t schedule[], uint8_t scheduleLength, uint32_t nextScheduleDeadline)
Manages and synchronizes safety system and application Tasks from a user defined schedule.
Definition task_manager_scheduler.c:32

Using Dynamic and Constant Application Schedules

Collapse / Expand Tip

The straightforward usage of the TM_Scheduler is to provide a constant schedule that is executed each Fault Detection Time Interval (FDTI) period. However, it is also possible to switch between two or more constant schedules or even update the schedule dynamically as required by the Application. This requires the addition of an application-specific Schedule Manager. A dynamic schedule array can have a fixed length where a NULL pointer is added for unused indices since TM_Scheduler will just skip them.

Examples:

static const scheduleEntry_t appScheduleA[LEN_A] = { { &T_HandleApplication0, DUR_HANDLE_APP_0 } };
static const scheduleEntry_t appScheduleB[LEN_B] = { { &T_HandleApplication1, DUR_HANDLE_APP_1 } };
// Application-specific Schedule Manager: selects and runs a schedule for the current FDTI period.
static void RunNextSchedule(void)
{
static bool useScheduleA = true;
if (useScheduleA)
{
TM_Scheduler(appScheduleA, LEN_A, DUR_UNTIL_NEXT_SCHEDULE);
}
else
{
TM_Scheduler(appScheduleB, LEN_B, DUR_UNTIL_NEXT_SCHEDULE);
}
useScheduleA = !useScheduleA; // Alternate the schedule each FDTI period
}
// Fixed-length, mutable schedule updated in place by the Schedule Manager.
{ &T_HandleApplication0, DUR_HANDLE_APP_0 }, // Always run
{ NULL, 0U } // Optional Task, added or removed at run time
};
// Application-specific Schedule Manager for dynamic scheduling
static void RunNextSchedule(void)
{
if (applicationEvent)
{
appSchedule[1].task = &T_HandleAppEvent;
appSchedule[1].duration = DUR_HANDLE_APP_EVENT;
applicationEvent = false;
// Ensure the total schedule duration is fixed
appSchedule[0].duration = DUR_HANDLE_APP_0;
}
else
{
// Remove event task for schedule
appSchedule[1].task = NULL; // A NULL Task is skipped by TM_Scheduler
appSchedule[1].duration = 0U;
// Ensure the total schedule duration is fixed
appSchedule[0].duration = DUR_HANDLE_APP_0 + DUR_HANDLE_APP_EVENT;
}
}

Using Optional Tasks in the Application Startup Schedule

Collapse / Expand Tip

The Framework offers a few optional Tasks that can be included in the Application Startup Schedule when running TM_Startup. For the Startup Schedule, T_HandleResetReason and T_HandleNotificationErrors are intended to be included. However, the ordering in the schedule may depend on the Error Handler configuration and application requirements.

If any application-specific error handling depends on the Application to be initialized by e.g. sending a message, all relevant Pre-startup and Startup Error IDs can be configured to NOTIFICATION criticality, and T_HandleNotificationErrors called last in the schedule to handle them before exiting Safe State. Alternatively, they can be set to NON_CRITICAL or CRITICAL to be handled immediately. If no Error IDs are set to NOTIFICATION, the Task can be omitted.

Likewise, if handling of the reset reason requires Application initialization, T_HandleResetReason can be called after the Application initialization Tasks in the schedule. If handling the reset reason is not required by the Application, this Task can be omitted, but requires manual clearing of persistent memory, see T_HandleResetReason for more information. Please note that handling the reset reason is highly recommended.

Note
See Total Startup Schedule Duration Longer than FDTI for tips on including T_HandleWatchdog in the Startup Schedule.

The example below places application-specific initialization Tasks first, then T_HandleResetReason (so it can rely on the initialized Application), and finally T_HandleNotificationErrors last to handle any NOTIFICATION errors before exiting the Safe State. The ordering should be adapted to the Error Handler configuration and application requirements.

// Depends on T_EnableAppCommunication to run first
void T_SendResetInfoCallback(resetInfo_t resetInfo);
void T_SendErrorIdCallback(errId_t errorId);
// tasks_config.c
const rstCallback_t resetReasonCallback = &T_SendResetInfoCallback;
// error_handler_config.c
const errCallback_t notificationErrorCallback = &T_SendErrorIdCallback;
static const scheduleEntry_t appStartupSchedule[LEN] = {
{ &T_HandleApplicationStartup0, DUR_HANDLE_APP_STARTUP_0 },
{ &T_EnableAppCommunication, DUR_ENABLE_APP_COMMUNICATION },
};
TM_Startup(appStartupSchedule, LEN); // Exit Safe State once all startup Tasks have run
const errCallback_t notificationErrorCallback
Configures the callback function used for 'NOTIFICATION' Error IDs in EH_ProcessNotificationLog.
Definition error_handler_config.c:146
errId_t
Defines unique Error IDs for reporting system errors to EH_HandleError.
Definition define_error_ids.h:49
void(* errCallback_t)(errId_t id)
Defines a function pointer type for error handling callback functions.
Definition define_error_callback.h:39
void T_HandleNotificationErrors(void)
Ensures Error IDs with 'NOTIFICATION' criticality are handled.
Definition tasks_shared.c:56
void T_HandleResetReason(void)
Retrieves and validates stored reset info from persistent memory and issues configurable callback for...
Definition tasks_startup.c:179
void(* rstCallback_t)(resetInfo_t resetInfo)
Defines a function pointer type for a reset handler callback function.
Definition define_reset_info.h:58
#define DUR_HANDLE_NOTIFICATION_ERRORS
Configures the run-time duration for T_HandleNotificationErrors in system clock cycles.
Definition tasks_config.h:1840
#define DUR_HANDLE_RESET_REASON
Configures the run-time duration for T_HandleResetReason in system clock cycles.
Definition tasks_config.h:1858
const rstCallback_t resetReasonCallback
Configures the user-configurable callback function used for application specific reset handling in T_...
Definition tasks_config.c:40
Defines a data structure for storing information about a device reset.
Definition define_reset_info.h:43

Using Optional Tasks in the Application Schedule

Collapse / Expand Tip

The Framework offers a few optional Tasks that can be included in the Application Schedule when running TM_Scheduler. This includes T_HandleNotificationErrors, T_RunNextLatentFaultDiag and T_EnterIdleSleep.

The T_HandleNotificationErrors Task is intended to be scheduled regularly to ensure all NOTIFICATION errors are eventually handled. However, how often it is called depends on the desired definition of NOTIFICATION errors. They can either be treated as errors that must be handled within FDTI, but not immediately, by including the Task each schedule window or as errors that should be handled whenever the Application has time by only including it occasionally. If choosing the latter, using an increased NOTIFICATION_LOG_SIZE value is recommended. If no Error IDs are set to NOTIFICATION, the Task can be omitted.

The T_RunNextLatentFaultDiag Task is provided for applications that require run-time detection of latent faults (faults occurring within the safety mechanisms themselves). Normally, such run-time diagnostics are unnecessary if the Application includes sufficiently frequent device resets (e.g., one driving cycle in automotive applications), since all latent fault diagnostics are executed at startup. However, applications with high up-time can benefit from run-time latent fault detection.

The Task executes one latent fault diagnostic each time it is called. Due to varying execution times, particularly when considering diagnostics that are disabled by default, scheduling the Task as-is can be challenging. It is primarily provided as a reference implementation to demonstrate possible usage. In general, the Task is intended to run at a very low frequency, ensuring that all diagnostics complete within the "Latent Fault Detection Time Interval (LFDTI)" of the Application, which is typically in the order of hours.

Note
Latent fault diagnostics typically require temporarily disabling the safety mechanism under test during execution. This introduces a safety risk that must be carefully evaluated for the specific Application. However, because the diagnostics run infrequently and execute quickly relative to the overall Application, the safety mechanism is disabled for only a very small percentage of the total operating time, minimizing its impact on overall system safety.

The T_EnterIdleSleep Task is provided for applications that have a long FDTI relative to each schedule window. In other words, the Task can be added at the end of a schedule to Idle sleep before starting the next schedule to reduce power consumption. Since the Synchronous Watchdog (SWDT) stops counting while in Idle sleep, the Task can be scheduled with a short DUR_ENTER_IDLE_SLEEP value. It is up to the Application to ensure a wake-up source that starts the next schedule within FDTI. Since the CRCSCAN can only run on Flash memory in Idle sleep, the Task is also intended to optionally start this action to eventually perform a full scan of a Flash section.

static const scheduleEntry_t appSchedule[LEN] = {
{ &T_HandleApplication0, DUR_HANDLE_APP_0 },
{ &T_HandleApplication1, DUR_HANDLE_APP_1 },
{ &T_EnableWakeupInterrupt, DUR_ENABLE_WAKEUP_INTERRUPT}, // Application-specific task, e.g. a timer interrupt to wake up before the next schedule
{ &T_EnterIdleSleep, DUR_ENTER_IDLE_SLEEP } // is placed last so the device sleeps until the next schedule
};
while (1)
{
}
void T_EnterIdleSleep(void)
Enters device sleep in Idle mode and optionally enables and handles CRC Scan of Flash.
Definition tasks_scheduler.c:171
void T_RunNextLatentFaultDiag(void)
Runs a different latent fault diagnostic function each time it is called.
Definition tasks_scheduler.c:153
#define DUR_ENTER_IDLE_SLEEP
Configures the run-time duration for T_EnterIdleSleep in system clock cycles.
Definition tasks_config.h:1880
#define DUR_RUN_NEXT_LATENT_FAULT_DIAG
Configures the run-time duration for T_RunNextLatentFaultDiag in system clock cycles.
Definition tasks_config.h:1821
Note
See Total Startup Schedule Duration Longer than FDTI and Increase WDT Resolution to Match FDTI for tips on including T_HandleWatchdog in schedules.
See Using Dynamic and Constant Application Schedules for tips on adding and removing Tasks run-time.

Using CRCSCAN for Application Data

Collapse / Expand Tip

The CRCSCAN peripheral can be used in Manual mode to perform hardware accelerated CRC computation on Application data to detect data corruption. It is recommended to use this on any applicable Application data, such as data transfers, EEPROM data validation or calibration data using the APIs provided in the Checksum Manager.

The example below shows an application-specific Task that verifies a range of EEPROM data by feeding it into a Manual Mode CRC-16 calculation and comparing the result against a known-good reference checksum, reporting the outcome to the Error Handler. See MW_StartCrcManualMode, MW_FeedCrcManualMode and MW_EndCrcManualMode for the CRC algorithms, bit widths and behavior.

// Reads one byte from the given EEPROM address.
uint8_t MW_ReadEepromByte(uint16_t eepromAddr); // Non-framework example middleware API
// Conceptual application-specific Task: verifies the integrity of a range of EEPROM data.
void T_VerifyEepromIntegrity(void)
{
const bool useCrc32 = false; // Select CRC-16
const errFlag_t startFlag = MW_StartCrcManualMode(useCrc32);
if (startFlag == NO_ERROR) // Only verify if Manual Mode started
{
for (uint16_t i = 0U; i < EEPROM_DATA_LENGTH; i++)
{
MW_FeedCrcManualMode(MW_ReadEepromByte(EEPROM_DATA_START + i));
}
const uint16_t dataCrc = (uint16_t)MW_EndCrcManualMode(); // Lower 16 bits for CRC-16
errFlag_t dataFlag = NO_ERROR;
if (dataCrc != EXPECTED_EEPROM_CRC) // Expected CRC could alternatively be stored in last address of EEPROM
{
dataFlag = ERROR;
}
EH_HandleError(dataFlag, ERRID_EEPROM_CRC);
}
}
void MW_FeedCrcManualMode(uint8_t data)
Feeds one byte into an ongoing CRC Manual Mode calculation.
Definition midware_checksum_manager.c:178
errFlag_t MW_StartCrcManualMode(bool useCrc32)
Starts a Cyclic Redundancy Check (CRC) Manual Mode calculation for manual data feeding.
Definition midware_checksum_manager.c:158
uint32_t MW_EndCrcManualMode(void)
Ends a CRC Manual Mode calculation and returns the computed CRC result.
Definition midware_checksum_manager.c:184
void EH_HandleError(errFlag_t flag, errId_t id)
Handles error based on configured Error ID criticality if the error flag is set.
Definition error_handler.c:37
@ ERRID_INIT_CRCSCAN
Definition define_error_ids.h:65
errFlag_t
Defines the error flag used by Middleware services to indicate error detection.
Definition define_error_flags.h:42
@ ERROR
Definition define_error_flags.h:44
@ NO_ERROR
Definition define_error_flags.h:43
Note
In this example, EEPROM_DATA_START, EEPROM_DATA_LENGTH,EXPECTED_EEPROM_CRC and ERRID_EEPROM_CRC are application-defined. The custom Error ID must be added to errId_t, and its criticality must be configured in the errorIdCritLut.

This example shows how the manual CRCSCAN APIs can be used to validate received messages:

// Conceptual application-specific task for interrupt based CRC scanning of messages
void T_HandleCommunicationInterrupt(void)
{
if(MW_GetClearRxByte()) // Non-framework example middleware API
{
uint8_t data = MW_GetRxByte(); // Non-framework example middleware API
}
}
// Conceptual application-specific Task: validates a message once fully received
void T_HandleCommunication(void)
{
if(MW_IsRxDone()) // Non-framework example middleware API
{
message_t message = MW_GetClearMessage(); // Non-framework example middleware API
uint32_t crc = MW_EndCrcManualMode();
if(crc == message.crc)
{
// Do something with message
}
else
{
// Discard message
EH_HandleError(ERROR, ERRID_CORRUPT_MESSAGE);
}
const bool useCrc32 = true; // Select CRC-32
const errFlag_t startFlag = MW_StartCrcManualMode(useCrc32); // Re-start manual mode for next message
}
}
Note
In this example, the message_t type is an application-defined struct type consisting of a message and its CRC. The Error ID ERRID_CORRUPT_MESSAGE is also application defined.

Using CRCSCAN for Device Configuration

Collapse / Expand Tip

To validate expected/correct device configuration after Special Function Registers (SFR) initialization of all peripherals, the CRCSCAN can be used in Manual mode to read the values of all relevant SFRs to generate a CRC checksum.

// Conceptual application-specific task for CRC scanning registers
void T_VerifyRegisterConfigs(void)
{
const bool useCrc32 = true; // Select CRC-32
const errFlag_t startFlag = MW_StartCrcManualMode(useCrc32);
if (startFlag == NO_ERROR) // Only verify if Manual Mode started
{
// Only feed a few register values from two peripherals for the example
MW_FeedCrcManualMode(BOD.CTRLA);
const uint32_t configCrc = MW_EndCrcManualMode();
errFlag_t configFlag = NO_ERROR;
if (configCrc != EXPECTED_REGISTER_CONFIG_CRC)
{
configFlag = ERROR;
}
EH_HandleError(configFlag, ERRID_REGISTER_CONFIG_CRC);
}
}
Note
In this example, EXPECTED_REGISTER_CONFIG_CRC and ERRID_REGISTER_CONFIG_CRC are application-defined. The custom Error ID must be added to errId_t, and its criticality must be configured in the errorIdCritLut.

Using an External Oscillator

Collapse / Expand Tip

It is recommended to use an external oscillator as either the main clock or as a reference clock in the CFM or CFD that monitors the main clock. However, since this is Application specific, the device is initialized using only internal oscillators in T_InitSafetySystem. It is recommended that the Application startup schedule includes Tasks that configures and enables the external oscillator, while also reconfiguring the CFMs and CFDs to account for this source once the oscillator is stable. Note that CFM and CFD should be disabled while switching clock sources. The free CFM and CFD resource can either be configured to monitor the same clock signals redundantly, or used for Application specific monitoring.

Refer to the Concept for Clock chapter in the Device Safety Manual to see the intended clock configurations used in the examples below.

// Application-specific Task: use XOSCHF and switch CFDs/CFMs to monitor the external oscillator.
void T_ConfigureExternalMainClock(void)
{
// Stop all monitors since the main clock is going to be switched
MW_StopCfd1(); // Only necessary if redundant monitoring is used
MW_StopCfm1(); // Only necessary if redundant monitoring is used
errFlag_t flag = MW_EnableExternalMainClock(); // Non-framework example middleware API
/*
* Monitor the main clock, since it now uses XOSCHF or EXTCLK as the source,
* the CLKCTRL will automatically switch to the internal OSCHF if an error is
* detected and report an error instead of immediate device reset.
*/
// Only necessary if redundant monitoring is used
flag = MW_StartCfm0(CFM_SRC_MAINCLK, CFM_REF_OSC32K, newWindowSetting, true);
// Only necessary if redundant monitoring is used
flag = MW_StartCfm1(CFM_SRC_MAINCLK, CFM_REF_OSC32K, newWindowSetting, true);
}
void MW_StopCfd1(void)
Disables Clock Failure Detect 1 (CFD1).
Definition midware_clock_manager.c:191
void MW_StopCfd0(void)
Disables Clock Failure Detect 0 (CFD0).
Definition midware_clock_manager.c:161
errFlag_t MW_StartCfd1(cfdSource_t src, cfdReference_t ref)
Starts Clock Failure Detect 1 (CFD1) to monitor the configured clock source with the configured refer...
Definition midware_clock_manager.c:168
errFlag_t MW_StartCfm0(cfmSource_t src, cfmReference_t ref, cfmWindow_t window, bool useContinuous)
Starts Clock Frequency Measure 0 (CFM0) to monitor the configured clock source with the configured re...
Definition midware_clock_manager.c:198
errFlag_t MW_StartCfm1(cfmSource_t src, cfmReference_t ref, cfmWindow_t window, bool useContinuous)
Starts Clock Frequency Measure 1 (CFM1) to monitor the configured clock source with the configured re...
Definition midware_clock_manager.c:240
errFlag_t MW_StartCfd0(cfdSource_t src, cfdReference_t ref)
Starts Clock Failure Detect 0 (CFD0) to monitor the configured clock source with the configured refer...
Definition midware_clock_manager.c:138
void MW_StopCfm0(void)
Disables Clock Frequency Measure 0 (CFM0).
Definition midware_clock_manager.c:233
void MW_StopCfm1(void)
Disables Clock Frequency Measure 1 (CFM1).
Definition midware_clock_manager.c:275
@ CFM_REF_OSC32K
Definition midware_clock_manager.h:151
@ CFD_SRC_MAINCLK
Definition midware_clock_manager.h:95
@ CFD_REF_OSC32K
Definition midware_clock_manager.h:115
@ CFM_SRC_MAINCLK
Definition midware_clock_manager.h:130
@ ERRID_INIT_CLOCKS
Definition define_error_ids.h:60

The example below shows an application specific task that enables an external 32 kHz oscillator and reassigns the CFM channels to use it as a measurement reference.

// Application-specific Task: use XOS32K and switch CFMs to use it as reference when monitoring main clock.
void T_ConfigureExternal32kClock(void)
{
// Only need to stop CFM since main clock is not changed and XOSC32K is not used as the reference for CFD for this scenario
MW_StopCfm1(); // Only necessary if redundant monitoring is used
errFlag_t flag = MW_EnableExternal32K(); // Application-specific function
flag = MW_StartCfm0(CFM_SRC_MAINCLK, CFM_REF_XOSC32K, newWindowSetting, true);
// Only necessary if redundant monitoring is used
flag = MW_StartCfm1(CFM_SRC_MAINCLK, CFM_REF_XOSC32K, newWindowSetting, true);
}
@ CFM_REF_XOSC32K
Definition midware_clock_manager.h:150

Using VMON in Deep Sleep

Collapse / Expand Tip

If INIT_VMON_ALWAYS_ON is set to DISABLED to reduce power consumption, the VREG Monitor (VMON) will be turned off when going into Standby or Power-down sleep modes. As an additional safety mechanism, the VMON is disabled (VDIS) flag in the SLPCTRL.INTFLAGS register may be set when this occurs. Normally, this may trigger a SLPCTRL interrupt and may result in an ERRCTRL error after wake. To avoid this, the VDIS bit in the SLPCTRL.INTCTRL register should be cleared.

If INIT_VMON_FULL_POWER_MODE is set to DISABLED (Auto Mode), the VMON Sleep Entry (VSLP) flag in the SLPCTRL.INTFLAGS register may be set when entering Standby or Power-down sleep. The flag may also be set when switching the main clock source to OSC32K or XOSC32K while the VMON is enabled. This may also trigger a SLPCTRL interrupt and may result in an ERRCTRL error after wake.
To avoid this, the VSLP bit in the SLPCTRL.INTCTRL register should be cleared.

In either case, the VREGWARN error channel should be configured to NOTIFICATION severity using the ERRCTRL.ESCVREGWARN register in the Application Shutdown Task called by TM_Shutdown.

void MW_DisableVregWarnSleepInterrupt(void) // Non-framework example middleware API
{
// Application-specific driver — clear only the bit(s) relevant to your configuration
SLPCTRL_ClearIntControl((uint8_t)(SLPCTRL_VDIS_bm | SLPCTRL_VSLP_bm));
}
/*
* The Framework configures all error channels at once, since each configuration requires
* entering and exiting the ERRCTRL CONFIG state. To reconfigure a single channel, the Error
* Manager can be extended with such an API, or expose its static ConfigVregwarn.
*/
errFlag_t MW_SetVregwarnNotification(void) // Non-framework example middleware API
{
// Add code for checking that the error controller is in a NORMAL state or return error, disabling global interrupts and entering CONFIG state here.
// ...
const errChConfig_t vregwarnConfig = {.severity = ERRCH_SEVERITY_NOTIFICATION, .floatIo = false};
ConfigVregwarn(vregwarnConfig); // See implementation in Error Manager
// Add code for exiting CONFIG state and restore global interrupts here
// ...
return NO_ERROR;
}
// Application-specific Shutdown Task: prevents VDIS and VSLP flags from causing errors after wake.
void T_DisableVmonInShutdown(void)
{
MW_DisableVregWarnSleepInterrupt();
// Disable global interrupts before reconfiguring Error Controller, restored in shutdown sequence
errFlag_t flag = MW_SetVregwarnNotification();
}
static const scheduleEntry_t appShutdownTask = { &T_DisableVmonInShutdown, DUR_SHUTDOWN_TASK };
@ ERRID_INIT_ERRCTRL
Definition define_error_ids.h:61
@ ERRCH_SEVERITY_NOTIFICATION
Definition midware_error_manager.h:83
void MW_DisableGlobalInterrupts(void)
Disables global interrupts on the device.
Definition midware_interrupt_manager.c:106
const scheduleEntry_t appShutdownTask
Defines an example Application Shutdown Task schedule entry.
Definition main_example.h:223
void TM_Shutdown(scheduleEntry_t appShutdownTask)
Manages Tasks that ensure safe execution of device sleeping in Standby or Power-Down mode.
Definition task_manager_shutdown.c:54
Holds an error channel configuration for configuring the ERRCTRL.
Definition midware_error_manager.h:92

Using CFD diagnostics in Startup

Collapse / Expand Tip

Some diagnostics provided can optionally be included or excluded from the Framework Tasks, see DIAG_STARTUP_CFD0, DIAG_STARTUP_CFD1, DIAG_STARTUP_ERRCH_CFD0 and DIAG_STARTUP_ERRCH_CFD1.

The CFD error injection takes in the range of approximately 300 µs - 600 µs to complete and has therefore been split into begin and end diagnostic functions. There is no built-in mechanism for knowing whether the error injection failed, and it is left to the Application to ensure sufficient time separation before calling the end API. This can be done by calling the end API in an application-specific task included in the Application Startup Schedule that runs after two CFD measurement periods have completed.

If the Application requirements permit it, the INIT_REDUNDANT_CFD setting can be enabled instead of performing error injection. In that case, it is recommended to enable DIAG_STARTUP_ERRCH_CFD0 and DIAG_STARTUP_ERRCH_CFD1.

If the Application only uses CFD0 (to monitor the main clock), it is still recommended to enable INIT_REDUNDANT_CFD while the error injection diagnostic is performed to ensure that CFD1 monitors the main clock while CFD0 is not. An application-specific Task in the Application Startup Schedule can disable CFD1 after ending the CFD0 diagnostic.

If the Application requires using CFD1 to monitor another clock, both CFD0 and CFD1 error injection diagnostics should be run. If both diagnostics are enabled in the Run Startup Diagnostics Task, both diagnostics will be started in parallel. During this time, no CFD is monitoring the main clock. While still in a Safe State, if this is not deemed acceptable, the INIT_REDUNDANT_CFD setting can be enabled and only DIAG_STARTUP_CFD0 set to enabled. In this configuration, the CFD1 is monitoring the main clock while the CFD0 is not. Once the application-specific Task in the Application Startup Schedule completes the diagnostic, it can begin the CFD1 diagnostic. Later in the schedule, the CFD1 diagnostic can be completed, and CFD0 and CFD1 can be re-configured as required by the Application.


Using CFD diagnostics in Mission Mode

Collapse / Expand Tip

Some diagnostics provided can optionally be included or excluded from the Framework Tasks, see DIAG_PERIODIC_CFD0, DIAG_PERIODIC_CFD1, DIAG_PERIODIC_ERRCH_CFD0 and DIAG_PERIODIC_ERRCH_CFD1.

Please see Using CFD diagnostics in Startup first for necessary context. If the Application requires latent fault detection while in Mission Mode and does not use the redundant CFD1 and/or CFM1 enabled by INIT_REDUNDANT_CFD and INIT_REDUNDANT_CFM, the provided error injection diagnostics can be enabled. However, this requires careful consideration.

The Run Next Latent Diagnostic Task only starts the CFDn diagnostic. It is the responsibility of the Application to ensure that the diagnostic is completed. This can be achieved by either scheduling an application-specific Task in the Application Schedule that ends it after sufficient time has passed, or use a timer resource to generate an interrupt once the diagnostic is complete. If not ended correctly, the CFD mechanism is effectively disabled.

Since the device is not in a Safe State, it can be considered a risk to disable the CFD or CFM monitoring the main clock while performing the error injection diagnostic. However, if done correctly, both diagnostics should still be completed within a minimum FDTI of 1 ms. As an additional measure, the CFM0 can monitor the main clock while CFD0 is disabled and vice versa, as long as both diagnostics are not run at the same time. However, it is still recommended to consider setting the other CFD or CFM to monitor the main clock for the duration of the diagnostic. In this case, the diagnostic of one CFD should be completed before starting a diagnostic of the other. This is automatically prevented if the Run Next Latent Diagnostic Task is scheduled infrequently relative to the Application FDTI.

Note
When CFD diagnostics are required, relying on T_RunNextLatentFaultDiag may not be ideal, since that Task executes one diagnostic from a queue of diagnostics. The application would have to take into consideration that a given CFD diagnostic is only started after a specific number of calls to that Task, which makes it harder to coordinate the timing concerns above, such as keeping another monitor on the main clock and preventing the CFD0 and CFD1 diagnostics from overlapping. Application-specific Tasks could instead be implemented to run a CFD diagnostic directly using MW_DiagClockCfd0Begin and MW_DiagClockCfd1Begin together with MW_DiagClockCfd0End and MW_DiagClockCfd1End, which also simplifies the logic for ending the diagnostic at a controlled time. In this case, DIAG_PERIODIC_CFD0 and DIAG_PERIODIC_CFD1 could be set to DISABLED to exclude them from the T_RunNextLatentFaultDiag diagnostic queue.

Using WDT Count Diagnostic

Collapse / Expand Tip

Some diagnostics provided can optionally be included or excluded from the Framework Tasks, see DIAG_PERIODIC_WDT_COUNT.

The WDT count diagnostic has a relatively small impact on Latent Fault coverage metrics. Moreover, WDT is a safety mechanism that has a dedicated monitor and a redundant Synchronous Watchdog, making any faults in the WDT latent faults. The WDT expire diagnostic is run at startup and also detects a stuck WDT counter. For this reason, the Application may not require running the count diagnostic within every FDTI period.

If enabled, it is the responsibility of the Application to begin the diagnostic, while the T_HandleWatchdog Task ends it. This is to avoid busy-waiting for the WDT counter register to synchronize every 4th 32kHz WDT clock tick. The Application must ensure a sufficient time separation of at least 4 WDT clocks with tolerance from beginning the diagnostic to ending it. This can be achieved by including an application-specific Task that calls MW_DiagWdtCountBegin in the beginning of the Application Startup Schedule and Application Schedule. The diagnostic can also be initiated in an ISR.


Configuring Wake-up Sources and Watchdog Handling in Shutdown Sleep

Collapse / Expand Tip

During shutdown, TM_Shutdown enters a Safe State, reconfigures the Watchdogs and then loops: it enters Standby or Power-Down sleep, wakes on an interrupt, optionally clears the Watchdogs and re-enters sleep. The loop ends only when an application-specific interrupt handler requests an exit, which restarts the device. See TM_Shutdown for the full sequence. Since application-specific interrupt handlers are expected to service the watchdogs through TM_ShutdownRequestWatchdogs and request exit through TM_ShutdownRequestExit, the choice of wake-up sources determines how the watchdogs and exit conditions are handled.

Because the device re-enters sleep after every wake-up, multiple unrelated wake-up sources are well behaved. The Watchdog Timer (WDT) continues to count in sleep, so it must be cleared before the configured SHUTDOWN_WDT_DURATION expires, which is why a periodic wake-up is recommended. The Synchronous Watchdog (SWDT), by contrast, does not count in any sleep mode and resumes from its retained value on each wake-up. However, it cannot be disabled once enabled, so TM_Shutdown configures it to its maximum non-windowed timeout of 0xFFFF peripheral cycles, which is approximately 3.3 ms at 20 MHz. Only handling watchdogs in a periodic wake-up should be enough in most use cases. However, if it is assumed that the application will spend more than 0xFFFF main clock cycles awake in other interrupts, handling the watchdogs in every wake-up interrupt should be considered.

The Framework is designed to accommodate the following use cases, ordered from the simplest configuration to the more complex ones:

  1. Watchdog disabled: By disabling the Watchdog timer, the application can be configured to essentially only wake-up when a reset is necessary.
  2. Periodic wake-up only: A single periodic interrupt, such as the Periodic Interrupt Timer (PIT), clears the Watchdogs on every wake-up and evaluates the exit condition, for example a software counter reaching a target value.
  3. Periodic wake-up with unrelated wake-up sources: Other interrupts may additionally wake the device for application reasons. Since the device simply re-enters sleep, this requires no extra handling beyond ensuring the periodic interrupt still clears the Watchdogs in time.
  4. Asynchronous exit request: The exit is requested from an asynchronous source, such as a pin change interrupt or a core-independent event routed through EVSYS and CCL, while a periodic interrupt remains responsible for clearing the Watchdogs. Asynchronous here means unsynchronized with respect to the periodic interrupt, which introduces the request-timing races described in the warnings below.
  5. Immediate restart from an interrupt handler: Calling T_RestartDevice directly in an application-specific interrupt handler exits shutdown without waiting for TM_Shutdown to observe a request. This is a last-resort option for applications that cannot tolerate the exit request latency described in the first warning below.

Both request APIs set a flag that TM_Shutdown reads once per shutdown cycle, so a request issued while the device is already awake can be missed for the current cycle.

Warning
If TM_ShutdownRequestExit is called after the exit flag has been read, the exit does not occur until the device wakes again, for example on the next periodic interrupt. If this delay is not acceptable, consider calling T_RestartDevice directly in the interrupt handler, or use an external reset signal.
If TM_ShutdownRequestWatchdogs is called after the Watchdog flag has been read, the device re-enters sleep and the Watchdogs are only cleared after the next wake-up. With several asynchronous wake-up sources this can accumulate enough awake time for the SWDT to expire, or delay the WDT clear beyond SHUTDOWN_WDT_DURATION.

A design pattern that mitigates these concerns is to let asynchronous interrupt handlers set software flags, and to let the periodic interrupt handler read them. The periodic handler then acts as a small schedule for acting on recorded events, requesting the exit and clearing the Watchdogs from a single, predictable point in time. The cost is that an event is only acted upon at the next periodic wake-up.

Use case 1: watchdog disabled

Setting SHUTDOWN_WDT_DURATION to WDT_TIMEOUT_OFF disables the WDT during shutdown, so no periodic wake-up is needed and the device can sleep indefinitely until an exit is requested. When the WDT is disabled, the device can get stuck in sleep, so an external reset signal is recommended. With an external reset available, the application may not need any interrupt handler at all if no interrupts are expected. If the application is still expected to wake-up multiple times while sleeping, TM_ShutdownRequestWatchdogs should be called to clear the SWDT through those interrupts. Alternatively, the application can also prevent the SWDT from creating any interrupts or resetting the device by reconfiguring the SWDT and ERRCTRL similarly to how it is accomplished for SLPCTRL in Using VMON in Deep Sleep.

Use case 2: periodic wake-up only

#define EXIT_WAKE_UP_COUNT (10U)
static volatile uint8_t wakeUpCounter = 0U;
// Application-specific Task: clears the periodic interrupt flag
static void T_HandlePitInterrupt(void)
{
MW_ClearPitInterrupt(); // Non-framework example middleware API
}
// Application-specific Interrupts Task Manager: schedules the Task run on the periodic interrupt
static void TM_InterruptsPit(void)
{
T_HandlePitInterrupt();
}
ISR(RTC_PIT_vect) // Application-specific periodic wake-up source
{
TM_InterruptsPit();
if (suspendScheduler == true) // Shutdown sequence is running, see main_example.c
{
TM_ShutdownRequestWatchdogs(); // Clear both Watchdogs before re-entering sleep
wakeUpCounter++;
if (wakeUpCounter >= EXIT_WAKE_UP_COUNT)
{
TM_ShutdownRequestExit(); // Request the restart that ends the shutdown sequence
}
}
}
volatile bool suspendScheduler
Defines an example boolean for the Application to optionally start the Shutdown sequence.
Definition main_example.c:159
void TM_ShutdownRequestWatchdogs(void)
Sets a request flag for the Shutdown Task Manager to clear both Watchdogs on the next shutdown cycle.
Definition task_manager_shutdown.c:44
void TM_ShutdownRequestExit(void)
Sets a request flag for the Shutdown Task Manager to exit the shutdown sequence by issuing a device r...
Definition task_manager_shutdown.c:49

Use case 3: periodic wake-up with unrelated wake-up sources

The PIT ISR from use case 2 is unchanged.

static volatile uint8_t appEventCounter = 0U;
// Application-specific Task: performs an unrelated application action on a pin change
static void T_HandlePortAInterrupt(void)
{
MW_ClearPortAInterrupt(); // Non-framework example middleware API
appEventCounter++; // Unrelated application activity, not tied to Watchdogs or exit
}
// Application-specific Interrupts Task Manager: schedules the Task run on a PORTA pin interrupt
static void TM_InterruptsPortA(void)
{
T_HandlePortAInterrupt();
}
ISR(PORTA_PORT_vect) // Application-specific unrelated wake-up source
{
TM_InterruptsPortA();
}

Use case 4: asynchronous exit request

static volatile bool exitRequested = false;
// Application-specific Task: records the asynchronous wake-up event
static void T_HandlePortAInterrupt(void)
{
MW_ClearPortAInterrupt(); // Non-framework example middleware API
exitRequested = true; // Only sets a flag, acted on by the periodic handler
}
// Application-specific Interrupts Task Manager: schedules the Task run on a PORTA pin interrupt
static void TM_InterruptsPortA(void)
{
T_HandlePortAInterrupt();
}
ISR(PORTA_PORT_vect) // Application-specific asynchronous wake-up source
{
TM_InterruptsPortA();
}
ISR(RTC_PIT_vect) // Application-specific periodic wake-up source
{
TM_InterruptsPit(); // See Use case 2
if (suspendScheduler == true) // Shutdown sequence is running, see main_example.c
{
TM_ShutdownRequestWatchdogs(); // Clear both Watchdogs before re-entering sleep
if (exitRequested == true)
{
TM_ShutdownRequestExit(); // Requested from the periodic handler only
}
}
}

Use case 5: immediate restart from an interrupt handler

// Application-specific Task: clears the interrupt flags for the asynchronous wake-up source
static void T_HandlePortAInterrupt(void)
{
MW_ClearPortAInterrupt(); // Non-framework example middleware API
}
// Application-specific Interrupts Task Manager: ends the shutdown sequence immediately
static void TM_InterruptsPortA(void)
{
T_HandlePortAInterrupt();
if (suspendScheduler == true) // Shutdown sequence is running, see main_example.c
{
T_RestartDevice(); // Framework Task: resets the device, never returns
}
}
ISR(PORTA_PORT_vect) // Application-specific asynchronous wake-up source
{
TM_InterruptsPortA();
}
void T_RestartDevice(void)
Restarts the device as part of the shutdown exit sequence.
Definition tasks_shutdown.c:156
Warning
Calling T_RestartDevice in an interrupt handler resets the device without returning to TM_Shutdown. Any application state that must be preserved across the restart has to be stored in persistent memory before the call.
Shutdown Architecture: Periodic Interrupt, Async Interrupt and WDT Disabled Examples

External Output During Shutdown Sleep

Collapse / Expand Tip

By default, SHUTDOWN_SAFE_STATE is enabled, so TM_Shutdown enters the Safe State by floating (tri-stating) all I/O pins before entering shutdown sleep. This disables output drivers and internal pull-ups. If the application needs to drive a pin while sleeping SHUTDOWN_SAFE_STATE can be disabled.

When it is disabled, it is the responsibility of the application to ensure safe operation before entering sleep. This can be achieved by manually tri-stating all I/O pins and then configuring the output pin in the application-specific shutdown Task passed to TM_Shutdown.

// Configuration in tasks_config.h
#define SHUTDOWN_SAFE_STATE DISABLED
// Floats all I/O pins through PORT configs; returns an error if any pin could not be floated.
errFlag_t MW_FloatAllPins(void); // Non-framework example middleware API
// Configures the given pin as an output driver; returns an error on failure.
errFlag_t MW_EnableSignalOutput(uint8_t pin); // Non-framework example middleware API
// Application-specific Shutdown Task: ensures a Safe State while driving an output from a pin.
void T_InitShutdownPins(void)
{
errFlag_t flag = MW_FloatAllPins();
EH_HandleError(flag, ERRID_MANUAL_SAFE_IO);
flag = MW_EnableSignalOutput(PWM_PIN); // PWM_PIN is application-defined
EH_HandleError(flag, ERRID_INIT_PINS);
}
static const scheduleEntry_t appShutdownTask = { &T_InitShutdownPins, DUR_SHUTDOWN_TASK };
TM_Shutdown(appShutdownTask); // Enter deep sleep without being in a strict Safe State
Note
PWM_PIN, DUR_SHUTDOWN_TASK, ERRID_MANUAL_SAFE_IO and ERRID_INIT_PINS are application-defined so the custom Error IDs must added to errId_t with its criticality configured in errorIdCritLut.

Reducing Power Consumption Further in Shutdown Sleep

Collapse / Expand Tip

Beyond selecting the sleep mode with SHUTDOWN_POWER_DOWN, several supply-supervision safety mechanisms remain active in sleep and can be relaxed to reduce power consumption further. Since these mechanisms exist to detect unsafe supply conditions, relaxing them trades safety coverage for lower power and should only be done while in a Safe State and with an external reset source available.

The Voltage Regulator Monitor (VMON) is controlled through Framework configuration. Setting INIT_VMON_ALWAYS_ON to DISABLED turns the VMON off in Standby and Power-Down sleep, which requires the handling described in Using VMON in Deep Sleep. When the VMON is kept enabled, INIT_VMON_FULL_POWER_MODE selects between full power mode (faster wake-up) and power-saving mode (lower consumption). Setting it to DISABLED uses Auto Mode, which also requires the handling described in Using VMON in Deep Sleep.

The Brown-out Detector (BOD) sleep settings should be configured through BODCFG user fuses selected based on the application requirements. If permissible, it can be disabled entirely while sleeping.

The Watchdog Timer (WDT) sleep settings should be configured through SHUTDOWN_WDT_DURATION, by setting it to WDT_TIMEOUT_OFF it will be disabled while sleeping, as further described in Configuring Wake-up Sources and Watchdog Handling in Shutdown Sleep. If the application requires the WDT to be enabled while sleeping, another power-saving measure could be to disable the WDT monitor in sleep by configuring the WDTMON bit field in the SYSCFG1 fuse to SLEEP.

Warning
Disabling the BOD or WDT Clock Monitor removes detection of supply and WDT clock failures. This should only be considered while in a Safe State, and an external reset source through the reset pin is strongly recommended. Refer to the device data sheet for the sleep-mode behavior and power figures of each mechanism.

Repairing ECC1 Errors in Memory

Collapse / Expand Tip

If a single-bit ECC error (ECC1) is detected in RAM, Flash or EEPROM, the data returned on the read access is automatically corrected by the ECC hardware. However, the erroneous bit in the stored word remains flipped, meaning that reading the same location again will trigger a new ECC1 error. This increases the risk that a later bit error in the same protected word will result in a non-correctable ECC2 error. If the failure is transient, meaning the memory cell is not permanently stuck, it can therefore be desirable for the Application to attempt repair of the affected memory location.

In general, an ECC1 error can correspond either to flipped data bits or to flipped ECC parity bits. If the issue is in the address portion protected by ECC, the hardware instead reports an ECC2 error, since the protected address no longer matches the data, meaning data has effectively been written to or read from the wrong location. In principle, repair of an ECC1 error is therefore done by reading the ECC-corrected value and writing it back to the same location in memory. If the issue is in the ECC parity bits rather than the data itself, rewriting the corrected word will also regenerate the correct ECC parity.

However, in practice, repair of ECC1 errors is Application specific and depends on the memory type, the memory architecture and whether the Application deems it safe to rewrite the affected location.

In general, run-time writes to Flash are discouraged. However, correcting EEPROM data is considered more feasible. In both cases, the same general principle applies. When an ECC error is detected, the NVMCTRL.ADDR register is frozen for as long as the ECC error flag remains set. This allows the Application to determine which NVM location triggered the error. If the ECC flag is cleared too soon, the register will resume updating on subsequent NVM accesses and instruction fetches, and the error context is lost. If a single-bit NVM ECC error is reported, either FECC1 (Flash) or EECC1 (EEPROM), the ADDR register can be used to determine the affected Flash or EEPROM location. The corrected value can then be read from that location and written back using the Flash or EEPROM write APIs available to the Application.

Note
NVMCTRL.SYNDROME, NVMCTRL.PARITY and NVMCTRL.DATA are not required for a basic ECC1 repair flow. Alternatively, SYNDROME can be used to determine whether the error is in the data bits or the ECC parity bits. If the error is in the ECC parity bits, the contents of DATA can be written back. If the error is in the data bits, the bit indicated by SYNDROME can be flipped in DATA and written back. However, it is typically simpler to read the corrected value from the address given by NVMCTRL.ADDR and write that value back.

For RAM, the same principle applies, but the implementation depends on the RAM word size of the device. RAMCTRL.ADDR reports the address of the failing location in the data space, so it can be used directly as a pointer to the affected RAM word. The corrected value can then be read from RAM and written back to the same word. When the location is read, the value returned is already corrected by the ECC hardware, and the subsequent write-back refreshes the stored data and ECC parity.

For devices where each RAM word corresponds to one byte, the address can be used directly as a byte address. For devices where each protected RAM word spans multiple bytes, the reported address is word-aligned and identifies the start of the affected word. A pointer to the full word can therefore be used to read and write back the entire corrected word, removing the need to decode which specific byte triggered the ECC1 error.

Note
RAMCTRL.SYNDROME is not required for a basic ECC1 repair flow. Alternatively, SYNDROME can be used to determine whether the error is in the ECC parity bits or in a specific byte. For RAM with multi-byte words, SYNDROME can be used to identify which byte in the affected word triggered the error, allowing only that byte to be rewritten. If the error is in the ECC parity bits, rewriting the corrected word is sufficient. However, it is typically simpler to read the corrected value from the address given by RAMCTRL.ADDR and write back the affected word.
Unlike NVMCTRL.ADDR, RAMCTRL.ADDR is not frozen on an ECC error. A later RAM ECC error may therefore overwrite the reported address before the Application has completed its repair attempt. However, once the affected address has been read, the repair can still be completed safely for that location. If the address is overwritten before being read, the original location can still be repaired later if the ECC1 error is triggered again.

If a rewrite succeeds, reading the same location again should no longer trigger ECC1. If an ECC2 error is reported at any point, the condition should be treated as non-correctable and the repair attempt considered failed.

Note
A repair routine can be called as part of the Application error handling when an ECC1 error is reported. However, the default interrupt handlers provided by the Framework clear ECC error flags before calling the Error Handler. This is not suitable for NVM repair flows that require reading the frozen NVMCTRL.ADDR value before clearing the ECC error flag.
As an alternative to repairing memory immediately, the Application can log affected addresses and decide later whether and when repair should be attempted. For example, EEPROM or Flash locations may be repaired in a dedicated maintenance routine or bootloader flow. Depending on the Application, a device reset may also be sufficient for transient RAM bit flips.
Warning
The Application must ensure that rewriting the affected memory location is safe. For instance, this may not be acceptable for RAM used for active control flow, hardware communication buffers or other timing-critical functionality.

The following example is given for reference only and shows one possible way to attempt repair of a RAM ECC1 error:

static bool IsRamEcc1Error(void)
{
const uint8_t regVal = RAMCTRL_ReadIntFlags();
const uint8_t ecc1Mask = (uint8_t)RAMCTRL_ECC1_bm;
return ((regVal & ecc1Mask) == ecc1Mask);
}
static bool IsRamEcc2Error(void)
{
const uint8_t regVal = RAMCTRL_ReadIntFlags();
const uint8_t ecc2Mask = (uint8_t)RAMCTRL_ECC2_bm;
return ((regVal & ecc2Mask) == ecc2Mask);
}
errFlag_t MW_RepairRamEcc1ErrorExample(void) // Non-framework example middleware API
{
const bool isOnlyEcc1Error = (IsRamEcc1Error() && (!IsRamEcc2Error()));
if (!isOnlyEcc1Error)
{
return ERROR;
}
const uint16_t reportedRamAddr = RAMCTRL_ReadAddress();
// Intentional MISRA deviation: integer to pointer cast for memory-mapped SRAM access
#if defined(AVR64SD) // Defined by the Application
volatile uint32_t* const ramWordPtr = (volatile uint32_t*)(uintptr_t)reportedRamAddr;
#else
volatile uint8_t* const ramWordPtr = (volatile uint8_t*)(uintptr_t)reportedRamAddr;
#endif
// Read the corrected value and write it back to refresh ECC/parity
*ramWordPtr = *ramWordPtr;
// Clear ECC1 before verifying the repair with a new read
RAMCTRL_WriteIntFlags((uint8_t)RAMCTRL_ECC1_bm);
// Read back to retrigger ECC evaluation on the repaired location
(void)(*ramWordPtr);
const bool isEccError = (IsRamEcc1Error() || IsRamEcc2Error());
if (isEccError)
{
return ERROR; // Repair failed (non-transient error) or ECC2 error has occurred
}
return NO_ERROR;
}
uint8_t RAMCTRL_ReadIntFlags(void)
Reads the INTFLAGS register value.
Definition driver_ramctrl.c:33
uint16_t RAMCTRL_ReadAddress(void)
Reads the ADDR register value.
Definition driver_ramctrl.c:45
void RAMCTRL_WriteIntFlags(uint8_t value)
Overwrites the INTFLAGS register value.
Definition driver_ramctrl.c:39