串口通信
1、串口简介
串行接口,COM接口,只需要两根线就能实现两台设备之间的通信。UART指的是异步的串行接口,通用异步收发。标准常用的是RS-232标准接口 现在电脑上没有串口了,所以使用的是USB转串口芯片,CH340芯片。
换句话说,只需要两根数据线UART_RXD和UART_TXD,就能完成两台设备之间的通信。
2、串口时序
两根数据线各自独立互不影响,二者的时序是相同的。不同之处是UART_RXD是主机MASTER发送给从机SLAVE,UART_TXD是SLAVE发送给MASTER。 由于两根线的时序完全相同且独立,下面以UART_TXD为例。 空闲状态时,UART_TXD一直拉高,当要传输数据之前,拉低一个数据位,此后开始传输数据。数据之后有一个校验位,校验位之后是停止位,停止位之后进入下一个传输周期。至此,完成了一个数据包的传输。
注意:
(1)、传输的数据是从低比特位开始传,比如101010,接受端的接受顺序是010101。
(2)、传输数据的位数是MASTER与SLAVE约定好的,可以是4、5、6、7、8位,时序图中是以八位为例。
(3)、校验位一般是奇偶检验。当然,也可以选择没有检验位,前提是MASTER与SLAVE约定好,在SLAVE解析接收到的数据的时候,不安排校验位的解析。
(4)、停止位,停止位是保证两段传输之间一定要有间隔。两段传输之间可以没有空闲时间,但是,停止位一定要有。
3、时间的问题
从时序图上可以看出,串口的发送和接受是没有时钟的,换句话说,这是一个异步时序。那么如何确定每个位所需要的时间就尤为重要。 这个问题的要点是波特率,每秒发送/接受单位的个数。我们使用的串口是以比特为单位,所以这里波特率与我们的比特率相同。常见的波特率的数值有9600,19200,38400,57600,115200等。以9600为例,表示一秒钟发送/接受9600个比特。所有我们可以计算出单个bit所占用的时间为 1s/9600 = 104166ns。我们传输的起始标志位,传输的数据的每一位,校验位(可有可无),在9600波特率的情况下,各自占据了104166ns的时间。 所以,假设从MASTER发送8'b11001110给SLAVE的的话,数据线的电平变化如下:
所以,FPGA在接收串口数据的时候,按照每个位的时间,设计计数器。假设是50MHz的时钟,那么接受一个bit需要的时钟周期是 104166ns/20ns = 5208个周期,所以在计数器数到5208/2 +1的时候,将UART_RXD的当前值寄存即可。注意接受数据的时候先接受到的是起始位,最后的空闲位置就没有必要接受了。
4、代码 项目要求:电脑通过串口发送八位数据给FPGA,FPGA通过这八位数据来控制八个LED灯的亮暗。波特率9600,无校验位。 代码如下: ``
1 module uart( 2 clk , 3 rst_n , 4 rx_uart , 5 led 6 ); 7 input clk ; 8 input rst_n ; 9 input rx_uart ; 10 output reg[7:0] led ; 11 12 reg flag_add; 13 //波特率9600 1s/9600 = 104166ns 14 //50M时钟,一个周期是20ns,104166ns/20ns=5208个时钟周期 15 //用计数器来计数时钟周期,每到5208从rx_uart取出一个数据 16 //一个数据包是八个数据+一个起始位数据 17 18 //计数器cnt0用来计数每个bit的5208个周期 19 reg[12:0] cnt0 ; 20 wire add_cnt0 ; 21 wire end_cnt0 ; 22 23 always @(posedge clk or negedge rst_n)begin 24 if(!rst_n)begin 25 cnt0 <= 0; 26 end 27 else if(add_cnt0)begin 28 if(end_cnt0) 29 cnt0 <= 0; 30 else 31 cnt0 <= cnt0 + 1; 32 end 33 end 34 assign add_cnt0 = flag_add; 35 assign end_cnt0 = add_cnt0 && cnt0 == 5208 - 1 ; 36 37 //计数器1用来计数接受的bit数量 38 reg[3:0] cnt1 ; 39 wire add_cnt1 ; 40 wire end_cnt1 ; 41 42 always @(posedge clk or negedge rst_n)begin 43 if(!rst_n)begin 44 cnt1 <= 0; 45 end 46 else if(add_cnt1)begin 47 if(end_cnt1) 48 cnt1 <= 0; 49 else 50 cnt1 <= cnt1 + 1; 51 end 52 end 53 assign add_cnt1 = end_cnt0; 54 assign end_cnt1 = add_cnt1 && cnt1 == 9 - 1; 55 56 //flag_add的定义,rx_uart下降沿的时候拉高,end_cnt1的时候拉低 57 //需要一个rx_uart边沿检测电路,注意,rx_uart是一个异步信号,与本地时钟无关。 58 //可以把rx_uart放进敏感列表,但是他会被FPGA误认为是个时钟,造成电路不稳定 59 //安全的做法:用本地时钟对rx_uart进行延一拍,然后前后两拍比较完成边沿检测 60 //但是,因为本项目中的rx_uart是个异步信号,可能在时钟边沿变化, 61 //不能满足setup time 和 保持时间 所以就延两拍更安全。(两拍以后的信号是稳定信号) 62 reg rx_uart_ff0; 63 reg rx_uart_ff1; 64 reg rx_uart_ff2; 65 always@(posedge clk or negedge rst_n) 66 if(!rst_n)begin 67 rx_uart_ff0 <= 1; 68 rx_uart_ff1 <= 1; 69 rx_uart_ff2 <= 1; 70 end 71 else begin 72 rx_uart_ff0 <= rx_uart; 73 //rx_uart_ff0是异步信号打一拍的结果,不能做条件用。以后的可以 74 rx_uart_ff1 <= rx_uart_ff0; 75 rx_uart_ff2 <= rx_uart_ff1; 76 end 77 78 always@(posedge clk or negedge rst_n) 79 if(!rst_n)begin 80 flag_add <= 0; 81 end 82 else if(!rx_uart_ff1 & rx_uart_ff2) begin//下降沿 83 flag_add <= 1; 84 end 85 else if(end_cnt1) 86 flag_add <= 0; 87 88 always@(posedge clk or negedge rst_n) 89 if(!rst_n)begin 90 led <= 8'hff; 91 end 92 else if(add_cnt0 && cnt0 == 5208/2-1 && cnt1>0)begin 93 led[cnt1-1] <= rx_uart_ff1; 94 end 95 96 97 endmodule