本帖最后由 UT发布 于 2025-4-10 16:04 编辑
软件版本: TD_ 5.6.4 _Release_ 97693
操作系统: WIN11 64bit
硬件平台:适用安路 (Anlogic)FPGA
1概述 本章将学习 UART 通信的原理及其硬件电路设计,并使用 FPGA 来 实现UART串口发送控制器的设计,实现主程序中调用串口发送控制器发送字符 “ HELLO FPGA ”。
在完成本实验前,请确保已经完成前面的实验,包括已经掌握以下能力:
2:实现主程序中调用串口发送控制器发送字符 “HELLO FPGA”
1.1 UART 串口简介 UART串口通信是应用非常广泛的一种串行异步通信方式 , 常用的 接口标准规范有RS232、RS42 2、RS485。
RS-232标准的串口最常见的接口类型为DB9(DB9 接口详细定义见上一课 ), 但是笔记本电脑以及较新一点的台式机都没有 D B9串口,它们一般通过 USB 转串口线来实现与外部设备的串口通信。比如我们常见的 U SB串口,就是通过 U SB接口芯片,实现了以 T TL电平方式的 U ART串口通信。数据通过 U SB接口进行传输,通过 U ART串口芯片完成 U SB协议到 U ART串口协议的相互转换。
由于传统的 DB9 接口体积较大,会占用开发板过多空间,在开发板上我们采用的是 Mini USB 接口,另一端直接和电脑 USB 相连。
Mini USB 串口线
1.2 硬件电路分析 CP2104是单芯片 USB 转 UART 桥接器控制器 ,它将 RS-232/RS-485 设计升级到 USB 提供了一种简单的解决方案, 以更新RS-232/RS-485 设计到 USB 使用最小的组件和 PCB 空间 。在许多现有 RS-232 设计中,如果要将设计从 RS-232 升级到 USB ,只需将 RS-232 电平转换器更换为 CP2104 即可。
2 UART发送驱动设计
2. 1 系统框图 本次实验使用 FPGA 来实现 UART 通信中的数据发送部分, 将数据发给上位机。本实验共有两个模块,分别为顶层模块 uart_top 和发送驱动模块 uiuart_tx 。
波特率 : U ART采用异步通信方式,数据收发双方只有在同一波特率才能正常通信。波特率代表了 U ART完成 1 个时间单位数据位或者控制位的时间。通常,我们需要对系统时钟进行分频来产生正确的波特率,所以计算分频系数尤为重要,比如系统时钟是 25 _ 000_ 000HZ,波特率是 115200 ,那么分频系数为 = 25 000000/115200 -1。
起始位 : U ART数据总线由高电平变低电平并且持续 1 个波特率时间代表数据的起始。
数据位 : 每个数据位占用 1 个波特率时间,本文实验发送 1BYTE字节需要占用 8 个波特率时间。
停止位 : 如果没有奇偶校验位,数据位结束后,保持 1 /1.5/2个波特率的高电平代表了停止位。
奇偶校验 : 用于校对数据,对于 U ART通信,可以根据实际情况选择是否需要支持奇偶校验。
下图中, U ART串口通信数据格式包括 1bit 起始位、 8bits 数据位、 1bit 停止位,不包含奇偶校验位。
当检测到发送数据请求,即 I_uart_wreq 为高时,发送使能( bps_start_en )拉高,代表传输开始,拉高发送忙状态标志( O_uart_wbusy ),以此来告诉其它功能模块不要发送数据的过程中更新发送的数据。根据板子的传输速率,传输 1bit 需要的时间由计数器( baud_div )控制,计满拉高发送时能( bps_en ),代表已发送 1bit , baud_div 清 0 并重新计数,直到所有数据发送完成。串口发送模块将数据并转串,因此需要一个移位模块,用移位计数器 tx_cnt 控制并行数据依次传输,每发送一个 bit 加 1, 计满清 0 ,代表数据发送完毕。
根据以上分析,米联客设计的 U ART发送控制器包含 3 个主要模块 :波特率发生器,发送使能模块,移位模块,其中移位模块中包含了移位计数器和移位控制器。串口发送模块的输入信号主要有系统时钟、系统复位以及发送使能信号和待发送数据,输出信号主要有发送忙状态标志和串口发送总线。
uart_top 模块将并行数据数据( uart_wdata )转成串行数据,并通过 U ART发送驱动模块,将数据通过 uart 发送串行总线( O_uart_tx )发回上位机。顶层模块 uart_top 需要 一个输入的端口为系统时钟 ,输出为串口发送端口 。
初始化内存,将要发送的数据按字节顺序寄存在 uart_tx_buf[0:11] 里,当发送请求拉高(状态 0 ),通过发送驱动模块开始由低位到高位传送 uart_tx_buf 里的数据,发送一字节数据时, uart_wbusy 信号拉高,等待 uart_wbusy 信号拉低(状态 1 ),说明上一个字节发送完毕。 uart_wbusy 信号拉低后 index 计数器加 1 (状态 2 ),再重新等待发送请求(状态 0 )发送的下一个字节,直到数据发送结束。数据发送结束后进入时间延迟状态(状态 3 ),延时计数完成后重新回到状态 0 。
2. 2 驱动接口时序图 米联客设计了一种通用简洁的驱动接口,包含以下信号:
2. 3 驱动源码
module uiuart_tx#
(
parameter integer BAUD_DIV = 216 //设置采样系数(时钟/采样率-1),便于外部修改
)
(
input I_clk,
input I_uart_rstn,
input I_uart_wreq,//发送数据请求
input [7:0] I_uart_wdata,//发送数据
output O_uart_wbusy,//发送状态忙,即是否正在发送数据
output O_uart_tx//发送总线
);
localparam UART_LEN = 4'd10;//设置uart发送的bit数量为10,代表1bit起始位,8bits数据,1bit停止位
wire bps_en ;//发送使能
reg uart_wreq_r = 1'b0;//寄存一次发送数据请求
reg bps_start_en = 1'b0;//波特率计数器启动使能,也是发送启动使能
reg [13:0] baud_div = 14'd0;//波特率计数器
reg [9 :0] uart_wdata_r = 10'h3ff;//寄存10bit数据(1+8+1)
reg [3 :0] tx_cnt = 4'd0;//计数已经发送出多少位数据
assign O_uart_tx = uart_wdata_r[0];//从低位开始输出,且总线上的数据始终为uart_wdata_r[0]
assign O_uart_wbusy = bps_start_en;//发送启动使能有效时,处于发送忙状态
// uart tx Baud rate divider
assign bps_en = (baud_div == BAUD_DIV);//产生一次发送使能信号,条件是baud_div == BAUD_DIV,波特率计数达成
//波特率计数器
always@(posedge I_clk )begin
if((I_uart_rstn== 1'b0) || (I_uart_wreq==1'b1&uart_wreq_r==1'b0))begin
baud_div <= 14'd0;
end
else begin
if(bps_start_en && baud_div < BAUD_DIV)//发送使能为1时,进行波特率计数
baud_div <= baud_div + 1'b1;
else
baud_div <= 14'd0;
end
end
always@(posedge I_clk)begin
uart_wreq_r <= I_uart_wreq;//寄存一次发送请求
end
//UART transmit enable ,Support gapless continuous enable
always@(posedge I_clk)begin
if(I_uart_rstn == 1'b0)
bps_start_en <= 1'b0;
else if(I_uart_wreq==1'b1&uart_wreq_r==1'b0)
bps_start_en <= 1'b1;
else if(tx_cnt == UART_LEN)
bps_start_en <= 1'b0;
else
bps_start_en <= bps_start_en;
end
//UART transmiter BIT counter
always@(posedge I_clk)begin
if(((I_uart_rstn== 1'b0) || (I_uart_wreq==1'b1&uart_wreq_r==1'b0))||(tx_cnt == 10))
tx_cnt <=4'd0;//复位、启动发送、发送完成,重置tx_cnt
else if(bps_en && (tx_cnt < UART_LEN))
tx_cnt <= tx_cnt + 1'b1;//tx_cnt计数器,每发送一个bit加1
end
//uart send controller shift control
always@(posedge I_clk)begin
if((I_uart_wreq==1'b1&uart_wreq_r==1'b0))
//寄存需要发送的数据,包括1bit起始位,8bits数据,1bit停止位
uart_wdata_r <= {1'b1,I_uart_wdata[7:0],1'b0};
else if(bps_en && (tx_cnt < (UART_LEN - 1'b1)))//shift 9 bits
//并串转换,将并行数据依次传输
uart_wdata_r <= {uart_wdata_r[0],uart_wdata_r[9:1]};
else
uart_wdata_r <= uart_wdata_r;
end
endmodule 复制代码
module uart_top
(
input I_sysclk, //系统时钟输入
output O_uart_tx //UART串口发送总线
);
wire uart_rstn; //内部同步复位
wire uart_wbusy;//UART发送驱动器正忙
reg t1s_dly_en;//1S延迟
reg[1:0] S_UART_TX;//UART 发送状态机
reg[3:0] tx_index;//发送index计数器
reg uart_wreq;//UART发送请求
reg[7:0] uart_wdata;//UART发送数据寄存器
reg[7:0] uart_tx_buf[0:11];//发送缓存
reg [15:0]rst_cnt = 16'd0;//复位计数器
reg [24:0]delay_cnt = 25'd0;//延迟计数器
assign uart_rstn = rst_cnt[15];
//Power-on Reset Delay
always @(posedge I_sysclk)begin
rst_cnt <= (rst_cnt[15] == 1'b0) ? (rst_cnt + 1'b1) : rst_cnt ;
end
//帧延迟计数器
always @(posedge I_sysclk)begin
if(t1s_dly_en == 1'b0)
delay_cnt <= 25'd0;
else
delay_cnt <= delay_cnt + 1'b1;
end
//数据发送状态机
//初始化uart_tx_buf,hello fpga等字符共计12 BYTES,以及其他寄存器
always @(posedge I_sysclk)begin
if(uart_rstn==1'b0)begin
uart_tx_buf[0] <=8'h48;//h
uart_tx_buf[1] <=8'h45;//e
uart_tx_buf[2] <=8'h4c;//l
uart_tx_buf[3] <=8'h4c;//l
uart_tx_buf[4] <=8'h4f;//o
uart_tx_buf[5] <=8'h20;//space
uart_tx_buf[6] <=8'h46;//f
uart_tx_buf[7] <=8'h50;//p
uart_tx_buf[8] <=8'h47;//g
uart_tx_buf[9] <=8'h41;//a
uart_tx_buf[10] <=8'h0d;//Enter
uart_tx_buf[11] <=8'h0a;//newline
//各寄存器清零
uart_wdata <= 8'd0;
uart_wreq <= 1'b0;
S_UART_TX <= 2'd0;
t1s_dly_en <= 1'b0;
tx_index <= 4'd0;
end
else begin
case(S_UART_TX)
0:begin
if(!uart_wbusy)begin//When the bus is not busy, request to send
uart_wdata <= uart_tx_buf[tx_index];//准备发送数据,发送tx_index所指向的数据
uart_wreq <= 1'b1;//设置uart_wreq为高电平,请求发送数据
end
else begin//当总线忙
uart_wreq <= 1'b0; //重置uart_wreq
S_UART_TX <= 2'd1;//进入下一状态
end
end
1:begin//该状态等待总线空闲
S_UART_TX <= (uart_wbusy == 1'b0) ? 2'd2: S_UART_TX;
end
2:begin//更新tx_index计数器
if(tx_index < 11)begin//每一帧发送11个字节
tx_index <= tx_index + 1'b1; //tx_index+1计数
S_UART_TX <= 2'd0;//进入下一状态
end
else begin//如果tx_index==11 代表所有数据发送完毕
tx_index <= 4'd0;//重置tx_index
t1s_dly_en <= 1'b1;//1s延迟计数器开始计数
S_UART_TX <= 2'd3;//下一状态
end
end
3:begin//1s延迟计数器计数
//这里的1S不是精确的,仅以delay_cnt[24]最高位作为判断可以节省逻辑资源
if(delay_cnt[24] == 1'b1)begin
S_UART_TX <= 2'd0;
t1s_dly_en <= 1'b0;
end
else
S_UART_TX <= S_UART_TX;
end
endcase
end
end
//例化UART 发送驱动器模块
uiuart_tx#
(
.BAUD_DIV(25000000/115200 -1) //波特率计算,BAUD_DIV = 系统时钟/波特率-1
)
uart_tx_u
(
.I_clk(I_sysclk),
.I_uart_rstn(uart_rstn),
.I_uart_wreq(uart_wreq),
.I_uart_wdata(uart_wdata),
.O_uart_wbusy(uart_wbusy),
.O_uart_tx(O_uart_tx)
);
endmodule 复制代码
3 FPGA工程 fpga工程的创建过程不再重复,如有不清楚的请看前面实验
米联客的代码管理规范 ,在对应的 FPGA 工程路径下创建 uisrc 路径,并且创建以下文件夹
04_pin:放 fpga 的 pin 脚约束文件或者时序约束文件
05_boot:放编译好的 bit 或者 bin 文件 ( 一般为空 )
4 Modelsim仿真
4 . 1 准备工作 Modelsim仿真的创建过程不再重复,如有不清楚的请看前面实验
module uart_top_tb;
reg I_sysclk;
wire O_uart_tx;
uart_top u_uart_top
(
.I_sysclk (I_sysclk),
.O_uart_tx (O_uart_tx)
);
initial begin
I_sysclk = 0;
#100;
end
always #20 I_sysclk = ~I_sysclk;
endmodule 复制代码
4 .2 启动 modelsim仿真 设置运行 100ms(如果运行时间太长可以修改小一些 )
5 下载演示 5 . 1 硬件连接 (该教程为通用型教程,教程中仅展示一款示例开发板的连接方式,具体连接方式以所购买的开发板型号以及结合配套代码管脚约束为准。)
请确保下载器和开发板已经正确连接,并且开发板已经上电 (注意 JTAG 端子不支持热插拔,而 USB 接口支持,所以在不通电的情况下接通好 JTAG 后,再插入 USB 到电脑,之后再上电,以免造成 JTAG IO 损坏 )
5 . 2 运行结果