1. Zynq PL端AXI UART通信实战指南
在FPGA开发中,UART通信是最基础也最常用的外设接口之一。Xilinx Zynq系列SoC提供了灵活的UART解决方案,既可以使用PS端的硬核UART,也可以通过PL端实现自定义的UART控制器。本文将重点探讨如何在Zynq PL端实现AXI UART通信,涵盖AXI UARTLite和AXI UART16550两种IP核的使用方法。
2. AXI UARTLite基础实现
2.1 IP核配置与硬件连接
AXI UARTLite是Xilinx提供的一个轻量级UART控制器IP,具有以下特点:
- 支持标准UART协议
- 32位AXI4-Lite从接口
- 可配置波特率(通过时钟分频实现)
- 16字节收发FIFO
- 中断支持
在Vivado中配置AXI UARTLite时,需要注意几个关键参数:
- 波特率:实际波特率由输入时钟频率和分频系数决定
- 数据位:通常选择8位数据
- 校验位:可选择无校验、奇校验或偶校验
- 停止位:通常选择1位停止位
硬件连接上,AXI UARTLite通过AXI总线连接到Zynq PS端,UART的TX和RX信号引出到FPGA引脚,通过电平转换芯片连接到外部设备。
2.2 寄存器级操作实现
直接操作寄存器是最底层的UART控制方式,适合对性能要求高或需要精细控制的场景。AXI UARTLite的寄存器映射如下:
c复制#define UART_RX_FIFO (*(volatile uint32_t *)(UART_BASE_ADDR + 0x00)) // 接收数据寄存器
#define UART_TX_FIFO (*(volatile uint32_t *)(UART_BASE_ADDR + 0x04)) // 发送数据寄存器
#define UART_STATUS (*(volatile uint32_t *)(UART_BASE_ADDR + 0x08)) // 状态寄存器
#define UART_CONTROL (*(volatile uint32_t *)(UART_BASE_ADDR + 0x0C)) // 控制寄存器
关键操作函数实现:
c复制// 初始化UART(复位FIFO)
void UART_Init() {
// 复位接收和发送FIFO(写1生效,自动清零)
UART_CONTROL = RX_RESET | TX_RESET;
usleep(100); // 短暂延时确保复位完成
}
// 发送一个字节
void UART_SendByte(uint8_t data) {
// 等待发送FIFO非满
while (UART_STATUS & TX_FULL);
// 写入发送FIFO
UART_TX_FIFO = data;
}
// 接收一个字节
uint8_t UART_RecvByte(uint8_t *data) {
if (UART_STATUS & RX_VALID) {
*data = (uint8_t)(UART_RX_FIFO & 0xFF);
return 1;
}
return 0;
}
注意事项:
- 直接操作寄存器时,必须确保对寄存器的访问是volatile的,防止编译器优化
- FIFO状态检查是必要的,避免数据丢失或覆盖
- 复位FIFO后需要适当延时,确保复位完成
2.3 使用Xilinx驱动库实现
Xilinx提供了更高级的UART驱动库,简化了开发流程。基本使用流程如下:
c复制#include "xuartlite.h"
XUartLite UartInstance;
int main() {
int status;
u8 recv_buf[MAX_RECV_LEN];
// 初始化UART
status = XUartLite_Initialize(&UartInstance, UART_DEVICE_ID);
if (status != XST_SUCCESS) return -1;
XUartLite_ResetFifos(&UartInstance);
while (1) {
// 发送数据
XUartLite_SendByte(UartInstance.RegBaseAddress, 0x55);
// 接收数据
u32 recv_len = XUartLite_Recv(&UartInstance, recv_buf, MAX_RECV_LEN);
if (recv_len > 0) {
// 处理接收数据
}
usleep(1000);
}
return 0;
}
高级初始化方式(使用LookupConfig + CfgInitialize):
c复制XUartLite_Config *Config = XUartLite_LookupConfig(UART_DEVICE_ID);
if (Config == NULL) return -1;
status = XUartLite_CfgInitialize(&UartInstance, Config, Config->RegBaseAddr);
if (status != XST_SUCCESS) return -1;
经验分享:
- 库函数方式代码更简洁,适合快速开发
- LookupConfig方式更灵活,可以获取更多配置信息
- 批量收发数据时,使用XUartLite_Send和XUartLite_Recv效率更高
3. AXI UART16550高级应用
3.1 IP核特点与配置
AXI UART16550是功能更强大的UART控制器,主要特点包括:
- 兼容NS16550标准
- 可编程波特率发生器
- 64字节收发FIFO
- 支持Modem控制信号
- 中断和DMA支持
在Vivado中配置时,需要注意:
- 选择正确的时钟频率
- 根据需要启用FIFO
- 配置中断选项(如使用中断模式)
3.2 寄存器级操作实现
AXI UART16550的寄存器更复杂,关键寄存器包括:
c复制#define UART_REG_RBR 0x00 // 接收缓冲寄存器
#define UART_REG_THR 0x00 // 发送保持寄存器
#define UART_REG_IER 0x04 // 中断使能寄存器
#define UART_REG_LCR 0x0C // 线路控制寄存器
#define UART_REG_LSR 0x14 // 线路状态寄存器
#define UART_REG_DLL 0x00 // 除数锁存低字节 (DLAB=1)
#define UART_REG_DLM 0x04 // 除数锁存高字节 (DLAB=1)
波特率设置实现:
c复制void uart_set_baud(u32 clk, u32 baud) {
u16 divisor = (u16)(clk / (16 * baud));
// 设置DLAB=1访问DLL/DLM
u8 lcr = UART_READ(UART_REG_LCR);
UART_WRITE(UART_REG_LCR, lcr | 0x80);
UART_WRITE(UART_REG_DLL, divisor & 0xFF);
UART_WRITE(UART_REG_DLM, (divisor >> 8) & 0xFF);
// 恢复DLAB=0,设置8N1格式
UART_WRITE(UART_REG_LCR, 0x03);
}
注意事项:
- 波特率设置需要先设置DLAB位
- 除数计算公式为:divisor = UART_CLK / (16 * baud)
- 操作完成后需要恢复数据格式设置
3.3 使用驱动库实现
Xilinx提供了XUartNs550驱动库,基本使用流程:
c复制XUartNs550 UartNs550;
int main() {
int Status = XUartNs550_Initialize(&UartNs550, UART_DEVICE_ID);
if (Status != XST_SUCCESS) return XST_FAILURE;
XUartNs550_SetBaud(UART_BASEADDR, UART_CLK_FREQ, UART_BAUD_RATE);
while (1) {
if (XUartNs550_IsReceiveData(UART_BASEADDR)) {
u8 c;
XUartNs550_Recv(&UartNs550, &c, 1);
XUartNs550_Send(&UartNs550, &c, 1); // 回显
}
}
return XST_SUCCESS;
}
3.4 中断模式实现
中断模式可以提高CPU利用率,实现步骤:
- 初始化中断控制器
- 配置UART中断
- 实现中断处理函数
关键代码:
c复制// 中断处理函数
void UartNs550Handler(void *CallBackRef, unsigned long Event, unsigned int EventData) {
XUartNs550 *UartInstPtr = (XUartNs550 *)CallBackRef;
if (Event == XUN_EVENT_RECV_DATA) {
XUartNs550_Recv(UartInstPtr, &RxChar, 1);
RxFlag = 1;
}
}
// 中断系统初始化
int SetupInterruptSystem(XScuGic *IntcInstPtr, XUartNs550 *UartInstPtr) {
// 初始化中断控制器
XScuGic_Config *IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
if (!IntcConfig) return XST_FAILURE;
// 配置中断
XScuGic_Connect(IntcInstPtr, UART_INT_IRQ_ID,
(Xil_InterruptHandler)XUartNs550_InterruptHandler,
(void *)UartInstPtr);
// 使能中断
XScuGic_Enable(IntcInstPtr, UART_INT_IRQ_ID);
// 注册UART回调
XUartNs550_SetHandler(UartInstPtr, UartNs550Handler, (void *)UartInstPtr);
// 使能RX/TX中断
XUartNs550_WriteReg(UART_BASEADDR, XUN_IER_OFFSET,
XUN_IER_RX_DATA | XUN_IER_TX_EMPTY);
return XST_SUCCESS;
}
经验分享:
- 中断模式适合高吞吐量场景
- 中断处理函数应尽量简短
- 注意中断共享和优先级设置
- 调试时可先使用轮询模式,稳定后再改为中断模式
4. 调试技巧与常见问题
4.1 硬件调试要点
-
信号完整性检查:
- 使用示波器检查TX/RX信号质量
- 确保波特率设置正确
- 检查信号电平是否符合标准(如RS232或TTL)
-
环路测试:
- 将TX和RX短接,测试自发自收
- 验证基本收发功能
-
上拉电阻:
- 对于TTL电平UART,RX信号建议加上拉电阻
- 避免信号浮空导致误触发
4.2 软件调试技巧
-
打印调试信息:
- 在关键流程添加调试打印
- 输出寄存器状态和关键变量值
-
逻辑分析仪:
- 使用Saleae等逻辑分析仪捕获UART波形
- 验证数据格式和时序
-
仿真测试:
- 对关键算法进行软件仿真
- 使用UART仿真工具测试通信协议
4.3 常见问题解决
-
数据丢失或乱码:
- 检查波特率误差(应小于3%)
- 确认双方数据格式一致(数据位、停止位、校验位)
- 检查FIFO设置和状态
-
无法接收数据:
- 验证RX信号连接是否正确
- 检查中断或轮询设置
- 确认UART IP核时钟和复位信号正常
-
性能问题:
- 对于高波特率,考虑使用DMA或FIFO
- 优化中断处理流程
- 减少不必要的状态检查
-
波特率不准确:
- 检查输入时钟频率
- 验证波特率分频系数计算
- 考虑使用自动波特率检测功能(如果支持)
5. 性能优化建议
-
FIFO使用策略:
- 合理设置FIFO触发阈值
- 批量读写提高效率
- 监控FIFO状态避免溢出
-
中断优化:
- 合并多个中断源
- 使用中断屏蔽机制
- 优化中断处理函数
-
DMA应用:
- 对于大数据量传输,考虑使用DMA
- 配置DMA描述符链
- 处理DMA完成中断
-
电源管理:
- 空闲时降低UART时钟频率
- 合理使用睡眠模式
- 动态调整波特率
在实际项目中,我曾遇到一个案例:系统在115200波特率下工作正常,但提高到921600时出现数据丢失。通过逻辑分析仪发现,问题是由于CPU处理速度跟不上高波特率的数据流。解决方案是启用FIFO并设置更高的触发阈值,同时优化中断处理函数,最终实现了稳定的高速通信。
