在ZYNQ7000中,硬核只集成了两个串口外设,除去终端使用的串口,只剩一下一个串口可用,而在大多数应用中一个串口往往不能满足要求。使用FPGA模拟串口可以解决串口外设不足的问题,Xilinx提供了两种串口IP:AXI UART Lite和AXI UART 16550,使用这两个IP可以非常方便的使用扩展串口,并且Xilinx提供了Linux中的相应的串口驱动程序,符合tty标准设备。本文介绍这两种串口IP的使用。
一. UART Lite与UART 16550介绍
1.1 AXI UART Lite
AXI UART Lite IP是完全使用FPGA资源模拟出来的异步串口收发器,使用AXI总线与ARM硬核进行通信控制。它支持常用的5~8位数据位,奇偶校验,可配置的波特率,发送与接收分别使用了16字节FIFO。其原理框图如图1-1所示。

在ZYNQ7010中,AXI UART LiteIP支持的最大波特率是921600,不到1MHz,基本满足大部分场景需求,如果系统要求波特率更高则使用另外一个IP,也就是本文提到的UART 16550。
1.2 AXI UART 16550
16550 uart最初是一个集成芯片,为了满足计算机串口的高速通信而设计,在最初的一版设计中其FIFO存在BUG,随后便推出了16550A版本。正如其是为了满足高速串口通信,它的波特率可以远远高于大部分单片机内部集成的串口。
UART16550除了拥有AXI UART Lite的全部功能外,还提供1.5bit和2bit停止位,在可配置波特率的基础上还可以使用外部时钟供给串口接收模块,经测试发现,其波特率可以达到linux中tty设备定义的最大波特率,也就是4.5Mbps。
功能如此齐全的UART IP在资源的利用上显然要比UART Lite要高,事实上同样的配置UART16550是UART Lite资源的3倍左右。
二. 使用串口AXI UART Lite IP
2.1 编译uboot源码
下载uboot源代码(https://github.com/Xilinx/u-boot-xlnx)
2.2 编译Linux内核
下载xilinx的linux源代码(https://github.com/Digilent/linux-digilent-Dev)
解压进入内核根目录
# make ARCH=arm CROSS_COMPILE=arm-xilinx-linux-gnueabi- xilinx_zynq_defconfig
# make ARCH=arm CROSS_COMPILE=arm-xilinx-linux-gnueabi- menuconfig
在内核中配置支持AXI UART:
-->Device Dirvers --> Character devices --> Serial drivers -->Xilinx uartlite serial port support
# make ARCH=arm CROSS_COMPILE=arm-xilinx-linux-gnueabi- UIMAGE_LOADADDR=0x8000 uImage
2.3 编译设备树
下载device-tree-generator工具:http://wiki.xilinx.com/device-tree-generator
解压到vivado安装目录下,在xilinx的SDK软件中选择Xilinx->Respositories工具,在Local Respositories中点击New,选择刚解压的Device tree generator工具的根目录,然后点击Apply,OK。

在点击Apply后在SDK的控制台上可以看到有软件的编译输出,说明安装正确。
点击File->New->Xilinx Board Support Package,可以发现在Board Support Package OS选项中多了一项device-tree.

点击Finish进入BSP的设置,首先需要设置console_device,zedboard使用的是串口1,所以必须选择ps7_uart_1.

BSP设置完后点击Finish,软件会自动生成所有需要的设备树,包括用户在PL中建立的IP核生成的设备树文件,如下图所示:

其中zynq-7000.dtsi是zynq的PS部分所需要的设备树,pl.dtsi中则是PL部分用户添加的IP的设备树,将其添加到开发板的dts文件中,在执行编译生成设备树即可。
# make ARCH=arm CROSS_COMPILE=arm-xilinx-linux-gnueabi- dtbs
由于内核中已经添加了xilinx的uartlite支持,并且设备树建立完成,在linux启动后,在dev目录下回生成一个ttyUL1的设备,如下图所示。

三. 使用UART16550
uart16550可以达到比较高的波特率,而且可以外接晶振,提高波特率。
3.1 添加UART16550 IP核
在vivado中添加IP,搜索uart16550,添加即可,点自动连线,然后删除连接的引脚。展开UART,从sin和sout创建引脚,如下图所示。

分配好地址后综合,添加引脚约束,导出硬件设计,进入SDK。首先为了确保硬件设计的正确与否,可以在裸机上测试串口的功能,常用的函数如下:
a.设置串口的输入时钟和波特率: XUartNs550_SetBaud()
b.发送一个字节:XUartNs550_SendByte(UINTPTR BaseAddress, u8 Data)
c.接收一个字节:XUartNs550_RecvByte(UINTPTR BaseAddress)
新建一个devicetree的BSP工程,console_device选择ps7_uart_1,在选择列表中出现了新增的UART16550的选项,注意不要选错。

3.2 编辑设备树文件
打开devicetree_bsp中生成的pl.dtsi文件,该文件是用户在PL中添加的硬件需要新增的设备树文件,全部复制到zed内核中配置设备树的文件中。
/ {
amba_pl: amba_pl {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
ranges ;
axi_uart16550_0: serial@43c00000 {
clock-frequency = <2.5e+08>;
clock-names = "ref_clk";
clocks = "clkc 0";
compatible = "xlnx,xps-uart16550-2.00.a", "ns16550a";
current-speed = <115200>;
device_type = "serial";
interrupt-parent = <&intc>;
interrupts = <0 29 4>;
port-number = <1>;
reg = <0x43c00000 0x10000>;
reg-offset = <0x1000>;
reg-shift = <2>;
xlnx,external-xin-clk-hz = <0x17d7840>;
xlnx,external-xin-clk-hz-d = <0x19>;
xlnx,has-external-rclk = <0x0>;
xlnx,has-external-xin = <0x0>;
xlnx,is-a-16550 = <0x1>;
xlnx,s-axi-aclk-freq-hz-d = "250.0";
xlnx,use-modem-ports = <0x1>;
xlnx,use-user-ports = <0x1>;
};
};
打开devicetree_bsp中的system-top.dts文件,可以看到在aliases节点中新增了用户添加的AXI外设,将新增的内容复制到zed的dts文件中。
/dts-v1/;
/include/ "zynq-7000.dtsi"
/include/ "pl.dtsi"
/include/ "pcw.dtsi"
/ {
chosen {
bootargs = "earlycon";
stdout-path = "serial0:115200n8";
};
aliases {
ethernet0 = &gem0;
serial0 = &uart1;
serial1 = &uart0;
serial2 = &axi_uart16550_0;
spi0 = &qspi;
};
memory {
device_type = "memory";
reg = <0x0 0x20000000>;
};
};
修改后zynq_ztrun.dts文件开始部分如下:
/dts-v1/;
/include/ "zynq-7000.dtsi"
/ {
model = "MYIR Z-turn Development Board";
compatible = "myir,zynq-zturn", "xlnx,zynq-7000";
aliases {
ethernet0 = &gem0;
serial0 = &uart1;
serial1 = &uart0;
spi0 = &qspi;
serial2 = &axi_uart16550_0;
};
memory {
device_type = "memory";
reg = <0x0 0x1c000000>; // Reserved 256MB for xylonfb driver
};
chosen {
bootargs = "console=ttyPS0,115200 root=/dev/ram rw earlyprintk";
linux,stdout-path = "/amba/serial@e0001000";
};
};
3.3 配置内核
进入内核进行配置,添加16550的支持,并且设置支持的个数为16,最后在zynq_zturn_defconfig文件中找到non-8250 serial port support位置,在下面的配置中添加CONFIG_SERIAL_OF_PLATFORM=y,添加后如下图所示:

编译内核及设备树。
3.4 测试
在上述所有的文件制作好之后,复制到SD卡,让开发板从SD卡启动,启动之后查看/dev目录,可以得到如下信息。

可以看到系统中多了ttyS0~ttyS15,本次系统中可以使用的是ttyS2~ttyS11, 因为在aliases节点下ARM端的两个串口已经占用了serial0和serial1。
使用USB转串口模块连接开发板,打开PC端的串口调试助手,设置波特率为1500000,在终端中使用命令测试串口:
> stty -F /dev/ttyS2 speed 1500000 cs8 #设置波特率
> echo heelo > /dev/ttyS2 #发送数据
> cat /dev/ttyS2 #接收数据

参考链接
[2]. Xilinx uart16550手册
[3]. Uartlite Driver
[4]. 16550 UART
欢迎关注亦梦云烟的微信公众号: 亦梦智能计算