10. 调试与测试方法
10.1 引言
在嵌入式系统开发中,调试与测试是确保系统稳定性和可靠性的关键步骤。LPC1100 系列单片机基于 ARM Cortex-M0 核心,提供了多种调试和测试工具,以帮助开发者识别和解决问题。本节将详细介绍 LPC1100 系列的调试与测试方法,包括硬件调试接口、软件调试工具、单元测试和系统测试等方面。
10.2 硬件调试接口
LPC1100 系列单片机支持多种硬件调试接口,包括 JTAG 和 SWD(Serial Wire Debug)。这些接口允许开发者通过调试工具连接到单片机,进行断点设置、单步执行、内存查看和修改等操作。
10.2.1 JTAG 接口
JTAG(Joint Test Action Group)接口是一种标准的调试接口,广泛用于嵌入式系统中。LPC1100 系列单片机通常有 4 个 JTAG 引脚:TMS、TCK、TDI 和 TDO。
- TMS(Test Mode Select): 用于选择 JTAG 状态机的不同状态。
- TCK(Test Clock): 用于同步 JTAG 操作的时钟信号。
- TDI(Test Data In): 用于输入测试数据。
- TDO(Test Data Out): 用于输出测试数据。
10.2.2 SWD 接口
SWD(Serial Wire Debug)接口是一种更简单的调试接口,适用于 ARM Cortex-M0 核心。LPC1100 系列单片机通常有 2 个 SWD 引脚:SWDIO 和 SWCLK。
- SWDIO(Serial Wire Debug I/O): 用于双向数据传输。
- SWCLK(Serial Wire Debug Clock): 用于同步数据传输的时钟信号。
10.2.3 调试适配器
为了连接调试接口,需要使用调试适配器。常用的调试适配器包括:
- LPC-Link2: NXP 官方提供的调试适配器,支持 JTAG 和 SWD 接口。
- Segger J-Link: 第三方提供的高性能调试适配器,支持多种 ARM 核心。
- OpenOCD: 开源的调试适配器软件,支持多种调试硬件。
10.2.4 示例:使用 LPC-Link2 进行调试
// LPC-Link2 连接示例
// 硬件连接:将 LPC-Link2 的 SWD 接口连接到 LPC1100 的 SWD 引脚
// 软件配置:使用 Keil MDK 或 IAR Embedded Workbench 配置调试适配器
// 1. 在 Keil MDK 中配置 LPC-Link2
// 打开项目设置 -> Debug -> Settings
// 选择 "LPC-Link2" 作为调试适配器
// 选择 "SWD" 作为调试接口
// 2. 在 IAR Embedded Workbench 中配置 LPC-Link2
// 打开项目选项 -> Debugger -> Setup
// 选择 "LPC-Link2" 作为调试适配器
// 选择 "SWD" 作为调试接口
10.3 软件调试工具
10.3.1 Keil MDK
Keil MDK(Microcontroller Development Kit)是一款常用的 ARM 单片机开发工具,提供了集成的调试环境。
10.3.1.1 断点设置
在 Keil MDK 中,可以通过以下方法设置断点:
- 源代码断点: 在代码行前点击鼠标左键,或使用快捷键
F9
。 - 数据断点: 在变量声明处或变量使用处设置断点,观察变量的值变化。
// 示例:在源代码中设置断点
void main(void) {
// 初始化硬件
SystemInit();
// 设置源代码断点
int count = 0; // 在这里设置断点
while (1) {
// 执行任务
count++;
if (count > 100) {
count = 0;
}
}
}
10.3.1.2 单步执行
在 Keil MDK 中,可以通过以下快捷键进行单步执行:
- F10: 单步执行,不进入函数。
- F11: 单步执行,进入函数。
10.3.1.3 内存查看与修改
在 Keil MDK 中,可以使用 Memory 视图查看和修改内存内容。
// 示例:查看特定地址的内存内容
// 打开 Memory 视图
// 输入地址:0x20000000
// 查看和修改该地址的内存内容
10.3.2 IAR Embedded Workbench
IAR Embedded Workbench 是另一款常用的 ARM 单片机开发工具,提供了强大的调试功能。
10.3.2.1 断点设置
在 IAR Embedded Workbench 中,可以通过以下方法设置断点:
- 源代码断点: 在代码行前点击鼠标左键,或使用快捷键
F9
。 - 数据断点: 在变量声明处或变量使用处设置断点,观察变量的值变化。
// 示例:在源代码中设置断点
void main(void) {
// 初始化硬件
SystemInit();
// 设置源代码断点
int count = 0; // 在这里设置断点
while (1) {
// 执行任务
count++;
if (count > 100) {
count = 0;
}
}
}
10.3.2.2 单步执行
在 IAR Embedded Workbench 中,可以通过以下快捷键进行单步执行:
- F10: 单步执行,不进入函数。
- F11: 单步执行,进入函数。
10.3.2.3 内存查看与修改
在 IAR Embedded Workbench 中,可以使用 Memory 视图查看和修改内存内容。
// 示例:查看特定地址的内存内容
// 打开 Memory 视图
// 输入地址:0x20000000
// 查看和修改该地址的内存内容
10.3.3 OpenOCD
OpenOCD 是一款开源的调试工具,支持多种调试硬件和接口。可以通过命令行或集成在开发环境中使用。
10.3.3.1 配置 OpenOCD
在使用 OpenOCD 之前,需要配置调试适配器和目标设备。
# 配置文件示例:lpc1100.cfg
source [find interface/lpc-link2.cfg]
source [find target/lpc1100.cfg]
10.3.3.2 启动调试
通过命令行启动 OpenOCD,并连接到调试适配器。
# 启动 OpenOCD
openocd -f lpc1100.cfg
10.3.3.3 使用 GDB 进行调试
可以使用 GDB(GNU Debugger)连接到 OpenOCD 进行调试。
# 启动 GDB
arm-none-eabi-gdb
# 连接到 OpenOCD
(gdb) target extended-remote :3333
# 设置断点
(gdb) break main
# 运行程序
(gdb) continue
10.4 单元测试
单元测试是测试软件模块或函数的一种方法,确保每个模块在集成之前都能正常工作。LPC1100 系列单片机支持多种单元测试框架,如 CMocka 和 Unity。
10.4.1 CMocka
CMocka 是一个用于 C 语言的单元测试框架,支持复杂的测试场景。
10.4.1.1 安装 CMocka
# 安装 CMocka
sudo apt-get install libcmocka0 libcmocka-dev
10.4.1.2 编写测试用例
// 示例:测试一个简单的函数
#include <cmocka.h>
// 被测试的函数
int add(int a, int b) {
return a + b;
}
// 测试用例
static void test_add(void **state) {
(void) state; // unused
assert_int_equal(add(1, 2), 3);
assert_int_equal(add(0, 0), 0);
assert_int_equal(add(-1, 1), 0);
}
// 测试套件
int main(void) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_add),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}
10.4.2 Unity
Unity 是一个轻量级的单元测试框架,适用于嵌入式系统。
10.4.2.1 安装 Unity
# 下载 Unity 源代码
git clone https://github.com/ThrowTheSwitch/Unity.git
# 将 Unity 源代码添加到项目中
10.4.2.2 编写测试用例
// 示例:测试一个简单的函数
#include "unity.h"
// 被测试的函数
int add(int a, int b) {
return a + b;
}
// 测试用例
void test_add(void) {
TEST_ASSERT_EQUAL_INT(3, add(1, 2));
TEST_ASSERT_EQUAL_INT(0, add(0, 0));
TEST_ASSERT_EQUAL_INT(0, add(-1, 1));
}
// 测试套件
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_add);
return UNITY_END();
}
10.5 系统测试
系统测试是在整个系统集成之后进行的测试,确保所有模块协同工作正常。LPC1100 系列单片机支持多种系统测试方法,如使用串口输出日志、使用 LED 指示灯等。
10.5.1 串口输出日志
通过串口输出日志是一种常用的系统测试方法,可以在运行时查看系统的状态和输出。
10.5.1.1 初始化串口
// 串口初始化示例
#include "LPC11xx.h"
void UART_Init(void) {
// 配置 UART 时钟
LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 12); // 使能 UART 时钟
// 配置 UART 引脚
LPC_IOCON->P0_0 |= (1 << 0); // P0.0 为 UART TX
LPC_IOCON->P0_1 |= (1 << 0); // P0.1 为 UART RX
// 配置 UART 波特率
LPC_USART->LCR = 0x83; // 8 位数据,1 位停止,无奇偶校验
LPC_USART->DLL = 24; // 设置波特率
LPC_USART->DLM = 0; // 设置波特率
LPC_USART->FDR = 0x11; // 设置波特率
LPC_USART->LCR = 0x03; // 8 位数据,1 位停止,无奇偶校验
// 使能 UART 传输
LPC_USART->IER = 1; // 使能 RX 中断
LPC_USART->FCR = 0x07; // 使能 FIFO,复位 FIFO,设置触发阈值
}
// 串口发送字符
void UART_SendChar(char data) {
while (!(LPC_USART->LSR & 0x20)); // 等待发送缓冲区为空
LPC_USART->THR = data; // 发送数据
}
// 串口发送字符串
void UART_SendString(const char *str) {
while (*str) {
UART_SendChar(*str++);
}
}
10.5.1.2 使用串口输出日志
// 示例:在主函数中使用串口输出日志
void main(void) {
// 初始化硬件
SystemInit();
UART_Init();
// 输出日志
UART_SendString("System initialized\n");
int count = 0;
while (1) {
// 执行任务
count++;
if (count > 100) {
count = 0;
UART_SendString("Count reset to 0\n");
}
}
}
10.5.2 使用 LED 指示灯
LED 指示灯是一种直观的系统测试方法,可以用来显示系统的状态。
10.5.2.1 初始化 LED
// LED 初始化示例
#include "LPC11xx.h"
void LED_Init(void) {
// 配置 LED 引脚
LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 9); // 使能 GPIO 时钟
LPC_GPIO->DIR |= (1 << 17); // P1.17 为输出
}
// LED 点亮
void LED_On(void) {
LPC_GPIO->DATA |= (1 << 17); // P1.17 点亮
}
// LED 熄灭
void LED_Off(void) {
LPC_GPIO->DATA &= ~(1 << 17); // P1.17 熄灭
}
10.5.2.2 使用 LED 指示系统状态
// 示例:在主函数中使用 LED 指示系统状态
void main(void) {
// 初始化硬件
SystemInit();
LED_Init();
int count = 0;
while (1) {
// 执行任务
count++;
if (count > 100) {
count = 0;
LED_On();
for (int i = 0; i < 100000; i++) {
__NOP(); // 延时
}
LED_Off();
}
}
}
10.6 性能测试
性能测试是评估系统运行效率和资源使用情况的一种方法。LPC1100 系列单片机可以通过定时器和性能分析工具进行性能测试。
10.6.1 定时器
LPC1100 系列单片机内置多个定时器,可以用来测量函数的执行时间。
10.6.1.1 初始化定时器
// 定时器初始化示例
#include "LPC11xx.h"
void Timer_Init(void) {
// 使能定时器时钟
LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 0); // 使能 Timer 0 时钟
// 配置定时器
LPC_TMR32B0->CTCR = 0x00; // 选择上升沿计数
LPC_TMR32B0->PR = 0x00; // 预分频值为 0
LPC_TMR32B0->TCR = 0x02; // 复位定时器
LPC_TMR32B0->TCR = 0x01; // 使能定时器
}
10.6.1.2 测量函数执行时间
// 测量函数执行时间示例
#include "LPC11xx.h"
void Timer_Start(void) {
LPC_TMR32B0->TCR = 0x02; // 复位定时器
LPC_TMR32B0->TCR = 0x01; // 使能定时器
}
uint32_t Timer_Stop(void) {
LPC_TMR32B0->TCR = 0x00; // 停止定时器
return LPC_TMR32B0->TC; // 获取计数值
}
void test_function(void) {
// 模拟一个耗时的操作
for (int i = 0; i < 1000000; i++) {
__NOP(); // 延时
}
}
int main(void) {
// 初始化硬件
SystemInit();
UART_Init();
Timer_Init();
// 测量函数执行时间
Timer_Start();
test_function();
uint32_t elapsed_time = Timer_Stop();
// 输出结果
UART_SendString("Elapsed time: ");
UART_SendString(itoa(elapsed_time));
UART_SendString(" ticks\n");
while (1) {
// 运行其他任务
}
}
10.6.2 性能分析工具
使用性能分析工具(如 IAR Embedded Workbench 的 C-SPY)可以更详细地分析系统的性能。
10.6.2.1 配置 C-SPY
在 IAR Embedded Workbench 中,可以通过以下步骤配置 C-SPY 性能分析工具:
- 打开项目选项 -> Debugger -> Setup
- 选择 “Performance Analysis” 选项卡
- 配置性能分析参数
10.6.2.2 使用 C-SPY 进行性能分析
// 示例:使用 C-SPY 进行性能分析
#include "LPC11xx.h"
void test_function(void) {
// 模拟一个耗时的操作
for (int i = 0; i < 1000000; i++) {
__NOP(); // 延时
}
}
int main(void) {
// 初始化硬件
SystemInit();
// 测量函数执行时间
// 使用 C-SPY 的性能分析功能
__iar_profile_start(); // 开始性能分析
test_function();
__iar_profile_stop(); // 停止性能分析
while (1) {
// 运行其他任务
}
}
10.6.3 使用 Keil MDK 的性能分析工具
Keil MDK 也提供了一套强大的性能分析工具,可以用来分析程序的执行时间和内存使用情况。
10.6.3.1 配置性能分析工具
在 Keil MDK 中,可以通过以下步骤配置性能分析工具:
- 打开项目设置 -> Debug -> Settings
- 选择 “Profiler” 选项卡
- 配置性能分析参数
10.6.3.2 使用性能分析工具进行分析
// 示例:使用 Keil MDK 的性能分析工具
#include "LPC11xx.h"
void test_function(void) {
// 模拟一个耗时的操作
for (int i = 0; i < 1000000; i++) {
__NOP(); // 延时
}
}
int main(void) {
// 初始化硬件
SystemInit();
// 测量函数执行时间
// 使用 Keil MDK 的性能分析功能
__CPROFILER_START(); // 开始性能分析
test_function();
__CPROFILER_STOP(); // 停止性能分析
while (1) {
// 运行其他任务
}
}
10.7 调试技巧
在调试过程中,掌握一些调试技巧可以显著提高效率和准确性。以下是一些常用的调试技巧:
10.7.1 使用条件断点
条件断点允许在满足特定条件时暂停程序执行,这对于调试循环或条件分支非常有用。
10.7.1.1 在 Keil MDK 中设置条件断点
- 在代码行前点击鼠标右键
- 选择 “Breakpoint” -> “Condition”
- 输入条件表达式
// 示例:在 Keil MDK 中设置条件断点
void main(void) {
// 初始化硬件
SystemInit();
UART_Init();
int count = 0;
while (1) {
// 执行任务
count++;
if (count > 100) {
count = 0;
UART_SendString("Count reset to 0\n");
}
}
}
10.7.1.2 在 IAR Embedded Workbench 中设置条件断点
- 在代码行前点击鼠标右键
- 选择 “Breakpoint” -> “Condition”
- 输入条件表达式
10.7.2 使用日志记录
通过在代码中添加日志记录,可以在运行时查看关键变量的值和程序的执行流程。
10.7.2.1 在代码中添加日志记录
// 示例:在代码中添加日志记录
void main(void) {
// 初始化硬件
SystemInit();
UART_Init();
int count = 0;
while (1) {
// 执行任务
count++;
UART_SendString("Count: ");
UART_SendString(itoa(count));
UART_SendString("\n");
if (count > 100) {
count = 0;
UART_SendString("Count reset to 0\n");
}
}
}
10.7.3 使用实时数据查看
实时数据查看功能允许开发者在运行时查看变量的值,这对于调试动态变化的系统非常有用。
10.7.3.1 在 Keil MDK 中使用实时数据查看
- 打开 “Watch” 视图
- 添加需要查看的变量
10.7.3.2 在 IAR Embedded Workbench 中使用实时数据查看
- 打开 “Watch” 视图
- 添加需要查看的变量
10.7.4 使用硬件断点
硬件断点利用单片机内部的硬件资源,可以更精确地控制断点位置。
10.7.4.1 在 Keil MDK 中设置硬件断点
- 在代码行前点击鼠标右键
- 选择 “Breakpoint” -> “Hardware Breakpoint”
10.7.4.2 在 IAR Embedded Workbench 中设置硬件断点
- 在代码行前点击鼠标右键
- 选择 “Breakpoint” -> “Hardware Breakpoint”
10.8 结论
调试与测试是嵌入式系统开发中不可或缺的环节。LPC1100 系列单片机提供了多种硬件和软件工具,帮助开发者高效地进行调试和测试。通过合理的使用这些工具和技巧,可以显著提高系统的稳定性和可靠性。希望本节的内容对您的开发工作有所帮助。