串口通信
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,无校验位。

代码如下:

`module uart(
clk ,
rst_n ,
rx_uart ,
led
);
input clk ;
input rst_n ;
input rx_uart ;
output reg[7:0] led ;

reg flag_add;
//波特率9600 1s/9600 = 104166ns
//50M时钟,一个周期是20ns,104166ns/20ns=5208个时钟周期
//用计数器来计数时钟周期,每到5208从rx_uart取出一个数据
//一个数据包是八个数据+一个起始位数据

//计数器cnt0用来计数每个bit的5208个周期
reg[12:0] cnt0 ;
wire add_cnt0 ;
wire end_cnt0 ;

always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 0;
end
else if(add_cnt0)begin
if(end_cnt0)
cnt0 <= 0;
else
cnt0 <= cnt0 + 1;
end
end
assign add_cnt0 = flag_add;
assign end_cnt0 = add_cnt0 && cnt0 == 5208 - 1 ;

//计数器1用来计数接受的bit数量
reg[3:0] cnt1 ;
wire add_cnt1 ;
wire end_cnt1 ;

always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(add_cnt1)begin
if(end_cnt1)
cnt1 <= 0;
else
cnt1 <= cnt1 + 1;
end
end
assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1 == 9 - 1;

//flag_add的定义,rx_uart下降沿的时候拉高,end_cnt1的时候拉低
//需要一个rx_uart边沿检测电路,注意,rx_uart是一个异步信号,与本地时钟无关。
//可以把rx_uart放进敏感列表,但是他会被FPGA误认为是个时钟,造成电路不稳定
//安全的做法:用本地时钟对rx_uart进行延一拍,然后前后两拍比较完成边沿检测
//但是,因为本项目中的rx_uart是个异步信号,可能在时钟边沿变化,
//不能满足setup time 和 保持时间 所以就延两拍更安全。(两拍以后的信号是稳定信号)
reg rx_uart_ff0;
reg rx_uart_ff1;
reg rx_uart_ff2;
always@(posedge clk or negedge rst_n)
if(!rst_n)begin
rx_uart_ff0 <= 1;
rx_uart_ff1 <= 1;
rx_uart_ff2 <= 1;
end
else begin
rx_uart_ff0 <= rx_uart;
//rx_uart_ff0是异步信号打一拍的结果,不能做条件用。以后的可以
rx_uart_ff1 <= rx_uart_ff0;
rx_uart_ff2 <= rx_uart_ff1;
end

always@(posedge clk or negedge rst_n)
if(!rst_n)begin
flag_add <= 0;
end
else if(!rx_uart_ff1 & rx_uart_ff2) begin//下降沿
flag_add <= 1;
end
else if(end_cnt1)
flag_add <= 0;

always@(posedge clk or negedge rst_n)
if(!rst_n)begin
led <= 8'hff;
end
else if(add_cnt0 && cnt0 == 5208/2-1 && cnt1>0)begin
led[cnt1-1] <= rx_uart_ff1;
end

endmodule`

内容来源于网络如有侵权请私信删除

文章来源: 博客园

原文链接: https://www.cnblogs.com/fpgapig/p/15137862.html

你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!