串口通信指串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。这里主要讲解串口接收模块的电路设计。
图1 串口传输时序图
如图1所示为串口传输时序图,空闲时刻数据线线路一直处于高电平状态(即逻辑1)。当有数据传送时,数据帧从起始位开始,到停止位结束。起始位为低电平(即逻辑0),停止位为高电平。一帧数据包括1位起始位,8位数据位,1位校验位以及1位停止位,总计11位。
本文分两个模块来讲解,一个是时钟模块(这里假设通信速率是9600bps),一个是串口接收模块。
时钟模块
FPGA主频一般比较高(这里以50M为例),而串口通信速度又很慢(9600),这里就需要设计一个分频模块。那么分频多少合适呢?是直接分频到9600呢?还是多少呢?这里分频的主要目的是为后面的采样提供一个时钟,而采样肯定是在数据保持稳定的时候采样最佳。那什么时候采样数据才是稳定的呢?没错,就是在每位数据的中间时刻采样最稳定。那么问题来了,如何确定每位数据的中间时刻呢?
我们知道传输速率是9600,要保证在数据中间时刻采样,那么分频肯定比9600大。假设我们分频后的频率为9600*16,那么我们在9600*8的时刻采样基本上可以算是中间时刻采样了。如图2所示,分频时钟频率为数据传输速率的16倍,在其8倍时刻采样,可近似为数据中间时刻采样。
图2 数据中间时刻采样
串口接收模块
这里采用状态机来设计串口接收模块。首先根据串口通信的特性定义5个状态,分别为:空闲状态、起始~、数据接收~、校验~、停止~。
初始化为空闲状态,当检测到数据线拉低的时候,进入起始状态。在起始状态需要判断信号线是真的拉低了还是由于其它原因导致的错误信号。若确认为拉低起始标志信号,则在“下一个时钟”进入数据接收状态,这里需要连续接收八位数据,然后进入校验状态。由于我们自己写的串口大多速率低,传输数据量小,要求也不是特别高,所以很多时候都没有用校验位,也就是说,校验位可以不采样,直接跳过进入停止位。停止状态接收到的数据肯定是1,否则说明这个设计有问题。停止状态之后再次回到空闲状态,等待下一次的数据接收。
源码
分频模块较简单,这里只贴出接收模块的代码:
module uart_r
(
clk,//50M
rst_n,
clk_dev,//9600 * 16
uart_rxd,
cnt,
state,
cnt_data,
rxd_data
);
input clk, rst_n, clk_dev;
input uart_rxd;//串口输入数据
output [3:0]cnt;
output [2:0]state, cnt_data;
output [7:0]rxd_data;
reg [7:0]rxd_data;
//*********检测clk_dev上升沿***************
reg clkr0, clkr1;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
clkr0 <= 0;
clkr1 <= 0;
end
else
begin
clkr0 <= clk_dev;
clkr1 <= clkr0;
end
end
wire clk_devp = (~clkr1 & clkr0) ? 1'b1 : 1'b0;
//*****************************************
localparam IDLE = 3'd0;
localparam STAR = 3'd1;
localparam RECEIVE = 3'd2;
localparam CHECK = 3'd3;
localparam STOP = 3'd4;
localparam cnt_center = 4'd7;
localparam cnt_top = 4'd15;
reg [3:0]cnt;//clk_dev上升沿计数
reg [2:0]state;//串口状态
reg [2:0]cnt_data;//接收数据计数
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
cnt <= 0;
state <= IDLE;
cnt_data <= 0;
end
else
begin
case(state)
IDLE:
begin
cnt <= 0;
cnt_data <= 0;
if(uart_rxd)
state <= IDLE;
else
state <= STAR;
end
STAR:
begin
if(clk_devp)
begin
cnt <= cnt + 1'b1;
if((cnt == cnt_center) && (uart_rxd))//数据无效
state <= IDLE;
else if(cnt == cnt_top)
begin
cnt <= 0;
state <= RECEIVE;
end
end
end
RECEIVE:
begin
if(clk_devp)
begin
cnt <= cnt + 1'b1;
if(cnt == cnt_top)//接收一位数据
begin
cnt <= 0;
if(cnt_data < 3'd7)
cnt_data <= cnt_data + 1'b1;
else
begin
cnt_data <= 3'd7;
state <= CHECK;
end
end
else
begin
state <= RECEIVE;
cnt_data <= cnt_data;
end
end
end
CHECK:
begin
if(clk_devp)
begin
cnt <= cnt + 1'b1;
if(cnt == cnt_top)
begin
cnt <= 0;
state <= STOP;
end
else
state <= state;
end
end
STOP:
begin
if(clk_devp)
begin
cnt <= cnt + 1'b1;
if(cnt == cnt_top)
begin
cnt <= 0;
state <= IDLE;
end
else
state <= state;
end
end
default: state <= IDLE;
endcase
end
end
//********************
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
rxd_data <= 0;
end
else if((cnt == cnt_center) && (state == RECEIVE) && (clk_devp))
begin
case(cnt_data)
3'd0 : rxd_data[0] <= uart_rxd;
3'd1 : rxd_data[1] <= uart_rxd;
3'd2 : rxd_data[2] <= uart_rxd;
3'd3 : rxd_data[3] <= uart_rxd;
3'd4 : rxd_data[4] <= uart_rxd;
3'd5 : rxd_data[5] <= uart_rxd;
3'd6 : rxd_data[6] <= uart_rxd;
3'd7 : rxd_data[7] <= uart_rxd;
default: rxd_data <= 0;
endcase
end
end
endmodule