This article describes how to create a debug interface for various microcontrollers. At the moment, you can debug your program through jtag, having a special debugger (JLINK, FT232 JTAG, STLINK, etc.). My idea is to debug the program through any interface of the microcontroller, even emulated, including remote interfaces such as GPRS, Wifi, Lora, etc. A typical debugging scheme looks like this.

My debugging scheme looks like this.

A similar scheme is used to debug Xtensa l106 microcontrollers (ESP8266), where debugging is performed via the uart interface. My idea is more extensive. I came up with how to implement this scheme not only on Xtensa, Cortex-M microcontrollers that have hardware support, but also microcontrollers that do not have such support, for example, AVR. The general algorithm looks like this, we flash the microcontroller and debug our program through the interface that is implemented in the firmware. It is also possible to reprogram the microcontroller through the same interface, in my opinion this makes sense if the interface is remote, if it is wired interface, better program  through the programmer and you still need it at least once. The source code of the interface and the debugger core itself are compiled with the user program.

The main idea is to create a 2-threaded application, each thread has its own stack, I refused to use the heap in the debugger stream (because this creates additional problems associated with the intersection of the end of the heap pointer and the current pointer to the stack from the options of constantly switching to user thread when allocating memory or additionally writing your own heap for debugger thread, and most importantly, when problems like stack overflow arise, it will not be known which thread did this, from my own experience I tested this option in practice, it’s better not to use the heap when writing the debug kernel) threads switch by timer. One thread for debugging, another main program.

 

To implement breakpoints (Breakpoints), you can use 2 options: 1 hardware breakpoints, 2 emulator instructions for this microcontroller. Fortunately, You don't need to emulate all instructions, but only those instructions that use Program Counter must be emulated, and the rest of the instructions can simply be moved to another place and executed there. Breakpoints can be implemented by using break instructions, if you can configure the microcontroller to generate an Exception when processor trying to execute the break instruction(Cortex-M, Xtensa). I use instructions that implement the while (1); logic, so the processor stops at this instruction until the timer fires. Then, the instruction previously copied, during the installation of Breakpoint, is emulated by the user command as described above.  

 

 

To implement instruction "single step", there are 3 options: 1 to install Breakpoint on the next instruction in hardware, 2 to delay the interruptions during which only one instruction can be executed, 3 to emulate or move the instruction to another place and execute there. The second option works perfectly on AVR microcontrollers, there is an ideal situation, the minimum delay between interrupts is 1 clock cycle, any instruction is executed to the end regardless of how many clock cycles are needed. Option 3 is suitable for Cortex-M microcontrollers; Option 2 is not suitable because all instructions are executed at different times, if It wasn’t enough processor's clocks to execute instruction, it is interrupted and executed again, or instructions such as push, pop can interrupt their execution in the middle, but this problem can be dispensed with by simply increasing the clock between interrupting the timer until the processor jumps to the next instruction, but there are instructions that can be executed with others at the same time, precisely because of this, option 2 is not suitable.

When executing an instruction from another place between timer interruptions, there are 2 options: set the time between interruptions equal to the execution time of the instruction, 2 add after the instruction another instruction that implement the while (1); logic.

 
Implementation of RUN, PAUSE commands. There are 3 options: 1 switch to the user stream and wait for an interrupt from the debugging interface, it is not always possible, 2 switch to the user stream and call interrupts at regular intervals by the timer, 3 constantly send the RUN command from GDB Server, to execute user code for a specified period of time. Option 1 is good for everyone, if possible; Option 2 is good for remote interfaces; Option 3 is good for wired interfaces (especially for emulated ones).


 

The implementation of the firmware loader, my idea is that the controller sends commands for flashing flash memory that is not occupied by the program, each command contains other commands for flashing the program memory. So, first we send everything we want to write to flash, then we send a command to the controller to execute the commands written to flash memory. To reduce the size of the data being sent, we send only the difference between the previous firmware and the new one. To minimize this difference, I decided, through a linker script, separate part of the user program from the part of the same program that implements the debugging interface. In the map file shown in the image, the section name may be different. And, so to make all things work, I wrote a function to initialize the user data section (copying all variables from flash to RAM) and a function to launch the user global constructors array (for the C ++ language).

  
To rename sections .text, .data, .bss, .contructors array, into .user text,. user data,. user bss, .user contractors array I wrote a program that renames them in all object files (extension .o) located in a specific folder, to call this program between the compilation of object files and the link, I replace the .exe compiler program with mine, which then calls compiler program and section replacement program.


Site language: