协议——UART(RS232)
一、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教程