野火FPGA跟练(四)——串口RS232、亚稳态、串口RS485


RS232简介

  • UART:Universal Asynchronous Receiver/Transmitter,异步串行通信接口。发送数据时并行转串行,接收数据时串行转并行。
  • RS232:UART包括多种接口标准规范和总线标准规范,RS232为其一,还有RS499、RS423、RS422、RS485等。

接口与引脚

在这里插入图片描述在这里插入图片描述
重点关注RXD、TXD即可。

UART通信协议

  • 帧结构:包含8bit有效数据和起始位、停止位。空闲状态是高电平,起始位低电平,停止位高电平。低位数据先发,后发高位。
    在这里插入图片描述
    假设我要传输字符“1”,对应的ASCII码值是0x31,即“0011 0001” ,低位先发,那么我的这帧数据波形为 0 1000 1100 1

  • 波特率:假设波特率为9600Bps,传输一个码元(一个码元在这里代表一个二进制数,就是1bit数据)需要1/9600秒。假设系统时钟50MHz(周期为20ns),那么传输1bit数据所需的时间为 (1/9600)/(20*e-9) = 5208.33 ≈ 5208 个时钟周期。

亚稳态

  • 产生原因:
    触发器采集输入信号时需要一定的建立时间和保持时间,如果在这一期间输入信号发生变化,那么触发器将无法稳定输出,导致输出信号在高低电平间快速振荡,即进入亚稳态。

  • 不良影响:
    如果处于亚稳态的信号直接接入组合逻辑电路,会导致亚稳态在整个系统中传递,从而影响整个电路的运行,导致不稳定。

  • 解决方案:
    单比特信号可以采用“打两拍”,多比特信号可以采用异步FIFO、格雷码、握手。

RS232接收模块

模块框图

在这里插入图片描述
输入输出描述:

  1. 时钟:50MHz,每10ns翻转一次
  2. 复位:高电平异步复位
  3. RX:接收的串口数据
  4. DATA:处理后的数据,并行输出
  5. FLAG:置高时代表已处理好一帧数据,只维持一个时钟周期

时序波形

示意时序图,以9600波特率为例。

在这里插入图片描述

时序图分析:

  • RX_REG
    • RX_REG1:是将 RX 同步到时钟信号上。
    • RX_REG2、3:打两拍,避免亚稳态。
  • START_FLAG 标志数据接收的开始、WORK_EN 标志数据接收的状态
    • START_FLAG:这里使用的是组合方式赋值,通过 RX_REG2、3 检测 RX_REG 的下降沿。由于 RX_REG 的数据位中也有可能存在下降沿,所以 START_FLAG 置高的前提条件还应有 WORK_EN 为 0 。
    • WORK_EN:WORK_EN 通过判断 START_FLAG 的状态而置高,通过判断 BIT_CNT、BIT_FLAG 的状态而置低,其余时刻保持不变。
  • BAUD_CNT、BIT_FLAG
    • BAUD_CNT:BAUD_CNT 只在 WORK_EN 为高时计数
    • BIT_FLAG:在每比特数据传输时的中间时刻拉高
  • RX_DATA
    由于是低位数据先发,所以 RX_REG 的数据存在 RX_DATA 的高位,每次更新时 RX_DATA 右移,实现数据的串行转并行。RX_DATA 的值在 BAUD_CNT 为 1-8 并且 BIT_FLAG 为高时采集 RX_REG。

RTL 代码

module RS232_RX
#(
    parameter UART_BPS = 'd9600,
    parameter CLK_FREQ = 'd50_000_000
)
(
    input   wire         clk,
    input   wire         rst,
    input   wire         rx,
    output  reg   [7:0]  data,
    output  reg          flag
    );
    
    parameter BAUD_CNT_MAX = CLK_FREQ / UART_BPS;
    
    reg rx_reg1;
    reg rx_reg2;
    reg rx_reg3;
    wire start_flag;
    reg work_en;
    reg [16:0] baud_cnt;
    reg bit_flag;
    reg [3:0] bit_cnt;
    reg [7:0] rx_data;
    reg data_flag;
    
    // 赋值rx_reg1
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            rx_reg1 <= 1'b1;
        else
            rx_reg1 <= rx;
    end
    // 赋值rx_reg2
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            rx_reg2 <= 1'b1;
        else
            rx_reg2 <= rx_reg1;
    end
    // 赋值rx_reg3
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            rx_reg3 <= 1'b1;
        else
            rx_reg3 <= rx_reg2;
    end
    // 赋值start_flag
    assign start_flag = ((rx_reg2 == 1'b0) && (rx_reg3 == 1'b1));
    // 赋值work_en
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            work_en <= 1'b0;
        else if(start_flag == 1'b1)
            work_en <= 1'b1;
        else if((bit_flag == 1'b1) && (bit_cnt == 4'd8))
            work_en <= 1'b0;
        else
            work_en <= work_en;
    end
    // 赋值baud_cnt
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            baud_cnt <= 17'b0;
        else if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
            baud_cnt <= 17'b0;
        else
            baud_cnt <= baud_cnt + 1'b1;
    end 
    // 赋值bit_flag
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            bit_flag <= 1'b0;
        else if(baud_cnt == BAUD_CNT_MAX / 2 - 1)
            bit_flag <= 1'b1;
        else
            bit_flag <= 1'b0;
    end  
    // 赋值bit_cnt
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            bit_cnt <= 4'b0;
        else if((bit_flag == 1'b1) && (bit_cnt == 4'd8))
            bit_cnt <= 4'b0;
        else if(bit_flag == 1'b1)
            bit_cnt <= bit_cnt + 1'b1;
        else
            bit_cnt <= bit_cnt;
    end
    // 赋值rx_data
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            rx_data <= 8'b0;
        else if((bit_cnt >= 4'd1) && (bit_cnt <= 4'd8) && (bit_flag == 1'b1))
            rx_data <= {rx_reg3,rx_data[7:1]};
        else
            rx_data <= rx_data;
    end
    // 赋值data_flag
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            data_flag <= 1'b0;
        else if((bit_flag == 1'b1) && (bit_cnt == 4'd8))
            data_flag <= 1'b1;
        else
            data_flag <= 1'b0;
    end
    // 赋值data
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            data <= 8'd0;
        else if(data_flag == 1'b1)
            data <= rx_data;
        else
            data <= data;
    end
    // 赋值flag
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            flag <= 1'b0;
        else
            flag <= data_flag;
    end
endmodule

易错点

如果数据是多位的,赋值时需要注意其位宽大小。如果一个多位宽的数据赋值为“1’b0”,这将只赋值给这个数据的最低位,其他位将保留原值,与本意相悖。

Testbench 代码

`timescale 1ns / 1ps
module tb_RS232_RX();

    reg     clk;
    reg     rst;
    reg     rx;
    wire [7:0]  data;
    wire    flag;
    
    // 初始化clk和rst
    initial begin
        clk <= 1'b0;
        rst <= 1'b1;
        #60;
        rst <= 1'b0;
    end 
    always #10 clk = ~clk;
    
    // 初始化rx
    initial begin
        rx <= 1'b1;
        #100;
        rx_bit(8'b1001_0001);
        rx_bit(8'b0101_0010);
        rx_bit(8'b1110_0101);
        rx_bit(8'b0110_1000);
        rx_bit(8'b0110_1011);
        rx_bit(8'b0011_0110);
        rx_bit(8'b0001_1110);
    end
    
    // rx_bit函数,生成串行数据
    task rx_bit(
        input [7:0] rx_data
    );
        integer i;
        for(i = 0; i < 10; i = i + 1) begin
            case(i)
                0: rx <= 1'b0;
                1: rx <= rx_data[0];
                2: rx <= rx_data[1];
                3: rx <= rx_data[2];
                4: rx <= rx_data[3];
                5: rx <= rx_data[4];
                6: rx <= rx_data[5];
                7: rx <= rx_data[6];
                8: rx <= rx_data[7];
                9: rx <= 1'b1;
            endcase
            #(5208*20);   
        end         
    endtask
    
    // 实例化模块
    RS232_RX #(
        .UART_BPS(9600),
        .CLK_FREQ(50_000_000)
    )tb_RS232_RX
    (
        .clk      (clk  ),
        .rst      (rst  ),
        .rx       (rx   ),
        .data     (data ),
        .flag     (flag )
    );
endmodule

仿真

在这里插入图片描述

RS232发送模块

模块框图

在这里插入图片描述
输入输出描述:

  1. 时钟:50MHz,每10ns翻转一次
  2. 复位:高电平异步复位
  3. DATA:待发送的并行数据
  4. FLAG:数据标志位
  5. TX:输出处理后的串行数据

时序波形

示意时序图,以9600波特率为例。

在这里插入图片描述

时序图分析:

  • WORK_EN:WORK_EN 通过判断 START_FLAG 的状态而置高,通过判断 BIT_CNT、BIT_FLAG 的状态而置低,其余时刻保持不变。
  • BAUD_CNT、BIT_FLAG
    • BAUD_CNT:BAUD_CNT 只在 WORK_EN 为高时计数
    • BIT_FLAG:在 BAUD_CNT 计数为1时刻拉高

RTL 代码

module RS232_TX
#(
    parameter UART_BPS = 'd9600,
    parameter CLK_FREQ = 'd50_000_000
)
(
    input wire clk,
    input wire rst,
    input wire [7:0] data,
    input wire flag,
    output reg tx
    );
    
    parameter BAUD_CNT_MAX = CLK_FREQ / UART_BPS;
    
    reg work_en;
    reg [16:0] baud_cnt;
    reg bit_flag;
    reg [3:0] bit_cnt;
    
    // 赋值work_en
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            work_en <= 1'b0;
        else if(flag == 1'b1)
            work_en <= 1'b1;
        else if((bit_flag == 1'b1) && (bit_cnt == 4'd10))
            work_en <= 1'b0;
        else
            work_en <= work_en;
    end
    // 赋值baud_cnt
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            baud_cnt <= 17'b0;
        else if((baud_cnt == BAUD_CNT_MAX - 1'b1) || (work_en == 1'b0))
            baud_cnt <= 17'b0;
        else
            baud_cnt <= baud_cnt + 1'b1;
    end
    // 赋值bit_flag
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            bit_flag <= 1'b0;
        else if(baud_cnt == 17'b1)
            bit_flag <= 1'b1;
        else
            bit_flag <= 1'b0;
    end
    // 赋值bit_cnt
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            bit_cnt <= 4'b0;
        else if((bit_flag == 1'b1) && (bit_cnt == 4'd10))
            bit_cnt <= 4'b0;
        else if(bit_flag == 1'b1)
            bit_cnt <= bit_cnt + 1'b1;
        else
            bit_cnt <= bit_cnt;
    end
    // 赋值tx
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            tx <= 1'b1;
        else begin
            case(bit_cnt)
                4'd1: tx <= 1'b0;
                4'd2: tx <= data[0];
                4'd3: tx <= data[1];
                4'd4: tx <= data[2];
                4'd5: tx <= data[3];
                4'd6: tx <= data[4];
                4'd7: tx <= data[5];
                4'd8: tx <= data[6];
                4'd9: tx <= data[7];
                4'd10: tx <= 1'b1;
                default: tx <= 1'b1;
            endcase
        end
    end
endmodule

Testbench 代码

`timescale 1ns / 1ps
module tb_RS232_TX();
    
    reg clk;     
    reg rst;    
    reg [7:0] data;
    reg flag;
    wire tx;       

    // 初始化clk和rst
    initial begin
        clk <= 1'b0;
        rst <= 1'b1;
        #60;
        rst <= 1'b0;
    end 
    always #10 clk = ~clk;
    
    // 初始化data和flag
    initial begin
        data <= 8'd0;
        flag <= 1'd0;
        #200;
        // 数据1
        data <= 8'b0010_1011;
        flag <= 1'd1;
        #20;
        flag <= 1'd0;
        #(5208*12*20);
        // 数据2
        data <= 8'b0111_0101;
        flag <= 1'd1;
        #20;
        flag <= 1'd0;
        #(5208*12*20);
        // 数据3
        data <= 8'b1010_0001;
        flag <= 1'd1;
        #20;
        flag <= 1'd0;
        #(5208*12*20);
    end
    
    // 实例化模块
    RS232_TX #(
        .UART_BPS(9600),
        .CLK_FREQ(50_000_000)
    )tb_RS232_TX
    (
        .clk    (clk),       
        .rst    (rst),       
        .data   (data),
        .flag   (flag),      
        .tx     (tx)      
    );
endmodule

仿真

在这里插入图片描述

RS485简介

  • RS485:同 RS232 相同,也是 UART 的一种标准规范。
  • 双向通信:是指既可接收又可发送,RS485 和 RS232 均是双向通信协议。
  • 半双工通信:是指同一时刻只能进行一项任务,数据的接收或者数据的发送。RS485 是半双工。
  • 全双工通信:是指同一时刻能进行两项任务,数据的接收和数据的发送。RS232 是全双工。
  • 差分传输:传输信号互为镜像,接收端通过比较这两个传输信号的电压差来解析数据。差分传输的方式可以有效抗干扰,更适合长距和高速传输。
    在这里插入图片描述
RS232RS485
双向通信双向通信
全双工通信半双工通信
——差分传输

RS485实现

使用 FPGA 实现 RS485 通信时,电平转换和差分信号传输的处理交给 RS485 转换器即可,FPGA 内部的设计与实现 RS232 通信类似。

<think>好的,我需要帮助用户寻找与野火FPGA相关的串口项目资料或教程,包括示例代码和教程。首先,我应该回顾用户提供的引用内容,看看是否有相关的信息。 引用[1]提到了基于野火FPGA ZYNQ开发板的项目,特别是小梅哥的FIFO转AXI4接口模块设计,以及摄像头寄存器配置。虽然这主要涉及图像处理,但可能串口项目也存在于同一资源库中。用户可能需要参考类似的代码结构或配置方法,比如使用AXI4接口或者FIFO缓冲来处理串口通信。 引用[2]和[3]都是关于嵌入式开源项目和资源的推荐,可能包含与FPGA串口相关的库或项目。例如,用户可能在这些资源中找到通用的串口通信协议实现,或者FPGA上的UART模块设计。虽然野火FPGA的具体示例可能不在其中,但用户可以参考这些开源项目来构建自己的串口应用。 接下来,我应该考虑用户可能需要的具体内容。野火FPGA通常会有官方提供的例程和文档,所以建议用户首先访问野火电子官网或他们的论坛,查找串口通信相关的开发指南。此外,像小梅哥这样的知名教程作者可能有相关的资料,尽管引用中提到的FIFO转AXI4可能与串口项目不同,但代码结构和接口设计可能有借鉴之处。 另外,在GitHub、Gitee等平台上搜索“野火FPGA 串口”或“FireBison FPGA UART”可能会找到开源项目或示例代码。用户可能需要自行调整这些代码以适应特定硬件配置,比如时钟频率或引脚分配。 还需要提醒用户,串口通信涉及波特率设置、数据帧格式、校验位等配置,确保FPGA的UART模块与外部设备匹配。可能还需要参考Xilinx的官方文档,了解ZYNQ系列中UART IP核的使用方法,如果野火的教程不够详细的话。 最后,建议用户结合已有的嵌入式资源(引用[2][3])中的开源库,学习如何高效实现串口协议,提升代码质量。例如,使用状态机处理串口接收和发送,或者利用FIFO缓冲来管理数据流,避免数据丢失。</think>### 分步解答 根据您的问题,以下是野火 FPGA 串口项目相关的资料获取和实现建议: --- #### **1. 官方资料查找** - **开发板配套资料** 野火 FPGA 开发板(如 ZYNQ 系列)通常附带详细的示例代码和文档。请检查以下内容: - 光盘或官网下载的“FPGA 例程”中查找 **UART** 或 **串口通信** 相关项目。 - 参考《野火 FPGA 开发指南》中“外设驱动”章节,通常包含串口配置步骤和寄存器说明[^1]。 - **小梅哥相关资源** 引用[1]提到小梅哥的 FIFO 转 AXI4 接口模块设计,虽然主要面向图像处理,但其代码架构(如 FIFO 缓冲、状态机设计)可复用于串口项目。例如: ```verilog // 示例:串口接收状态机片段(参考小梅哥代码风格) always @(posedge clk) begin case(state) IDLE: if (rx_start) state <= DATA_BITS; DATA_BITS: // 处理数据位 ... end ``` --- #### **2. 开源平台搜索** - **GitHub/Gitee 关键词搜索** 尝试搜索以下关键词: - `野火 FPGA UART`、`FireBison FPGA Serial` - `ZYNQ UART Example`、`FPGA 串口 FIFO` - **推荐项目参考** 引用[2][3]中提到的嵌入式开源库可能包含通用串口协议实现,例如: - **libserialport**:跨平台的串口库,可参考其数据帧解析逻辑[^2]。 - **FPGA UART Core**:开源的 Verilog/VHDL UART 核,可直接移植到野火开发板[^3]。 --- #### **3. 自主实现关键步骤** 若缺乏直接示例,可按以下流程实现: 1. **硬件连接** - 确认 FPGA 引脚与串口模块(如 CH340)的连接,参考开发板原理图。 - 设置正确的波特率(如 115200)和时钟分频参数。 2. **Verilog 代码框架** ```verilog module uart_tx ( input clk, input [7:0] data, output tx_pin ); // 状态机 + 计数器实现串并转换 endmodule ``` 3. **FIFO 缓冲设计** 引用[1]中提到的乒乓操作可优化数据吞吐,防止丢失: ```verilog // 双 FIFO 乒乓缓冲示例 fifo fifo1, fifo2; always @(posedge clk) begin if (fifo1_full) switch_to_fifo2(); ... end ``` --- #### **4. 调试与验证** - 使用 **串口调试助手**(如 SecureCRT)发送测试数据。 - 通过 SignalTap II 抓取 FPGA 内部信号,检查时序是否符合 UART 协议。 --- ### 相关问题 1. 如何在野火 FPGA 中实现高速串口通信? 2. FPGA 串口通信如何避免数据丢失? 3. 如何将 AXI4 接口与串口模块结合使用? 如果需要更具体的代码片段或配置细节,可进一步说明开发板型号或功能需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值