跳转至

底层调试技术:JTAG与SWD深入解析

学习目标

完成本教程学习后,你将能够:

  • 深入理解JTAG和SWD调试协议的工作原理
  • 掌握调试端口(Debug Port)的寄存器访问方法
  • 了解访问端口(Access Port)的配置和使用
  • 掌握实时跟踪技术(ITM、ETM、DWT)
  • 能够使用OpenOCD等工具进行底层调试
  • 了解调试器开发的基础知识
  • 掌握高级调试技巧和故障排查方法

前置要求

在开始本教程学习之前,你需要:

知识要求: - 深入理解ARM Cortex-M架构 - 熟悉C语言和汇编语言 - 了解数字电路和通信协议基础 - 掌握寄存器操作和位操作

技能要求: - 有嵌入式系统开发经验 - 熟悉调试工具的使用(GDB、OpenOCD等) - 能够阅读和理解技术规范文档 - 具备基本的硬件调试能力

硬件要求: - ARM Cortex-M开发板(支持SWD/JTAG) - 调试器(ST-Link、J-Link或CMSIS-DAP) - 逻辑分析仪(可选,用于协议分析)

概述

JTAG(Joint Test Action Group)和SWD(Serial Wire Debug)是嵌入式系统中最常用的调试接口。理解这些协议的底层工作原理,对于高级调试、性能优化和调试器开发至关重要。

为什么需要深入理解调试协议

  1. 高级调试需求
  2. 解决复杂的调试问题
  3. 实现自定义调试功能
  4. 优化调试性能

  5. 调试器开发

  6. 开发自定义调试工具
  7. 集成调试功能到产品中
  8. 实现生产测试工具

  9. 实时跟踪

  10. 实现非侵入式性能分析
  11. 实时监控系统行为
  12. 记录程序执行轨迹

  13. 故障诊断

  14. 诊断调试接口问题
  15. 解决连接失败
  16. 优化调试配置

调试架构概览

ARM Cortex-M处理器使用CoreSight调试架构:

┌─────────────────────────────────────────────────────────┐
│              ARM CoreSight 调试架构                      │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌──────────────┐      ┌──────────────┐               │
│  │  调试主机    │      │  调试探针    │               │
│  │  (Host PC)   │◄────►│ (Debugger)   │               │
│  └──────────────┘      └──────────────┘               │
│                              │                          │
│                              │ JTAG/SWD                │
│                              ↓                          │
│  ┌─────────────────────────────────────────────────┐  │
│  │          调试端口 (Debug Port - DP)              │  │
│  │  - JTAG-DP: 5线接口                             │  │
│  │  - SW-DP: 2线接口                               │  │
│  └─────────────────────────────────────────────────┘  │
│                              │                          │
│                              ↓                          │
│  ┌─────────────────────────────────────────────────┐  │
│  │         访问端口 (Access Port - AP)              │  │
│  │  - MEM-AP: 内存访问                             │  │
│  │  - JTAG-AP: JTAG链访问                          │  │
│  └─────────────────────────────────────────────────┘  │
│                              │                          │
│                              ↓                          │
│  ┌──────────┬──────────┬──────────┬──────────────┐   │
│  │   CPU    │  Memory  │   DWT    │     ITM      │   │
│  │  Debug   │  Access  │  (Trace) │   (Trace)    │   │
│  └──────────┴──────────┴──────────┴──────────────┘   │
└─────────────────────────────────────────────────────────┘

第一部分:JTAG协议详解

JTAG基础

JTAG最初是为芯片测试设计的标准(IEEE 1149.1),后来被广泛用于调试。

JTAG信号线

┌──────────────────────────────────────────┐
│         JTAG 5线接口                      │
├──────────────────────────────────────────┤
│  TCK  - Test Clock (时钟)                │
│  TMS  - Test Mode Select (模式选择)      │
│  TDI  - Test Data In (数据输入)          │
│  TDO  - Test Data Out (数据输出)         │
│  TRST - Test Reset (复位,可选)          │
└──────────────────────────────────────────┘

JTAG状态机

JTAG使用一个16状态的状态机控制操作:

                    TMS=1
        ┌───────────────────────────┐
        │                           │
        ↓                           │
   Test-Logic-Reset                 │
        │ TMS=0                     │
        ↓                           │
    Run-Test/Idle ←─────────────────┘
        │ TMS=1
   Select-DR-Scan
        │ TMS=0          TMS=1
        ↓                 ↓
   Capture-DR        Select-IR-Scan
        │                 │
        ↓                 ↓
    Shift-DR          Capture-IR
        │                 │
        ↓                 ↓
    Exit1-DR          Shift-IR
        │                 │
        ↓                 ↓
    Pause-DR          Exit1-IR
        │                 │
        ↓                 ↓
    Exit2-DR          Pause-IR
        │                 │
        ↓                 ↓
   Update-DR          Exit2-IR
        │                 │
        └─────────────────┘
             TMS=0

JTAG寄存器

JTAG定义了两类寄存器:

  1. 指令寄存器(IR)
  2. 选择要访问的数据寄存器
  3. 控制JTAG操作模式
  4. 长度通常为4-5位

  5. 数据寄存器(DR)

  6. BYPASS:1位,用于绕过设备
  7. IDCODE:32位,设备识别码
  8. DPACC:35位,调试端口访问
  9. APACC:35位,访问端口访问

JTAG指令示例

/**
 * @brief JTAG指令定义
 */
#define JTAG_IR_BYPASS      0x0F    // 绕过
#define JTAG_IR_IDCODE      0x0E    // 读取ID
#define JTAG_IR_DPACC       0x0A    // DP访问
#define JTAG_IR_APACC       0x0B    // AP访问
#define JTAG_IR_ABORT       0x08    // 中止操作

/**
 * @brief JTAG状态机状态
 */
typedef enum {
    JTAG_STATE_TEST_LOGIC_RESET = 0,
    JTAG_STATE_RUN_TEST_IDLE,
    JTAG_STATE_SELECT_DR_SCAN,
    JTAG_STATE_CAPTURE_DR,
    JTAG_STATE_SHIFT_DR,
    JTAG_STATE_EXIT1_DR,
    JTAG_STATE_PAUSE_DR,
    JTAG_STATE_EXIT2_DR,
    JTAG_STATE_UPDATE_DR,
    JTAG_STATE_SELECT_IR_SCAN,
    JTAG_STATE_CAPTURE_IR,
    JTAG_STATE_SHIFT_IR,
    JTAG_STATE_EXIT1_IR,
    JTAG_STATE_PAUSE_IR,
    JTAG_STATE_EXIT2_IR,
    JTAG_STATE_UPDATE_IR
} jtag_state_t;

JTAG操作序列

读取IDCODE示例

/**
 * @brief 读取JTAG IDCODE
 * @retval 32位IDCODE值
 */
uint32_t JTAG_Read_IDCODE(void)
{
    uint32_t idcode = 0;

    // 1. 复位到Test-Logic-Reset状态
    JTAG_Reset();

    // 2. 进入Run-Test/Idle状态
    JTAG_Move_To_State(JTAG_STATE_RUN_TEST_IDLE);

    // 3. 进入Select-DR-Scan状态
    JTAG_Set_TMS(1);
    JTAG_Clock();

    // 4. 进入Capture-DR状态(自动加载IDCODE)
    JTAG_Set_TMS(0);
    JTAG_Clock();

    // 5. 进入Shift-DR状态
    JTAG_Set_TMS(0);
    JTAG_Clock();

    // 6. 移出32位IDCODE
    for (int i = 0; i < 32; i++) {
        JTAG_Set_TMS(0);  // 保持在Shift-DR
        JTAG_Clock();

        // 读取TDO
        if (JTAG_Read_TDO()) {
            idcode |= (1 << i);
        }
    }

    // 7. 返回到Run-Test/Idle
    JTAG_Move_To_State(JTAG_STATE_RUN_TEST_IDLE);

    return idcode;
}

/**
 * @brief JTAG复位
 */
void JTAG_Reset(void)
{
    // 发送5个TMS=1,进入Test-Logic-Reset
    for (int i = 0; i < 5; i++) {
        JTAG_Set_TMS(1);
        JTAG_Clock();
    }
}

/**
 * @brief 移动到指定状态
 * @param target_state 目标状态
 */
void JTAG_Move_To_State(jtag_state_t target_state)
{
    // 根据当前状态和目标状态,生成TMS序列
    // 这里简化处理,实际需要状态机转换表

    // 示例:移动到Run-Test/Idle
    if (target_state == JTAG_STATE_RUN_TEST_IDLE) {
        JTAG_Set_TMS(0);
        JTAG_Clock();
    }
}

/**
 * @brief 产生一个时钟周期
 */
void JTAG_Clock(void)
{
    // TCK低电平
    GPIO_ResetBits(JTAG_TCK_PORT, JTAG_TCK_PIN);
    Delay_us(1);

    // TCK高电平
    GPIO_SetBits(JTAG_TCK_PORT, JTAG_TCK_PIN);
    Delay_us(1);
}

/**
 * @brief 设置TMS信号
 * @param value TMS值(0或1)
 */
void JTAG_Set_TMS(uint8_t value)
{
    if (value) {
        GPIO_SetBits(JTAG_TMS_PORT, JTAG_TMS_PIN);
    } else {
        GPIO_ResetBits(JTAG_TMS_PORT, JTAG_TMS_PIN);
    }
}

/**
 * @brief 设置TDI信号
 * @param value TDI值(0或1)
 */
void JTAG_Set_TDI(uint8_t value)
{
    if (value) {
        GPIO_SetBits(JTAG_TDI_PORT, JTAG_TDI_PIN);
    } else {
        GPIO_ResetBits(JTAG_TDI_PORT, JTAG_TDI_PIN);
    }
}

/**
 * @brief 读取TDO信号
 * @retval TDO值(0或1)
 */
uint8_t JTAG_Read_TDO(void)
{
    return GPIO_ReadInputDataBit(JTAG_TDO_PORT, JTAG_TDO_PIN);
}

JTAG链扫描

当多个设备连接在JTAG链上时,需要扫描链以识别所有设备:

/**
 * @brief JTAG链扫描
 * @param idcodes 存储IDCODE的数组
 * @param max_devices 最大设备数
 * @retval 检测到的设备数量
 */
int JTAG_Scan_Chain(uint32_t *idcodes, int max_devices)
{
    int device_count = 0;

    // 1. 复位JTAG
    JTAG_Reset();

    // 2. 进入Shift-DR状态(IDCODE自动加载)
    JTAG_Move_To_State(JTAG_STATE_SHIFT_DR);

    // 3. 扫描链,读取所有IDCODE
    for (int dev = 0; dev < max_devices; dev++) {
        uint32_t idcode = 0;

        // 读取32位
        for (int bit = 0; bit < 32; bit++) {
            JTAG_Set_TMS(0);  // 保持在Shift-DR
            JTAG_Clock();

            if (JTAG_Read_TDO()) {
                idcode |= (1 << bit);
            }
        }

        // 检查IDCODE是否有效
        if (idcode == 0xFFFFFFFF || idcode == 0x00000000) {
            break;  // 链结束
        }

        idcodes[device_count++] = idcode;
    }

    // 4. 返回到Run-Test/Idle
    JTAG_Move_To_State(JTAG_STATE_RUN_TEST_IDLE);

    return device_count;
}

/**
 * @brief 解析IDCODE
 * @param idcode IDCODE值
 */
void JTAG_Parse_IDCODE(uint32_t idcode)
{
    uint8_t version = (idcode >> 28) & 0x0F;
    uint16_t part_number = (idcode >> 12) & 0xFFFF;
    uint16_t manufacturer = (idcode >> 1) & 0x7FF;
    uint8_t lsb = idcode & 0x01;

    printf("IDCODE: 0x%08X\n", idcode);
    printf("  Version: 0x%X\n", version);
    printf("  Part Number: 0x%04X\n", part_number);
    printf("  Manufacturer: 0x%03X\n", manufacturer);
    printf("  LSB: %d (should be 1)\n", lsb);
}

第二部分:SWD协议详解

SWD基础

SWD(Serial Wire Debug)是ARM专门为调试设计的2线协议,相比JTAG更简单高效。

SWD信号线

┌──────────────────────────────────────────┐
│         SWD 2线接口                       │
├──────────────────────────────────────────┤
│  SWCLK - Serial Wire Clock (时钟)        │
│  SWDIO - Serial Wire Data I/O (数据)     │
│  (可选) SWO - Serial Wire Output (跟踪)  │
└──────────────────────────────────────────┘

SWD vs JTAG对比

特性 JTAG SWD
信号线数量 5线(TCK, TMS, TDI, TDO, TRST) 2线(SWCLK, SWDIO)
数据传输 单向(TDI/TDO分离) 双向(SWDIO)
速度 较慢 较快
引脚占用
链式连接 支持 不支持
实时跟踪 需要额外引脚 集成SWO

SWD协议格式

SWD使用包(Packet)进行通信,每个包包含:

SWD数据包格式(8位请求 + 3位ACK + 32位数据 + 1位奇偶校验)

请求阶段(8位):
┌────┬────┬────┬────┬────┬────┬────┬────┐
│Start│APnDP│ RnW │ A2 │ A3 │Parity│Stop│Park│
└────┴────┴────┴────┴────┴────┴────┴────┘
  1     1     1     1    1     1     1    1

- Start: 起始位,固定为1
- APnDP: 0=DP访问, 1=AP访问
- RnW: 0=写, 1=读
- A2, A3: 地址位[2:3]
- Parity: 奇偶校验位
- Stop: 停止位,固定为0
- Park: 停车位,固定为1

ACK阶段(3位):
┌────┬────┬────┐
│ b0 │ b1 │ b2 │
└────┴────┴────┘

- 001 (OK): 操作成功
- 010 (WAIT): 等待,需要重试
- 100 (FAULT): 错误
- 111: 协议错误

数据阶段(33位,仅在读/写操作时):
┌──────────────────────────┬────────┐
│    32位数据               │ Parity │
└──────────────────────────┴────────┘

SWD操作实现

SWD初始化序列

/**
 * @brief SWD初始化
 */
void SWD_Init(void)
{
    // 1. 发送至少50个时钟周期,SWDIO保持高电平
    //    这会复位任何现有的JTAG状态
    SWD_Set_SWDIO(1);
    for (int i = 0; i < 60; i++) {
        SWD_Clock();
    }

    // 2. 发送JTAG到SWD切换序列(16位)
    //    序列值:0xE79E (0b1110011110011110)
    uint16_t switch_seq = 0xE79E;
    for (int i = 0; i < 16; i++) {
        SWD_Set_SWDIO((switch_seq >> i) & 1);
        SWD_Clock();
    }

    // 3. 再发送至少50个时钟周期,SWDIO保持高电平
    SWD_Set_SWDIO(1);
    for (int i = 0; i < 60; i++) {
        SWD_Clock();
    }

    // 4. 发送线复位序列(至少50个时钟,SWDIO保持高电平)
    //    然后发送2个空闲周期(SWDIO=0)
    for (int i = 0; i < 2; i++) {
        SWD_Set_SWDIO(0);
        SWD_Clock();
    }

    printf("SWD初始化完成\n");
}

/**
 * @brief 产生一个SWD时钟周期
 */
void SWD_Clock(void)
{
    // SWCLK低电平
    GPIO_ResetBits(SWD_CLK_PORT, SWD_CLK_PIN);
    Delay_ns(100);  // 根据目标速度调整

    // SWCLK高电平
    GPIO_SetBits(SWD_CLK_PORT, SWD_CLK_PIN);
    Delay_ns(100);
}

/**
 * @brief 设置SWDIO为输出模式
 */
void SWD_SWDIO_Output(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = SWD_DIO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    HAL_GPIO_Init(SWD_DIO_PORT, &GPIO_InitStruct);
}

/**
 * @brief 设置SWDIO为输入模式
 */
void SWD_SWDIO_Input(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = SWD_DIO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(SWD_DIO_PORT, &GPIO_InitStruct);
}

/**
 * @brief 设置SWDIO信号
 * @param value SWDIO值(0或1)
 */
void SWD_Set_SWDIO(uint8_t value)
{
    if (value) {
        GPIO_SetBits(SWD_DIO_PORT, SWD_DIO_PIN);
    } else {
        GPIO_ResetBits(SWD_DIO_PORT, SWD_DIO_PIN);
    }
}

/**
 * @brief 读取SWDIO信号
 * @retval SWDIO值(0或1)
 */
uint8_t SWD_Read_SWDIO(void)
{
    return GPIO_ReadInputDataBit(SWD_DIO_PORT, SWD_DIO_PIN);
}

SWD读写操作

/**
 * @brief 计算奇偶校验位
 * @param data 数据
 * @param bits 位数
 * @retval 奇偶校验位(奇校验)
 */
uint8_t SWD_Parity(uint32_t data, int bits)
{
    uint8_t parity = 0;
    for (int i = 0; i < bits; i++) {
        parity ^= (data >> i) & 1;
    }
    return parity;
}

/**
 * @brief 发送SWD请求
 * @param APnDP 0=DP, 1=AP
 * @param RnW 0=写, 1=读
 * @param addr 地址(A[3:2])
 * @retval ACK响应
 */
uint8_t SWD_Send_Request(uint8_t APnDP, uint8_t RnW, uint8_t addr)
{
    uint8_t request = 0;
    uint8_t parity = 0;

    // 构建请求字节
    request |= (1 << 0);        // Start bit
    request |= (APnDP << 1);    // APnDP
    request |= (RnW << 2);      // RnW
    request |= ((addr & 0x0C) >> 2) << 3;  // A[3:2]

    // 计算奇偶校验
    parity = APnDP ^ RnW ^ ((addr >> 2) & 1) ^ ((addr >> 3) & 1);
    request |= (parity << 5);   // Parity
    request |= (0 << 6);        // Stop bit
    request |= (1 << 7);        // Park bit

    // 发送请求(8位)
    SWD_SWDIO_Output();
    for (int i = 0; i < 8; i++) {
        SWD_Set_SWDIO((request >> i) & 1);
        SWD_Clock();
    }

    // 切换到输入模式,读取ACK
    SWD_SWDIO_Input();

    // 读取ACK(3位)
    uint8_t ack = 0;
    for (int i = 0; i < 3; i++) {
        SWD_Clock();
        if (SWD_Read_SWDIO()) {
            ack |= (1 << i);
        }
    }

    return ack;
}

/**
 * @brief SWD读操作
 * @param APnDP 0=DP, 1=AP
 * @param addr 地址
 * @param data 读取的数据
 * @retval 0=成功, -1=失败
 */
int SWD_Read(uint8_t APnDP, uint8_t addr, uint32_t *data)
{
    uint8_t ack;
    int retry = 0;

    do {
        // 发送读请求
        ack = SWD_Send_Request(APnDP, 1, addr);

        if (ack == 0x01) {  // OK
            // 读取数据(32位)
            uint32_t value = 0;
            for (int i = 0; i < 32; i++) {
                SWD_Clock();
                if (SWD_Read_SWDIO()) {
                    value |= (1 << i);
                }
            }

            // 读取奇偶校验位
            SWD_Clock();
            uint8_t parity_bit = SWD_Read_SWDIO();

            // 验证奇偶校验
            uint8_t calc_parity = SWD_Parity(value, 32);
            if (parity_bit != calc_parity) {
                printf("SWD读取奇偶校验错误\n");
                return -1;
            }

            *data = value;

            // 发送2个空闲周期
            SWD_SWDIO_Output();
            SWD_Set_SWDIO(0);
            SWD_Clock();
            SWD_Clock();

            return 0;  // 成功

        } else if (ack == 0x02) {  // WAIT
            // 等待并重试
            retry++;
            if (retry > 100) {
                printf("SWD读取超时(WAIT)\n");
                return -1;
            }
            Delay_us(10);

        } else if (ack == 0x04) {  // FAULT
            printf("SWD读取错误(FAULT)\n");
            return -1;

        } else {
            printf("SWD读取协议错误(ACK=0x%X)\n", ack);
            return -1;
        }
    } while (ack == 0x02);

    return -1;
}

/**
 * @brief SWD写操作
 * @param APnDP 0=DP, 1=AP
 * @param addr 地址
 * @param data 要写入的数据
 * @retval 0=成功, -1=失败
 */
int SWD_Write(uint8_t APnDP, uint8_t addr, uint32_t data)
{
    uint8_t ack;
    int retry = 0;

    do {
        // 发送写请求
        ack = SWD_Send_Request(APnDP, 0, addr);

        if (ack == 0x01) {  // OK
            // 发送一个周转周期
            SWD_SWDIO_Input();
            SWD_Clock();

            // 切换到输出模式
            SWD_SWDIO_Output();

            // 写入数据(32位)
            for (int i = 0; i < 32; i++) {
                SWD_Set_SWDIO((data >> i) & 1);
                SWD_Clock();
            }

            // 写入奇偶校验位
            uint8_t parity = SWD_Parity(data, 32);
            SWD_Set_SWDIO(parity);
            SWD_Clock();

            // 发送2个空闲周期
            SWD_Set_SWDIO(0);
            SWD_Clock();
            SWD_Clock();

            return 0;  // 成功

        } else if (ack == 0x02) {  // WAIT
            retry++;
            if (retry > 100) {
                printf("SWD写入超时(WAIT)\n");
                return -1;
            }
            Delay_us(10);

        } else if (ack == 0x04) {  // FAULT
            printf("SWD写入错误(FAULT)\n");
            return -1;

        } else {
            printf("SWD写入协议错误(ACK=0x%X)\n", ack);
            return -1;
        }
    } while (ack == 0x02);

    return -1;
}

第三部分:调试端口(Debug Port)

DP寄存器

调试端口提供了访问目标系统的接口,主要寄存器包括:

/**
 * @brief DP寄存器地址定义
 */
#define DP_IDCODE       0x00    // 识别码寄存器(只读)
#define DP_ABORT        0x00    // 中止寄存器(只写)
#define DP_CTRL_STAT    0x04    // 控制/状态寄存器
#define DP_SELECT       0x08    // 选择寄存器
#define DP_RDBUFF       0x0C    // 读缓冲寄存器(只读)

/**
 * @brief CTRL/STAT寄存器位定义
 */
#define DP_CTRL_CSYSPWRUPREQ    (1 << 30)  // 系统电源请求
#define DP_CTRL_CSYSPWRUPACK    (1 << 31)  // 系统电源确认
#define DP_CTRL_CDBGPWRUPREQ    (1 << 28)  // 调试电源请求
#define DP_CTRL_CDBGPWRUPACK    (1 << 29)  // 调试电源确认
#define DP_CTRL_STICKYERR       (1 << 5)   // 粘性错误标志
#define DP_CTRL_STICKYCMP       (1 << 4)   // 粘性比较标志
#define DP_CTRL_STICKYORUN      (1 << 1)   // 粘性溢出标志

/**
 * @brief ABORT寄存器位定义
 */
#define DP_ABORT_ORUNERRCLR     (1 << 4)   // 清除溢出错误
#define DP_ABORT_WDERRCLR       (1 << 3)   // 清除写错误
#define DP_ABORT_STKERRCLR      (1 << 2)   // 清除粘性错误
#define DP_ABORT_STKCMPCLR      (1 << 1)   // 清除粘性比较
#define DP_ABORT_DAPABORT       (1 << 0)   // DAP中止

DP初始化

/**
 * @brief 初始化调试端口
 * @retval 0=成功, -1=失败
 */
int DP_Init(void)
{
    uint32_t idcode;
    uint32_t ctrl_stat;

    // 1. 初始化SWD接口
    SWD_Init();

    // 2. 读取IDCODE
    if (SWD_Read(0, DP_IDCODE, &idcode) != 0) {
        printf("读取IDCODE失败\n");
        return -1;
    }

    printf("DP IDCODE: 0x%08X\n", idcode);

    // 解析IDCODE
    uint8_t version = (idcode >> 28) & 0x0F;
    uint16_t part_no = (idcode >> 12) & 0xFFFF;
    uint16_t designer = (idcode >> 1) & 0x7FF;

    printf("  Version: 0x%X\n", version);
    printf("  Part Number: 0x%04X\n", part_no);
    printf("  Designer: 0x%03X\n", designer);

    // 3. 清除错误标志
    if (SWD_Write(0, DP_ABORT, 0x1E) != 0) {
        printf("清除错误标志失败\n");
        return -1;
    }

    // 4. 上电调试域
    ctrl_stat = DP_CTRL_CSYSPWRUPREQ | DP_CTRL_CDBGPWRUPREQ;
    if (SWD_Write(0, DP_CTRL_STAT, ctrl_stat) != 0) {
        printf("上电调试域失败\n");
        return -1;
    }

    // 5. 等待上电完成
    int timeout = 1000;
    do {
        if (SWD_Read(0, DP_CTRL_STAT, &ctrl_stat) != 0) {
            printf("读取CTRL/STAT失败\n");
            return -1;
        }

        if ((ctrl_stat & (DP_CTRL_CSYSPWRUPACK | DP_CTRL_CDBGPWRUPACK)) ==
            (DP_CTRL_CSYSPWRUPACK | DP_CTRL_CDBGPWRUPACK)) {
            break;  // 上电完成
        }

        Delay_us(10);
        timeout--;
    } while (timeout > 0);

    if (timeout == 0) {
        printf("上电超时\n");
        return -1;
    }

    printf("调试端口初始化成功\n");
    return 0;
}

/**
 * @brief 检查DP错误
 * @retval 0=无错误, -1=有错误
 */
int DP_Check_Error(void)
{
    uint32_t ctrl_stat;

    if (SWD_Read(0, DP_CTRL_STAT, &ctrl_stat) != 0) {
        return -1;
    }

    if (ctrl_stat & DP_CTRL_STICKYERR) {
        printf("DP粘性错误\n");

        // 清除错误
        SWD_Write(0, DP_ABORT, DP_ABORT_STKERRCLR);
        return -1;
    }

    if (ctrl_stat & DP_CTRL_STICKYORUN) {
        printf("DP溢出错误\n");

        // 清除错误
        SWD_Write(0, DP_ABORT, DP_ABORT_ORUNERRCLR);
        return -1;
    }

    return 0;
}

第四部分:访问端口(Access Port)

MEM-AP寄存器

MEM-AP(Memory Access Port)用于访问目标系统的内存和寄存器:

/**
 * @brief MEM-AP寄存器地址定义
 */
#define AP_CSW          0x00    // 控制/状态字寄存器
#define AP_TAR          0x04    // 传输地址寄存器
#define AP_DRW          0x0C    // 数据读/写寄存器
#define AP_BD0          0x10    // 字节数据寄存器0
#define AP_BD1          0x14    // 字节数据寄存器1
#define AP_BD2          0x18    // 字节数据寄存器2
#define AP_BD3          0x1C    // 字节数据寄存器3
#define AP_CFG          0xF4    // 配置寄存器
#define AP_BASE         0xF8    // 基地址寄存器
#define AP_IDR          0xFC    // 识别寄存器

/**
 * @brief CSW寄存器位定义
 */
#define AP_CSW_DBGSWENABLE      (1 << 31)  // 调试软件使能
#define AP_CSW_PROT_MASK        (0x7F << 24)  // 保护位
#define AP_CSW_SPIDEN           (1 << 23)  // 安全特权指令使能
#define AP_CSW_MODE_MASK        (0x0F << 8)   // 模式
#define AP_CSW_TRINPROG         (1 << 7)   // 传输进行中
#define AP_CSW_DEVICEEN         (1 << 6)   // 设备使能
#define AP_CSW_ADDRINC_OFF      (0 << 4)   // 地址不递增
#define AP_CSW_ADDRINC_SINGLE   (1 << 4)   // 单次递增
#define AP_CSW_ADDRINC_PACKED   (2 << 4)   // 打包递增
#define AP_CSW_SIZE_8BIT        (0 << 0)   // 8位访问
#define AP_CSW_SIZE_16BIT       (1 << 0)   // 16位访问
#define AP_CSW_SIZE_32BIT       (2 << 0)   // 32位访问

AP访问操作

/**
 * @brief 选择AP
 * @param ap_num AP编号
 * @retval 0=成功, -1=失败
 */
int AP_Select(uint8_t ap_num)
{
    uint32_t select = (ap_num << 24);
    return SWD_Write(0, DP_SELECT, select);
}

/**
 * @brief 读取AP寄存器
 * @param addr AP寄存器地址
 * @param data 读取的数据
 * @retval 0=成功, -1=失败
 */
int AP_Read(uint8_t addr, uint32_t *data)
{
    // 1. 发起AP读操作
    if (SWD_Read(1, addr, data) != 0) {
        return -1;
    }

    // 2. 读取RDBUFF获取实际数据
    //    (AP读操作有一个周期的延迟)
    if (SWD_Read(0, DP_RDBUFF, data) != 0) {
        return -1;
    }

    return 0;
}

/**
 * @brief 写入AP寄存器
 * @param addr AP寄存器地址
 * @param data 要写入的数据
 * @retval 0=成功, -1=失败
 */
int AP_Write(uint8_t addr, uint32_t data)
{
    return SWD_Write(1, addr, data);
}

/**
 * @brief 初始化MEM-AP
 * @retval 0=成功, -1=失败
 */
int MEM_AP_Init(void)
{
    uint32_t idr;

    // 1. 选择AP0(通常是MEM-AP)
    if (AP_Select(0) != 0) {
        printf("选择AP失败\n");
        return -1;
    }

    // 2. 读取IDR
    if (AP_Read(AP_IDR, &idr) != 0) {
        printf("读取AP IDR失败\n");
        return -1;
    }

    printf("AP IDR: 0x%08X\n", idr);

    // 3. 配置CSW
    uint32_t csw = AP_CSW_DBGSWENABLE | 
                   AP_CSW_SIZE_32BIT | 
                   AP_CSW_ADDRINC_SINGLE;

    if (AP_Write(AP_CSW, csw) != 0) {
        printf("配置CSW失败\n");
        return -1;
    }

    printf("MEM-AP初始化成功\n");
    return 0;
}

内存读写操作

/**
 * @brief 读取32位内存
 * @param addr 内存地址
 * @param data 读取的数据
 * @retval 0=成功, -1=失败
 */
int MEM_Read32(uint32_t addr, uint32_t *data)
{
    // 1. 设置传输地址
    if (AP_Write(AP_TAR, addr) != 0) {
        return -1;
    }

    // 2. 读取数据
    if (AP_Read(AP_DRW, data) != 0) {
        return -1;
    }

    return 0;
}

/**
 * @brief 写入32位内存
 * @param addr 内存地址
 * @param data 要写入的数据
 * @retval 0=成功, -1=失败
 */
int MEM_Write32(uint32_t addr, uint32_t data)
{
    // 1. 设置传输地址
    if (AP_Write(AP_TAR, addr) != 0) {
        return -1;
    }

    // 2. 写入数据
    if (AP_Write(AP_DRW, data) != 0) {
        return -1;
    }

    return 0;
}

/**
 * @brief 读取内存块
 * @param addr 起始地址
 * @param data 数据缓冲区
 * @param count 字数(32位)
 * @retval 0=成功, -1=失败
 */
int MEM_Read_Block(uint32_t addr, uint32_t *data, uint32_t count)
{
    // 1. 配置CSW为自动递增模式
    uint32_t csw = AP_CSW_DBGSWENABLE | 
                   AP_CSW_SIZE_32BIT | 
                   AP_CSW_ADDRINC_SINGLE;

    if (AP_Write(AP_CSW, csw) != 0) {
        return -1;
    }

    // 2. 设置起始地址
    if (AP_Write(AP_TAR, addr) != 0) {
        return -1;
    }

    // 3. 连续读取
    for (uint32_t i = 0; i < count; i++) {
        if (AP_Read(AP_DRW, &data[i]) != 0) {
            return -1;
        }
    }

    return 0;
}

/**
 * @brief 写入内存块
 * @param addr 起始地址
 * @param data 数据缓冲区
 * @param count 字数(32位)
 * @retval 0=成功, -1=失败
 */
int MEM_Write_Block(uint32_t addr, const uint32_t *data, uint32_t count)
{
    // 1. 配置CSW为自动递增模式
    uint32_t csw = AP_CSW_DBGSWENABLE | 
                   AP_CSW_SIZE_32BIT | 
                   AP_CSW_ADDRINC_SINGLE;

    if (AP_Write(AP_CSW, csw) != 0) {
        return -1;
    }

    // 2. 设置起始地址
    if (AP_Write(AP_TAR, addr) != 0) {
        return -1;
    }

    // 3. 连续写入
    for (uint32_t i = 0; i < count; i++) {
        if (AP_Write(AP_DRW, data[i]) != 0) {
            return -1;
        }
    }

    return 0;
}

第五部分:CPU调试功能

CPU调试寄存器

ARM Cortex-M提供了一组调试寄存器用于控制CPU:

/**
 * @brief CPU调试寄存器地址定义
 */
#define DHCSR           0xE000EDF0  // 调试停机控制和状态寄存器
#define DCRSR           0xE000EDF4  // 调试核心寄存器选择器
#define DCRDR           0xE000EDF8  // 调试核心寄存器数据
#define DEMCR           0xE000EDFC  // 调试异常和监视器控制寄存器

/**
 * @brief DHCSR寄存器位定义
 */
#define DHCSR_DBGKEY            (0xA05F << 16)  // 调试密钥
#define DHCSR_S_RESET_ST        (1 << 25)  // 复位状态
#define DHCSR_S_RETIRE_ST       (1 << 24)  // 指令退休状态
#define DHCSR_S_LOCKUP          (1 << 19)  // 锁定状态
#define DHCSR_S_SLEEP           (1 << 18)  // 睡眠状态
#define DHCSR_S_HALT            (1 << 17)  // 停机状态
#define DHCSR_S_REGRDY          (1 << 16)  // 寄存器就绪
#define DHCSR_C_SNAPSTALL       (1 << 5)   // 快照停顿
#define DHCSR_C_MASKINTS        (1 << 3)   // 屏蔽中断
#define DHCSR_C_STEP            (1 << 2)   // 单步执行
#define DHCSR_C_HALT            (1 << 1)   // 停机请求
#define DHCSR_C_DEBUGEN         (1 << 0)   // 调试使能

/**
 * @brief DCRSR寄存器位定义
 */
#define DCRSR_REGWnR            (1 << 16)  // 0=读, 1=写
#define DCRSR_REGSEL_MASK       0x1F       // 寄存器选择

/**
 * @brief DEMCR寄存器位定义
 */
#define DEMCR_TRCENA            (1 << 24)  // 跟踪使能
#define DEMCR_MON_REQ           (1 << 19)  // 监视器请求
#define DEMCR_MON_STEP          (1 << 18)  // 监视器单步
#define DEMCR_MON_PEND          (1 << 17)  // 监视器挂起
#define DEMCR_MON_EN            (1 << 16)  // 监视器使能
#define DEMCR_VC_HARDERR        (1 << 10)  // 硬错误向量捕获
#define DEMCR_VC_INTERR         (1 << 9)   // 中断错误向量捕获
#define DEMCR_VC_BUSERR         (1 << 8)   // 总线错误向量捕获
#define DEMCR_VC_STATERR        (1 << 7)   // 状态错误向量捕获
#define DEMCR_VC_CHKERR         (1 << 6)   // 检查错误向量捕获
#define DEMCR_VC_NOCPERR        (1 << 5)   // 无协处理器错误向量捕获
#define DEMCR_VC_MMERR          (1 << 4)   // 内存管理错误向量捕获
#define DEMCR_VC_CORERESET      (1 << 0)   // 核心复位向量捕获

CPU控制操作

/**
 * @brief 停止CPU
 * @retval 0=成功, -1=失败
 */
int CPU_Halt(void)
{
    uint32_t dhcsr;

    // 1. 发送停机请求
    dhcsr = DHCSR_DBGKEY | DHCSR_C_DEBUGEN | DHCSR_C_HALT;
    if (MEM_Write32(DHCSR, dhcsr) != 0) {
        return -1;
    }

    // 2. 等待CPU停机
    int timeout = 1000;
    do {
        if (MEM_Read32(DHCSR, &dhcsr) != 0) {
            return -1;
        }

        if (dhcsr & DHCSR_S_HALT) {
            printf("CPU已停机\n");
            return 0;
        }

        Delay_us(10);
        timeout--;
    } while (timeout > 0);

    printf("CPU停机超时\n");
    return -1;
}

/**
 * @brief 恢复CPU运行
 * @retval 0=成功, -1=失败
 */
int CPU_Resume(void)
{
    uint32_t dhcsr;

    // 清除停机位
    dhcsr = DHCSR_DBGKEY | DHCSR_C_DEBUGEN;
    if (MEM_Write32(DHCSR, dhcsr) != 0) {
        return -1;
    }

    printf("CPU已恢复运行\n");
    return 0;
}

/**
 * @brief CPU单步执行
 * @retval 0=成功, -1=失败
 */
int CPU_Step(void)
{
    uint32_t dhcsr;

    // 1. 设置单步标志
    dhcsr = DHCSR_DBGKEY | DHCSR_C_DEBUGEN | DHCSR_C_STEP | DHCSR_C_MASKINTS;
    if (MEM_Write32(DHCSR, dhcsr) != 0) {
        return -1;
    }

    // 2. 恢复运行(执行一条指令后自动停止)
    dhcsr = DHCSR_DBGKEY | DHCSR_C_DEBUGEN | DHCSR_C_STEP;
    if (MEM_Write32(DHCSR, dhcsr) != 0) {
        return -1;
    }

    // 3. 等待停机
    int timeout = 1000;
    do {
        if (MEM_Read32(DHCSR, &dhcsr) != 0) {
            return -1;
        }

        if (dhcsr & DHCSR_S_HALT) {
            return 0;
        }

        Delay_us(10);
        timeout--;
    } while (timeout > 0);

    return -1;
}

/**
 * @brief 复位CPU
 * @retval 0=成功, -1=失败
 */
int CPU_Reset(void)
{
    uint32_t aircr;

    // 1. 读取AIRCR
    if (MEM_Read32(0xE000ED0C, &aircr) != 0) {
        return -1;
    }

    // 2. 保留优先级分组,设置复位请求
    aircr = (0x05FA << 16) | (aircr & 0x700) | (1 << 2);

    // 3. 写入AIRCR触发复位
    if (MEM_Write32(0xE000ED0C, aircr) != 0) {
        return -1;
    }

    printf("CPU复位请求已发送\n");

    // 4. 等待复位完成
    Delay_ms(100);

    return 0;
}

寄存器读写

/**
 * @brief 读取CPU核心寄存器
 * @param reg_num 寄存器编号(0-20)
 * @param value 读取的值
 * @retval 0=成功, -1=失败
 */
int CPU_Read_Register(uint8_t reg_num, uint32_t *value)
{
    uint32_t dhcsr;

    // 1. 确保CPU已停机
    if (MEM_Read32(DHCSR, &dhcsr) != 0) {
        return -1;
    }

    if (!(dhcsr & DHCSR_S_HALT)) {
        printf("CPU未停机,无法读取寄存器\n");
        return -1;
    }

    // 2. 选择要读取的寄存器
    uint32_t dcrsr = reg_num & DCRSR_REGSEL_MASK;
    if (MEM_Write32(DCRSR, dcrsr) != 0) {
        return -1;
    }

    // 3. 等待寄存器就绪
    int timeout = 1000;
    do {
        if (MEM_Read32(DHCSR, &dhcsr) != 0) {
            return -1;
        }

        if (dhcsr & DHCSR_S_REGRDY) {
            break;
        }

        Delay_us(1);
        timeout--;
    } while (timeout > 0);

    if (timeout == 0) {
        return -1;
    }

    // 4. 读取寄存器值
    if (MEM_Read32(DCRDR, value) != 0) {
        return -1;
    }

    return 0;
}

/**
 * @brief 写入CPU核心寄存器
 * @param reg_num 寄存器编号(0-20)
 * @param value 要写入的值
 * @retval 0=成功, -1=失败
 */
int CPU_Write_Register(uint8_t reg_num, uint32_t value)
{
    uint32_t dhcsr;

    // 1. 确保CPU已停机
    if (MEM_Read32(DHCSR, &dhcsr) != 0) {
        return -1;
    }

    if (!(dhcsr & DHCSR_S_HALT)) {
        printf("CPU未停机,无法写入寄存器\n");
        return -1;
    }

    // 2. 写入寄存器值
    if (MEM_Write32(DCRDR, value) != 0) {
        return -1;
    }

    // 3. 选择要写入的寄存器并设置写标志
    uint32_t dcrsr = DCRSR_REGWnR | (reg_num & DCRSR_REGSEL_MASK);
    if (MEM_Write32(DCRSR, dcrsr) != 0) {
        return -1;
    }

    // 4. 等待写入完成
    int timeout = 1000;
    do {
        if (MEM_Read32(DHCSR, &dhcsr) != 0) {
            return -1;
        }

        if (dhcsr & DHCSR_S_REGRDY) {
            return 0;
        }

        Delay_us(1);
        timeout--;
    } while (timeout > 0);

    return -1;
}

/**
 * @brief 打印所有核心寄存器
 */
void CPU_Print_Registers(void)
{
    const char *reg_names[] = {
        "R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7",
        "R8", "R9", "R10", "R11", "R12", "SP", "LR", "PC",
        "xPSR", "MSP", "PSP", "CONTROL"
    };

    printf("\n=== CPU寄存器 ===\n");

    for (int i = 0; i < 20; i++) {
        uint32_t value;
        if (CPU_Read_Register(i, &value) == 0) {
            printf("%-8s: 0x%08X\n", reg_names[i], value);
        }
    }

    printf("=================\n\n");
}

第六部分:实时跟踪技术

ITM(Instrumentation Trace Macrocell)

ITM提供了一种非侵入式的调试输出方法,通过SWO引脚输出跟踪数据。

/**
 * @brief ITM寄存器地址定义
 */
#define ITM_STIM0           0xE0000000  // 激励端口0
#define ITM_TER             0xE0000E00  // 跟踪使能寄存器
#define ITM_TPR             0xE0000E40  // 跟踪特权寄存器
#define ITM_TCR             0xE0000E80  // 跟踪控制寄存器
#define ITM_LAR             0xE0000FB0  // 锁访问寄存器

/**
 * @brief 初始化ITM
 * @param baudrate SWO波特率
 * @retval 0=成功, -1=失败
 */
int ITM_Init(uint32_t baudrate)
{
    uint32_t demcr;

    // 1. 使能跟踪
    if (MEM_Read32(DEMCR, &demcr) != 0) {
        return -1;
    }

    demcr |= DEMCR_TRCENA;
    if (MEM_Write32(DEMCR, demcr) != 0) {
        return -1;
    }

    // 2. 解锁ITM
    if (MEM_Write32(ITM_LAR, 0xC5ACCE55) != 0) {
        return -1;
    }

    // 3. 配置ITM
    uint32_t itm_tcr = (1 << 0) |   // ITM使能
                       (1 << 3) |   // 同步包使能
                       (1 << 16);   // 跟踪总线ID

    if (MEM_Write32(ITM_TCR, itm_tcr) != 0) {
        return -1;
    }

    // 4. 使能激励端口0
    if (MEM_Write32(ITM_TER, 0x00000001) != 0) {
        return -1;
    }

    // 5. 配置TPIU(Trace Port Interface Unit)
    // 这里需要根据具体芯片配置TPIU的分频器
    // 以匹配所需的SWO波特率

    printf("ITM初始化完成\n");
    return 0;
}

/**
 * @brief 通过ITM发送字符
 * @param ch 要发送的字符
 * @retval 0=成功, -1=失败
 */
int ITM_SendChar(char ch)
{
    uint32_t stim0;
    int timeout = 1000;

    // 等待激励端口就绪
    do {
        if (MEM_Read32(ITM_STIM0, &stim0) != 0) {
            return -1;
        }

        if (stim0 & 0x01) {  // 端口就绪
            break;
        }

        Delay_us(1);
        timeout--;
    } while (timeout > 0);

    if (timeout == 0) {
        return -1;
    }

    // 写入字符
    if (MEM_Write32(ITM_STIM0, ch) != 0) {
        return -1;
    }

    return 0;
}

/**
 * @brief 通过ITM发送字符串
 * @param str 要发送的字符串
 * @retval 0=成功, -1=失败
 */
int ITM_SendString(const char *str)
{
    while (*str) {
        if (ITM_SendChar(*str++) != 0) {
            return -1;
        }
    }
    return 0;
}

DWT(Data Watchpoint and Trace)

DWT提供了数据观察点和性能计数功能:

/**
 * @brief DWT寄存器地址定义
 */
#define DWT_CTRL            0xE0001000  // 控制寄存器
#define DWT_CYCCNT          0xE0001004  // 周期计数器
#define DWT_CPICNT          0xE0001008  // CPI计数器
#define DWT_EXCCNT          0xE000100C  // 异常计数器
#define DWT_SLEEPCNT        0xE0001010  // 睡眠计数器
#define DWT_LSUCNT          0xE0001014  // LSU计数器
#define DWT_FOLDCNT         0xE0001018  // 折叠计数器
#define DWT_COMP0           0xE0001020  // 比较器0
#define DWT_MASK0           0xE0001024  // 掩码0
#define DWT_FUNCTION0       0xE0001028  // 功能0

/**
 * @brief 初始化DWT
 * @retval 0=成功, -1=失败
 */
int DWT_Init(void)
{
    uint32_t demcr;

    // 1. 使能跟踪
    if (MEM_Read32(DEMCR, &demcr) != 0) {
        return -1;
    }

    demcr |= DEMCR_TRCENA;
    if (MEM_Write32(DEMCR, demcr) != 0) {
        return -1;
    }

    // 2. 复位周期计数器
    if (MEM_Write32(DWT_CYCCNT, 0) != 0) {
        return -1;
    }

    // 3. 使能周期计数器
    uint32_t dwt_ctrl;
    if (MEM_Read32(DWT_CTRL, &dwt_ctrl) != 0) {
        return -1;
    }

    dwt_ctrl |= (1 << 0);  // CYCCNTENA
    if (MEM_Write32(DWT_CTRL, dwt_ctrl) != 0) {
        return -1;
    }

    printf("DWT初始化完成\n");
    return 0;
}

/**
 * @brief 设置数据观察点
 * @param comp_num 比较器编号(0-3)
 * @param addr 观察地址
 * @param size 数据大小(0=字节, 1=半字, 2=字)
 * @param function 功能(0=禁用, 4=读, 5=写, 6=读写)
 * @retval 0=成功, -1=失败
 */
int DWT_Set_Watchpoint(uint8_t comp_num, uint32_t addr, 
                       uint8_t size, uint8_t function)
{
    uint32_t comp_base = DWT_COMP0 + (comp_num * 0x10);
    uint32_t mask_base = DWT_MASK0 + (comp_num * 0x10);
    uint32_t func_base = DWT_FUNCTION0 + (comp_num * 0x10);

    // 1. 设置比较地址
    if (MEM_Write32(comp_base, addr) != 0) {
        return -1;
    }

    // 2. 设置掩码(0表示精确匹配)
    if (MEM_Write32(mask_base, 0) != 0) {
        return -1;
    }

    // 3. 设置功能
    uint32_t func_val = (function << 0) | (size << 10);
    if (MEM_Write32(func_base, func_val) != 0) {
        return -1;
    }

    printf("数据观察点%d已设置:地址=0x%08X\n", comp_num, addr);
    return 0;
}

/**
 * @brief 清除数据观察点
 * @param comp_num 比较器编号(0-3)
 * @retval 0=成功, -1=失败
 */
int DWT_Clear_Watchpoint(uint8_t comp_num)
{
    uint32_t func_base = DWT_FUNCTION0 + (comp_num * 0x10);

    // 禁用比较器
    if (MEM_Write32(func_base, 0) != 0) {
        return -1;
    }

    printf("数据观察点%d已清除\n", comp_num);
    return 0;
}

断点设置

/**
 * @brief FPB寄存器地址定义
 */
#define FPB_CTRL            0xE0002000  // 控制寄存器
#define FPB_REMAP           0xE0002004  // 重映射寄存器
#define FPB_COMP0           0xE0002008  // 比较器0

/**
 * @brief 初始化FPB(Flash Patch and Breakpoint)
 * @retval 0=成功, -1=失败
 */
int FPB_Init(void)
{
    uint32_t fpb_ctrl;

    // 读取FPB控制寄存器
    if (MEM_Read32(FPB_CTRL, &fpb_ctrl) != 0) {
        return -1;
    }

    // 获取比较器数量
    uint8_t num_code = ((fpb_ctrl >> 8) & 0x70) | ((fpb_ctrl >> 4) & 0x0F);
    uint8_t num_lit = (fpb_ctrl >> 8) & 0x0F;

    printf("FPB: %d个代码比较器, %d个字面量比较器\n", num_code, num_lit);

    // 使能FPB
    fpb_ctrl |= (1 << 0);  // ENABLE
    fpb_ctrl |= (1 << 1);  // KEY

    if (MEM_Write32(FPB_CTRL, fpb_ctrl) != 0) {
        return -1;
    }

    printf("FPB初始化完成\n");
    return 0;
}

/**
 * @brief 设置硬件断点
 * @param comp_num 比较器编号
 * @param addr 断点地址
 * @retval 0=成功, -1=失败
 */
int FPB_Set_Breakpoint(uint8_t comp_num, uint32_t addr)
{
    uint32_t comp_addr = FPB_COMP0 + (comp_num * 4);

    // 构建比较器值
    // bit[31:2]: 地址[31:2]
    // bit[1]: 保留
    // bit[0]: 使能
    uint32_t comp_val = (addr & 0xFFFFFFFC) | 0x01;

    if (MEM_Write32(comp_addr, comp_val) != 0) {
        return -1;
    }

    printf("硬件断点%d已设置:地址=0x%08X\n", comp_num, addr);
    return 0;
}

/**
 * @brief 清除硬件断点
 * @param comp_num 比较器编号
 * @retval 0=成功, -1=失败
 */
int FPB_Clear_Breakpoint(uint8_t comp_num)
{
    uint32_t comp_addr = FPB_COMP0 + (comp_num * 4);

    // 禁用比较器
    if (MEM_Write32(comp_addr, 0) != 0) {
        return -1;
    }

    printf("硬件断点%d已清除\n", comp_num);
    return 0;
}

第七部分:使用OpenOCD进行调试

OpenOCD简介

OpenOCD(Open On-Chip Debugger)是一个开源的调试工具,支持多种调试接口和目标芯片。

OpenOCD架构

┌─────────────────────────────────────────┐
│         OpenOCD架构                      │
├─────────────────────────────────────────┤
│                                         │
│  ┌──────────────┐                      │
│  │   GDB客户端  │                      │
│  └──────────────┘                      │
│         │                               │
│         ↓ GDB协议                       │
│  ┌──────────────┐                      │
│  │  OpenOCD     │                      │
│  │  服务器      │                      │
│  └──────────────┘                      │
│         │                               │
│         ↓ 调试接口                      │
│  ┌──────────────┐                      │
│  │  调试适配器  │                      │
│  │ (ST-Link等)  │                      │
│  └──────────────┘                      │
│         │                               │
│         ↓ JTAG/SWD                     │
│  ┌──────────────┐                      │
│  │  目标芯片    │                      │
│  └──────────────┘                      │
└─────────────────────────────────────────┘

OpenOCD配置文件

接口配置(stlink.cfg)

# ST-Link调试器配置
interface stlink

# 传输协议选择
transport select hla_swd

# 适配器速度(kHz)
adapter speed 1000

目标配置(stm32f1x.cfg)

# STM32F1系列目标配置

# 芯片类型
set CHIPNAME stm32f1x
set WORKAREASIZE 0x5000

# 创建目标
target create $_CHIPNAME.cpu cortex_m -chain-position $_CHIPNAME.cpu

# 工作区域(用于算法)
$_CHIPNAME.cpu configure -work-area-phys 0x20000000 \
                         -work-area-size $WORKAREASIZE \
                         -work-area-backup 0

# Flash配置
flash bank $_CHIPNAME.flash stm32f1x 0x08000000 0 0 0 $_CHIPNAME.cpu

# 复位配置
reset_config srst_only

OpenOCD命令

启动OpenOCD

# 使用配置文件启动
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg

# 或使用自定义配置
openocd -f my_config.cfg

Telnet连接

# 连接到OpenOCD Telnet服务器(默认端口4444)
telnet localhost 4444

常用OpenOCD命令

# 停止目标
halt

# 恢复运行
resume

# 单步执行
step

# 复位
reset
reset halt  # 复位并停止

# 读取内存
mdw 0x20000000 10  # 读取10个字32
mdh 0x20000000 10  # 读取10个半字16
mdb 0x20000000 10  # 读取10个字节8

# 写入内存
mww 0x20000000 0x12345678  # 写入字
mwh 0x20000000 0x1234      # 写入半字
mwb 0x20000000 0x12        # 写入字节

# 读取寄存器
reg
reg r0
reg pc

# 写入寄存器
reg r0 0x12345678
reg pc 0x08000000

# 设置断点
bp 0x08000100 2 hw  # 硬件断点
bp 0x08000200 2     # 软件断点

# 删除断点
rbp 0x08000100

# 设置观察点
wp 0x20000000 4 r   # 读观察点
wp 0x20000000 4 w   # 写观察点
wp 0x20000000 4 a   # 读写观察点

# 删除观察点
rwp 0x20000000

# Flash操作
flash probe 0           # 探测Flash
flash info 0            # 显示Flash信息
flash erase_sector 0 0 last  # 擦除所有扇区
flash write_image erase firmware.bin 0x08000000  # 烧写固件

# 显示目标信息
targets
target current

使用GDB调试

启动GDB

# 启动ARM GDB
arm-none-eabi-gdb firmware.elf

# 在GDB中连接到OpenOCD
(gdb) target extended-remote localhost:3333

# 加载程序
(gdb) load

# 复位并停止
(gdb) monitor reset halt

# 设置断点
(gdb) break main
(gdb) break *0x08000100

# 运行
(gdb) continue

# 单步执行
(gdb) step
(gdb) next

# 查看寄存器
(gdb) info registers
(gdb) print $r0
(gdb) print $pc

# 查看内存
(gdb) x/10x 0x20000000
(gdb) x/10i $pc

# 查看变量
(gdb) print variable_name
(gdb) print *pointer

# 查看调用栈
(gdb) backtrace
(gdb) frame 0

# 观察点
(gdb) watch variable_name
(gdb) rwatch variable_name  # 读观察点
(gdb) awatch variable_name  # 读写观察点

OpenOCD脚本示例

自动化调试脚本(debug.tcl)

# 连接到目标
init

# 停止CPU
halt

# 读取设备ID
set idcode [dap info]
puts "Device ID: $idcode"

# 擦除Flash
flash erase_sector 0 0 last

# 烧写固件
flash write_image erase firmware.bin 0x08000000

# 验证
flash verify_image firmware.bin 0x08000000

# 复位并运行
reset run

# 退出
shutdown

执行脚本

openocd -f interface/stlink.cfg -f target/stm32f1x.cfg -f debug.tcl

第八部分:高级调试技巧

调试死锁问题

/**
 * @brief 检测死锁
 * @retval 0=正常, 1=死锁
 */
int Debug_Check_Deadlock(void)
{
    uint32_t dhcsr;
    uint32_t pc1, pc2;

    // 1. 读取当前PC
    if (CPU_Halt() != 0) {
        return -1;
    }

    if (CPU_Read_Register(15, &pc1) != 0) {
        return -1;
    }

    // 2. 恢复运行一小段时间
    if (CPU_Resume() != 0) {
        return -1;
    }

    Delay_ms(100);

    // 3. 再次读取PC
    if (CPU_Halt() != 0) {
        return -1;
    }

    if (CPU_Read_Register(15, &pc2) != 0) {
        return -1;
    }

    // 4. 比较PC
    if (pc1 == pc2) {
        printf("检测到死锁!PC=0x%08X\n", pc1);

        // 打印寄存器帮助诊断
        CPU_Print_Registers();

        return 1;  // 死锁
    }

    return 0;  // 正常
}

内存泄漏检测

/**
 * @brief 监控堆使用情况
 */
void Debug_Monitor_Heap(void)
{
    // 假设堆起始地址和大小已知
    uint32_t heap_start = 0x20001000;
    uint32_t heap_size = 0x1000;

    // 定期读取堆内存
    uint32_t *heap_data = malloc(heap_size);
    if (heap_data == NULL) {
        return;
    }

    if (MEM_Read_Block(heap_start, heap_data, heap_size / 4) != 0) {
        free(heap_data);
        return;
    }

    // 分析堆使用模式
    // 这里可以实现更复杂的分析逻辑

    free(heap_data);
}

性能分析

/**
 * @brief 使用DWT进行性能分析
 */
void Debug_Profile_Function(uint32_t func_addr)
{
    uint32_t start_cycles, end_cycles;

    // 1. 在函数入口设置断点
    if (FPB_Set_Breakpoint(0, func_addr) != 0) {
        return;
    }

    // 2. 恢复运行
    if (CPU_Resume() != 0) {
        return;
    }

    // 3. 等待断点触发
    // (这里需要轮询或使用中断)

    // 4. 读取开始周期数
    if (MEM_Read32(DWT_CYCCNT, &start_cycles) != 0) {
        return;
    }

    // 5. 单步执行整个函数
    // (这里需要循环单步直到函数返回)

    // 6. 读取结束周期数
    if (MEM_Read32(DWT_CYCCNT, &end_cycles) != 0) {
        return;
    }

    // 7. 计算执行时间
    uint32_t cycles = end_cycles - start_cycles;
    printf("函数执行周期数: %u\n", cycles);

    // 8. 清除断点
    FPB_Clear_Breakpoint(0);
}

实时日志输出

/**
 * @brief 实时读取ITM输出
 */
void Debug_Read_ITM_Output(void)
{
    // 这个函数应该在主机端运行
    // 通过SWO引脚读取ITM数据

    // 伪代码示例:
    while (1) {
        // 读取SWO数据
        uint8_t data = SWO_Read_Byte();

        // 解析ITM包
        if (data & 0x01) {  // 激励端口包
            uint8_t port = (data >> 3) & 0x1F;
            uint8_t size = (data & 0x06) >> 1;

            // 读取数据
            for (int i = 0; i < (1 << size); i++) {
                uint8_t byte = SWO_Read_Byte();
                printf("%c", byte);
            }
        }
    }
}

总结

本教程深入讲解了JTAG和SWD调试协议的底层原理和实现方法,主要内容包括:

核心要点

  1. JTAG协议
  2. 5线接口,16状态状态机
  3. 支持链式连接多个设备
  4. 通过IR和DR寄存器进行操作

  5. SWD协议

  6. 2线接口,更简单高效
  7. 双向数据传输
  8. 包格式:请求+ACK+数据

  9. 调试端口(DP)

  10. 提供调试接口访问
  11. 控制电源域
  12. 错误管理

  13. 访问端口(AP)

  14. MEM-AP用于内存访问
  15. 支持自动地址递增
  16. 高效的块传输

  17. CPU调试

  18. 停机、恢复、单步执行
  19. 寄存器读写
  20. 复位控制

  21. 实时跟踪

  22. ITM:非侵入式日志输出
  23. DWT:数据观察点和性能计数
  24. FPB:硬件断点

  25. OpenOCD

  26. 开源调试工具
  27. 支持多种接口和目标
  28. 与GDB集成

最佳实践

  1. 调试接口选择
  2. 单设备优先使用SWD(引脚少)
  3. 多设备链式连接使用JTAG
  4. 需要实时跟踪时使用SWD+SWO

  5. 性能优化

  6. 使用块传输提高效率
  7. 合理设置时钟频率
  8. 减少不必要的读写操作

  9. 错误处理

  10. 始终检查ACK响应
  11. 处理WAIT和FAULT
  12. 清除粘性错误标志

  13. 安全考虑

  14. 保护调试接口
  15. 使用调试认证
  16. 生产环境禁用调试

应用场景

  1. 开发调试
  2. 使用GDB进行源码级调试
  3. 设置断点和观察点
  4. 查看变量和内存

  5. 性能分析

  6. 使用DWT测量执行时间
  7. 分析性能瓶颈
  8. 优化关键代码

  9. 故障诊断

  10. 检测死锁和死循环
  11. 分析崩溃原因
  12. 监控系统状态

  13. 生产测试

  14. 自动化测试脚本
  15. Flash烧写
  16. 功能验证

延伸阅读

推荐进一步学习的内容:

  1. 调试技术
  2. 基于性能计数器的系统性能分析工具
  3. 中断向量表与异常处理机制
  4. 处理器工作模式与特权级别

  5. CoreSight架构

  6. ARM CoreSight Architecture Specification
  7. ETM(Embedded Trace Macrocell)
  8. CTI(Cross Trigger Interface)

  9. 调试工具

  10. OpenOCD官方文档
  11. GDB调试指南
  12. SEGGER J-Link文档

  13. 高级主题

  14. 多核调试
  15. 安全调试(TrustZone)
  16. 实时操作系统调试

参考资料

  1. ARM Debug Interface Architecture Specification (ADIv5)
  2. ARM CoreSight Components Technical Reference Manual
  3. OpenOCD User's Guide
  4. STM32 Programming Manual
  5. SEGGER J-Link / J-Trace User Guide

练习题

  1. 实现一个简单的SWD调试器,能够读取目标芯片的IDCODE。

  2. 编写代码通过MEM-AP读写目标芯片的内存,并验证数据正确性。

  3. 使用OpenOCD编写脚本,自动化完成以下任务:

  4. 连接目标
  5. 擦除Flash
  6. 烧写固件
  7. 验证固件
  8. 复位运行

  9. 实现一个性能分析工具,使用DWT测量函数执行时间。

  10. 配置ITM,实现通过SWO输出调试信息,并在主机端接收显示。

下一步:建议学习 基于性能计数器的系统性能分析工具,将调试技术应用到实际的性能分析中。