Дебаг на CH32V00x та SDI Print
Зазвичай для відлагодження коду я використовую логування через UART TX. Проте іноді цей спосіб недоступний: апаратний UART може бути зайнятий іншою периферією, або ж під час розводки плати лінію TX просто не вивели, бо вона здавалася непотрібною. Але відлагоджувати прошивку якось треба.
Для мікроконтролерів серії CH32V існує чудова альтернатива UART - механізм SDI Print (Serial Debug Interface). Він дозволяє відправляти лог-повідомлення через єдиний дріт програмування та читати їх на ПК.
На апаратному рівні для передачі даних використовуються два 32-бітні регістри:
#define DEBUG_DATA0_ADDRESS ((volatile uint32_t*)0xE00000F4)
#define DEBUG_DATA1_ADDRESS ((volatile uint32_t*)0xE00000F8)
Спосіб взаємодії з цими регістрами кардинально відрізняється залежно від обраного фреймворку: офіційного від WCH (noneos-sdk) чи мінімалістичного ch32fun.
У цій інструкції я опишу, як налаштувати логування саме для офіційного фреймворку від WCH.
У коді програми, замість стандартної ініціалізації UART, потрібно
увімкнути SDI Print викликом функції
SDI_Printf_Enable():
#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)
{
}
}
Але просто викликати функцію в коді недостатньо. Щоб стандартний
printf почав перенаправляти дані у відлагоджувальні
регістри замість UART, потрібно правильно сконфігурувати збірку за
допомогою макросів препроцесора:
DEBUG=0 — відключає стандартне логування через UART.
SDI_PRINT=SDI_PR_OPEN — вмикає алгоритм “пакування” даних у регістри DATA0 та DATA1 (детальніше про те, як це працює “під капотом”, можна підглянути у файлі debug.c фреймворку, функція _write() ).
Оскільки я використовую PlatformIO, найзручніше задати ці прапорці
безпосередньо у конфігураційному файлі platformio.ini:
...
build_flags = -DDEBUG=0 -DSDI_PRINT=SDI_PR_OPEN
Після цього компілюємо та заливаємо прошивку на мікроконтролер.
Звичанйо я одразу після цього спробував minichlink -T
він мав би запустити термінал та показати вивід, але з прошивкою що
використовує офіційний wch фремворк це не спрацювало, і термінал
залишається порожнім. Отже відкладаємо minichlink вбік — цей інструмент
заточений переважно під взаємодію з прошивками на ch32fun.
Натомість ми використаємо сучаснішу утиліту — wlink. Її можна легко встановити за допомогою пакетного менеджера cargo (якщо у вас встановлений Rust):
cargo install wlink
Поекспериментувавши з wlink, я знайшов потрібну команду
для активації нашого відлагодження:
$ 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
І ось тут відбувається найцікавіше! Програматор WCH-LinkE має вбудований USB-UART перехідник, яким я часто користуюся. Виконана команда дає вказівку програматору перехоплювати дані зі шлейфа SDI та апаратно перенаправляти їх у свій віртуальний COM-порт.
Тепер, якщо відкрити /dev/ttyACM0 (або відповідний порт
у вашій ОС) у Serial Monitor від PlatformIO чи будь-якому іншому
терміналі, ми нарешті побачимо наш вивід:
--- 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
Важливий нюанс: Перемикати WCH-LinkE в режим SDI Print (командою wlink sdi-print enable) потрібно кожного разу після прошивки мікроконтролера або перепідключення USB, оскільки програматор скидає цей стан.
P.S. Важливе зауваження щодо економії пам’яті
Не використовуйте printf без нагальної потреби — це
надзвичайно “важка” функція, яка з’їдає доволі багато дорогоцінної
Flash-пам’яті (а у CH32V003 її всього 16 КБ). Оскільки вона тягне за
собою під капотом весь механізм розбору форматів (%d, %s, %x тощо), її
виклик обійдеться вам у кілька кілобайт.
Ось наочний приклад. Якщо використати стандартний printf:
char *msg = "Hello from printf!\n";
uint8_t msg_len = strlen(msg);
printf(msg);
Компілятор покаже таке використання пам’яті:
Flash: [=== ] 27.6% (used 4520 bytes from 16384 bytes)
А якщо для відлагодження вам потрібно виводити лише звичайні текстові повідомлення без форматування змінних, краще викликати низькорівневу функцію _write безпосередньо:
char *msg = "Hello from printf!\n";
uint8_t msg_len = strlen(msg);
_write(0, msg, msg_len);
У цьому випадку споживання ресурсів значно впаде:
Flash: [= ] 8.9% (used 1456 bytes from 16384 bytes)
Різниця у 3 кілобайти для мікроконтролера такого класу — це величезний обсяг.
Коментарі
Дописати коментар