3. Hardfaults Session

This section provides a brief description of hardfaults on Cortex-M0 processors. It also describes the tools that can be used to deal with system faults.

It also explains how the SDK handles system faults and demonstrates a real use case of a hardfault, including all the steps that need to be followed to handle the fault.

3.1. Introduction

In ARM processors, when a program goes wrong and the processor detects the cause that made the device to fail, an exception is raised. On the Cortex-M0 processor integrated in the DA1468x family of devices, there is only one exception type that handles hardfaults. This is named “the hardfault handler”. There are many reasons for a fault to occur, such as accessing invalid memory address during bus transaction or attempting to generate an unaligned memory access.

The way the SDK handles the various system faults depends on whether the application is built in development or production mode.

  • In development mode (enabled by default), the SDK stores the system status in a predefined retained location in memory (SySRAM) and then adds an infinite loop. This allows the developer to attach a debugger, extract all the information stored in that memory area, and eventually identify the reason for the fault.
  • In production mode, adding an infinite loop is not practical as it would require the user to get involved with debugging and recovery of the system. Instead, the system status is stored in a dedicated retained area (hard_fault_info) in SySRAM memory and a system reset is triggered so that the device starts its execution from start.
'SW FSM of the hardfault exception handler'

Fig. 12 SW FSM of the Hardfault Exception Handler

Note

When in production mode and before executing the main application tasks, it can be determined whether or not a reset derives from a system fault by examining the information stored during a hardfault exception. For validity purposes, the first entry in the memory area, where the system status has been stored, should be 0xBADC0FFE. This entry can be considered a special flag to indicate that the data that follows is valid.

3.2. Manually Triggering a Hardfault

The following real use case demonstrates triggering a hardfault. The next section demonstrates how to identify the cause of the fault.

  1. Make a copy of the freertos_retarget sample code found in the SDK of the DA1468x family of devices. For information on how to create a new project, see Create a New Project in the Starting a Project tutorial.
  1. In the main.c source file of the newly created application, insert the following code which will trigger a hardfault. The function tries to access an invalid memory address, which triggers an exception to be issued when executed.
/* This is an invalid memory address - outside the recognized memory boundaries */
#define INVALID_ADDRESS 0x99999999UL;

void trigger_hardfault(void);
void trigger_hardfault(void)
{
        /* Declare a pointer that points to an invalid memory address */
        uint8_t *p = (uint8_t *) INVALID_ADDRESS;

        /* Try to access that invalid memory address */
        *p = 0x50;
}
  1. In the main task of the application, that is prvTemplateTask, call the trigger_hardfault() function somewhere within its main loop, for example:
/* Place this task in the blocked state until it is time to run again.
   The block time is specified in ticks, the constant used converts ticks
   to ms.  While in the blocked state, this task will not consume any CPU
   time. */
vTaskDelayUntil( &xNextWakeTime, mainCOUNTER_FREQUENCY_MS );


/* Trigger a hardfault deliberately! */
trigger_hardfault();

test_counter++;
  1. Optionally, to enable debugging messages on the serial console, add the following macro definition in the config/custom_config_qspi.h header file.
/* Enable hardfault debugging messages */
#define VERBOSE_HARDFAULT                       (1)
  1. Build the project in Debug mode (for example, in the case of DA14681 SoC select the DA14681-01-Debug-QSPI build scheme) and burn the generated image to the chip.

Note

Debug mode is preferred over production mode when a debugging session is to be performed, as stepping the code is a more straightforward task. In production mode, the source code is built using optimizations, thus making tracing more complex.

  1. Press the K2 button on Pro DevKit. This starts the chip executing its firmware. After a while, the hardfault will be triggered.

3.3. Dealing with a Hardfault

This section provides the steps to identify the cause of a hardfault. Pointing to the exact location in the source code where a fault occurred, can be a tough task. However, a debugging session can reveal the point where things started to go wrong.

  1. Initiate a debugging session by selecting the ATTACH mode. When switching to Debug view, select Suspend to pause the code execution.

    Program execution should be stuck in an infinite loop under the HardFault_HandlerC interrupt handler.

'Hardfault handler function'

Fig. 13 Hardfault Handler Function

The hardfault handler function provides the whole register set values when the hardfault was triggered. For example, the values of registers R0 to R3, R12, LR, PC, and xPSR are stored in memory position 0x07FC5600.
  1. Use the Memory Browser tool (1) to view the contents stored in memory. In this tool, enter the base address where the stack frame is stored (2). Either enter the physical memory address value or the name of the corresponding macro, that is, STATUS_BASE.
'Probing the Stack Frame Captured Upon a Hardfault Event'

Fig. 14 Probing the Stack Frame Captured Following a Hardfault Event

If debugging output is enabled, the following information will be displayed on the console:
Debugging Messages Upon a Hardfault Event

Fig. 15 Debugging Messages Following a Hardfault Event

The most useful information is held in the Program Counter (PC) and Link Register (LR). The PC holds the current instruction address plus four bytes (this is caused by the pipeline nature of the Cortex-M0 processor). The LR is used for storing the return address of a subroutine or function call. At the end of the subroutine or function, the return address stored in LR is loaded into the PC so that the execution of the calling program is resumed.

This information, together with the Disassembly tool, can be used to identify the exact assembly command that caused the error.

  1. At this stage, we can examine the command pointed to by the PC register value. To do this, select the Dissassembly window (1), enter the value of the PC (2), and press Enter. Next locate the command pointed by the PC register (3) (displayed both in C and Assembly language).
'Probing the Contents of the Program Counter'

Fig. 16 Probing the Contents of the Program Counter

  1. Similarly, examine the instruction pointed by the Link register. Since the command that caused the fault is part of the trigger_hardfault() function, the LR should point to the instruction that will be executed upon the return of this function call.
'Probing the Contents of the Link Register'

Fig. 17 Probing the Contents of the Link Register