一、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.5即可,即加0.5个空闲位。其他时候建议cnt1计数9下,这样即使停止位是1也不会出现数据丢失的情况。
1 //========================================================================== 2 // --- 名称 : uart_rx.v 3 // --- 作者 : xianyu_FPGA 4 // --- 日期 : 2019-06-24 5 // --- 描述 : 串口接收模块,cnt1计数10下,是为配合发送模块的计数10下。 6 // 此时需要将串口助手停止位设置为1.5,即加0.5个空闲位。 7 // 其他时候建议cnt1计数9下,停止位为1,以保证连续接收数据无误。 8 //========================================================================== 9 10 module uart_rx 11 //---------------------<参数定义>------------------------------------------- 12 #( 13 parameter BPS = 5208 , //波特率 14 parameter BPS_W = 13 , //波特率位宽 15 parameter BPS_half = 2604 //波特率中间采样点 16 ) 17 //---------------------<端口声明>------------------------------------------- 18 ( 19 input wire clk , //时钟,50Mhz 20 input wire rst_n , //复位,低电平有效 21 input wire din , //输入数据 22 output reg [7:0] dout , //输出数据 23 output reg dout_vld //输出数据的有效指示 24 ); 25 //---------------------<信号定义>------------------------------------------- 26 reg rx0 ; 27 reg rx1 ; 28 reg rx2 ; 29 wire rx_en ; 30 reg flag ; 31 reg [BPS_W-1:0] cnt0 ; 32 wire add_cnt0 ; 33 wire end_cnt0 ; 34 reg [3:0] cnt1 ; 35 wire add_cnt1 ; 36 wire end_cnt1 ; 37 reg [7:0] data ; 38 39 //-------------------------------------------------------------------------- 40 //-- 消除亚稳态 + 下降沿检测 41 //-------------------------------------------------------------------------- 42 always @(posedge clk) begin 43 {rx2,rx1,rx0} <= {rx1,rx0,din}; 44 end 45 46 assign rx_en = rx2 && ~rx1; 47 48 //-------------------------------------------------------------------------- 49 //-- 接收状态指示 50 //-------------------------------------------------------------------------- 51 always @(posedge clk or negedge rst_n) begin 52 if(!rst_n) 53 flag <= 0; 54 else if(rx_en) 55 flag <= 1; 56 else if(end_cnt1) 57 flag <= 0; 58 end 59 60 //-------------------------------------------------------------------------- 61 //-- 波特率计数 62 //-------------------------------------------------------------------------- 63 always @(posedge clk or negedge rst_n) begin 64 if(!rst_n) 65 cnt0 <= 0; 66 else if(add_cnt0) begin 67 if(end_cnt0) 68 cnt0 <= 0; 69 else 70 cnt0 <= cnt0 + 1; 71 end 72 else 73 cnt0 <= cnt0; 74 end 75 76 assign add_cnt0 = flag; 77 assign end_cnt0 = add_cnt0 && cnt0== BPS-1; 78 79 //-------------------------------------------------------------------------- 80 //-- 开始位(不接收) + 8个数据位 + 停止位(不接收)= 10 81 //-------------------------------------------------------------------------- 82 always @(posedge clk or negedge rst_n) begin 83 if(!rst_n) 84 cnt1 <= 0; 85 else if(add_cnt1) begin 86 if(end_cnt1) 87 cnt1 <= 0; 88 else 89 cnt1 <= cnt1 + 1; 90 end 91 else 92 cnt1 <= cnt1; 93 end 94 95 assign add_cnt1 = end_cnt0; 96 assign end_cnt1 = add_cnt1 && cnt1==10-1; 97 98 //-------------------------------------------------------------------------- 99 //-- 缓存数据 100 //-------------------------------------------------------------------------- 101 always @ (posedge clk or negedge rst_n)begin 102 if(!rst_n) 103 data <= 8'd0; 104 else if(cnt1>=1 && cnt1<=8 && cnt0==BPS_half-1) //中间采样 105 data[cnt1-1] <= rx2; //或 dout <= {rx2,dout[7:1]}; 106 end 107 108 //-------------------------------------------------------------------------- 109 //-- 输出数据 110 //-------------------------------------------------------------------------- 111 always @ (posedge clk or negedge rst_n)begin 112 if(!rst_n) 113 dout <= 0; 114 else if(end_cnt1) 115 dout <= data; 116 end 117 118 always @ (posedge clk or negedge rst_n)begin 119 if(!rst_n) 120 dout_vld <= 0; 121 else if(end_cnt1) 122 dout_vld <= 1; 123 else 124 dout_vld <= 0; 125 end 126 127 128 129 endmodule
2.uart_tx
设计和协议完全匹配。
1 //========================================================================== 2 // --- 名称 : uart_tx.v 3 // --- 作者 : xianyu_FPGA 4 // --- 日期 : 2019-06-24 5 // --- 描述 : 串口发送模块 6 //========================================================================== 7 8 module uart_tx 9 //---------------------<参数定义>------------------------------------------- 10 #( 11 parameter BPS = 5208 , //波特率 12 parameter BPS_W = 13 //波特率位宽 13 ) 14 //---------------------<端口声明>------------------------------------------- 15 ( 16 input wire clk , //时钟,50Mhz 17 input wire rst_n , //复位,低电平有效 18 input wire [7:0] din , //输入数据 19 input wire din_vld , //输入数据的有效指示 20 output reg dout //输出数据 21 ); 22 //---------------------<信号定义>------------------------------------------- 23 reg flag ; 24 reg [7:0] din_tmp ; 25 reg [BPS_W-1:0] cnt0 ; 26 wire add_cnt0 ; 27 wire end_cnt0 ; 28 reg [3:0] cnt1 ; 29 wire add_cnt1 ; 30 wire end_cnt1 ; 31 wire [9:0] data ; 32 33 //-------------------------------------------------------------------------- 34 //-- 数据暂存(din可能会消失,暂存住) 35 //-------------------------------------------------------------------------- 36 always @ (posedge clk or negedge rst_n) begin 37 if(!rst_n) 38 din_tmp <=8'd0; 39 else if(din_vld) 40 din_tmp <= din; 41 end 42 43 //-------------------------------------------------------------------------- 44 //-- 发送状态指示 45 //-------------------------------------------------------------------------- 46 always @(posedge clk or negedge rst_n)begin 47 if(!rst_n) 48 flag <= 0; 49 else if(din_vld) 50 flag <= 1; 51 else if(end_cnt1) 52 flag <= 0; 53 end 54 55 //-------------------------------------------------------------------------- 56 //-- 波特率计数 57 //-------------------------------------------------------------------------- 58 always @(posedge clk or negedge rst_n) begin 59 if(!rst_n) 60 cnt0 <= 0; 61 else if(add_cnt0) begin 62 if(end_cnt0) 63 cnt0 <= 0; 64 else 65 cnt0 <= cnt0 + 1; 66 end 67 else 68 cnt0 <= cnt0; 69 end 70 71 assign add_cnt0 = flag; 72 assign end_cnt0 = add_cnt0 && cnt0== BPS-1; 73 74 //-------------------------------------------------------------------------- 75 //-- 开始 + 数据 + 结束,共10位 76 //-------------------------------------------------------------------------- 77 always @(posedge clk or negedge rst_n) begin 78 if(!rst_n) 79 cnt1 <= 0; 80 else if(add_cnt1) begin 81 if(end_cnt1) 82 cnt1 <= 0; 83 else 84 cnt1 <= cnt1 + 1; 85 end 86 else 87 cnt1 <= cnt1; 88 end 89 90 assign add_cnt1 = end_cnt0; 91 assign end_cnt1 = add_cnt1 && cnt1== 10-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==1) 102 dout <= data[cnt1]; 103 end 104 105 106 107 endmodule
3.uart_top顶层文件
1 //========================================================================== 2 // --- 名称 : uart_top.v 3 // --- 作者 : xianyu_FPGA 4 // --- 日期 : 209-06-24 5 // --- 描述 : 串口实验顶层文件 6 //========================================================================== 7 8 module uart_top 9 //---------------------<端口声明>------------------------------------------- 10 ( 11 input wire clk , //时钟,50Mhz 12 input wire rst_n , //复位,低电平有效 13 input wire uart_rx , //FPGA通过串口接收的数据 14 output wire uart_tx //FPGA通过串口发送的数据 15 ); 16 17 //---------------------<模块连线>------------------------------------------- 18 wire [7:0] data ; 19 wire data_vld ; 20 21 //-------------------------------------------------------------------------- 22 //-- 模块例化 23 //-------------------------------------------------------------------------- 24 uart_rx u_uart_rx 25 ( 26 .clk (clk ), 27 .rst_n (rst_n ), 28 .din (uart_rx ), 29 .dout (data ), 30 .dout_vld (data_vld ) 31 ); 32 33 uart_tx u_uart_tx 34 ( 35 .clk (clk ), 36 .rst_n (rst_n ), 37 .din_vld (data_vld ), 38 .din (data ), 39 .dout (uart_tx ) 40 ); 41 42 endmodule
4.testbench
里面的data.txt是一个文本文件,其内容为:0 1 2 3 4 5 6 7 8 9 a b c d e f ,放到你的工作文件夹区即可。
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 //-- tase任务 39 //---------------------------------------------------------------------- 40 reg [7:0] mem[15:0] ; //位宽为8,深度为16 41 integer i ; 42 integer j ; 43 44 //读取外部数据 45 initial begin 46 $readmemh("./data.txt",mem); 47 end 48 49 //位 50 task rx_bit(input [7:0]data); 51 begin 52 for(i=0;i<10;i=i+1) begin //0-9 53 case(i) 54 0:uart_rx = 0; 55 1:uart_rx = data[i-1]; 56 2:uart_rx = data[i-1]; 57 3:uart_rx = data[i-1]; 58 4:uart_rx = data[i-1]; 59 5:uart_rx = data[i-1]; 60 6:uart_rx = data[i-1]; 61 7:uart_rx = data[i-1]; 62 8:uart_rx = data[i-1]; 63 9:uart_rx = 1; 64 endcase 65 #104260; //一个完整波特延时:5208*20=104160 66 end //考虑到空闲位,也可以设置得104160稍大一些 67 end 68 endtask 69 70 //字节 71 task rx_byte; 72 begin 73 for(j=0;j<16;j=j+1) 74 rx_bit(mem[j]); 75 end 76 endtask 77 78 //-------------------------------------------------------------------------- 79 //-- 调用task 80 //-------------------------------------------------------------------------- 81 initial begin 82 #(`Clock*20+1); 83 rx_byte(); 84 end 85 86 87 88 endmodule
5.仿真波形,软件:isim
上半波形是uart_rx模块,下半波形是uart_tx模块,其中紫色的信号为输出。可以看到,波形和我们的设计相符合,能正确的接收和发送数据。
6.实际上板
软件:友善串口助手。停止位是1.5位或2位,成功!
四、总结
UART是我学习FPGA后设计的第一个协议,之前一直是拿着教程demo跑一跑完事,自己亲自设计才体会到时序的不容易。难怪串口的停止位上设置了选项:1位、1.5位、2位,看来这都是前人遇到过的坑啊!
总结一下:由于串口助手的发送机制是停止位和下一波的开始位连着(中间没有空闲位),因此我们设计uart_rx接收串口助手的数据时,如果计数10下,则串口助手的停止位要设置成1.5或2位。如果停止位设置成1位,则计数必须改成9下。
五、更改!!!
等等哈,为什么要弄的这么复杂?不就是个串口吗?别人的串口回环实验中串口助手的停止位是1就行,到你这就要1.5位或2位?
经过思考,我发现一个很好的办法:停止位计0.5下!这样就解决了串口助手发送数据时,停止位和下一波的开始位连着(中间没有空闲位)的问题啊!我自己制造一个0.5的空闲位,完美解决问题,美滋滋!
1.uart_rx更改
1 //========================================================================== 2 // --- 名称 : uart_rx.v 3 // --- 作者 : xianyu_FPGA 4 // --- 日期 : 2019-06-27 5 // --- 描述 : 串口接收模块,计数9.5下,其中停止位0.5下 6 // * 因为串口助手发送本次停止位和下次开始位中间没有留空闲位 7 // * 若计满10下,则才结束本次传输下次数据就来了,会来不及接收 8 //========================================================================== 9 10 module uart_rx 11 //---------------------<参数定义>------------------------------------------- 12 #( 13 parameter BPS = 5208 , //波特率 14 parameter BPS_W = 13 , //波特率位宽 15 parameter BPS_half = 2604 //波特率中间采样点 16 ) 17 //---------------------<端口声明>------------------------------------------- 18 ( 19 input wire clk , //时钟,50Mhz 20 input wire rst_n , //复位,低电平有效 21 input wire din , //输入数据 22 output reg [7:0] dout , //输出数据 23 output reg dout_vld //输出数据的有效指示 24 ); 25 //---------------------<信号定义>------------------------------------------- 26 reg rx0 ; 27 reg rx1 ; 28 reg rx2 ; 29 wire rx_en ; 30 reg flag ; 31 reg [BPS_W-1:0] cnt0 ; 32 wire add_cnt0 ; 33 wire end_cnt0 ; 34 reg [3:0] cnt1 ; 35 wire add_cnt1 ; 36 wire end_cnt1 ; 37 reg [7:0] data ; 38 39 //-------------------------------------------------------------------------- 40 //-- 消除亚稳态 + 下降沿检测 41 //-------------------------------------------------------------------------- 42 always @(posedge clk) begin 43 {rx2,rx1,rx0} <= {rx1,rx0,din}; 44 end 45 46 assign rx_en = rx2 && ~rx1; 47 48 //-------------------------------------------------------------------------- 49 //-- 接收状态指示 50 //-------------------------------------------------------------------------- 51 always @(posedge clk or negedge rst_n) begin 52 if(!rst_n) 53 flag <= 0; 54 else if(rx_en) 55 flag <= 1; 56 else if(end_cnt1) 57 flag <= 0; 58 end 59 60 //-------------------------------------------------------------------------- 61 //-- 波特率计数 62 //-------------------------------------------------------------------------- 63 always @(posedge clk or negedge rst_n) begin 64 if(!rst_n) 65 cnt0 <= 0; 66 else if(add_cnt0) begin 67 if(end_cnt0) 68 cnt0 <= 0; 69 else 70 cnt0 <= cnt0 + 1; 71 end 72 else 73 cnt0 <= cnt0; 74 end 75 76 assign add_cnt0 = flag; 77 assign end_cnt0 = cnt0== BPS-1 || end_cnt1; 78 79 //-------------------------------------------------------------------------- 80 //-- 开始1位(不接收) + 数据8位 + 停止0.5位(不接收),共10位 81 //-------------------------------------------------------------------------- 82 always @(posedge clk or negedge rst_n) begin 83 if(!rst_n) 84 cnt1 <= 0; 85 else if(add_cnt1) begin 86 if(end_cnt1) 87 cnt1 <= 0; 88 else 89 cnt1 <= cnt1 + 1; 90 end 91 else 92 cnt1 <= cnt1; 93 end 94 95 assign add_cnt1 = end_cnt0; 96 assign end_cnt1 = cnt1==10-1 && cnt0==BPS_half-1; 97 98 //-------------------------------------------------------------------------- 99 //-- 缓存数据 100 //-------------------------------------------------------------------------- 101 always @ (posedge clk or negedge rst_n)begin 102 if(!rst_n) 103 data <= 8'd0; 104 else if(cnt1>=1 && cnt1<=8 && cnt0==BPS_half-1) //中间采样 105 data[cnt1-1] <= rx2; //或 dout <= {rx2,dout[7:1]}; 106 end 107 108 //-------------------------------------------------------------------------- 109 //-- 输出数据 110 //-------------------------------------------------------------------------- 111 always @ (posedge clk or negedge rst_n)begin 112 if(!rst_n) 113 dout <= 0; 114 else if(end_cnt1) 115 dout <= data; 116 end 117 118 always @ (posedge clk or negedge rst_n)begin 119 if(!rst_n) 120 dout_vld <= 0; 121 else if(end_cnt1) 122 dout_vld <= 1; 123 else 124 dout_vld <= 0; 125 end 126 127 128 129 endmodule
2.uart_tx更改
1 //========================================================================== 2 // --- 名称 : uart_tx.v 3 // --- 作者 : xianyu_FPGA 4 // --- 日期 : 2019-06-27 5 // --- 描述 : 串口接收模块,计数9.5下,其中停止位0.5下 6 // * 因为极端情况是本次停止位和下次开始位中间没有留空闲位 7 // * 若计满10下,则才结束本次传输下次数据就来了,会来不及发送 8 //========================================================================== 9 10 module uart_tx 11 //---------------------<参数定义>------------------------------------------- 12 #( 13 parameter BPS = 5208 , //波特率 14 parameter BPS_W = 13 , //波特率位宽 15 parameter BPS_half = 2604 //波特率中间采样点 16 ) 17 //---------------------<端口声明>------------------------------------------- 18 ( 19 input wire clk , //时钟,50Mhz 20 input wire rst_n , //复位,低电平有效 21 input wire [7:0] din , //输入数据 22 input wire din_vld , //输入数据的有效指示 23 output reg dout //输出数据 24 ); 25 //---------------------<信号定义>------------------------------------------- 26 reg flag ; 27 reg [7:0] din_tmp ; 28 reg [BPS_W-1:0] cnt0 ; 29 wire add_cnt0 ; 30 wire end_cnt0 ; 31 reg [3:0] cnt1 ; 32 wire add_cnt1 ; 33 wire end_cnt1 ; 34 wire [9:0] data ; 35 36 //-------------------------------------------------------------------------- 37 //-- 数据暂存(din可能会消失,暂存住) 38 //-------------------------------------------------------------------------- 39 always @ (posedge clk or negedge rst_n) begin 40 if(!rst_n) 41 din_tmp <=8'd0; 42 else if(din_vld) 43 din_tmp <= din; 44 end 45 46 //-------------------------------------------------------------------------- 47 //-- 发送状态指示 48 //-------------------------------------------------------------------------- 49 always @(posedge clk or negedge rst_n)begin 50 if(!rst_n) 51 flag <= 0; 52 else if(din_vld) 53 flag <= 1; 54 else if(end_cnt1) 55 flag <= 0; 56 end 57 58 //-------------------------------------------------------------------------- 59 //-- 波特率计数 60 //-------------------------------------------------------------------------- 61 always @(posedge clk or negedge rst_n) begin 62 if(!rst_n) 63 cnt0 <= 0; 64 else if(add_cnt0) begin 65 if(end_cnt0) 66 cnt0 <= 0; 67 else 68 cnt0 <= cnt0 + 1; 69 end 70 else 71 cnt0 <= cnt0; 72 end 73 74 assign add_cnt0 = flag; 75 assign end_cnt0 = cnt0== BPS-1 || end_cnt1; 76 77 //-------------------------------------------------------------------------- 78 //-- 开始1位 + 数据8位 + 停止0.5位,共10位 79 //-------------------------------------------------------------------------- 80 always @(posedge clk or negedge rst_n) begin 81 if(!rst_n) 82 cnt1 <= 0; 83 else if(add_cnt1) begin 84 if(end_cnt1) 85 cnt1 <= 0; 86 else 87 cnt1 <= cnt1 + 1; 88 end 89 else 90 cnt1 <= cnt1; 91 end 92 93 assign add_cnt1 = end_cnt0; 94 assign end_cnt1 = cnt1==10-1 && cnt0==BPS_half-1; 95 96 //-------------------------------------------------------------------------- 97 //-- 数据输出(用case语句也行) 98 //-------------------------------------------------------------------------- 99 assign data = {1'b1,din_tmp,1'b0}; //结束,数据,开始 100 101 always @(posedge clk or negedge rst_n) begin 102 if(!rst_n) 103 dout <= 1'b1; 104 else if(flag==1) 105 dout <= data[cnt1]; 106 end 107 108 109 110 endmodule
3.实际上板
停止位是1位,成功!
六、后记
一个串口搞了我两次!唉,不想说什么了,菜就是罪啊!
参考资料:
[1]明德扬FPGA教程
[2]正点原子FPGA教程
[2]威三学院FPGA教程