1. 项目概述与核心价值
拿到一块Xilinx的AX7103开发板,很多朋友的第一反应可能是直接上手写Verilog或VHDL,用硬件描述语言去点灯、做逻辑。这当然没问题,但对于一些更偏向系统级、需要运行复杂控制逻辑或软件算法的应用来说,纯FPGA逻辑开发的门槛和周期就成了问题。这时候,MicroBlaze软核处理器的价值就凸显出来了。你可以把它理解为一个“软件可编程”的硬件大脑,嵌入在你的FPGA芯片里,用C语言就能轻松控制整个系统。今天要做的这个“串口发送Hello World”实验,就是点亮这个软核系统的“第一盏灯”。它看似简单,就像软件世界的“Hello World”,但在FPGA嵌入式开发领域,这一步至关重要。它验证了从硬件平台搭建、处理器系统集成、到软件驱动编写、最终在真实硬件上运行的完整链路。对于从单片机转向FPGA-SoC,或者从纯逻辑开发转向软硬协同设计的工程师来说,这个实验是绕不开的起点。通过它,你将亲手打通Vivado硬件工程与Vitis(或早期SDK)软件开发的任督二脉,理解如何让一段C代码在你自己搭建的“芯片”上跑起来,并通过串口这个最经典的调试窗口与外界对话。
2. 实验环境搭建与工程创建
2.1 硬件平台与工具链确认
进行本实验,你需要准备以下几样东西:
- 硬件平台:Xilinx AX7103开发板。这块板子的核心是一颗Artix-7系列的FPGA(具体型号通常为XC7A100T或类似),它为我们提供了运行MicroBlaze的物理载体。请确保板子状态良好,特别是用于供电和下载的USB线,以及连接电脑与板载USB-UART桥接芯片(通常是FTDI或CP210x系列)的串口线。
- 软件工具:Xilinx Vivado Design Suite。版本建议使用2019.2或更高(如2022.2),但需注意Vitis从2020.1开始取代了SDK。本实验流程对Vivado 2019.2(配合SDK)和Vivado 2022.2(配合Vitis)都适用,核心步骤相通,主要是图形界面和部分术语有差异。请确保软件已正确安装并获取了有效的License。
- 串口调试助手:用于在电脑端接收FPGA发来的数据。Putty、SecureCRT、MobaXterm的串口功能,或者国产的SSCOM、XCOM都可以。关键在于正确设置波特率等参数。
注意:安装Vivado时,如果选择Vitis统一安装器,它会包含Vivado、Vitis HLS和Vitis IDE。对于本实验,我们主要使用Vivado进行硬件系统搭建,使用Vitis(或SDK)进行软件开发。
2.2 创建Vivado硬件工程
启动Vivado,点击“Create Project”开始。
- 项目命名与路径:给项目起个名字,例如“mb_uart_hello”。路径不要包含中文或空格,避免工具链出现奇怪问题。
- 项目类型:选择“RTL Project”,并勾选“Do not specify sources at this time”。因为我们是从头搭建一个基于IP的系统,暂时不需要添加现有的HDL文件。
- 选择开发板:在“Default Part”页面,最便捷的方式是在右下角的“Boards”选项卡中搜索“AX7103”。如果列表中存在,直接选中它。Vivado会自动匹配正确的芯片型号和板级约束文件。如果找不到,说明你的Vivado版本可能未预装该板的板级支持包(BSP),这时需要手动在“Parts”选项卡中选择芯片型号,通常为“xc7a100tfg484-2”。但手动选择意味着后续需要自己编写或导入引脚约束文件(.xdc),会增加步骤。因此,优先使用Boards搜索。
- 完成创建:点击Finish,Vivado主界面打开。
2.3 使用IP Integrator搭建MicroBlaze系统
这是整个硬件部分的核心,我们将在图形化界面中“组装”一个最小的可运行系统。
- 创建Block Design:在Vivado左侧的Flow Navigator中,找到“IP INTEGRATOR”,点击“Create Block Design”。设计名称可以就用默认的“design_1”。
- 添加MicroBlaze处理器IP:在打开的Diagram画布中,点击左侧工具栏的“+”号,或右键画布选择“Add IP”。在搜索框输入“MicroBlaze”,双击添加。
- 运行Block Automation:添加MicroBlaze IP后,Vivado通常会弹出一个黄色横幅,提示“Run Block Automation”。点击它。这是一个关键步骤,自动化工具会帮助我们配置处理器核心并添加一系列必要的外设IP。
- Local Memory:会为MicroBlaze添加本地内存(DLMB和ILMB的BRAM控制器)。
- Debug Module:添加必要的调试模块(MDM),用于后续通过JTAG下载和调试程序。
- 时钟与复位:添加Clock Wizard(时钟生成IP)和Processor System Reset(处理器系统复位IP),并自动连接。
- 外部存储器接口:如果板子有DDR,这里可能会提示添加MIG(Memory Interface Generator)IP。对于本实验,我们只使用片内BRAM,可以跳过DDR配置以简化系统。在自动化向导中,注意相关选项。
- 添加AXI Uartlite IP:再次点击“Add IP”,搜索“Uartlite”(AXI UartLite),双击添加。这是实现串口通信的关键IP。
- 连接系统:
- 时钟与复位:将Clock Wizard输出的
clk_out1连接到MicroBlaze的Clk和AXI UartLite的s_axi_aclk。将Processor System Reset输出的peripheral_aresetn和interconnect_aresetn分别连接到各IP的复位端口(通常是s_axi_aresetn)。 - 处理器互联:MicroBlaze需要通过AXI总线访问外设。点击Diagram上方工具栏的“Run Connection Automation”。在弹出的窗口中,勾选“All Automation”。
- 这会将MicroBlaze的
M_AXI_DP(数据端口)通过一个AXI Interconnect(互联器)连接到UartLite的S_AXI从接口。 - 同时,也会自动将UartLite的外部端口
tx和rx引出到顶层。
- 这会将MicroBlaze的
- 时钟与复位:将Clock Wizard输出的
- 验证与地址分配:点击Diagram上方工具栏的“Validate Design”(图标像一张打勾的纸)。确保没有错误。然后,在左侧的“Address Editor”标签页中,你可以看到Vivado已经为UartLite等外设自动分配了地址空间。记下UartLite的
Base Address,例如0x40600000,后续软件中会用到。 - 生成输出产品:在Sources面板中,右键点击你的Block Design(如
design_1.bd),选择“Generate Output Products”。这个过程会为我们的图形化设计生成对应的HDL网表、约束文件等。 - 创建顶层HDL包装器:再次右键点击Block Design,选择“Create HDL Wrapper”。选择“Let Vivado manage wrapper and auto-update”。这会将我们的Block Design封装成一个顶层的Verilog或VHDL模块,以便进行综合实现。
- 生成比特流:在Flow Navigator中,点击“PROGRAM AND DEBUG” -> “Generate Bitstream”。Vivado将开始综合、布局布线并生成最终的
.bit配置文件。这个过程可能需要几分钟到十几分钟,取决于电脑性能。
实操心得:在“Run Block Automation”时,关于内存的配置要留心。对于最简单的Hello World,只使用片上BRAM(如64KB)作为程序和数据存储是完全足够的,这样可以避免配置复杂的DDR控制器,加快编译速度并减少潜在问题。等系统跑通后,再根据需要添加DDR。
3. 软件工程开发与代码编写
硬件比特流生成后,我们就要切换到软件开发环境,为MicroBlaze编写“大脑”里的程序。
3.1 导出硬件平台至Vitis/SDK
- 导出硬件平台:在Vivado中,点击菜单栏“File” -> “Export” -> “Export Hardware”。在弹出窗口中,务必勾选“Include bitstream”。这样会将我们刚生成的硬件描述文件(.xsa文件,在旧版本中是.hdf文件)打包出来。指定一个输出路径。
- 启动Vitis/SDK:同样是“File”菜单,选择“Launch Vitis”(或“Launch SDK”,取决于版本)。Vitis IDE会启动,并自动关联刚才导出的硬件平台文件。
3.2 在Vitis中创建应用工程
- 创建工作区:Vitis会要求指定一个工作区目录,用于存放所有软件工程。
- 创建平台项目:首先需要基于导出的硬件创建一个平台项目(Platform Project)。在Vitis的Explorer视图,右键选择“Create Platform Project”。输入项目名,如
mb_uart_platform,在“Hardware Specification”中选择刚才导出的.xsa文件。点击Finish。 - 创建应用项目:平台项目生成后,右键选择“Create Application Project”。
- 第一步:选择刚才创建的平台项目作为“Target hardware”。
- 第二步:输入应用项目名,如
hello_world。 - 第三步:选择模板。这里非常关键,为了最清晰地理解过程,我们选择“Empty Application (C)”空工程。这能让我们从零开始编写所有代码,而不是依赖模板中可能隐藏的细节。
- 编写Hello World程序:在新建的
hello_world项目的src文件夹下,右键新建一个源文件,命名为helloworld.c。
3.3 深入解析Hello World代码
下面是一个完整、可运行的helloworld.c示例,我们逐段分析:
#include <stdio.h> #include "platform.h" #include "xparameters.h" // 包含硬件平台参数,如外设基地址 #include "xuartlite.h" // 使用Xilinx提供的UartLite驱动库 #include "xil_printf.h" // 提供轻量级打印函数,适合嵌入式环境 // UARTLite设备实例和驱动程序 XUartLite UartLite; int main() { int Status; // 初始化平台(可能包含一些基础硬件初始化) init_platform(); // 打印信息到标准输出(如果配置了,可能指向UART) xil_printf("MicroBlaze UART Hello World Test Start!\r\n"); // 1. 初始化UARTLite驱动 Status = XUartLite_Initialize(&UartLite, XPAR_AXI_UARTLITE_0_DEVICE_ID); if (Status != XST_SUCCESS) { xil_printf("UARTLite Initialization Failed\r\n"); return XST_FAILURE; } // 2. 发送字符串 "Hello World from MicroBlaze!\r\n" char *hello_msg = "Hello World from MicroBlaze!\r\n"; XUartLite_Send(&UartLite, (u8*)hello_msg, strlen(hello_msg)); // 3. 简单循环,防止程序退出(嵌入式程序主函数通常不应返回) while (1) { // 可以在这里添加其他任务,或者只是空循环 // 例如,可以添加一个延时,然后再次发送 // for (int i = 0; i < 10000000; i++); // 简单延时 // XUartLite_Send(&UartLite, (u8*)hello_msg, strlen(hello_msg)); } // 清理平台(通常不会执行到这里,因为上面是死循环) cleanup_platform(); return 0; }代码关键点解析:
- 头文件:
xparameters.h是由Vitis根据你的硬件设计自动生成的,它定义了诸如XPAR_AXI_UARTLITE_0_DEVICE_ID(设备ID)和XPAR_AXI_UARTLITE_0_BASEADDR(基地址)等宏。驱动函数XUartLite_Initialize内部会使用这些信息来配置和操作正确的硬件寄存器。 - 驱动库的使用:Xilinx为所有标准IP(如UartLite)提供了成熟的驱动库(BSP - Board Support Package)。使用这些库函数(
XUartLite_*)而不是直接操作寄存器,能极大提高开发效率和代码可移植性。 xil_printfvsprintf:标准的printf函数功能强大但也很臃肿,会占用大量存储空间。xil_printf是Xilinx提供的轻量级版本,通常用于嵌入式环境下的简单格式化输出,它默认可能指向标准输出(stdout),而这个标准输出在BSP设置中可以被重定向到UART。为了更直接、明确地控制,本例选择了直接调用UART驱动发送数据。- 主循环:嵌入式程序的主函数
main通常不应返回,因此用一个while(1)循环保持程序持续运行。在实际应用中,这里会是你的主任务调度循环。
3.4 配置BSP设置与编译
- 修改BSP设置:在
hello_world项目下,你会看到一个hello_world_bsp的子项目。右键点击它,选择“Board Support Package Settings”。- stdin/stdout:在“Overview”页面,找到“stdin”和“stdout”选项。确保它们被设置为“axi_uartlite_0”(或其他对应的UART实例名)。这样,标准输入输出流就会被重定向到串口,你甚至可以直接使用
printf和scanf。 - 优化等级:在“drivers” ->
uartlite下,可以检查驱动版本。在“Overview” -> “Extra Compiler Flags”中,对于调试阶段,建议使用-O0(无优化)和-g(包含调试信息),方便单步调试。
- stdin/stdout:在“Overview”页面,找到“stdin”和“stdout”选项。确保它们被设置为“axi_uartlite_0”(或其他对应的UART实例名)。这样,标准输入输出流就会被重定向到串口,你甚至可以直接使用
- 编译工程:右键点击
hello_world应用项目,选择“Build Project”。Vitis会编译BSP库和你的应用程序,最终生成一个.elf(可执行与可链接格式)文件,这就是MicroBlaze处理器要运行的程序。
4. 系统调试与硬件验证
这是最激动人心的环节,将硬件和软件结合起来,在真实的开发板上看到结果。
4.1 连接硬件与配置下载
- 硬件连接:
- 使用USB线连接开发板的JTAG口(通常标记为PROG/UART)到电脑,用于下载配置比特流和程序。
- 使用另一根USB线(或同一根线的不同接口,取决于板卡设计)连接开发板的UART口到电脑,用于串口通信。确保你知道了这个串口在电脑上对应的COM端口号(在Windows设备管理器的“端口”中查看,如COM3;在Linux下通常是
/dev/ttyUSB0之类)。
- 配置串口调试助手:打开你选择的串口助手软件(如Putty)。选择正确的COM端口,设置波特率为115200(这是AXI UartLite IP的默认速率,除非你在Vivado中修改过),数据位8,停止位1,无校验位,无流控。
- 在Vitis中配置硬件服务器:确保开发板已上电。在Vitis中,需要先建立与硬件的连接。点击“Xilinx”菜单 -> “Program FPGA”。这会打开硬件管理器。
- 如果第一次使用,可能需要“Open Target” -> “Auto Connect”。
- 在“Program FPGA”对话框中,“Hardware Configuration”部分应该自动识别到你的比特流文件(.bit)。同时,在“Software Configuration”部分,选择你刚编译生成的
.elf文件。关键一步:勾选“Program FPGA and BSCAN only”或类似选项旁边的“Bootloop”或“Run after programming”。这样可以在下载后自动开始运行程序。
4.2 下载、运行与观察结果
- 点击“Program”。Vivado/Vitis会通过JTAG将比特流下载到FPGA中,配置硬件逻辑;然后将
.elf文件下载到MicroBlaze的指令存储器(BRAM)中。 - 如果一切顺利,程序将开始运行。立即切换到串口调试助手窗口。
- 你应该能看到串口接收区打印出两行信息:
- 第一行是
xil_printf输出的:MicroBlaze UART Hello World Test Start! - 第二行是
XUartLite_Send发送的:Hello World from MicroBlaze!
- 第一行是
- 恭喜!这证明你的MicroBlaze系统从硬件到软件完全正确运行了。
4.3 使用调试器进行深入排查
如果串口没有输出,不要慌张,调试是嵌入式开发的常态。
- 启动调试:在Vitis中,右键点击
hello_world应用项目,选择“Debug As” -> “Launch on Hardware (Single Application Debug)”。Vitis会启动GDB调试器,连接板载的MicroBlaze调试模块。 - 设置断点:在
main函数的入口、XUartLite_Initialize调用后、以及XUartLite_Send调用前设置断点。 - 单步执行:点击“Resume”(F8)或“Step Over”(F6)。观察程序是否能顺利执行到各个断点。
- 查看变量与寄存器:
- 在“Variables”视图查看
Status变量的值,确认初始化是否成功(XST_SUCCESS)。 - 在“Expressions”视图可以添加表达式,例如直接查看
UartLite结构体的成员,或者查看XPAR_AXI_UARTLITE_0_BASEADDR的值是否正确。 - 在“Memory”视图,输入UARTLite的基地址,可以查看其控制状态寄存器(SR)和发送FIFO寄存器(TX_FIFO),手动验证硬件状态。
- 在“Variables”视图查看
- 检查外设地址:最常见的错误之一是地址不匹配。确保
XPAR_AXI_UARTLITE_0_DEVICE_ID和XPAR_AXI_UARTLITE_0_BASEADDR的定义与Vivado Address Editor中分配的地址一致。你可以打开hello_world_bsp项目下的include文件夹中的xparameters.h文件进行核对。
5. 常见问题与深度排查指南
即使按照步骤操作,也可能会遇到各种问题。下面是一个常见问题排查表,涵盖了从硬件到软件的各个环节。
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| Vivado生成比特流失败 | 1. 时序约束不满足。 2. 资源超限。 3. 引脚约束冲突。 | 1. 检查时钟约束是否正确添加。对于简单系统,时钟频率不要设太高(如先设50MHz)。 2. 查看综合和实现后的报告,确认LUT、FF、BRAM等资源使用率未超过芯片容量。 3. 检查.xdc约束文件,确认UART的Tx/Rx引脚号与原理图一致,且无重复约束。 |
| Vitis无法连接硬件 | 1. JTAG电缆未连接或驱动未安装。 2. 硬件服务器未启动或配置错误。 3. 板卡供电不足。 | 1. 检查设备管理器,确认JTAG电缆(如Digilent/USB-Blaster)驱动正常。 2. 在Vitis中,Window -> Show View -> Other -> Xilinx -> Hardware,打开硬件视图,尝试“Open New Hardware Target”。 3. 确保使用原装或可靠的电源适配器,某些USB口供电可能不稳定。 |
| 程序下载后无任何输出 | 1. 串口线连接错误或COM口选错。 2. 波特率等串口参数设置错误。 3. 程序未实际运行(卡在启动或复位状态)。 4. UART IP的引脚未正确约束。 | 1. 用万用表测量UART Tx引脚(板卡连接电脑的RXD)在程序运行时的电压,应有高低电平变化,证明有数据发出。 2. 核对串口助手设置与IP配置(默认115200, 8N1)。 3. 使用调试器,看PC指针是否停在 _start或main入口。检查复位信号是否被正确释放。4. 在Vivado中“Open Implemented Design”,查看I/O Ports,确认UART引脚已按约束文件分配到了正确的FPGA物理引脚。 |
| 只能收到部分字符或乱码 | 1. 波特率不匹配(时钟分频计算错误)。 2. 发送函数使用错误(如未等待发送完成)。 3. 板卡电平不匹配(如3.3V vs 5V)。 | 1.重点检查:在Vivado中双击AXI UartLite IP,查看其配置。Baud Rate和Clock Frequency必须匹配。计算公式为:波特率 = 时钟频率 / (16 * 分频系数)。确保你设置的时钟频率与Clock Wizard输出给UART的时钟一致。2. XUartLite_Send是阻塞函数,会等待发送完成。但如果你在循环中快速连续调用,而FIFO深度有限,可能丢失数据。可以使用XUartLite_IsSending检查状态。3. AX7103的UART电平通常是3.3V LVTTL,确保你的USB-UART转换器兼容此电平。 |
| 调试器无法打断点或单步 | 1. 调试模块(MDM)未正确添加或连接。 2. 优化等级过高(-O2, -O3)。 3. 程序未包含调试信息。 | 1. 回查Vivado Block Design,确认MDM(MicroBlaze Debug Module)存在,且其DEBUG接口连接到MicroBlaze的DEBUG接口,并已连接到顶层端口。2. 在BSP设置中将编译优化改为 -O0 -g。3. 确认编译时生成了带有调试信息的elf文件。 |
| 程序运行一次后停止 | main函数执行完毕返回。 | 嵌入式程序main函数不应返回。必须在末尾添加while(1);死循环,或调用exit(0)前确保已完成所有操作。否则处理器会跑飞。 |
深度避坑技巧:关于时钟和波特率,这是串口通信最经典的坑。一个快速验证的方法是:在Vitis中,在调用
XUartLite_Send发送一个已知字符(如‘A’, ASCII 0x41)后,添加一个大的延时循环(几秒),然后用逻辑分析仪或示波器测量UART Tx引脚上的波形。一个起始位(低电平)+ 8位数据(0x41 = 01000001b,低位先发)+ 停止位(高电平)。测量一个位的时间宽度T,计算波特率Baud = 1 / T。看是否等于115200。这个方法能最直接地定位是软件配置问题还是硬件时钟问题。
6. 项目进阶与扩展思考
成功打印出“Hello World”只是一个开始。这个最小系统可以成为你探索更多功能的基石。
- 添加中断处理:目前的串口发送是查询(阻塞)方式。在实际应用中,为了不阻塞主程序,通常会使用中断。你可以尝试配置UartLite的中断,当发送完成或接收到数据时,触发MicroBlaze的中断服务程序(ISR)进行处理。这需要学习Xilinx驱动库中的中断相关API(如
XUartLite_SetSendHandler)和MicroBlaze的中断控制器(INTC)配置。 - 连接外部DDR内存:片上BRAM容量有限。下一步就是在Vivado中添加MIG IP,连接板载的DDR3内存。然后在Vitis的BSP设置中,将程序和数据段链接到DDR的地址空间。这样就能运行更大、更复杂的应用程序。
- 集成自定义IP:MicroBlaze的强大之处在于可以像搭积木一样扩展系统。你可以用Verilog/VHDL编写一个自定义功能的IP(例如PWM控制器、传感器接口),通过AXI-Lite或AXI-Stream总线挂接到MicroBlaze的系统上。在Vitis中,这个IP的寄存器就会像内存一样被C语言访问和控制。
- 使用FreeRTOS:当系统功能变复杂,需要多任务调度时,可以引入实时操作系统。Xilinx提供了FreeRTOS的BSP支持。在创建应用工程时,直接选择“FreeRTOS Hello World”模板,就能创建一个带任务管理的工程框架。
- 性能分析与优化:利用MicroBlaze的性能计数器,或者添加AXI Performance Monitor IP,可以分析你的C代码在硬件上的执行效率,找出瓶颈,进行优化。
从点亮一个LED,到让一个软核处理器说出“Hello World”,再到构建一个包含操作系统、丰富外设的复杂片上系统,这条路径正是Xilinx All Programmable SoC的魅力所在。这个实验就像一把钥匙,为你打开了用软件思维驾驭硬件灵活性的大门。后续无论你是想做工业控制、图像处理还是网络通信,这套从硬件设计到软件开发的完整流程都是相通的。多动手,多调试,遇到问题善用Xilinx文档(UG)和论坛,积累的经验会让你在嵌入式FPGA开发中越来越得心应手。