基于ARM的裸机程序设计和开发(六):Zynq裸机程序调试方法
Zynq裸机程序在线调试方法 本文介绍了Zynq裸机开发中的在线调试技术。在Xilinx SDK环境下,通过调试模式可以控制程序执行流程,设置断点暂停程序,单步执行观察变量变化。重点讲解了变量查看、断点设置、内存监视等核心调试方法,帮助开发者分析程序逻辑错误、寄存器配置问题。调试能力是裸机开发的核心技能,能够有效定位LED控制异常、外设初始化失败等常见问题。通过Memory窗口可直接查看DDR数据
文章目录
概要
在 Zynq 裸机开发过程中,程序写出来之后并不一定能一次运行正确。很多时候,程序无法实现预期功能,并不是因为语法错误,而是由于逻辑设计不合理、变量值异常、寄存器配置错误,或者程序流程与预期不一致。这时,掌握在线调试方法就显得尤为重要。本文结合 Xilinx SDK 开发环境,介绍 Zynq 裸机程序的基本调试思路,包括如何进入调试模式、如何设置断点、如何单步执行程序、如何查看局部变量与全局变量,以及如何通过 Memory 窗口观察 DDR 和外设寄存器内容,为后续独立分析和定位程序问题打下基础。
关键词
Zynq;裸机开发;SDK;Debug;断点;变量;Memory;在线调试
一、前言
在前面的内容中,我们已经学习了基于 SDK 硬件驱动库的编程方法,也通过 GPIO 实现了 LED 点亮和简单控制。
但是在真正的开发过程中,程序并不会总是一次就写对。
很多初学者都有过类似经历:
- 程序可以编译通过,但运行现象不对;
- LED 不亮,或者亮灭节奏不对;
- 按键逻辑和预期不一致;
- 串口没有输出;
- 程序似乎“卡住了”,却不知道停在什么地方。
这类问题,单靠“猜”是很难解决的。
真正有效的方法,是进入调试状态,一步一步观察程序运行过程,查看变量值、分支走向和寄存器状态,从而找到问题所在。
这就是所谓的 Debug,也常被很多开发者称为在线调试或在线仿真。
对于 ARM 裸机开发来说,调试能力并不是附属技能,而是非常核心的开发能力。
如果说写代码是“搭系统”,那么调试代码就是“查问题”。一个只会照着例程抄程序、但不会调试程序的人,很难真正具备独立开发能力。
因此,这一篇就来系统整理 Zynq 裸机程序的基本调试方法。
二、什么是在线调试
所谓在线调试,就是在程序下载到开发板后,不是让它直接全速运行,而是让开发环境接管程序执行过程,从而实现:
- 控制程序暂停和继续;
- 单步执行每一行代码;
- 设置断点,在指定位置自动停下;
- 查看当前变量值;
- 修改某些变量值;
- 观察内存数据;
- 查看外设寄存器状态。
对于 Zynq 来说,Xilinx SDK 已经提供了较为便捷的在线调试功能。
开发者不需要额外搭建复杂环境,只要在 SDK 中进入调试模式,就可以完成大部分基础调试工作。
可以把它简单理解为:
程序不再是“一跑到底”,而是可以被我们“暂停、观察、分析、继续”。
这对于分析裸机程序问题非常重要。
三、为什么裸机开发一定要学会调试
在裸机开发中,很多错误并不会明确报出来。
例如:
- 某个 GPIO 方向没有配置对;
- 某个循环条件写错了;
- 某个寄存器写入值不正确;
- 某个数组没有按预期存到 DDR;
- 某个分支根本没有执行进去。
这些问题和普通 PC 软件编程不太一样。
普通应用程序出错时,可能会弹出异常信息,或者直接报错退出;但裸机程序很多时候只是“现象不对”,并不会告诉你具体哪里错了。
这时,调试的作用就体现出来了。
通过调试,我们可以回答下面这些关键问题:
- 程序到底有没有执行到某一行?
- 变量的值是不是和预期一样?
- if 分支到底进了哪一边?
- 某个函数是否真的被调用了?
- DDR 中是否真的写入了想要的数据?
- 外设寄存器当前值是不是对的?
所以说,在线调试对 ARM 裸机开发的意义,有点像 ModelSim 对 FPGA 逻辑设计的意义:
它不是可有可无的辅助工具,而是定位问题、分析问题的重要手段。
四、调试时主要关注哪些内容
在实际调试中,通常最常看的有四类信息:
- 变量(Variables)
用于查看当前函数中的局部变量,以及部分能够直接识别到的全局变量。
- 断点(Breakpoints)
用于让程序运行到指定代码行时自动停下,便于集中分析某一位置附近的逻辑。
- 表达式(Expressions)
用于手动添加想观察的变量、地址或表达式,便于持续监视其变化。
- 存储器(Memory)
用于查看某段 DDR 内存中的数据,或者直接查看某个外设寄存器地址上的内容。
这四类信息基本构成了裸机程序调试时最常用的观察窗口。
五、如何进入调试模式
在 SDK 中完成程序编译后,通常可以通过工具栏或右键菜单进入调试模式。
进入调试状态后,开发环境会自动连接处理器,并将程序加载到目标板运行环境中。
与普通“Run”不同,进入“Debug”后,程序不会简单地全速跑完,而是会在入口位置或者预设断点处停下来,等待开发者进一步控制。
进入调试模式后,SDK 的界面通常会切换到专门的 Debug 视图,此时可以看到:
- 当前程序执行位置
- 调用栈
- 断点信息
- 变量窗口
- 内存窗口
- 表达式窗口
对于初学者来说,第一次看到这些界面可能会觉得有点复杂,但其实只要抓住几个最常用的功能,就足够完成大部分基本调试任务。
六、单步调试的几种基本操作
进入调试模式之后,最常用的就是对程序执行流程进行控制。
- 单步运行
单步运行的意思是:
让程序执行当前这一行,然后停在下一行。
这适合用来观察每执行一步后,变量值发生了什么变化。
例如在一个 for 循环中,就可以通过单步执行来看循环变量 i 是如何变化的。
- 进入函数
当程序执行到某个函数调用语句时,如果选择“进入函数”,那么调试器会直接跳进这个函数内部,逐行观察函数内部代码执行过程。
这在分析驱动函数、自己封装的函数或者判断某个函数到底做了什么时,非常有用。
- 跳出函数
如果当前已经在一个函数内部,但不想继续一行一行往下跟,就可以使用“跳出函数”。
这样程序会把当前函数剩余部分执行完,然后停在调用该函数之后的位置。
- 全速运行
当你已经分析清楚某一段逻辑,不想再慢慢单步执行时,可以让程序恢复全速运行,直到遇到下一个断点,或者程序自然结束。
这在调试较长程序时能大幅节省时间。
七、断点的添加与使用
如果每次都从 main() 开始一行一行往下单步执行,会非常低效。
特别是程序前面有大量初始化代码,而你真正关心的是后面某个位置时,这种方式就很不实用了。
这时候就要用到断点。
- 什么是断点
断点可以理解成程序运行过程中的一个“拦截点”。
当程序执行到设置断点的那一行时,会自动停下来,等待开发者查看当前状态。
- 为什么要用断点
断点最大的作用,就是让程序:
平时全速运行,只在你关心的位置停下。
例如你想分析某个 if 分支是否会进入,或者想看某个循环跑到第几次时出现异常,就可以在对应位置设置断点。
- 断点的基本操作
调试时,常见的断点操作包括:
- 添加断点
- 取消断点
- 暂时禁用断点
- 查看当前所有断点
对于初学者来说,最实用的用法通常就是:
- 在怀疑有问题的代码行打断点;
- 全速运行程序;
- 程序停下后查看变量与流程;
- 根据结果继续单步或继续运行。
这样比从头慢慢单步要高效得多。
八、如何查看局部变量和全局变量
程序停在某一位置后,最先要看的通常就是变量。
- 局部变量
局部变量是当前函数内部定义的变量,例如:
int i;
int rev[32];
当程序执行到这些变量所在作用域时,调试器通常会自动在 Variables 窗口中显示它们的当前值。
通过查看局部变量,我们可以判断:
- 循环变量是不是按预期递增;
- 某个数组元素有没有被正确赋值;
- 某个临时结果是不是异常;
- 某个函数内部计算结果是否正确。
- 全局变量
全局变量不一定总会自动显示在局部变量窗口里,这时可以通过 Expressions 窗口手动添加需要观察的变量名。
全局变量的调试特别适合用来观察:
- 程序状态标志位
- 全局缓冲区
- 中断标记
- 外设控制状态
在某些情况下,调试器还允许直接修改变量值,这对于验证程序逻辑也很有帮助。例如你怀疑某个分支条件永远进不去,就可以尝试修改变量值,看程序行为是否改变。
九、Expressions 的作用
Expressions,也就是表达式窗口,是调试中非常好用但容易被忽视的功能。
它和 Variables 的区别在于:
- Variables 更偏向于自动显示当前作用域中的变量;
- Expressions 则允许你主动添加想观察的内容。
例如你可以添加:
- 某个局部变量
- 某个全局变量
- 某个数组元素
- 某个指针地址
- 某个表达式结果
这对于长期监视某个关键变量特别有用。
举个例子,如果你正在调试 LED 闪烁程序,就可以把控制状态变量、计数变量、GPIO 输出变量都加入 Expressions 窗口中,这样每次单步执行时都能快速看到它们的变化,而不用反复展开查看。
十、如何查看 Memory 内容
在裸机程序调试中,Memory 窗口非常重要。
因为很多时候我们关心的并不只是普通变量,而是某一段固定地址中的内容。
Memory 窗口主要有两个典型用途:
- 查看 DDR 中的数据
比如在数据采集系统中,FPGA 可能会把采集到的数据通过 AXI 或 DMA 写入 PS 侧 DDR。
这时如果想知道数据到底有没有写进去、写进去后内容对不对,就可以直接在 Memory 窗口中输入对应 DDR 地址,查看那段内存中的真实数据。
- 查看外设寄存器值
从 CPU 的角度看,外设控制器也是映射到地址空间中的。
因此,外设寄存器本质上也可以像内存一样查看。
例如,如果你想知道 GPIO 控制寄存器的当前值,或者确认某个 UART 状态寄存器是否变化,就可以把相应寄存器地址加到 Memory 窗口中观察。
这一点非常重要,因为有时程序看起来“写了寄存器”,但实际上写入值并不对,或者某个寄存器值根本没变。
直接看 Memory,往往比单纯猜测更可靠。
十一、通过示例理解 Memory 调试方法
下面这个例程就是一个很典型的 DDR 调试示例:
#include "xparameters_ps.h"
#include "xil_io.h"
#define DDR_BASEARDDR XPAR_DDR_MEM_BASEADDR + 0x10000000
int main()
{
int i;
int rev[32];
for(i=0; i<32; i++)
{
Xil_Out32(DDR_BASEARDDR+i*4, i);
}
for(i=0; i<32; i++)
{
rev[i] = Xil_In32(DDR_BASEARDDR+i*4);
}
return 0;
}
这段程序做了两件事:
第一步,把 0 到 31 这 32 个整数依次写入 DDR 某段地址空间;
第二步,再把这 32 个地址中的内容读回来,存到数组 rev[32] 中。
- 这段程序为什么适合调试演示
因为它同时涉及:
- 局部变量 i
- 局部数组 rev
- 固定地址的 DDR 空间
- 写内存操作
- 读内存操作
所以在调试时,我们可以从多个角度观察程序运行结果。
- 可以观察什么
调试这段程序时,可以重点观察:
- i 在循环中是否按预期从 0 递增到 31;
- rev[i] 中读回的数据是否和写入的一致;
- DDR_BASEARDDR 起始地址对应的内存区域中,是否真的出现了 0、1、2、3……31 这些数据。
如果程序逻辑正确,那么最终应该看到:
- DDR 中保存的是顺序递增数据;
- rev 数组中也保存着相同内容。
- 这个例子说明了什么
这个例子很好地说明了一点:
调试不仅可以看“变量”,也可以直接看“地址空间中的数据”。
这对于后续分析 DDR 缓冲区、图像数据、采样数据、DMA 结果等场景非常有帮助。
十二、调试外设寄存器的思路
除了看 DDR,Memory 窗口还可以直接用于观察外设寄存器。
例如你正在调试 GPIO 点灯程序,就可以把 GPIO 控制器对应的基地址输入到 Memory 窗口里,然后重点观察:
- 方向寄存器值是否正确
- 输出使能寄存器值是否正确
- 数据寄存器值是否发生变化
这样你就能把“程序行为”和“寄存器变化”对应起来。
举个简单例子:
- 如果程序里调用了设置方向为输出的函数,但在 Memory 中看到方向寄存器那一位并没有被置位,那么说明程序并没有真正把配置写进去;
- 如果方向已经正确,但数据寄存器始终没变化,那问题可能出在输出逻辑;
- 如果寄存器都对,但 LED 还是不亮,那就要进一步考虑硬件连接、电平极性或引脚复用配置。
这就是在线调试相比单纯看代码的最大价值:
它能把“代码逻辑”和“硬件实际状态”联系起来。
十三、调试程序时的一般分析流程
很多初学者虽然知道怎么点调试按钮,但真到程序出问题时,还是不知道从哪开始看。
这里可以总结一个比较实用的思路。
- 先确认程序有没有跑到关键位置
最先做的,不是急着看一堆变量,而是先打断点确认程序是否真的执行到了你关心的那一段代码。
- 再看关键变量是否正常
如果程序执行到了目标位置,就重点看几个关键变量,例如:
- 循环变量
- 条件判断变量
- 状态标志位
- 输入输出数据
不要一开始就什么都看,重点盯几个最可能影响逻辑的变量。
- 再看分支走向是否符合预期
if、while、for 这些地方,最容易出现逻辑偏差。
要重点观察程序到底进了哪个分支,而不是只看自己“以为它该进哪里”。
- 最后结合 Memory 看寄存器或 DDR
如果变量和流程都基本正常,但现象还是不对,就要进一步看底层地址空间中的数据,确认:
- DDR 是否写入成功
- 寄存器是否真的被正确配置
- 某些状态寄存器是否有预期变化
这个流程比“哪里不对就乱点一通”要有效得多。
十四、在线调试和 FPGA 仿真的类比
对于学过 FPGA 的同学来说,可以把 ARM 裸机的在线调试类比成软件侧的“仿真分析”。
在 FPGA 逻辑开发里,大家习惯用 ModelSim 去看波形、查时序、找问题;
而在 ARM 裸机开发里,SDK 的在线调试就承担了类似角色:
- 观察程序一步步执行
- 看变量如何变化
- 看存储器中数据是否正确
- 分析程序为什么没有实现预期功能
虽然形式不同,一个看波形,一个看代码执行过程,但目的其实是一样的:
都是在问题发生时,找到“程序实际做了什么”,而不是只凭主观猜测。
十五、初学者调试时容易出现的几个问题
- 只会全速运行,不会打断点
很多人程序一运行就直接点 Run,结果现象不对也不知道从哪看起。
正确做法是先找到关键位置打断点,让程序在最值得分析的地方停下来。
- 只看代码,不看变量
有些同学调试时还是盯着代码发呆,觉得“我写的应该没问题”。
但调试最重要的是看运行结果,而不是反复脑补代码逻辑。
- 只看变量,不看寄存器或内存
在裸机开发中,很多问题最终都要落到地址空间上。
如果只看 C 变量,不去确认寄存器值和 DDR 内容,有时会漏掉真正关键的信息。
- 不会结合现象来定位范围
例如 LED 不亮,不应该一上来就什么都看,而应该先想:
- 程序跑到控制 LED 的那一行了吗?
- GPIO 配置成功了吗?
- 数据寄存器变了吗?
- 板子的 LED 是高电平点亮还是低电平点亮?
调试不是盲目查看,而是带着问题逐步缩小范围。
十六、知识小结
本文围绕 Zynq 裸机程序调试方法,主要介绍了以下内容:
- 在线调试是 ARM 裸机开发中非常重要的程序分析方法。
- SDK 提供了便捷的 Debug 功能,可以让程序暂停、单步执行和全速运行。
- 调试时最常关注的内容包括 Variables、Breakpoints、Expressions 和 Memory。
- 断点可以让程序在指定位置自动停下,大大提高调试效率。
- Variables 适合查看局部变量和部分全局变量,Expressions 适合主动添加需要长期观察的对象。
- Memory 窗口既可以查看 DDR 中的数据,也可以查看外设寄存器值。
- 通过观察固定地址空间的数据,可以更直观地分析程序是否真正完成了读写操作。
- 调试程序时,建议按照“确认流程位置—查看关键变量—分析分支走向—检查 Memory 内容”的思路逐步定位问题。
- 在线调试对 ARM 裸机开发的重要性,类似于 ModelSim 对 FPGA 逻辑设计的重要性。
更多推荐



所有评论(0)