一、UART简介
UART(universal asynchronous receiver-transmitter)是一种采用异步串行通信方式的通用异步收发传输器。一般来说,UART总是和RS232成对出现,那RS232又是什么呢? RS232也就是我们计算机上的串口,它的全称是EIA-RS-232C (简称232,或者是RS232 )。其中EIA(Electronic Industry Association)代表美国电子工业协会,RS是Recommended Standard的缩写,代表推荐标准,232 是标识符,C表示修改次数,它被广泛用于计算机串行接口外设连接。如果你的计算机上还有串口的话,那么你就可以在主机箱后面看到RS232的接口:
随着时代的发展,这种借口已经很少用了,取而代之的是“USB转串口”,功能和原先一样,但接口更高效了。
串口的主要功能为:在发送数据时将并行数据转换成串行数据进行传输,在接收数据时将接收到的串行数据转换成并行数据。这应该是大多数人接触电子后学习到的第一个通信协议吧。
二、通信格式
下面来说说串口的具体要点:
1.传输时序
UART串口通信需要两个信号线来实现,一根用于串口发送,另外一根负责串口接收。一开始高电平,然后拉低表示开始位,接着8个数据位,然后校验位,最后拉高表示停止位,并且进入空闲状态,等待下一次的数据传输。
很多时候我们的校验位是允许省略的,所以协议就变成了:开始+数据+停止。
2.传输速率:波特率
串口通信的速率用波特率表示,它表示麦苗传输二进制数据的位数,单位是bps(位/秒)。常用的波特率有9600、19200、35400、57600以及115200等。
FPGA开发串口时,设计波特率的方法:FPGA的时钟频率/波特率。例如我的FPGA开发板时钟频率为50Mhz,即50_000_000hz,我想使用的波特率为9600bps,因此我需要的计数为:50000000/9600≈5208。
三、串口回环设计
现在用FPGA开发板做一个串口回环的实验,要求是PC端通过串口助手发送数据给FPGA,FPGA接收到数据后返回给PC端,并在串口助手处显示数值。即串口助手发什么就能收回什么。实验框图如下:
1.uart_rx
1 //**************************************************************************
2 // *** 名称 : uart_rx.v
3 // *** 作者 : xianyu_FPGA
4 // *** 博客 : https://www.cnblogs.com/xianyufpga/
5 // *** 日期 : 2019-01-10
6 // *** 描述 : 串口接收模块,计数9.5下,其中停止位0.5下
7 // 因为串口助手发送本次停止位和下次开始位中间没有留空闲位
8 // 若计满10下,则才结束本次传输下次数据就来了,会来不及接收
9 //**************************************************************************
10
11 module uart_rx
12 //========================< 参数 >==========================================
13 #(
14 parameter CLK = 50_000_000 , //系统时钟,50Mhz
15 parameter BPS = 9600 , //波特率
16 parameter BPS_CNT = CLK/BPS //波特率计数
17 )
18 //========================< 端口 >==========================================
19 (
20 input wire clk , //时钟,50Mhz
21 input wire rst_n , //复位,低电平有效
22 input wire din , //输入数据
23 output reg [7:0] dout , //输出数据
24 output reg dout_vld //输出数据的有效指示
25 );
26 //========================< 信号 >==========================================
27 reg rx0 ;
28 reg rx1 ;
29 reg rx2 ;
30 wire rx_en ;
31 reg flag ;
32 reg [15:0] cnt0 ;
33 wire add_cnt0 ;
34 wire end_cnt0 ;
35 reg [ 3:0] cnt1 ;
36 wire add_cnt1 ;
37 wire end_cnt1 ;
38 reg [ 7:0] data ;
39
40 //==========================================================================
41 //== 消除亚稳态 + 下降沿检测
42 //==========================================================================
43 always @(posedge clk or negedge rst_n) begin
44 if(!rst_n) begin
45 rx0 <= 1;
46 rx1 <= 1;
47 rx2 <= 1;
48 end
49 else begin
50 rx0 <= din;
51 rx1 <= rx0;
52 rx2 <= rx1;
53 end
54 end
55
56 assign rx_en = rx2 && ~rx1;
57
58 //==========================================================================
59 //== 接收状态指示
60 //==========================================================================
61 always @(posedge clk or negedge rst_n) begin
62 if(!rst_n)
63 flag <= 0;
64 else if(rx_en)
65 flag <= 1;
66 else if(end_cnt1)
67 flag <= 0;
68 end
69
70 //==========================================================================
71 //== 波特率计数
72 //==========================================================================
73 always @(posedge clk or negedge rst_n) begin
74 if(!rst_n)
75 cnt0 <= 0;
76 else if(add_cnt0) begin
77 if(end_cnt0)
78 cnt0 <= 0;
79 else
80 cnt0 <= cnt0 + 1;
81 end
82 end
83
84 assign add_cnt0 = flag;
85 assign end_cnt0 = cnt0== BPS_CNT-1 || end_cnt1;
86
87 //==========================================================================
88 //== 开始1位(不接收) + 数据8位 + 停止0.5位(不接收),共10位
89 //==========================================================================
90 always @(posedge clk or negedge rst_n) begin
91 if(!rst_n)
92 cnt1 <= 0;
93 else if(add_cnt1) begin
94 if(end_cnt1)
95 cnt1 <= 0;
96 else
97 cnt1 <= cnt1 + 1;
98 end
99 end
100
101 assign add_cnt1 = end_cnt0;
102 assign end_cnt1 = cnt1==10-1 && cnt0==BPS_CNT/2-1;
103
104 //==========================================================================
105 //== 缓存数据
106 //==========================================================================
107 always @ (posedge clk or negedge rst_n)begin
108 if(!rst_n)
109 data <= 8'd0;
110 else if(cnt1>=1 && cnt1<=8 && cnt0==BPS_CNT/2-1) //中间采样
111 data[cnt1-1] <= rx2; //或 dout <= {rx2,dout[7:1]};
112 end
113
114 //==========================================================================
115 //== 输出数据
116 //==========================================================================
117 always @ (posedge clk or negedge rst_n)begin
118 if(!rst_n)
119 dout <= 0;
120 else if(end_cnt1)
121 dout <= data;
122 end
123
124 always @ (posedge clk or negedge rst_n)begin
125 if(!rst_n)
126 dout_vld <= 0;
127 else if(end_cnt1)
128 dout_vld <= 1;
129 else
130 dout_vld <= 0;
131 end
132
133
134
135 endmodule
2.uart_tx
1 //**************************************************************************
2 // *** 名称 : uart_tx.v
3 // *** 作者 : xianyu_FPGA
4 // *** 博客 : https://www.cnblogs.com/xianyufpga/
5 // *** 日期 : 2019-01-10
6 // *** 描述 : 串口接收模块,计数9.5下,其中停止位0.5下
7 // 因为极端情况是本次停止位和下次开始位中间没有留空闲位
8 // 若计满10下,则才结束本次传输下次数据就来了,会来不及发送
9 //**************************************************************************
10
11 module uart_tx
12 //========================< 参数 >==========================================
13 #(
14 parameter CLK = 50_000_000 , //系统时钟,50Mhz
15 parameter BPS = 9600 , //波特率
16 parameter BPS_CNT = CLK/BPS //波特率计数
17 )
18 //========================< 端口 >==========================================
19 (
20 input wire clk , //时钟,50Mhz
21 input wire rst_n , //复位,低电平有效
22 input wire [7:0] din , //输入数据
23 input wire din_vld , //输入数据的有效指示
24 output reg dout //输出数据
25 );
26 //========================< 信号 >==========================================
27 reg flag ;
28 reg [ 7:0] din_tmp ;
29 reg [15:0] cnt0 ;
30 wire add_cnt0 ;
31 wire end_cnt0 ;
32 reg [ 3:0] cnt1 ;
33 wire add_cnt1 ;
34 wire end_cnt1 ;
35 wire [ 9:0] data ;
36
37 //==========================================================================
38 //== 数据暂存(din可能会消失,暂存住)
39 //==========================================================================
40 always @ (posedge clk or negedge rst_n) begin
41 if(!rst_n)
42 din_tmp <=8'd0;
43 else if(din_vld)
44 din_tmp <= din;
45 end
46
47 //==========================================================================
48 //== 发送状态指示
49 //==========================================================================
50 always @(posedge clk or negedge rst_n)begin
51 if(!rst_n)
52 flag <= 0;
53 else if(din_vld)
54 flag <= 1;
55 else if(end_cnt1)
56 flag <= 0;
57 end
58
59 //==========================================================================
60 //== 波特率计数
61 //==========================================================================
62 always @(posedge clk or negedge rst_n) begin
63 if(!rst_n)
64 cnt0 <= 0;
65 else if(add_cnt0) begin
66 if(end_cnt0)
67 cnt0 <= 0;
68 else
69 cnt0 <= cnt0 + 1;
70 end
71 end
72
73 assign add_cnt0 = flag;
74 assign end_cnt0 = cnt0== BPS_CNT-1 || end_cnt1;
75
76 //==========================================================================
77 //== 开始1位 + 数据8位 + 停止0.5位,共10位
78 //==========================================================================
79 always @(posedge clk or negedge rst_n) begin
80 if(!rst_n)
81 cnt1 <= 0;
82 else if(add_cnt1) begin
83 if(end_cnt1)
84 cnt1 <= 0;
85 else
86 cnt1 <= cnt1 + 1;
87 end
88 end
89
90 assign add_cnt1 = end_cnt0;
91 assign end_cnt1 = cnt1==10-1 && cnt0==BPS_CNT/2-1;
92
93 //==========================================================================
94 //== 数据输出(用case语句也行)
95 //==========================================================================
96 assign data = {1'b1,din_tmp,1'b0}; //停止,数据,开始
97
98 always @(posedge clk or negedge rst_n) begin
99 if(!rst_n)
100 dout <= 1'b1;
101 else if(flag)
102 dout <= data[cnt1];
103 end
104
105
106
107 endmodule
3.top层
1 //**************************************************************************
2 // *** 名称 : uart_top.v
3 // *** 作者 : xianyu_FPGA
4 // *** 博客 : https://www.cnblogs.com/xianyufpga/
5 // *** 日期 : 2019-01-10
6 // *** 描述 : 串口实验顶层文件
7 //**************************************************************************
8
9 module uart_top
10 //========================< 端口 >==========================================
11 (
12 input wire clk , //时钟,50Mhz
13 input wire rst_n , //复位,低电平有效
14 input wire uart_rx , //FPGA通过串口接收的数据
15 output wire uart_tx //FPGA通过串口发送的数据
16 );
17
18 //========================< 连线 >==========================================
19 wire [7:0] data ;
20 wire data_vld ;
21
22 //==========================================================================
23 //== 模块例化
24 //==========================================================================
25 uart_rx
26 #(
27 .BPS_CNT (52 ) //仿真用
28 )
29 u_uart_rx
30 (
31 .clk (clk ),
32 .rst_n (rst_n ),
33 .din (uart_rx ),
34 .dout (data ),
35 .dout_vld (data_vld )
36 );
37
38 uart_tx
39 #(
40 .BPS_CNT (52 ) //仿真用
41 )
42 u_uart_tx
43 (
44 .clk (clk ),
45 .rst_n (rst_n ),
46 .din_vld (data_vld ),
47 .din (data ),
48 .dout (uart_tx )
49 );
50
51
52
53 endmodule
四、仿真调试
1、testbench
1 `timescale 1ns/1ps //时间精度
2 `define Clock 20 //时钟周期
3
4 module uart_top_tb;
5
6 //========================< 端口 >==========================================
7 reg clk ; //时钟,50Mhz
8 reg rst_n ; //复位,低电平有效
9 reg uart_rx ;
10 wire uart_tx ;
11
12 //==========================================================================
13 //== 模块例化
14 //==========================================================================
15 uart_top u_uart_top
16 (
17 .clk (clk ),
18 .rst_n (rst_n ),
19 .uart_rx (uart_rx ),
20 .uart_tx (uart_tx )
21 );
22
23 //==========================================================================
24 //== 时钟信号和复位信号
25 //==========================================================================
26 initial begin
27 clk = 1;
28 forever
29 #(`Clock/2) clk = ~clk;
30 end
31
32 initial begin
33 rst_n = 0; #(`Clock*20+1);
34 rst_n = 1;
35 end
36
37 //==========================================================================
38 //== task任务
39 //==========================================================================
40 reg [7:0] mem[15:0] ; //位宽为8,深度为16个数据
41 integer i ;
42 integer j ;
43
44 //读取外部数据
45 initial $readmemh("./data.txt",mem);
46
47 //位赋值
48 task rx_bit
49 (
50 input [7:0] data
51 );
52 begin
53 for(i=0;i<=9;i=i+1) begin //10个bit为
54 case(i)
55 0: uart_rx = 1'b0;
56 1: uart_rx = data[i-1];
57 2: uart_rx = data[i-1];
58 3: uart_rx = data[i-1];
59 4: uart_rx = data[i-1];
60 5: uart_rx = data[i-1];
61 6: uart_rx = data[i-1];
62 7: uart_rx = data[i-1];
63 8: uart_rx = data[i-1];
64 9: uart_rx = 1'b1;
65 endcase
66 #1040; //一个完整波特延时:52*20=1040
67 end //考虑到空闲位,也可以设置得1040稍大一些
68 end
69 endtask
70
71 //字节赋值
72 task rx_byte;
73 begin
74 for(j=0;j<=15;j=j+1) //16个byte数据
75 rx_bit(mem[j]);
76 end
77 endtask
78
79 //==========================================================================
80 //== 调用task
81 //==========================================================================
82 initial begin
83 #(`Clock*20+1);
84 rx_byte();
85 end
86
87 initial begin
88 #180000;
89 $stop;
90 end
91
92 endmodule
2、data.txt
testbench中调用了一个 data.txt 文本文档,里面存储了此次仿真的16个数据,将其放置到 Modelsim 软件的工程目录中(非 work)即可。
0
1
2
3
4
5
6
7
8
9
a
b
c
d
e
f
3、仿真波形
由波形可以看到,本次设计应该是成功的。
五、上板验证
本次上位机采用友善串口助手,无校验位,停止位为1。当串口助手发送数据给FPGA后,FPGA很快又将原数据返回给上位机。
经上板验证,本次设计成功!
参考资料:
[1]明德扬FPGA教程
[2]正点原子FPGA教程
[2]威三学院FPGA教程
来源:oschina
链接:https://my.oschina.net/u/4383702/blog/3678680