This article is about my first open source project "repl" (link to repository below). The idea of โโthis project is to allow the microcontroller programmer to debug the program in the microcontroller through any of its interfaces, while debugging does not differ much from debugging through the jtag interface. It was possible to stop the program, set breakpoints, view registers, memory, by instructive debugging of the program.
The first thing that comes to mind is to create a 2x application, one of the threads is responsible for the debugging interface, the other for the user program, which I did. Switching between threads is performed on a timer, each thread has its own stack. I decided not to use a bunch to write the debugging interface. they must be used 2 different, or when working with a heap, constantly switch to one thread.
The first idea for implementation for instructional debugging was to reduce the time between timer interrupts just enough so that only 1 instruction could be executed. This option showed its ideal work on the Atmega328p microcontroller, the fact is that the minimum time between interrupts for Atmega is 1 processor clock, any instruction, regardless of the number of clock cycles required for its execution, will always end if its execution has begun.
Unfortunately, when I switched to stm32, this option did not work, because the Cortex-M core can interrupt the execution of an instruction in the middle, and then, upon returning from the interrupt, start executing it again. Then I decided well, no problems, I will just increase the number of clock cycles between interrupts until the instruction is executed, then I stumbled upon another problem, some instructions are executed only with the instruction following them or not executed at all, unfortunately this problem cannot be solved, so I decided to radically change the approach.
I decided to emulate these instructions. At first glance, the idea of โโwriting a Cortex M core emulator, and even one that would fit into the microcontroller's memory, looks fantastic. But I realized that I did not need to emulate all the instructions, but only those of them that are associated with the program counter, there were not so many of them. All the rest I can simply move to another memory location and execute there, and so that only one instruction is executed, add the jump instruction โbโ after it.
And then the turn of breakpoints came, for their implementation I decided to use an instruction that implements the while (1); logic. We replace the instruction located at the address where we want to put the breakpoint and wait for the timer interrupt. I understand that something like an instruction that would throw an exception would be better, but I wanted to make a universal version. When executed, we replace this instruction back. A good option is to run the program in the RAM of the microcontroller, otherwise the flash memory of the microcontroller will not last long. But by this point I had already finished writing an instruction emulator for stm32 and decided why not write the same one for Atmega328 and wrote it. Now you do not need to replace the instructions back; they can be emulated.
In order to make all this friends with the runtime environment, I first wanted to write my own gdb client. Unfortunately, it supports two interfaces for working with ide. It is up to her to decide which ide to use. Implementing both of them (the first seemed to me quite simple, the second not very), plus I would have to combine the sources with the firmware, which seemed to me not a very good idea. So I decided to write my own gdbserver, luckily there was only one protocol and it was pretty simple.
My first interface, which I decided to implement, was GSM. As a transceiver, I decided to use SIM800, as a server, a server with php support. The main idea was that after the arrival of a post request from the microcontroller, keep the connection to the microcontroller for 30 seconds and contact the database every 100 ms, if the data appeared to send them as a response to the request and wait for the next request from the microcontroller.
The first connection of the gdb client to the server showed that there were too many requests from the gdb client to the server with the pause or step command. Therefore, it was decided to combine all these requests into one large one for the microcontroller, for this I understood the logic of these requests and learned how to predict them. Now these commands were not executed so quickly, I would like faster, but bearable.
The next interface was usb, for the Atmega328 microcontroller I decided to use the V-usb library. To work with usb, I rewrote the run command logic. Now the microcontroller, after this command, did not start the program waiting for the pause command, it started it for 1 second, then a new run command was sent to it, etc. This logic is necessary because I disable the interface while the program is running. To save myself the hassle of writing a driver, I decided to use the standard hid driver. As communication features hid get feature report, hid set feature report.
As for the flashing of microcontrollers, I decided that this is superfluous for the usb interface, so for the first firmware you still need a programmer. But for the GSM interface it is the most. Usually, a separate program is written for this purpose, but I decided to do it differently, for the place of writing a separate program, I decided to load the program completely into the flash memory of the microcontroller, then after the download is complete, copy this program to the beginning of the memory. Then I thought why should I send the entire program, I can only send the difference between the current and the previous binary file of the firmware.
To minimize this difference, I decided to rename the .text, .data, .bss, .contructors array sections for a part of the user program (the generalized name is different for different microcontrollers) and place them in memory right after the main program.
I also had to write my own functions to initialize these sections. Now, in most cases, a small program change is equal to a small binary change equal to a small amount of data being transferred. As a result, the microcontroller is often flashed faster than the RUN, STEP, PAUSE commands work.
And finally, a video of work:
Stm32 debugging via usb interface.
Stm32 debugging via gsm interface.
Atmega328 debugging via usb interface.
Atmega328 debugging via gsm interface.
Git repository