侧边栏壁纸
    • 累计撰写 296 篇文章
    • 累计收到 520 条评论
    【FPGA】深度理解串口发送模块设计与验证
    我的学记|刘航宇的博客

    【FPGA】深度理解串口发送模块设计与验证

    刘航宇
    2022-12-18 / 0 评论 / 334 阅读 / 正在检测是否收录...

    本章导读

    在当今的电子系统中,经常需要板内、板间或者下位机与上位机之间进行数据的发送与接收,这就需要双方共同遵循一定的通信协议来保证数据传输的正确性。常见的协议有UART(通用异步收发传输器)、IIC(双向两线总线)、SPI(串行外围总线)、USB2.0/3.0(通用串行总线)以及 Ethernet(以太网)等。在这些协议当中,最为基础的就是 UART,因其电路结构简单、成本较低,所以在注重性价比的情况下,使用非常广泛。
    本章将学习 UART 通信的原理及其硬件电路设计,并使用 FPGA 来实现 UART 通信中的数据发送部分设计。在仿真验证时除进行正常的功能仿真以外,还将在 Quartus Prime 中使用 In system sources and probes editor(ISSP)工具进行板级验证,具体方法是:输入需要通过串口发送出去的数据,然后通过按下 AC620 开发板上的按键来控制 FPGA 将待发送的数据发送出去,并在串口助手中查看 PC 端接收到的数据。

    异步串行通信原理及电路设计

    RS232 通信接口标准

    通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,UART)是一种异步收发传输器,其在数据发送时将并行数据转换成串行数据来传输,在数据接收时将接收到的串行数据转换成并行数据,可以实现全双工传输和接收。它包括了 RS232、RS449、RS423、RS422 和 RS485 等接口标准规范和总线标准规范。换句话说,UART 是异步串行通信的总称。而 RS232、RS449、RS423、RS422 和 RS485 等,是对应各种异步串行通信口的接口标准和总线标准,它们规定了通信口的电气特性、传输速率、连接特性和接口的机械特性等内容。
    本章要重点学习的 RS-232 是美国电子工业联盟(EIA)制定的串行数据通信的接口标准,原始编号全称是 EIA-RS-232(简称 232,RS232),被广泛用于计算机串行接口外设连接。其 DB9 接口的针脚定义如图 16.1 所示,引脚功能如表 16.1 所示。若系统存在多个 UART接口,则可分别称为 COM1、COM2 等。

    UART 关键参数及时序图

    UART 通信在使用前需要做多项设置,最常见的设置包括数据位数、波特率大小、奇偶校验类型和停止位数。
    数据位(Data bits):该参数定义单个 UART 数据传输在开始到停止期间发送的数据位数。可选择为:5、6、7 或者 8(默认)。
    波特率(Baud):是指从一设备发到另一设备的波特率,即每秒钟可以通信的数据比特个数。典型的波特率有 300, 1200, 2400, 9600, 19200, 115200 等。一般通信两端设备都要设为相同的波特率,但有些设备也可设置为自动检测波特率。
    奇偶校验类型(Parity Type):是用来验证数据的正确性。奇偶校验一般不使用,如果使用,则既可以做奇校验(Odd)也可以做偶校验(Even)。在偶校验中,因为奇偶校验位会被相应的置 1 或 0(一般是最高位或最低位),所以数据会被改变以使得所有传送的数位(含字符的各数位和校验位)中“1”的个数为偶数;在奇校验中,所有传送的数位(含字符的各数位和校验位)中“1”的个数为奇数。奇偶校验可以用于接受方检查传输是否发送生错
    误,如果某一字节中“1”的个数发生了错误,那么这个字节在传输中一定有错误发生。如果奇偶校验是正确的,那么要么没有发生错误,要么发生了偶数个的错误。如果用户选择数据长度为 8 位,则因为没有多余的比特可被用来作为奇偶校验位,因此就叫做“无奇偶校验(Non)”。
    停止位(Stop bits):在每个字节的数据位发送完成之后,发送停止位,来标志着一次数据传输完成,同时用来帮助接受信号方硬件重同步。可选择为:1(默认)、1.5 或者 2 位。
    在 RS-232 标准中,最常用的配置是 8N1(即八个数据位、无奇偶校验、一个停止位),其发送一个字节时序图如图 16.2 所示。
    按照一个完整的字节包括一位起始位、8 位数据位、一位停止位即总共十位数据来算,要想完整的实现这十位数据的发送,就需要 11 个波特率时钟脉冲,第 1 个脉冲标记一次传输的起始,第 11 个脉冲标记一次传输的结束,如下所示:

    RS232 通信电路设计

    RS232 通信协议需要一定的硬件支持,早期大多使用的方案是 RS232 转 TTL,这时需要 MAX232 或者 SP3232 等电平转换芯片来做数据转换。其外围电路简单,最少只需要 4 个电容即可正常工作,其典型电路图如 16.3 所示。在这里只使用了两路通信中的一路,且通过加入的 D7、D8 两个发光二极管可以更好的观察数据状态。

    现在系统集成度越来越高,DB9 的 RS232 接口占用 PCB 面积过大,多数系统已经转用USB 转 TTL,其电路图如图 16.4 所示。

    CH340G 是一个支持 5V 或 3.3V 供电的 USB 总线的转接芯片,实现 USB 转串口、USB 转 IrDA 红外或者 USB 转打印口。支持硬件全双工串口、内置收发缓冲区,支持通讯波特率 50bps~2Mbps 并支持常用的 MODEM 联络信号 RTS、DTR、DCD、RI、DSR、CTS。
    并可通过外加电平转换器件,可以提供 RS232、RS485、RS422 等接口。在 Windows 操作系统下,CH340 的驱动程序能够仿真标准串口,所以与绝大部分原串口应用程序完全兼容,不需要作任何修改。

    UART 异步串行通信发送模块设计与实现

    串口发送模块接口设计

    基于上述原理,本章要实现的串口发送模块整体框图,如图 16.9 所示,其接口列表如表 16.2 所示。

    根据功能需求,串口发送模块可进一步细化为如图 16.10 所示详细结构图,其中每一子模块的作用如表 16.3 所示。其中绿色的框代表单一结构的寄存器,来实现数据的稳定输入以及输出。

    波特率时钟生成模块设计

    从原理部分可知,波特率是 UART 通信中需要设置的参数之一。在波特率时钟生成模块中,计数器需要的计数值与波特率之间的关系如表 16.4 所示,其中系统时钟周期为System_clk_period,这里为 20ns。如果接入到该模块的时钟频率为其他值,需要根据具体的频率值修改该参数。

    本模块的设计是为了保证模块的复用性。当需要不同的波特率时,只需设置不同的波特率时钟计数器的计数值。使用查找表即可实现,下面的设计代码中只包含了针对 5 个波特率的设置,如需要其他波特率可根据实际使用情况具体修改。

     reg [15:0]bps_DR;//分频计数最大值 
     always@(posedge Clk or negedge Rst_n)
     if(!Rst_n)
     bps_DR <= 16'd5207;
     else begin
     case(baud_set)
     0:bps_DR <= 16'd5207; //9600bps 
     1:bps_DR <= 16'd2603; //19200bps
     2:bps_DR <= 16'd1301; //38400bps
     3:bps_DR <= 16'd867; //57600bps
     4:bps_DR <= 16'd433; //115200bps
     default:bps_DR <= 16'd5207; 
     endcase
     end

    利用计数器来生成波特率时钟。

     reg bps_clk; //波特率时钟 
     reg [15:0]div_cnt;//分频计数器
    always@(posedge Clk or negedge Rst_n)
     if(!Rst_n)
     div_cnt <= 16'd0;
     else if(uart_state)begin
     if(div_cnt == bps_DR)
     div_cnt <= 16'd0;
     else
     div_cnt <= div_cnt + 1'b1;
     end
     else
     div_cnt <= 16'd0;
     
     always@(posedge Clk or negedge Rst_n)
     if(!Rst_n)
     bps_clk <= 1'b0;
     else if(div_cnt == 16'd1)
     bps_clk <= 1'b1;
     else
     bps_clk <= 1'b0;

    所谓波特率生成,就是用一个定时器来定时,产生频率与对应波特率时钟频率相同的时钟信号。例如,我们使用波特率为 115200bps,则我们需要产生一个频率为 115200Hz 的时钟信号。那么如何产生这样一个 115200Hz 的时钟信号呢?这里,我们首先将 115200Hz 时钟信号的周期计算出来,1 秒钟为 1000_000_000ns,因此波特率时钟的周期 Tb= 1000000000/115200 =8680.6ns,即115200信号的一个周期为8680.6ns,那么,我们只需要设定我们的定时器定时时间为8680.6ns,每当定时时间到,产生一个系统时钟周期长度的高脉冲信号即可。系统时钟频率为 50MHz,即周期为 20ns,那么,我们只需要计数 8680/20 个系统时钟,就可获得 8680ns 的定时,bps115200=Tb/Tclk - 1=Tb*fclk - 1=fclk/115200-1。相应的,其它波特率定时值的计算与此相同为了能够通过外部控制波特率,设计中使用了一个 3 位的波特率选择端口:Baud_Set。通过给此端口不同的值,就能选择不同的波特率,此端口控制不同波特率的原理很简单,就是一个多路选择器,多路选择器通过选择不同的定时器计数最大值来设置不同的比特率时钟频率。
    Baud_Set 的值与各波特率的对应关系如下:
    000 : 9600bps;
    001 : 19200bps;
    010 :38400bps;
    011 :57600bps;
    100 :115200bps;

    数据输出模块设计

    通过对波特率时钟进行计数,来确定数据发送的循环状态。

    reg [3:0]bps_cnt;//波特率时钟计数器
    always@(posedge Clk or negedge Rst_n)
     if(!Rst_n) 
     bps_cnt <= 4'd0;
     else if(bps_cnt == 4'd11)
     bps_cnt <= 4'd0;
      else if(bps_clk)
     bps_cnt <= bps_cnt + 1'b1;
     else
     bps_cnt <= bps_cnt;

    同样为了使得模块可以对其他模块进行控制或者调用,这里产生一个 byte 传送结束的信号。一个数据位传输结束后 Tx_done 信号输出一个时钟的高电平。

    always@(posedge Clk or negedge Rst_n)
     if(!Rst_n)
     Tx_Done <= 1'b0;
     else if(bps_cnt == 4'd11)
     Tx_Done <= 1'b1;
     else
     Tx_Done <= 1'b0;

    产生数据传输状态信号,即当在正常传输的时候 uart_state 信号为高电平,其他情况均为低电平。这里实现的电路结构同样是具有优先级顺序的,但与 C 语言本质是不同的。在图16.10 中的 MUX2_1 与 MUX2_2 就是下面的设计实现的 if—else if—else 的电路结构。

     always@(posedge Clk or negedge Rst_n)
     if(!Rst_n)
     uart_state <= 1'b0;
     else if(send_en)
     uart_state <= 1'b1;
     else if(bps_cnt == 4'd11)
     uart_state <= 1'b0;
     else
     uart_state <= uart_state;

    由于 RS232 是一个异步的收发器,因此为了保证发送的数据在时钟到来的时候是稳定的,这里也需要对输入数据进行寄存。

    reg [7:0]r_data_byte;
    always@(posedge Clk or negedge Rst_n)
     if(!Rst_n)
     r_data_byte <= 8'd0;
     else if(send_en)
     r_data_byte <= data_byte;
     else
     r_data_byte <= r_data_byte;

    数据传输状态控制模块设计

    在模块结构图 16.10 中还有一个十选一多路器 ,作用是根据 bps_cnt 的值来确定数据传输的状态。如时序图 16.2 所示,在不同的波特率时钟计数值时,有对应的传输数据。

     localparam START_BIT = 1'b0;
     localparam STOP_BIT = 1'b1;
     always@(posedge Clk or negedge Rst_n)
     if(!Rst_n)
     Rs232_Tx <= 1'b1;
     else begin
     case(bps_cnt)
     0:Rs232_Tx <= 1'b1;
     1:Rs232_Tx <= START_BIT;
     2:Rs232_Tx <= r_data_byte[0];
     3:Rs232_Tx <= r_data_byte[1];
     4:Rs232_Tx <= r_data_byte[2];
     5:Rs232_Tx <= r_data_byte[3];
     6:Rs232_Tx <= r_data_byte[4];
     7:Rs232_Tx <= r_data_byte[5];
     8:Rs232_Tx <= r_data_byte[6];
     9:Rs232_Tx <= r_data_byte[7];
     10:Rs232_Tx <= STOP_BIT;
     default:Rs232_Tx <= 1'b1;
     endcase
     end

    激励创建及仿真测试

    完成设计之后,需要对其进行功能仿真。在下面的 testbench 文件中,生成了复位信号以及使能信号、待传输数据。这里将所有数据变化与系统时钟错开 1ns,是为了能更清楚看到输入输出数据与时钟的时序关系。

    initial begin
     Rst_n = 1'b0;
     data_byte = 8'd0;
     send_en = 1'd0;
     baud_set = 3'd4;
     #(`clk_period*20 + 1 )
     Rst_n = 1'b1;
     #(`clk_period*50);
     data_byte = 8'haa;
     send_en = 1'd1;
     #`clk_period;
     send_en = 1'd0;
     
     @(posedge Tx_Done)
     
     #(`clk_period*5000);
     data_byte = 8'h55;
     send_en = 1'd1;
     #`clk_period;
     send_en = 1'd0;
     @(posedge Tx_Done)
     #(`clk_period*5000);
     $stop; 
     end

    设置好仿真脚本后进行功能仿真,得到如图 16.11 所示的波形文件,可以看出在复位信号置高以及使能信号有效之前输出信号 Rs232_Tx 均为 0,在复位结束以及使能后输出信号才开始正常,且当输入数据为 10101010b(MSB)后,输出信号依次为 1、0(起始位)、01010101b(LSB)、1(停止位);当输入数据为 01010101b(MSB)后,输出信号依次为 1、0(起始位)、10101010b(LSB)、1(停止位)。同时 uart_state 处于发送状态时为 1,即仿真通过。

    按键控制串口发送设计

    为了实现导读中所设定的目标,将以前编写好的按键消抖模块添加到工程当中,并再次使用 ISSP,其主要参数配置如图 16.12 所示,并加入到工程中。

    然后,新建一个顶层文件,并将按键消抖、串口发送以及 ISSP 例化,并将按键状态与串口发送使能端连接即可。部分设计如下所示,并将串口发送状态连接到 LED 上,以更好的观察数据发送状态。

     assign send_en = key_flag0 & !key_state0;
     uart_byte_tx uart_byte_tx(
     .Clk(Clk),
     .Rst_n(Rst_n),
     .data_byte(data_byte),
     .send_en(send_en),
     .baud_set(3'd0),
     
     .Rs232_Tx(Rs232_Tx),
     .Tx_Done(),
     .uart_state(led)
     );

    编译无误后,点击 RTL_viewer 可以看到如图 16.13 的各模块连接图。

    分配引脚并全编译无误后下载工程到 AC620 开发板中。然后,在 Quartus Prime 中点击Tools→In-System Source and Probes Editor 启动 ISSP,手动选择下载器后,并将数据格式改为设计中的 hex 格式。打开电脑上的串口助手,将主要参数设置为:波特率为 9600、无校验位、8 位数据位以及 1bit 停止位。
    在 Quartus Prime 中,使用 In system sources and probes editor 工具,输入需要通过串口发送出去的数据,然后按下学习板上的按键 0,FPGA 自动将所需要发送的数据发送出去,即可在串口助手中看到相关数据。且板载的 LED_RX 每接收一 byte 数据均会亮一下,这里由于时钟较快,数据传输过程很快,因此代表传输状态的 led0 看着“常亮”。

    至此,完成了基于 FPGA 的串口通信发送部分实现。
    在本章中学习了 UART 的分类以及原理,介绍了 RS232 协议的数据格式以及相关参数所代表的含义,并设计了串口的硬件电路,且在板级调试中再次使用了 ISSP 中的源功能(source)。

    代码工程

    1
    【FPGA】深度理解串口接收模块设计与验证
    « 上一篇 2022-12-20
    【FPGA】【SPI】线性序列机与串行接口 DAC 驱动设计与验证
    下一篇 » 2022-12-15

    评论 (0)

    取消