Common debugging methods
There are some common methods to debug microcontrollers. We may hook up a debugger, toggle GPIO in test code, print debugging info to a serial port, etc. Here is a table comparing the common methods:
| Method | Pros | Cons |
|---|---|---|
| Hardware Debugger | Full control over code excution and data | Need to pause the code, require hardware support |
| Print on serial | Easy to use and get human-readable info | Not very fast, may need interrupts for a lot of data |
| Toggle GPIOs | Minimal affect to the original code | May need logic analyzer, difficult to send complicated data |
Using high speed UART on any GPIO
The proposed method is a combination between GPIO toggling and serial printing. Instead of using a hardware UART module, we can use the assembly code to generate the UART signal with precise timing on any GPIO. We can print certain characters in certain places of the code, to know when and how the code is executed, or we can print the value of variables.
The method does not need to use any peripheral or interrupts. And it only takes a few microseconds to run each time. So the impact on the original codes is minimal. However, this method will need some special hardware to get the data from the microcontroller.
Here is a code sample on Arduino Uno (ATmega328P). The baud rate is 5333333 with a 16M CPU clock, and it takes less than 3us to send a byte.
And the code to generate the UART signal.
void tx_on_PB5(char c) { //this approach takes 3 clks to send a bit, that is 5333333bit/s for a 16M Arduino //use __tmp_reg__ to store SREG to recover interrupt status //use __zero_reg__ as temp register, clear at the end of code
asm volatile("IN __tmp_reg__,__SREG__ \n\t" //backup SREG "CLI \n\t"
"IN __zero_reg__,%[ADDR_PORTB] \n\t"
"CLT \n\t" //1CLK start bit "BLD __zero_reg__,5 \n\t" //1CLK "OUT %[ADDR_PORTB],__zero_reg__ \n\t" //1CLK
"BST %[OUT_CHAR],0 \n\t" //1CLK bit 0 "BLD __zero_reg__,5 \n\t" //1CLK "OUT %[ADDR_PORTB],__zero_reg__ \n\t" //1CLK "BST %[OUT_CHAR],1 \n\t" //1CLK bit 1 "BLD __zero_reg__,5 \n\t" //1CLK "OUT %[ADDR_PORTB],__zero_reg__ \n\t" //1CLK "BST %[OUT_CHAR],2 \n\t" //1CLK bit 2 "BLD __zero_reg__,5 \n\t" //1CLK "OUT %[ADDR_PORTB],__zero_reg__ \n\t" //1CLK "BST %[OUT_CHAR],3 \n\t" //1CLK bit 3 "BLD __zero_reg__,5 \n\t" //1CLK "OUT %[ADDR_PORTB],__zero_reg__ \n\t" //1CLK "BST %[OUT_CHAR],4 \n\t" //1CLK bit 4 "BLD __zero_reg__,5 \n\t" //1CLK "OUT %[ADDR_PORTB],__zero_reg__ \n\t" //1CLK "BST %[OUT_CHAR],5 \n\t" //1CLK bit 5 "BLD __zero_reg__,5 \n\t" //1CLK "OUT %[ADDR_PORTB],__zero_reg__ \n\t" //1CLK "BST %[OUT_CHAR],6 \n\t" //1CLK bit 6 "BLD __zero_reg__,5 \n\t" //1CLK "OUT %[ADDR_PORTB],__zero_reg__ \n\t" //1CLK "BST %[OUT_CHAR],7 \n\t" //1CLK bit 7 "BLD __zero_reg__,5 \n\t" //1CLK "OUT %[ADDR_PORTB],__zero_reg__ \n\t" //1CLK
"SET \n\t" //1CLK stop bit "BLD __zero_reg__,5 \n\t" //1CLK "OUT %[ADDR_PORTB],__zero_reg__ \n\t" //1CLK
"CLR __zero_reg__ \n\t" "OUT __SREG__,__tmp_reg__ \n\t" //restore SREG
::[OUT_CHAR]"r" (c), [ADDR_PORTB]"I" (_SFR_IO_ADDR(PORTB)));
}
In short, the assembly code checks each bit of the outgoing byte and then sets the GPIO high or low according to each bit. In the AVR architecture, this is done by using the Bit Copy Storage (T Flag). Each bit is copied from the outgoing byte to the GPIO bit in PORTB, and each bit takes exactly 3 clks. The whole process needs to be done without interruption. No branching, loop, or jump is used, so the code is simpler and faster.
In modern architecture, we need to avoid the flash memory wait states because the timing will be unpredictable. Also, the GPIO operation should be as fast as possible. On Arduino M0 (Arm Cortex-M0+), the function needs to be put in SRAM, and the GPIO should be accessed via IOBUS instead of APB.
Code for several platforms can be found in the uartTxDemoCode folder.
Read the output from the microcontroller
We can use a logic analyzer or a high-speed UART receiver to read the output from the microcontroller. And the 2 methods...
Read more »
deqing
Ai-Thinker
Bruce Land
Christoph