A Little About Debugging on CH32V00x and SDI Print
I usually use UART TX logging to debug my code. However, sometimes this method is unavailable: the hardware UART might be occupied by other peripherals, or the TX line simply wasn’t routed during the PCB layout because it seemed unnecessary. But you still need a way to debug the firmware.
For the CH32V series microcontrollers, there is an excellent alternative to UART - the SDI Print (Serial Debug Interface) mechanism. It allows you to send log messages over the single programming wire and read them on your PC.
At the hardware level, two 32-bit registers are used for data transmission:
#define DEBUG_DATA0_ADDRESS ((volatile uint32_t*)0xE00000F4)
#define DEBUG_DATA1_ADDRESS ((volatile uint32_t*)0xE00000F8)
The way you interact with these registers differs drastically depending on the framework you choose: the official one from WCH (noneos-sdk) or the minimalist ch32fun.
In this guide, I will describe how to set up logging specifically for the official WCH framework.
In the application code, instead of the standard UART initialization,
you need to enable SDI Print by calling the
SDI_Printf_Enable() function:
#include <ch32v00x.h>
#include <debug.h>
void NMI_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void HardFault_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
int main(void) {
SystemCoreClockUpdate();
Delay_Init();
SDI_Printf_Enable();
uint16_t counter = 0;
while (1) {
printf("Hello from printf! %d\n", counter);
counter++;
Delay_Ms(1000);
}
return 0;
}
void NMI_Handler(void) {}
void HardFault_Handler(void)
{
while (1)
{
}
}
But merely calling the function in your code isn’t enough. For the
standard printf to start redirecting data to the debug
registers instead of UART, you need to properly configure the build
using preprocessor macros:
DEBUG=0 — disables standard logging via UART.
SDI_PRINT=SDI_PR_OPEN — enables the data “packing” algorithm into the DATA0 and DATA1 registers (you can peek under the hood to see how this works in the framework’s
debug.cfile, specifically in the_write()function).
Since I use PlatformIO, the most convenient way to set these flags is
directly in the platformio.ini configuration file:
...
build_flags = -DDEBUG=0 -DSDI_PRINT=SDI_PR_OPEN
After that, compile and flash the firmware to the microcontroller.
I would try running minichlink -T, which is supposed to
launch a terminal and display the output. However, with firmware that
uses the official WCH framework, this didn’t work, and the terminal
remained blank. So, we put minichlink aside — this tool is primarily
tailored for interacting with firmwares built on ch32fun.
Instead, we will use a another utility — wlink. It can
be installed using the cargo package manager (if you have Rust
installed):
cargo install wlink
After experimenting with wlink, I found the necessary
command to activate our debugging:
$ wlink sdi-print enable
19:15:14 [INFO] Connected to WCH-Link v2.21(v41) (WCH-LinkE-CH32V305)
19:15:14 [INFO] Attached chip: CH32V003 [CH32V003A4M6] (ChipID: 0x00320510)
19:15:14 [INFO] Enabling SDI print
19:15:14 [INFO] Now you can connect to the WCH-Link serial port
And here is where it gets really interesting! The WCH-LinkE programmer has a built-in USB-UART adapter, which I use quite often. The executed command instructs the programmer to intercept data from the SDI line and redirect it at the hardware level to its own virtual COM port.
Now, if we open /dev/ttyACM0 (or the corresponding port
on your OS) in the PlatformIO Serial Monitor or any other terminal, we
will finally see our output:
--- Terminal on /dev/ttyACM0 | 9600 8-N-1
--- Available filters and text transformations: debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at https://bit.ly/pio-monitor-filters
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H
Hello from printf! 0
Hello from printf! 1
Hello from printf! 2
Hello from printf! 3
Important detail: You need to switch the WCH-LinkE
into SDI Print mode (using the wlink sdi-print enable
command) every time after flashing the microcontroller or reconnecting
the USB, as the programmer resets this state.
P.S. An important note on saving memory
Avoid using printf unless absolutely necessary — it’s an
extremely “heavy” function that eats up quite a bit of precious Flash
memory (and the CH32V003 only has 16 KB of it). Since it drags along the
entire format parsing mechanism under the hood (%d, %s, %x, etc.),
calling it will cost you a few kilobytes.
Here is a clear example. If you use the standard printf:
char *msg = "Hello from printf!\n";
uint8_t msg_len = strlen(msg);
printf(msg);
The compiler will show the following memory usage:
Flash: [=== ] 27.6% (used 4520 bytes from 16384 bytes)
But if you only need to output plain text messages for debugging
without variable formatting, it is better to call the low-level
_write function directly:
char *msg = "Hello from printf!\n";
uint8_t msg_len = strlen(msg);
_write(0, msg, msg_len);
In this case, resource consumption drops significantly:
Flash: [= ] 8.9% (used 1456 bytes from 16384 bytes)
A difference of 3 kilobytes for a microcontroller of this class is a massive amount of space.
Comments
Post a Comment