FPGA-串口232回环实验学习

目录

一、理论知识

1、串口简介

2、232帧结构

3、实现的关键步骤与环节:

 1、传输一个比特的时间是多少

 2、在每个比特的周期的时间内什么时候进行数据的读取与发送

3、怎么进行一帧数据有效传输时间段的判断与分析(以uart_rx模块为例)

 4、怎么进行数据的串/并转换

二、代码实现

1、uart_rx模块

2、uart_tx模块

3、顶层uart_232模块

总结

        最近两天学习了FPGA的串口232章节,虽然之前在学习STM32的时候也学习过232串口,但是那时候更多是在逻辑调用层面的了解,学习FPGA的相应章节后了解了232串口协议更深层次的含义。本文章记录了串口回环实验的具体实现细节与思路,下面是整个模块框图:

一、理论知识

1、串口简介

        串口作为常用的三大低速总线(UART、 SPI、 IIC)之一,在设计众多通信接口和调试时占有重要地位。但 UART 和 SPI、 IIC 不同的是,它是异步通信接口。

        串口有两种,分别为UART和USART,首先我们要说明两者的区别。两者是同步和异步的区别,UART(通用异步收发器),USART(通用同步异步收发器)。一般来说,在单片机上,名为UART的接口只能用于异步串行通信,而名为USART的接口既可用于同步串行通信,也可用于异步串行通信。而本章节我们学习的是uart。它包括了 RS232、 RS499、 RS423、 RS422 和 RS485 等接口标准规范和总线标准规范。

2、232帧结构

        这里以最简单的串口数据帧为例,一个数据帧中只包含了八位有效的数据和一个起始位以及一个停止位。不难看出,一帧里只有十个比特的的数据,我们实验的目的就是实现上面帧结构的解析与发送,也就是通过时序代码编写来模拟上面的数据帧的结构的过程。

        我们要知道的是串口信号定义高电平为空闲状态,也就是说在十个比特当中第一个起始位要进行拉低信号线,而在第十个停止位的时候拉高信号线,中间是有效的数据,这样就完成了一帧数据的传输。

3、实现的关键步骤与环节:

        1、传输一个比特的时间是多少

        2、在每个比特的周期的时间内什么时候进行数据的读取与发送

        3、怎么进行一帧数据有效传输时间段的判断与分析(以uart_rx模块为例)

        4、怎么进行数据的串/并转换

 1、传输一个比特的时间是多少

        每个比特的传输时间由系统的时钟频率和传输的波特率来确定。系统的时钟频率很容易理解,那么我们讲一下波特率。

        波特率的定义:波特率表示单位时间(1s)内传送的码元符号的个数。与之对应的还有比特率,比特率是单位时间内传送的比特的个数。这里我们指出,这里我们的一个码元符号就对应一个比特,所以说数值上波特率也就等于了比特率。

        本次实验我们预设波特率等于9600Baud,也就是1s传输9600个码元(比特)。本次使用的是基于 Intel Cyclone IV的Altera-EP4CE10-FPGA 开发板-征途 Pro 开发平台,主时钟频率为50Mhz,也就是1s震荡50M次。针对传输一个比特的周期,我们不要需要计算出实际的时间,只需要换算成FPGA时钟对应的震荡次数即可。

        一个比特对应的FPGA的时钟周期是:50_000_000/9600=5208。也就是说我们在sys_clk的计数范围0~(5208-1)可以进行1bit数据的传输。

 2、在每个比特的周期的时间内什么时候进行数据的读取与发送

(1)亚稳态问题

        首先是数据的接收(开发板通过引脚接收外部(计算机USB接口)的并行数据),对于数据的接收我们特别要注意针对高速的数据信号接收,FPGA会存在一个亚稳态的问题(亚稳态指触发器由于违反自身的建立或保持时间而导致输出信号处于不确定状态的情况,产生的直接原因是没有满足建立或者保持时间,亚稳态问题通常发生在一些跨时钟域信号传输以及异步信号采集上),下面对亚稳态进行一下说明:

        如果 FPGA 的系统时钟刚好采集到 rx 信号上升沿或下降沿的中间位置附近(按照概率来讲,如果数据传输量足够大或传输速度足够快时一定会产生这种情况),即 FPGA 在接收 rx 数据时不满足内部寄存器的建立时间 Tsu(指触发器的时钟信号上升沿到来以前,数据稳定不变的最小时间)和保持时间 Th(指触发器的时钟信号上升沿到来以后,数据稳定不变的最小时间)

        如上图所示,在sys_clk的第一个上升沿到来的读取时刻前后(Tsu和Th之间时刻)rx数据不稳定,是0或者1的不确定状态。rx 信号进入 FPGA 后会首先经过一级寄存器 rx_reg1 ,出现如图所示的震荡(亚稳态)现象。上图中rx_reg1在下一个sys_clk时钟信号上升沿到来时已经处在了稳定的状态,这里注意我们上述所说的只是处于稳定的01状态,而具体的Data是0还是1确实随机的。也就是说只要出现了亚稳态效应,当前获得的数据信息就可能已经出错了,而我们后面进行所谓的“打两拍”只是为了让后续的rx_reg输出状态保持0或者1的稳定状态并不能保证亚稳态后数据的正确性

        

        另外一种情况是rx_reg1的亚稳态持续时间超过了一个时钟周期,这样打拍后的rx_reg2会受到亚稳态的影响使rx_reg1的亚稳态传递了下去,这里我们将从 rx_reg1 信号再打一拍后产生 rx_reg2 信号的目的就是为了进一步消除亚稳态现象,因为寄存器本身就具有减少判断时间Tmet的作用(原因不解释),就会导致Tmet2<Tmet1。这样也就起到了减小了亚稳态传播的作用。

        单比特信号从慢速时钟域同步到快速时钟域需要使用打两拍的方式消除亚稳态。第一级寄存器产生亚稳态并经过自身后可以稳定输出的概率为 70%~80%左右,第二级寄存器可以稳定输出的概率为 99%左右,后面再多加寄存器的级数改善效果就不明显了,所以数据进来后一般选择打两拍即可。
(2)数据的读取

         我们知道baud需要从0~5207进行计数来进行一个bit周期之间的形成。那么我们在哪里进行数据的读取呢?为了数据的稳定,我们一般可以在计数的中间时刻5206/2进行数据的读取(其实只要 baud_cnt 计数器在计数值不是在 0 和 5207 这两个最不稳定的时刻取数都可以,更为准确的是多次取值取概率最大的情况),在cnt_baud的最大值的中间时刻拉高flag_bit并且对接收到的bit位进行数据的更新,具体代码如下:

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        flag_bit <= 1'b0;
    else if(cnt_baud == 13'd2603)
        flag_bit <= 1'b1;
    else
        flag_bit <= 1'b0;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data_rx <= 8'd0;
    else if(cnt_bit && flag_bit)//每到(cnt_baud最大值)/2进行数据的更新
        data_rx <= {rx_reg3,data_rx[7:1]};

(3)数据的发送

        因为本实验要完成的是串口的回环,所以要对接收的数据进行发送处理。发送不同于接收,不需要考虑等待数据的稳定问题,因为是对已知的数据进行发送而不是接收,所以可以考虑在cnt_baud=1的时候进行bit数据的发送。但是笔者考虑到与接收部分的设计统一,没有采用cnt_baud=1时对flag_bit进行置位,而是仍然等到cnt_baud=(cnt_baud最大值)/2的时候进行对flag_bit置位。代码与上面的类似就不再展示。

3、怎么进行一帧数据有效传输时间段的判断与分析(以uart_rx模块为例)

        下面以uart的接收模块为例讲解。对于一帧数据,我们知道它由10个bit位构成,所以我们首先想到进行cnt_baud十次计数,这样可以正好发送10个bit位,实际上我们只需要进行9个半的cnt_baud计数循环。为什么这么说?后面有交代:

         从上图看出,我们在检测到起始信号(下降沿)的时候立刻把flag_data拉高一个时钟周期,然后我们根据flag_data的高电平为起始信号来控制一帧数据的传输。这里我们使用work_en的指示作用来表示一帧数据的获取过程(work_en为高电平时代表正在一帧数据的接收),那么我们怎么知道什么时候是一帧数据的结束(也就是拉低work_en的时间)呢?我们可以通过cnt_bit来计数已经接收的bit数据量,在{cnt_bit == 9 && cnt_baud == 2603}的时候拉低work_en,代表数据传输的结束。

        那么接下来的任务就是在flag_bit置高的时刻进行1bit的数据传输。通过检测flag_bit==1就可以依次进行起始位,rx_data[0],rx_data[1],rx_data[2]......的数据传输。最后检测到{cnt_bit == 9 && cnt_baud == 2603}进行停止位的传输。因为最后一个停止位发送时cnt_baud只计数到了最大值的一半,所以这就解释了开头讲的计数九个半的cnt_baud,至此实现了一帧数据的接收。

 4、怎么进行数据的串/并转换

        首先是串->并转换。下面讲一下原理:

         因为发送是从低位向高位发送的,所以接收的时候要对数据进行右移更新,并且添加新的bit到最高位,也就是直接通过data_rx <= {rx_reg3,data_rx[7:1]}。

        然后是并->串,这部分比较简单,直接对数据进行每个bit的发送即可。

二、代码实现

1、uart_rx模块

module uart_rx(
    input wire       sys_clk      ,
    input wire       sys_rst_n    ,         
    input wire       rx           ,

    output reg  [7:0]uart_data    ,
    output reg       flag_data      
);


reg       rx_reg1     ;
reg       rx_reg2     ;
reg       rx_reg3     ;
reg       start_nedge ;
reg       work_en     ;
reg [12:0]cnt_baud    ;//5208  13
reg       flag_bit    ;
reg  [3:0]cnt_bit     ;
reg  [7:0]data_rx     ;
reg       flag_rx     ;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_reg1 <= 1'b1;
    else
        rx_reg1 <= rx;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_reg2 <= 1'b1;
    else
        rx_reg2 <= rx_reg1;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_reg3 <= 1'b1;
    else
        rx_reg3 <= rx_reg2;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        start_nedge <= 1'b0;
    else if((~rx_reg2) && (rx_reg3))
        start_nedge <= 1'b1;
    else
        start_nedge <= 1'b0;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        work_en <= 1'b0;
    else if(start_nedge == 1'b1)
        work_en <= 1'b1;
    else if(cnt_bit == 4'd8 && flag_bit)//到终止信号结束
        work_en <= 1'b0;
    else
        work_en <= work_en;



always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_baud <= 1'b0;
    else if(cnt_baud == 13'd5207 || ~work_en)
        cnt_baud <= 1'b0; 
    else if(work_en == 1'b1)
        cnt_baud <= cnt_baud + 1'b1;


always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        flag_bit <= 1'b0;
    else if(cnt_baud == 13'd2603)
        flag_bit <= 1'b1;
    else
        flag_bit <= 1'b0;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_bit <= 1'b0;
    else if(flag_bit && cnt_bit == 4'd8)
        cnt_bit <= 1'b0;
    else if(flag_bit)
        cnt_bit <= cnt_bit + 1'b1;
    else
        cnt_bit <= cnt_bit;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data_rx <= 8'd0;//下面的条件应该没问题
    else if(cnt_bit && flag_bit)//每次进行数据的更新
        data_rx <= {rx_reg3,data_rx[7:1]};
        //data_rx <= (data_rx << 1) | rx_reg3;//??

        
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        flag_rx <= 1'b0;        
    else if(cnt_bit == 4'd8 && flag_bit)
        flag_rx <= 1'b1;
    else
        flag_rx <= 1'b0;
        
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        uart_data <= 8'd0;   
    else if(flag_rx)
        uart_data <= data_rx;   
    //else
        //uart_data <= 8'd0;//保持上次的数据即可

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        flag_data <= 1'b0;  
    else if(flag_rx)
        flag_data <= 1'b1; 
    else
        flag_data <= 1'b0; 



endmodule


2、uart_tx模块

module uart_tx(
    input wire          sys_clk     ,
    input wire          sys_rst_n   ,
    input wire     [7:0]uart_data   ,
    input wire          flag_data   ,

    output reg          tx
);
reg  [12:0]cnt_baud ;
reg        flag_bit ;
reg        work_en  ;
reg   [3:0]cnt_bit  ;

always@(posedge sys_clk or negedge sys_rst_n)
    if(~sys_rst_n)
        cnt_baud <= 1'b0;
    else if(work_en && cnt_baud == 13'd5207)
        cnt_baud <= 1'b0;
    else if(work_en)
        cnt_baud <= cnt_baud + 1'b1;
    else
        cnt_baud <= 1'b0;

always@(posedge sys_clk or negedge sys_rst_n)
    if(~sys_rst_n)
        work_en <= 1'b0;
    else if(flag_data)
        work_en <= 1'b1;    
    else if(cnt_bit == 4'd9 && cnt_baud == 13'd2603)
        work_en <= 1'b0;
    
always@(posedge sys_clk or negedge sys_rst_n)
    if(~sys_rst_n)
        flag_bit <= 1'b0;   
    else if(cnt_baud == 13'd2603)
        flag_bit <= 1'b1;   
    else
        flag_bit <= 1'b0;   
        
        
always@(posedge sys_clk or negedge sys_rst_n)
    if(~sys_rst_n)
        cnt_bit <= 1'b0;    
    else if((flag_bit && cnt_bit == 4'd9))
        cnt_bit <= 1'b0; 
    else if(flag_bit)
        cnt_bit <= cnt_bit + 1'b1;  

always@(posedge sys_clk or negedge sys_rst_n)
    if(~sys_rst_n)
        tx <= 1'b1;
    else if(flag_bit)
        case(cnt_bit)
          0:tx <= 1'b0;
          1:tx <= uart_data[0];
          2:tx <= uart_data[1];
          3:tx <= uart_data[2];
          4:tx <= uart_data[3];
          5:tx <= uart_data[4];
          6:tx <= uart_data[5];
          7:tx <= uart_data[6];
          8:tx <= uart_data[7];
          9:tx <= 1'b1;
        endcase
   

endmodule



3、顶层uart_232模块

module uart_232(
    input wire      sys_clk     ,
    input wire      sys_rst_n   ,
    input wire      rx          ,
    
    output wire     tx
);



wire [7:0]uart_data;
wire flag_data;


uart_rx uart_rx_inst(
    .sys_clk     (sys_clk  ),
    .sys_rst_n   (sys_rst_n),         
    .rx          (rx       ),

    .uart_data   (uart_data),
    .flag_data   (flag_data)  
);

uart_tx uart_tx_inst(
    .sys_clk     (sys_clk  ),
    .sys_rst_n   (sys_rst_n),
    .uart_data   (uart_data),
    .flag_data   (flag_data),
    
    .tx          (tx       )
);



endmodule

总结

        在理解了串口232的通信协议后,通过设置相应的计数器标志信号来规定一帧数据中各个比特的传输时刻与一帧的开始和结束时刻。特别地,我们在本章节的学习中引出了一个比较重要的概念-亚稳态,我们了解了它产生的原因和减小亚稳态的影响的方法。

        本文参考了野火的教程,感谢野火细致的FPGA教程。本文从理论和代码实现两个方面对串口232实验进行叙述,希望可以给大家提供一些思路。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值