韦东山-单片机开发过程中的调试绝招

28 阅读8分钟

t04e5f7b00835159c8a.jpg

韦东山-单片机开发过程中的调试绝招---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);
}

五、韦东山调试心法总结

  1. 寄存器思维:直接操作寄存器比库函数更能暴露问题本质
  2. 信号完整性:异常现象80%与电源和时钟相关
  3. 时序还原术:用GPIO+定时器自制逻辑分析仪
  4. 非侵入调试:通过SWD读取内核寄存器而不影响程序执行
  5. 暴力采样法:高频ADC采样配合DMA捕获瞬态异常
// 终极调试函数:捕获任何异常并生成诊断报告
void __attribute__((naked)) DebugMon_Handler(void) {
    __asm volatile(
        "mrs r0, msp\n"
        "mov r1, lr\n"
        "b generate_debug_report