Debugging ARM Cortex-M microcontrollers by UART

In this article I will tell you how to use debug and breakpoint registers in microcontrollers built on ARM Cortex-M cores

Introduction


Did you know that ARM Cortex-M cores in microcontrollers can debug themselves?
It turns out they can.

Reading the Technical Reference Manual on the core, the Cortex M3 found that it had a DebugMon interrupt. Next, I analyzed all the registers associated with it. As a result, I found out that MK can fall into this interrupt if the PC register and one of the FP_COMP registers are equal.
This means that we can set breakpoints in debugged firmware. You can also force a DebugMon interrupt by setting the MON_PEND bit of the DEMCR register to 1.

Theory check


Since these registers are present in the entire family of ARM Cortex-M cores, we take the first debugging board that comes across. It turned out to be stm32f723e-disco for me. In order not to waste time writing the initialization code for the peripherals, we use CubeMX
From the peripherals we only need UART6 connected to the ST-Link and the LED on the board:

Settings in CubeMX
image

We generate the project and immediately open its IDE.

In order not to get confused in the registers associated with debugging, we immediately introduce the definitions for them in the code:

Register Definitions
#define DHCSR (*(uint32_t*)0xE000EDF0)
#define DCRSR (*(uint32_t*)0xE000EDF4)
#define DCRDR (*(uint32_t*)0xE000EDF8)
#define DEMCR (*(uint32_t*)0xE000EDFC)

#define NUMOFBKPTS 8

typedef struct {
  uint32_t FP_CTRL;
  uint32_t FP_REMAP;
  uint32_t FP_COMP[NUMOFBKPTS];
} fp_t;

#define FP ((fp_t*)0xE0002000)


To verify that our project works, in principle, write a code that flashes with an LED:

LED flashing
  while (1)
  {
    for (int i=0;i<1000000;i++)
      asm("nop");
    
    HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_7);
  }


The LED blinked successfully, which means the project is successfully compiled and launched.

Add the line to this loop:

asm("BKPT #0");

This assembler insert is a software breakpoint. Now, when debugging via ST-Link, the program will always stop at this line. We exit Debug in the IDE and the LED starts blinking as before.

But what did it give us?

And the fact that we can catch a breakpoint by interrupting DebugMon.

We write its handler:

DebugMon handler
void DebugMon_Handler(void)
{
  while (1)
  {
    if ((USART6->ISR & USART_ISR_TXE) != 0U)
    {
      USART6->TDR = '!';
    }
  }
}


We compile, flash and nothing has changed. To enable the DebugMon interrupt, you need to raise the MON_EN bit in the DEMCR register.

Now, when debugging via ST-Link, the program will stop as before on this line. As soon as we exit debug mode, exclamation marks will appear in the terminal:

Terminal output
image

Next, check the same with the FP_COMP register.

We find the address of any instruction in the main function loop using the IDE and activate the breakpoint:

FP->FP_COMP[0] = 0x080017CC | 1; //     = 0x080017CC

When the hardware debugger is disabled, the microcontroller also gets into the DebugMon interrupt.

How to use it


Using the above registers and DebugMon interrupts, it is possible to debug a microcontroller without a SWD / JTAG debugger. It also becomes possible to debug the firmware of a device with which there is a connection, but access to the SWD pins is difficult.

To be continued…

All Articles