GM-3568JHF开发板是一款基于ARM+FPGA异构架构的嵌入式开发平台,这种架构在工业控制、边缘计算和高速信号处理领域有着广泛应用。今天我要分享的是在这块开发板上实现UART通信的完整开发流程,这个案例虽然基础,但却是嵌入式开发中最常用也最容易踩坑的接口之一。
我选择这个案例作为系列教程的第一篇,是因为UART不仅是新手入门嵌入式开发的"Hello World",更是实际项目中设备调试、模块通信的基础手段。通过这个案例,你将掌握从硬件连接到软件配置的全套技能,包括ARM端驱动开发、FPGA逻辑设计以及两者之间的协同工作机制。
GM-3568JHF开发板提供了多个UART接口,我们需要重点关注以下硬件细节:
本次案例我们使用UART2进行演示,其物理接口为2.54mm排针,引脚定义如下:
| 引脚编号 | 信号名称 | 功能说明 | 电压等级 |
|---|---|---|---|
| 1 | TXD | 数据发送 | 3.3V |
| 2 | RXD | 数据接收 | 3.3V |
| 3 | CTS | 清除发送(输入) | 3.3V |
| 4 | RTS | 请求发送(输出) | 3.3V |
注意:开发板UART电平为3.3V,连接外部设备时务必确认电平兼容性,否则可能损坏接口。
ARM端开发需要准备以下工具链:
FPGA端开发需要:
建议在Ubuntu 20.04 LTS环境下进行开发,避免Windows路径和权限问题。安装基础工具:
bash复制sudo apt install build-essential git minicom libncurses5-dev
GM-3568JHF的Linux系统已经内置了UART驱动,设备节点对应关系如下:
我们可以通过stty命令配置串口参数:
bash复制stty -F /dev/ttyS2 115200 cs8 -parenb -cstopb
这个命令将UART2配置为115200波特率、8数据位、无校验、1停止位。
下面是一个完整的UART读写示例程序:
c复制#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>
int main() {
int uart_fd = open("/dev/ttyS2", O_RDWR | O_NOCTTY);
if (uart_fd < 0) {
perror("打开串口失败");
return -1;
}
struct termios options;
tcgetattr(uart_fd, &options);
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag |= CLOCAL | CREAD;
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
options.c_oflag &= ~OPOST;
tcsetattr(uart_fd, TCSANOW, &options);
char tx_buf[] = "Hello GM-3568JHF!\n";
write(uart_fd, tx_buf, strlen(tx_buf));
char rx_buf[256];
int n = read(uart_fd, rx_buf, sizeof(rx_buf));
if (n > 0) {
rx_buf[n] = '\0';
printf("收到数据: %s", rx_buf);
}
close(uart_fd);
return 0;
}
关键点说明:
O_NOCTTY标志防止串口成为控制终端CLOCAL标志忽略调制解调器状态ICANON)以支持原始数据收发编译命令:
bash复制arm-linux-gnueabihf-gcc uart_test.c -o uart_test
将生成的可执行文件通过scp或TF卡拷贝到开发板,运行测试:
bash复制./uart_test
在Vivado中创建AXI UART 16550 IP核,关键参数配置:
FPGA端的UART控制器通过AXI4-Lite总线与ARM处理器通信,地址空间映射到0x43C00000。
verilog复制module uart_top (
input wire clk,
input wire rst_n,
output wire uart_txd,
input wire uart_rxd
);
// AXI4-Lite接口定义
wire [31:0] axi_awaddr;
wire axi_awvalid;
wire axi_awready;
wire [31:0] axi_wdata;
wire [3:0] axi_wstrb;
wire axi_wvalid;
wire axi_wready;
wire [1:0] axi_bresp;
wire axi_bvalid;
wire axi_bready;
// 实例化AXI UART IP
axi_uart16550 uart_inst (
.s_axi_aclk(clk),
.s_axi_aresetn(rst_n),
.s_axi_awaddr(axi_awaddr),
.s_axi_awvalid(axi_awvalid),
.s_axi_awready(axi_awready),
.s_axi_wdata(axi_wdata),
.s_axi_wstrb(axi_wstrb),
.s_axi_wvalid(axi_wvalid),
.s_axi_wready(axi_wready),
.s_axi_bresp(axi_bresp),
.s_axi_bvalid(axi_bvalid),
.s_axi_bready(axi_bready),
.sin(uart_rxd),
.sout(uart_txd)
);
// 其他逻辑...
endmodule
在XDC约束文件中指定引脚位置和电气特性:
tcl复制set_property PACKAGE_PIN J15 [get_ports uart_txd]
set_property IOSTANDARD LVCMOS33 [get_ports uart_txd]
set_property PACKAGE_PIN K16 [get_ports uart_rxd]
set_property IOSTANDARD LVCMOS33 [get_ports uart_rxd]
FPGA端的UART控制器寄存器映射如下:
| 地址偏移 | 寄存器名称 | 功能描述 |
|---|---|---|
| 0x00 | RBR/THR | 接收缓冲/发送保持寄存器 |
| 0x04 | IER | 中断使能寄存器 |
| 0x08 | IIR | 中断标识寄存器 |
| 0x0C | LCR | 线路控制寄存器 |
| 0x10 | MCR | 调制解调器控制寄存器 |
| 0x14 | LSR | 线路状态寄存器 |
通过mmap方式访问FPGA端UART的示例代码:
c复制#include <sys/mman.h>
#include <fcntl.h>
#define FPGA_UART_BASE 0x43C00000
#define UART_LSR 0x14
#define LSR_THRE 0x20 // 发送保持寄存器空标志
int main() {
int fd = open("/dev/mem", O_RDWR | O_SYNC);
void *base = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, FPGA_UART_BASE);
volatile uint32_t *lsr = (uint32_t *)(base + UART_LSR);
while (!(*lsr & LSR_THRE)) {
// 等待发送寄存器就绪
}
// 发送数据...
munmap(base, 4096);
close(fd);
return 0;
}
配置FPGA端UART中断的完整流程:
dts复制uart@43c00000 {
compatible = "xlnx,xps-uart16550-2.00.a";
reg = <0x43c00000 0x10000>;
interrupts = <0 29 4>;
interrupt-parent = <&intc>;
clock-frequency = <100000000>;
};
c复制irqreturn_t uart_interrupt(int irq, void *dev_id) {
// 读取IIR寄存器判断中断类型
// 处理接收或发送中断
return IRQ_HANDLED;
}
// 在probe函数中注册中断
request_irq(irq_num, uart_interrupt, IRQF_SHARED, "fpga_uart", dev);
bash复制dmesg | grep tty
bash复制stty -F /dev/ttyS2 -a
问题1:接收数据不完整或乱码
问题2:发送数据丢失
问题3:无法打开设备节点
对于高速UART通信(>1Mbps),建议使用DMA方式减少CPU开销:
dts复制uart@43c00000 {
dmas = <&dmac 16>, <&dmac 17>;
dma-names = "tx", "rx";
};
c复制struct dma_handle *tx_handle = dma_request_channel(DMA_MEM_TO_DEV);
dma_config_uart(tx_handle, uart_fd, buf, len);
dma_start_transfer(tx_handle);
在工业应用中,通常需要在UART基础上实现可靠通信协议:
c复制// 自定义协议帧结构
#pragma pack(1)
typedef struct {
uint8_t head; // 0xAA
uint16_t len; // 数据长度
uint8_t cmd; // 命令字
uint8_t data[]; // 数据载荷
uint16_t crc; // CRC16校验
} uart_frame_t;
协议实现要点:
对于需要管理多个UART端口的应用:
c复制struct uart_manager {
int fds[MAX_UARTS];
fd_set readfds;
int max_fd;
};
void uart_event_loop(struct uart_manager *mgr) {
while (1) {
fd_set tmpfds = mgr->readfds;
select(mgr->max_fd + 1, &tmpfds, NULL, NULL, NULL);
for (int i = 0; i < MAX_UARTS; i++) {
if (FD_ISSET(mgr->fds[i], &tmpfds)) {
handle_uart_event(mgr->fds[i]);
}
}
}
}
掌握了基础UART通信后,可以进一步探索:
在实际项目中,UART通信的稳定性往往决定了整个系统的可靠性。我在多个工业现场就遇到过因为接地不良导致的UART通信间歇性失败,后来通过以下措施彻底解决: