[米联客-安路飞龙DR1-FPSOC] FPGA基础篇连载-10 UART串口发送驱动设计
软件版本:Anlogic -TD5.9.1-DR1_ES1.1
操作系统:WIN10 64bit
硬件平台:适用安路(Anlogic)FPGA
实验平台:米联客-MLK-L1-CZ06-DR1M90G开发板
板卡获取平台:https://milianke.tmall.com/
登录"米联客"FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!
1概述
本章将学习 UART 通信的原理及其硬件电路设计,并使用FPGA来实现UART串口发送控制器的设计,实现主程序中调用串口发送控制器发送字符"HELLO FPGA"。
在完成本实验前,请确保已经完成前面的实验,包括已经掌握以下能力:
1:完成了TD软件安装
2:完成了modelsim安装以及TD库的编译
3:掌握了TD仿真环境的设置
4:掌握了modesim通过do文件启动仿真
实验目的:
1:实现UART串口发送控制器的设计
2:实现主程序中调用串口发送控制器发送字符"HELLO FPGA"
3:实用modelsim完成仿真验证
4:编译并且固化程序到FPGA验证
1.1 UART串口简介
UART串口通信是应用非常广泛的一种串行异步通信方式,常用的接口标准规范有RS232、RS422、RS485。
RS-232标准的串口最常见的接口类型为DB9(DB9 接口详细定义见上一课),但是笔记本电脑以及较新一点的台式机都没有DB9串口,它们一般通过USB 转串口线来实现与外部设备的串口通信。比如我们常见的USB串口,就是通过USB接口芯片,实现了以TTL电平方式的UART串口通信。数据通过USB接口进行传输,通过UART串口芯片完成USB协议到UART串口协议的相互转换。
DB9接口USB串口线
由于传统的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。
发送驱动模块uiuart_tx:
在开始编写串口发送驱动前,我们需要了解以下概念:
波特率:UART采用异步通信方式,数据收发双方只有在同一波特率才能正常通信。波特率代表了UART完成1个时间单位数据位或者控制位的时间。通常,我们需要对系统时钟进行分频来产生正确的波特率,所以计算分频系数尤为重要,比如系统时钟是100_000_000HZ,波特率是115200,那么分频系数为=100000000/115200-1。
起始位:UART数据总线由高电平变低电平并且持续1个波特率时间代表数据的起始。
数据位:每个数据位占用1个波特率时间,本文实验发送1BYTE字节需要占用8个波特率时间。
停止位:如果没有奇偶校验位,数据位结束后,保持1/1.5/2个波特率的高电平代表了停止位。
奇偶校验:用于校对数据,对于UART通信,可以根据实际情况选择是否需要支持奇偶校验。
下图中,UART串口通信数据格式包括1bit起始位、8bits数据位、1bit停止位,不包含奇偶校验位。
UART发送时序
当检测到发送数据请求,即I_uart_wreq为高时,发送使能(bps_start_en)拉高,代表传输开始,拉高发送忙状态标志(O_uart_wbusy),以此来告诉其它功能模块不要发送数据的过程中更新发送的数据。根据板子的传输速率,传输1bit需要的时间由计数器(baud_div)控制,计满拉高发送时能(bps_en),代表已发送1bit,baud_div清0并重新计数,直到所有数据发送完成。串口发送模块将数据并转串,因此需要一个移位模块,用移位计数器tx_cnt控制并行数据依次传输,每发送一个bit加1,计满清0,代表数据发送完毕。
根据以上分析,米联客设计的UART发送控制器包含3个主要模块:波特率发生器,发送使能模块,移位模块,其中移位模块中包含了移位计数器和移位控制器。串口发送模块的输入信号主要有系统时钟、系统复位以及发送使能信号和待发送数据,输出信号主要有发送忙状态标志和串口发送总线。
顶层模块uart_top:
uart_top模块将并行数据数据(uart_wdata)转成串行数据,并通过UART发送驱动模块,将数据通过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驱动接口时序图
米联客设计了一种通用简洁的驱动接口,包含以下信号:
xxx_wreq:数据发送请求
xxx_wdata:需要发送的数据
xxx_wbusy:数据发送忙状态指示
这里xxx代表了uart
2.3驱动源码
代码如下:
1 `timescale 1ns / 1ns//仿真时间刻度/精度 2 3 module uiuart_tx# 4 ( 5 parameter integer BAUD_DIV = 10416 //设置采样系数 (时钟/采样率-1) 6 ) 7 ( 8 input I_clk,//系统时钟输入 9 input I_uart_rstn,//系统复位输入 10 input I_uart_wreq, //发送数据请求 11 input [7:0] I_uart_wdata, //发送数据 12 output O_uart_wbusy,//发送状态忙,代表正在发送数据 13 output O_uart_tx//uart tx 发送总线 14 ); 15 16 localparam UART_LEN = 4'd10; //设置uart 发送的bit数量为10,代表1bit起始位,8bits数据,1bit停止位 17 wire bps_en ; //发送使能 18 reg uart_wreq_r = 1'b0;//寄存一次I_uart_wreq 19 reg bps_start_en = 1'b0; //波特率计数器启动使能,也是发送启动使能 20 reg [13:0] baud_div = 14'd0;//波特率计数器 21 reg [9 :0] uart_wdata_r = 10'h3ff;//寄存I_uart_wreq 22 reg [3 :0] tx_cnt = 4'd0;//计数发送了多少bits 23 24 assign O_uart_tx = uart_wdata_r[0];//总线上的数据,始终是uart_wdata_r[0] 25 26 assign O_uart_wbusy = bps_start_en;//总线忙标志,即是bps_start_en为有效,即当总线忙于发送,总线忙 27 28 // 发送使能 29 assign bps_en = (baud_div == BAUD_DIV); //产生一次发送使能信号,条件是baud_div == BAUD_DIV,波特率计数达成 30 31 //波特率计数器 32 always@(posedge I_clk )begin 33 if((I_uart_rstn== 1'b0) || (I_uart_wreq==1'b1&uart_wreq_r==1'b0))begin 34 baud_div <= 14'd0; 35 end 36 else begin 37 if(bps_start_en && baud_div < BAUD_DIV) //bps_start_en的信号拉高,表示开始发送 38 baud_div <= baud_div + 1'b1; //且baud_div < BAUD_DIV波特率计算,未达到波特率baud_div+1 39 else 40 baud_div <= 14'd0; //达到清零 41 end 42 end 43 44 always@(posedge I_clk)begin 45 uart_wreq_r <= I_uart_wreq; //寄存一次I_uart_wreq信号 46 end 47 48 //当I_uart_wreq从低电平变为高电平,启动发送 49 always@(posedge I_clk)begin 50 if(I_uart_rstn == 1'b0) 51 bps_start_en <= 1'b0; //复位,计数清零 52 else if(I_uart_wreq==1'b1&uart_wreq_r==1'b0) //I_uart_wreq上升沿激活 53 bps_start_en <= 1'b1; //激活后将 bps_start_en拉高,传输开始 54 else if(tx_cnt == UART_LEN) //tx_cnt用于计数当前发送的bits数量,当达到预定值UART_LEN 55 bps_start_en <= 1'b0; //将 bps_start_en拉低,传输结束 56 else 57 bps_start_en <= bps_start_en; 58 end 59 60 //发送bits计数器 61 always@(posedge I_clk)begin 62 if(((I_uart_rstn== 1'b0) || (I_uart_wreq==1'b1&uart_wreq_r==1'b0))||(tx_cnt == 10))//当复位、启动发送、发送完成,重置tx_cnt 63 tx_cnt <=4'd0; 64 else if(bps_en && (tx_cnt < UART_LEN)) //tx_cnt计数器,每发送一个bit加1 65 tx_cnt <= tx_cnt + 1'b1; 66 end 67 68 //uart发送并串移位控制器 69 always@(posedge I_clk)begin 70 if((I_uart_wreq==1'b1&uart_wreq_r==1'b0)) //当发送请求有效,寄存需要发送的数据到uart_wdata_r 71 uart_wdata_r <= {1'b1,I_uart_wdata[7:0],1'b0};//寄存需要发送的数据,包括1bit 起始位,8bits数据,1bit停止位 72 else if(bps_en && (tx_cnt < (UART_LEN - 1'b1))) //shift 9 bits 73 uart_wdata_r <= {uart_wdata_r[0],uart_wdata_r[9:1]}; //并串转换,将并行数据依次传输 74 else 75 uart_wdata_r <= uart_wdata_r; 76 end 77 endmodule
顶层模块uart_top
1 `timescale 1ns / 1ns 2 module uart_top 3 ( 4 input I_sysclk,//系统时钟输入 5 output O_uart_tx //UART串口发送总线 6 ); 7 8 wire uart_rstn_i; //内部同步复位 9 wire uart_wbusy; //UART发送驱动器正忙 10 reg t1s_dly_en; //1S延迟 11 reg[1:0] S_UART_TX; //UART 发送状态机 12 reg[3:0] tx_index; //发送index计数器 13 reg uart_wreq; //UART发送请求 14 reg[7:0] uart_wdata; //UART发送数据寄存器 15 reg[7:0] uart_tx_buf[0:11]; //发送缓存 16 17 reg [15:0]rst_cnt = 16'd0; //复位计数器 18 reg [24:0]delay_cnt = 25'd0; //延迟计数器 19 20 assign uart_rstn_i = rst_cnt[15]; //复位 21 22 //上电通过计数器计数,实现复位 23 always @(posedge I_sysclk)begin 24 rst_cnt <= (rst_cnt[15] == 1'b0) ? (rst_cnt + 1'b1) : rst_cnt ; 25 end 26 27 //帧延迟计数器 28 always @(posedge I_sysclk)begin 29 if(t1s_dly_en == 1'b0) 30 delay_cnt <= 25'd0; 31 else 32 delay_cnt <= delay_cnt + 1'b1; 33 end 34 35 //数据发送状态机 36 always @(posedge I_sysclk)begin 37 if(uart_rstn_i==1'b0)begin //初始化uart_tx_buf,为hello fpga等字符共计12 BYTES,以及其他寄存器 38 uart_tx_buf[0] <=8'h48;//h 39 uart_tx_buf[1] <=8'h45;//e 40 uart_tx_buf[2] <=8'h4c;//l 41 uart_tx_buf[3] <=8'h4c;//l 42 uart_tx_buf[4] <=8'h4f;//o 43 uart_tx_buf[5] <=8'h20;//space 44 uart_tx_buf[6] <=8'h46;//f 45 uart_tx_buf[7] <=8'h50;//p 46 uart_tx_buf[8] <=8'h47;//g 47 uart_tx_buf[9] <=8'h41;//a 48 uart_tx_buf[10] <=8'h0d;//Enter 49 uart_tx_buf[11] <=8'h0a;//newline 50 51 uart_wdata <= 8'd0; 52 uart_wreq <= 1'b0; 53 S_UART_TX <= 2'd0; 54 t1s_dly_en <= 1'b0; 55 tx_index <= 4'd0; 56 end 57 else begin 58 case(S_UART_TX) 59 0:begin 60 if(!uart_wbusy)begin//如果UART发送驱动器不忙 61 uart_wdata <= uart_tx_buf[tx_index];//准备发送数据,发送tx_index所指向的数据 62 uart_wreq <= 1'b1; //设置uart_wreq为高电平,请求发送数据 63 end 64 else begin //当总线忙 65 uart_wreq <= 1'b0; //重置uart_wreq 66 S_UART_TX <= 2'd1; //进入下一状态 67 end 68 end 69 1:begin//该状态等待总线空闲 70 S_UART_TX <= (uart_wbusy == 1'b0) ? 2'd2: S_UART_TX; 71 end 72 2:begin//更新tx_index计数器 73 if(tx_index < 11)begin //每一帧发送12个字节 74 tx_index <= tx_index + 1'b1; //tx_index 加计数 75 S_UART_TX <= 2'd0; //进入下一状态 76 end 77 else begin //如果tx_index==11 代表所有数据发送完毕 78 tx_index <= 4'd0; //重置tx_index 79 t1s_dly_en <= 1'b1; //1s 延迟计数器开始计数 80 S_UART_TX <= 2'd3; //下一状态 81 end 82 end 83 3:begin//登台延迟计数器计数 84 if(delay_cnt[24] == 1'b1)begin //这里的1S不是精确的,使用delay_cnt[24]可以节省逻辑资源 85 S_UART_TX <= 2'd0; //回到状态0 86 t1s_dly_en <= 1'b0; //关闭1S延迟计数器 87 end 88 else //否则还是在当前状态等待 89 S_UART_TX <= S_UART_TX; 90 end 91 endcase 92 end 93 94 end 95 96 //例化UART 发送驱动器模块 97 uiuart_tx# 98 ( 99 .BAUD_DIV(25_000_000/115200-1) //波特率计算 BAUD_DIV = 系统时钟/波特率-1 100 ) 101 uart_tx_u 102 ( 103 .I_clk(I_sysclk),//系统时钟输入 104 .I_uart_rstn(uart_rstn_i), //系统复位输入 105 .I_uart_wreq(uart_wreq), //UART发送(写)数据请求 106 .I_uart_wdata(uart_wdata), //UART发送(写)数据 107 .O_uart_wbusy(uart_wbusy),//UART发送驱动器忙 108 .O_uart_tx(O_uart_tx) //UART 发送串行总线 109 ); 110 111 endmodule
3 FPGA工程
fpga工程的创建过程不再重复,如有不清楚的请看前面实验
米联客的代码管理规范,在对应的FPGA工程路径下创建uisrc路径,并且创建以下文件夹
01_rtl:放用户编写的rtl代码
02_sim:仿真文件或者工程
03_ip:放使用到的ip文件
04_pin:放fpga的pin脚约束文件或者时序约束文件
05_boot:放编译好的bit或者bin文件(一般为空)
06_doc:放本一些相关文档(一般为空)
4 Modelsim仿真
4.1准备工作
Modelsim仿真的创建过程不再重复,如有不清楚的请看前面实验
仿真测试文件源码如下:
1 `timescale 1ns / 1ns 2 3 module uart_top_TB; 4 5 reg I_sysclk; 6 wire O_uart_tx; 7 8 uart_top u_uart_top 9 ( 10 .I_sysclk (I_sysclk), 11 .O_uart_tx (O_uart_tx) 12 ); 13 14 initial begin 15 I_sysclk = 0; 16 #100; 17 end 18 19 always #20 I_sysclk = ~I_sysclk; 20 21 endmodule
4.2启动modelsim仿真
启动后,右击需要观察的信号,添加到波形窗口
设置restart
设置运行100ms(如果运行时间太长可以修改小一些)
5下载演示
下载程序前,先确保FPGA工程已经编译。
5.1硬件连接
(该教程为通用型教程,教程中仅展示一款示例开发板的连接方式,具体连接方式以所购买的开发板型号以及结合配套代码管脚约束为准。)
请确保下载器和开发板已经正确连接,并且开发板已经上电(注意JTAG端子不支持热插拔,而USB接口支持,所以在不通电的情况下接通好JTAG后,再插入USB到电脑,之后再上电,以免造成JTAG IO损坏)
5.2运行结果
本文来米联客(milianke),作者:米联客(milianke),转载请注明原文链接:https://www.cnblogs.com/milianke/p/18330055