韦东山-单片机开发过程中的调试绝招---youkeit.xyz/4562/
单片机调试黑科技:韦东山教你用底层技术破解硬件难题
一、裸机调试的终极武器:JTAG/SWD深度应用
1.1 自制低成本调试器
// STM32 SWD协议底层实现 (基于CH552G单片机)
#include <ch552.h>
#include <stdint.h>
#define SWDIO_PIN P1_4
#define SWCLK_PIN P1_5
#define NRST_PIN P3_0
void swd_delay() {
_nop_(); _nop_(); _nop_(); _nop_();
}
void swd_write_bit(uint8_t bit) {
SWDIO_PIN = bit;
swd_delay();
SWCLK_PIN = 1;
swd_delay();
SWCLK_PIN = 0;
}
uint8_t swd_read_bit() {
SWCLK_PIN = 1;
swd_delay();
uint8_t val = SWDIO_PIN;
SWCLK_PIN = 0;
swd_delay();
return val;
}
void swd_line_reset() {
for(int i=0; i<55; i++) {
swd_write_bit(1);
}
}
uint32_t swd_read_ap(uint8_t addr) {
uint32_t ack, data = 0;
// 发送请求包
swd_write_bit(0); // Start
swd_write_bit(1); // APnDP
swd_write_bit(0); // Read
swd_write_bit(addr>>2 & 1);
swd_write_bit(addr>>3 & 1);
swd_write_bit(1); // Parity
swd_write_bit(0); // Stop
swd_write_bit(1); // Park
// 读取ACK
SWDIO_PIN = 1; // 释放总线
ack |= swd_read_bit() << 0;
ack |= swd_read_bit() << 1;
ack |= swd_read_bit() << 2;
if(ack == 0x1) { // OK响应
for(int i=0; i<32; i++) {
data |= swd_read_bit() << i;
}
swd_read_bit(); // 读取奇偶校验位
}
swd_write_bit(0); // 结束
return data;
}
1.2 内存暴力破解技巧
// STM32 Flash读写底层操作 (绕过保护机制)
#define FLASH_KEY1 0x45670123
#define FLASH_KEY2 0xCDEF89AB
void flash_unlock() {
// 解除写保护
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
// 等待解锁完成
while(FLASH->SR & FLASH_SR_BSY);
}
void flash_erase_page(uint32_t addr) {
FLASH->CR |= FLASH_CR_PER; // 页擦除模式
FLASH->AR = addr; // 设置擦除地址
FLASH->CR |= FLASH_CR_STRT; // 开始擦除
while(FLASH->SR & FLASH_SR_BSY);
FLASH->CR &= ~FLASH_CR_PER;
}
void flash_program_halfword(uint32_t addr, uint16_t data) {
FLASH->CR |= FLASH_CR_PG; // 编程模式
*(volatile uint16_t*)addr = data;
while(FLASH->SR & FLASH_SR_BSY);
FLASH->CR &= ~FLASH_CR_PG;
}
// 暴力读取整个Flash内容
void dump_flash(uint32_t start, uint32_t end, uint8_t *buf) {
for(uint32_t addr = start; addr < end; addr += 2) {
uint16_t val = *(volatile uint16_t*)addr;
buf[(addr-start)] = val & 0xFF;
buf[(addr-start)+1] = (val >> 8) & 0xFF;
}
}
二、示波器的高级玩法
2.1 用GPIO模拟逻辑分析仪
// STM32高速GPIO采样代码 (72MHz系统时钟下)
#define SAMPLE_RATE 10 // MHz
#define BUFFER_SIZE 1024
volatile uint32_t timestamp[BUFFER_SIZE];
volatile uint8_t state[BUFFER_SIZE];
volatile uint16_t idx = 0;
void setup_gpio_sampler() {
// 配置被监测的GPIO (PA0-PA7)
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
GPIOA->CRL = 0x88888888; // 浮空输入模式
// 配置定时器2作为采样时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
TIM2->PSC = SystemCoreClock/(SAMPLE_RATE*1000000) - 1;
TIM2->ARR = 1;
TIM2->DIER |= TIM_DIER_UIE;
NVIC_EnableIRQ(TIM2_IRQn);
}
void TIM2_IRQHandler() {
if(TIM2->SR & TIM_SR_UIF) {
TIM2->SR &= ~TIM_SR_UIF;
if(idx < BUFFER_SIZE) {
timestamp[idx] = DWT->CYCCNT; // 使用CPU周期计数器
state[idx] = GPIOA->IDR & 0xFF;
idx++;
}
}
}
void start_sampling() {
idx = 0;
DWT->CYCCNT = 0; // 重置周期计数器
TIM2->CR1 |= TIM_CR1_CEN;
}
void stop_sampling() {
TIM2->CR1 &= ~TIM_CR1_CEN;
}
// 导出数据到串口
void export_data(UART_HandleTypeDef *huart) {
for(int i=0; i<idx; i++) {
char buf[64];
int len = sprintf(buf, "%u,%02X\n", timestamp[i], state[i]);
HAL_UART_Transmit(huart, (uint8_t*)buf, len, HAL_MAX_DELAY);
}
}
2.2 信号协议逆向工程
# 用Python分析I2C波形数据 (示波器导出的CSV)
import pandas as pd
import numpy as np
def decode_i2c(csv_file):
df = pd.read_csv(csv_file)
timestamps = df['Time'].values
scl = df['SCL'].values
sda = df['SDA'].values
# 寻找起始条件
start_cond = np.where((scl[:-1] == 1) & (scl[1:] == 1) &
(sda[:-1] == 1) & (sda[1:] == 0))[0]
packets = []
for start in start_cond:
# 提取时钟上升沿
rising_edges = np.where((scl[start:-1] == 0) & (scl[start+1:] == 1))[0] + start
if len(rising_edges) < 9: # 至少1字节+ACK
continue
# 解析地址字节
addr_byte = 0
for i in range(8):
addr_byte |= (sda[rising_edges[i]] << (7-i))
rw_bit = addr_byte & 0x1
addr = addr_byte >> 1
# 检查ACK
ack = sda[rising_edges[8]] == 0
# 解析数据字节
data_bytes = []
ptr = 9
while ptr + 8 < len(rising_edges):
byte = 0
for i in range(8):
byte |= (sda[rising_edges[ptr+i]] << (7-i))
data_bytes.append(byte)
ptr += 9 # 跳过ACK
packets.append({
'address': addr,
'direction': 'Read' if rw_bit else 'Write',
'ack': ack,
'data': bytes(data_bytes).hex(),
'start_time': timestamps[start],
'duration': timestamps[rising_edges[-1]] - timestamps[start]
})
return packets
三、寄存器级调试技巧
3.1 死机现场还原术
// ARM Cortex-M 异常诊断工具
void HardFault_Handler(void) {
__asm volatile(
"tst lr, #4\n"
"ite eq\n"
"mrseq r0, msp\n"
"mrsne r0, psp\n"
"mov r1, %0\n"
"b HardFault_Diagnose\n"
: : "i" (&hf_dump) : "r0", "r1"
);
while(1);
}
typedef struct {
uint32_t r0, r1, r2, r3;
uint32_t r12, lr, pc, psr;
} HFDump;
HFDump hf_dump;
void HardFault_Diagnose(HFDump *dump) {
// 通过SWD发送故障信息
uint32_t cfsr = SCB->CFSR;
uint32_t hfsr = SCB->HFSR;
uint32_t mmfar = SCB->MMFAR;
uint32_t bfar = SCB->BFAR;
// 将寄存器值编码为特殊波形
generate_debug_signal(dump->pc);
// 死循环保持现场
while(1) {
__asm("nop");
}
}
// 通过PWM生成可被示波器捕获的调试信号
void generate_debug_signal(uint32_t pc) {
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
GPIOB->CRL = (GPIOB->CRL & 0xF0FFFFFF) | 0x0B000000; // PB6 as AF PP
TIM3->ARR = 100; // 10kHz carrier
TIM3->PSC = 71; // 1MHz timer clock
TIM3->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1; // PWM mode 1
TIM3->CCER = TIM_CCER_CC1E;
TIM3->CR1 = TIM_CR1_CEN;
// 使用脉冲宽度编码PC值
for(int i=0; i<32; i++) {
int bit = (pc >> (31-i)) & 1;
TIM3->CCR1 = bit ? 75 : 25; // 75% or 25% duty
// 每个bit持续100us
delay_us(100);
}
}
3.2 外设寄存器热修改
// STM32 USART寄存器热修复代码
void usart_hotfix(USART_TypeDef *usart) {
// 保存当前状态
uint32_t cr1 = usart->CR1;
uint32_t cr2 = usart->CR2;
uint32_t cr3 = usart->CR3;
// 禁用USART
usart->CR1 &= ~USART_CR1_UE;
// 重新配置波特率 (115200)
uint32_t brr = SystemCoreClock / 115200;
usart->BRR = brr;
// 修复常见配置错误
if((cr1 & USART_CR1_M) == 0) { // 8位数据长度
cr1 |= USART_CR1_M_0;
}
if((cr3 & USART_CR3_HDSEL) == 0) { // 禁用半双工
cr3 &= ~USART_CR3_HDSEL;
}
// 恢复寄存器
usart->CR2 = cr2;
usart->CR3 = cr3;
usart->CR1 = cr1 | USART_CR1_UE;
// 发送测试字符
usart->DR = 0x55;
while(!(usart->SR & USART_SR_TC));
}
四、电源问题诊断黑科技
4.1 动态电流波形分析
// 使用ADC监测电源电流 (STM32内部参考电压)
void setup_current_monitor() {
// 配置ADC1 Channel 8 (PB0)
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
GPIOB->CRL &= 0xFFFFFFF0; // PB0 analog input
ADC1->SMPR2 = ADC_SMPR2_SMP8_2 | ADC_SMPR2_SMP8_1; // 239.5 cycles
ADC1->SQR3 = 8; // 转换通道8
// 启用内部电压参考
ADC->CCR |= ADC_CCR_VREFEN;
// 开始连续转换
ADC1->CR2 |= ADC_CR2_CONT;
ADC1->CR2 |= ADC_CR2_ADON;
// 等待ADC稳定
delay_ms(100);
}
uint32_t measure_current() {
static uint16_t buffer[256];
static uint32_t idx = 0;
// 启动转换
ADC1->CR2 |= ADC_CR2_SWSTART;
// 等待转换完成
while(!(ADC1->SR & ADC_SR_EOC));
// 存储采样值
buffer[idx++] = ADC1->DR;
if(idx >= 256) idx = 0;
// 计算动态电流特征
uint32_t avg = 0;
uint32_t max = 0;
uint32_t min = 0xFFFF;
for(int i=0; i<256; i++) {
avg += buffer[i];
if(buffer[i] > max) max = buffer[i];
if(buffer[i] < min) min = buffer[i];
}
avg /= 256;
// 返回纹波系数 (百分比)
return ((max - min) * 100) / avg;
}
4.2 低功耗模式调试
// STM32超低功耗调试技巧
void enter_stop_mode() {
// 配置唤醒引脚 (PA0)
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
GPIOA->CRL &= 0xFFFFFFF0;
GPIOA->CRL |= 0x00000008; // 浮空输入
EXTI->IMR |= EXTI_IMR_MR0;
EXTI->RTSR |= EXTI_RTSR_TR0; // 上升沿触发
// 配置PWR
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
PWR->CR |= PWR_CR_LPDS; // 进入低功耗停止模式
// 设置调试保持 (保持调试器连接)
DBGMCU->CR |= DBGMCU_CR_DBG_STOP;
// 进入停止模式
__WFI();
}
// 使用RTC唤醒定时器
void setup_rtc_wakeup(uint32_t seconds) {
RCC->APB1ENR |= RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN;
PWR->CR |= PWR_CR_DBP; // 允许访问RTC和备份寄存器
RCC->BDCR |= RCC_BDCR_LSEON;
while(!(RCC->BDCR & RCC_BDCR_LSERDY));
RCC->BDCR |= RCC_BDCR_RTCSEL_0; // LSE作为RTC时钟
RCC->BDCR |= RCC_BDCR_RTCEN;
RTC->WPR = 0xCA;
RTC->WPR = 0x53;
RTC->CR &= ~RTC_CR_WUTE;
while(!(RTC->ISR & RTC_ISR_WUTWF));
RTC->WUTR = seconds * 32768; // 设置唤醒间隔
RTC->CR |= RTC_CR_WUTIE | RTC_CR_WUCKSEL_2;
RTC->CR |= RTC_CR_WUTE;
EXTI->IMR |= EXTI_IMR_MR20;
EXTI->RTSR |= EXTI_RTSR_TR20;
NVIC_EnableIRQ(RTC_WKUP_IRQn);
}
void RTC_WKUP_IRQHandler() {
EXTI->PR = EXTI_PR_PR20;
RTC->ISR &= ~RTC_ISR_WUTF;
// 唤醒后记录时间戳
uint32_t wake_time = RTC->TR;
log_wake_event(wake_time);
}
五、韦东山调试心法总结
- 寄存器思维:直接操作寄存器比库函数更能暴露问题本质
- 信号完整性:异常现象80%与电源和时钟相关
- 时序还原术:用GPIO+定时器自制逻辑分析仪
- 非侵入调试:通过SWD读取内核寄存器而不影响程序执行
- 暴力采样法:高频ADC采样配合DMA捕获瞬态异常
// 终极调试函数:捕获任何异常并生成诊断报告
void __attribute__((naked)) DebugMon_Handler(void) {
__asm volatile(
"mrs r0, msp\n"
"mov r1, lr\n"
"b generate_debug_report