你的位置:EETOP 赛灵思(Xilinx) 社区 >> >> 全部 >> 详细内容

Verilog实现UART之一:接收模块

发布者:jackzhang 时间:2017-02-16 10:59:29

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

最新课程

  • 深入浅出玩儿转FPGA

    本视频基于Xilinx公司的Artix-7FPGA器件以及各种丰富的入门和进阶外设,提供了一些典型的工程实例,帮助读者从FPGA基础知识、逻辑设计概念

  • 从零开始大战FPGA基础篇

    本课程为“从零开始大战FPGA”系列课程的基础篇。课程通俗易懂、逻辑性强、示例丰富,课程中尤其强调在设计过程中对“时序”和“逻辑”的把控,以及硬件描述语言与硬件电路相对应的“

  • Verilog基础及典型数字

    课程中首先会给大家讲解在企业中一般数字电路从算法到流片这整个过程中会涉及到哪些流程,都分别使用什么工具,以及其中每个流程都分别做了