Verilog实现UART之一:接收模块

热度8票  浏览217次 【共0条评论】【我要评论 时间:2017年2月16日 10:59

1.UART的帧格式

  异步串行数据的一般格式是:起始位+数据位+停止位,其中起始位1位,8位数据位,奇校验、偶校验或无校验位;停止位可以是1、2位,LSB first:

2.接收原理:

  由于UART是异步传输,没有传输同步时钟。为了能保证数据传输的正确性,采样模块利用16倍数据波特率的时钟进行采样,假设波特率为115200,则采样时钟为clk16x=115200×16。每个数据占据16个采样时钟周期,1bit起始位+8bit数据为+1bit停止位=10bit,因此一帧共占据16×10=160个采样时钟,考虑到每个数据为可能有1-2个采样时钟周期的便宜,因此将各个数据位的中间时刻作为采样点,以保证采样不会滑码或误码。一般UART一帧的数据位数为8,这样即使每个数据有一个时钟的误差,接收端也能正确地采样到数据。因此,采样时刻为24(跳过起始位的16个时钟)、40、56、72、88、104、120、136、152(停止位),如下图所示:

其中,RX为接收引脚,CNT为对采样时钟进行计数的计数器;

3.代码实现:
/******************************************************************************
*
* Module : rx_module
* File Name : rx_module.v
* Author : JC_Wang
* Version : 1.0
* Date : 2012/12/5
* Description : UART接收模块
*
*
********************************************************************************/

module rx_module(
input GClk, /* system topest clock */
input clk16x, /* sample clock,16×115200 */
input rst_n, /* glabol reset signal */
input rx, /* serial data in */
output reg DataReady, /* a complete byte has been received */
output reg[7:0] DataReceived /* Bytes received */
);

/* 捕获rx的下降沿,即起始信号 */
reg trigger_r0;
wire neg_tri;
always@(posedge GClk or negedge rst_n) /*下降沿使用全局时钟来捕获的,其实用clk16x来捕获也可以*/
begin
if(!rst_n)
begin
trigger_r0<=1'b0;
end
else
begin
trigger_r0<=rx;
end
end

assign neg_tri = trigger_r0 & ~rx;

//----------------------------------------------
/* counter control */
reg cnt_en;
always@(posedge GClk or negedge rst_n)
begin
if(!rst_n)
cnt_en<=1'b0;
else if(neg_tri==1'b1) /*如果捕获到下降沿,则开始计数*/
cnt_en<=1'b1;
else if(cnt==8'd152)
cnt_en<=1'b0;

end
//---------------------------------------------
/* counter module ,对采样时钟进行计数 */
reg [7:0] cnt;
always@(posedge clk16x or negedge rst_n)
begin
if(!rst_n)
cnt<=8'd0;
else if(cnt_en)
cnt<=cnt+1'b1;
else
cnt<=8'd0;

end
//---------------------------------------------
/* receive module */
reg StopBit_r;
always@(posedge clk16x or negedge rst_n)
begin
if(!rst_n)
begin
DataReceived<=8'b0;
end
else if(cnt_en)
case(cnt)
8'd24: DataReceived[0] <= rx; /*在各个采样时刻,读取接收到的数据*/
8'd40: DataReceived[1] <= rx;
8'd56: DataReceived[2] <= rx;
8'd72: DataReceived[3] <= rx;
8'd88: DataReceived[4] <= rx;
8'd104: DataReceived[5] <= rx;
8'd120: DataReceived[6] <= rx;
8'd136: DataReceived[7] <= rx;

endcase

end

always@(posedge clk16x or negedge rst_n)
begin
if(!rst_n)
DataReady<=1'b0;
else if (cnt==8'd152)
DataReady<=1'b1; //接收到停止位后,给出数据准备好标志位
else
DataReady<=1'b0;
end

endmodule

注:

1)采样时钟clk16x必须是波特率的16倍,波特率任意设置如57600、9600等皆可,只要满足16倍关系;

2)此模块经过测试上万字节的接收,从未出错!

3)引入了最高时钟对起始信号下降沿进行捕获,会造成什么不良影响,比如所谓的“时钟满天飞”问题,博主还不清楚,若您有好的讲解,请您发链接给我,在此感谢!

4.如果需要校验位的朋友,可以参考xilinx 公司的参考设计:
`timescale 1 ns / 1 ns

module rcvr (dout,data_ready,framing_error,parity_error,rxd,clk16x,rst,rdn) ;

input rxd ;        /*数据接收端*/
input clk16x ;          /*采样时钟*/
input rst ;       /*复位信号,高电平有效*/
input rdn ;        /*数据接收使能,低电平有效*/
output [7:0] dout ;      /*数据输出*/
output data_ready ;      /*数据接收完毕标志位*/
output framing_error ;     /*帧错误标志位*/
output parity_error ;   /*校验位错误标志位*/

reg rxd1 ;
reg rxd2 ;
reg clk1x_enable ;
reg [3:0] clkdiv ;
reg [7:0] rsr ;
reg [7:0] rbr ;
reg [3:0] no_bits_rcvd ;

reg data_ready ;

reg parity ;
reg parity_error ;
reg framing_error ;

wire clk1x ;

assign dout = !rdn ? rbr : 8'bz ; /*在允许接收的情况下,输出接收到的数据,否则保持高阻态*/

/*下降沿捕获模块*/
always @(posedge clk16x or posedge rst)
begin
if (rst)
begin
rxd1 <= 1'b1 ;
rxd2 <= 1'b1 ;
end
else
begin
rxd1 <= rxd ;
rxd2 <= rxd1 ;
end
end

always @(posedge clk16x or posedge rst)
begin
if (rst)
clk1x_enable <= 1'b0;
else if (!rxd1 && rxd2) /*如果捕获到下降沿,则开始计数*/
clk1x_enable <= 1'b1 ;
else if (no_bits_rcvd == 4'b1100)
clk1x_enable <= 1'b0 ;
end

/*数据准备好标志位的控制模块*/
always @(posedge clk16x or posedge rst or negedge rdn)
begin
if (rst)
data_ready = 1'b0 ;
else if (!rdn)
data_ready = 1'b0 ;
else
if (no_bits_rcvd == 4'b1011)
data_ready = 1'b1 ;
end

/*计数模块,产生一个对clk16x进行16分频的时钟信号,用该信号的上升沿进行采样 */
always @(posedge clk16x or posedge rst)
begin
if (rst)
clkdiv = 4'b0000 ;
else if (clk1x_enable)
clkdiv = clkdiv +1 ;
end

assign clk1x = clkdiv[3] ;

/*对clk1x进行计数*/
always @(posedge clk1x or posedge rst or negedge clk1x_enable)
if (rst)
  no_bits_rcvd = 4'b0000;
else
  if (!clk1x_enable)
    no_bits_rcvd = 4'b0000 ;
  else
    no_bits_rcvd = no_bits_rcvd + 1 ;

/*采样进程*/
always @(posedge clk1x or posedge rst)
if (rst)
begin
rsr <= 8'b0 ;
rbr <= 8'b0 ;
parity <= 1'b1 ;
parity_error = 1'b0 ;
end
else
begin
if (no_bits_rcvd >= 4'b0001 && no_bits_rcvd <= 4'b1001)
    begin
      rsr[0] <= rxd2 ;
      rsr[7:1] <= rsr[6:0] ; //数据左移
      parity <= parity ^ rsr[7] ; //校验位
    end
    else if (no_bits_rcvd == 4'b1010)
    begin
      rbr <= rsr ; //输出接收到的数据
    end
    else if (!parity)
      parity_error = 1'b1 ; //判断校验位是否正确
   else if ((no_bits_rcvd == 4'b1011) && (rxd2 != 1'b1))//判断是否收到停止位,否则给出帧错误信号
      framing_error = 1'b1 ;
    else framing_error = 1'b0 ;
    end
endmodule

上一篇 下一篇
查看全部回复【已有0位网友发表了看法】