Debug hard faults in ARM Cortex-M0 based SoCs

Article By : Shashikant Joshi, Hanumanthaiah Shruti

As the complexity of programmable system-on-chip architectures and their MCU increases, so do the issues that can occur at each stage of design.

Programmable system-on-chip (PSoC) architectures integrate a wide range of capabilities, including MCU cores like the Cortex-M0, programmable analog blocks (PAB), programmable digital blocks (PDB), programmable interconnect and routing, a wide range of interfaces and peripherals, and advanced capabilities such as capacitive touch sensing. These architectures over many advantages over traditional microcontrollers and can substantially reduce design time and system bill of materials (BOM) cost.

As the complexity of programmable system-on-chip architectures and their MCU increases, so do the issues that can occur at each stage of design. One common issue developers face in Cortex-M0-based embedded systems is the hard fault. In some cases, we might get lucky and be able to quickly locate the source of the hard fault. However, most of the time chasing down a hard fault can be very time consuming. In this article, we will discuss some common errors programmers make and how to debug the hard fault caused by these errors.

Hard faults

A hard fault is an exception that occurs because of an error during normal or exception processing. As per the Cortex-M0 Devices Generic User Guide (revision r0p0), the following sources can cause a hard fault:

  • execution of an SVC instruction at a priority equal or higher than SVCall
  • execution of a BKPT instruction without a debugger attached
  • system-generated bus error on a load or store
  • system-generated bus error on a vector fetch
  • execution of an instruction from a location for which the system generates a bus fault
  • execution of an instruction when not in Thumb-State as a result of the T-bit being previously cleared to 0
  • execution of an undefined instruction
  • attempted load or store to an unaligned address
  • execution of an instruction from an XN memory address

The most common user-created causes for hard fault are:

  • execution of an undefined instruction
  • attempted load or store to an unaligned address
  • execution of an instruction from an XN memory address

Detecting the hard fault

When your system is hung up, the first step is to detect the cause for the hang up. To detect the cause for system hang up, first execute your program in debugging mode and allow the system to run until the system hangs up again, then halt the debugger. There are two ways to determine whether the hang up is due to the hard fault. The first is to watch the Program Counter (PC) register. If this is a hard fault, the PC register will indicate operation in the hard fault handler. The second method is to watch the Interrupt Program Status Register (IPSR). A value of 0x3 indicates a hard fault. Here is the IPSR bit assignment in detail (as per Cortex -M0 Devices Generic User Guide):

[Cypress hardfault 01 (cr)]
__Figure 1:__ *IPSR Register Definition (Source: ARM)*

The IPSR register is a part of the ARM Cortex-M0’s Program Status Register (PSR). The PSR combines three 32-bit registers–APSR, IPSR and EPSR–as shown in Figure 2. The IPSR register indicates a hard fault occurrence. PSR is referred to as xPSR in some IDEs like PSoC Creator.

[Cypress hardfault 02 (cr)]
__Figure 2:__ *PSR Register Definition (Source: ARM)*

Execution of undefined instruction

As the name suggests, this type of hard fault is caused when the core attempts to execute an undefined instruction. This can occur when either the instruction fetched by the PC register is corrupted and takes an undefined instruction value or the PC register itself gets corrupted because of stack corruption.

The stack can be corrupted when a pointer is passed to a function and the function writes beyond the allowed size of the pointer. This may end up corrupting the stack. Here is one such illustration:

include <project.h>
/* Application level function /
uint8 i2cRead(void);
/
HAL level function */
void halI2cReadData(uint8 *buffer);
int main()
{
volatile uint8 data = 0;

for(;;)
{
data = i2cRead();
CyDelay(1);
}
}
#define I2C_BUFFER_SZ 64
uint8 i2cRead(void)
{
uint8 i2cBuffer;

  halI2cReadData(&i2cBuffer);

  return(i2cBuffer);

}
void halI2cReadData(uint8 buffer)
{
uint8 bufIndex = 0;
/
Read I2C data from hardware /
/
Fill the buffer */
for(bufIndex = 0; bufIndex < I2C_BUFFER_SZ; bufIndex++)
{
buffer[bufIndex] = bufIndex;
}
}

In the above example, buffer is defined as an array of 1 byte. However the halI2cReadData function tries to fill 64 bytes. This operation will corrupt the stack. The stack contents at any given time are, from top to bottom, the local variable of the current function, current stack frame context, and calling stack frame context. The hard fault occurs when the CPU writes into the PC register of the calling stack frame and the execution return is attempted.

 
Next: Avoiding hard faults in ARM Cortex-M0 based SoCs »

Leave a comment