FPGA实现串口通信(回环)与奇偶校验

FPGA实现串口通信(回环)与奇偶校验

1. 概述

本文详细介绍如何使用FPGA实现带奇偶校验的串口通信回环测试系统。该系统通过Verilog硬件描述语言设计,包含完整的发送、接收模块,支持奇偶校验功能,并可通过回环连接进行自测试验证。

2. 串口通信基础

串口通信采用异步传输方式,常见参数包括:

  • 波特率:9600、115200等
  • 数据位:8位
  • 停止位:1位
  • 校验位:1位(本文采用奇校验)
  • 流控制:无

在FPGA中实现串口通信需要精确的时序控制和状态机设计,确保数据按照协议规范正确传输。

3. 如何实现回环测试

回环测试是验证串口通信功能的有效方法,实现方式如下:通过上位机串口发送数据,接收模块将数据接收,并传到顶层模块进行缓存,再将数据传到发送模块,观察上位机串口的接收部分是否有数据传回来以及是否一致。

4. 如何实现奇偶校验

奇偶校验是一种简单的错误检测机制,本文采用奇校验实现:

4.1 奇校验原理

奇校验要求数据位和校验位中"1"的总数为奇数。发送端计算校验位,接收端验证校验位。

4.2 校验位计算

对于8位数据data[7:0],奇校验位计算公式:^data_rx,通过使用异或门来实现判断数据中1的个数,若1的个数为奇数,则^data_rx等于1;反之为0;

5. 接收模块代码实现

完整的UART接收模块Verilog代码:

module uart_rx(
    input wire clk,
    input wire rst_n,
    input wire RX,
    output wire [7:0] data,
    output wire done
  
);
    parameter CLK=50000000;
    parameter baudrate=115200;
    parameter baud_1bit=CLK/baudrate;
    //两级寄存器,并检测下降沿
    reg rx_z1,rx_z2;
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            rx_z1<=1;
            rx_z2<=1;
        end
        else begin
            rx_z1<=RX;
            rx_z2<=rx_z1;
        end
    end

    wire rx_negedge;
    assign rx_negedge=!rx_z1&&rx_z2;

     parameter  [1:0]   STATE0=0,//空闲位
                        STATE1=1,//起始位,数据位
                        STATE2=2,//校验位
                        STATE3=3;//停止位

    reg[1:0] state;
    reg [12:0]cnt;
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt<=0;
            state<=STATE0;
        end
       else 
       case(state)
    STATE0:begin
        if(rx_negedge)begin
            state<=STATE1;
            cnt<=0;
        end
        else begin
            state<=STATE0;
        end
       end  
    STATE1:begin//起始位与数据位
        if(cnt==baud_1bit*9-1)begin
            state<=STATE2;
        end
        else  begin
            cnt<=cnt+1;
            state<=STATE1;
        end
       end
    STATE2:begin//校验
        if(parity_err==1)
            state<=STATE0;
        else if(cnt==baud_1bit*10-1)begin
            state<=STATE3;
        end
        else begin
            cnt<=cnt+1;
            state<=STATE2;
        end
       end
    STATE3:begin//停止位,只有半个周期,然后进入状态0去等待下一组数据到来
        if(cnt==baud_1bit*10+(baud_1bit>>1))
        state<=STATE0;
        else begin
            cnt<=cnt+1;
            state<=STATE3;
        end
    end
    default:;   
    endcase
 end
    reg [7:0]data_rx;
    always @(posedge clk or negedge rst_n) 
    if(!rst_n)
        data_rx<=0;
    else    begin
        if(cnt==baud_1bit*1+(baud_1bit>>1))
            data_rx[0]<=rx_z2;
        else if(cnt==baud_1bit*2+(baud_1bit>>1))
            data_rx[1]<=rx_z2;
        else if(cnt==baud_1bit*3+(baud_1bit>>1))
            data_rx[2]<=rx_z2;    
        else if(cnt==baud_1bit*4+(baud_1bit>>1))
            data_rx[3]<=rx_z2; 
        else if(cnt==baud_1bit*5+(baud_1bit>>1))
            data_rx[4]<=rx_z2; 
        else if(cnt==baud_1bit*6+(baud_1bit>>1))
            data_rx[5]<=rx_z2;    
        else if(cnt==baud_1bit*7+(baud_1bit>>1))
            data_rx[6]<=rx_z2;                  
        else if(cnt==baud_1bit*8+(baud_1bit>>1))
            data_rx[7]<=rx_z2;    
    end
reg [7:0]data_reg;
always @(posedge clk  or negedge rst_n) begin
    if(!rst_n)
        data_reg<=0;
    else if(cnt==baud_1bit*9)   
            data_reg<=data_rx;
    else 
        data_reg<=data_reg;         
end

reg parity_err;//判断校验位
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        parity_err <= 0;
    else if(cnt == baud_1bit*9 + (baud_1bit>>1)) begin
        // 奇校验:数据中1个数为奇数,校验位应为0;偶数则校验位应为1
        if(^data_rx == 1)
            parity_err <= (rx_z2 == 0) ? 0 : 1;
        else
            parity_err <= (rx_z2 == 1) ? 0 : 1;
    end
end
    assign data=(parity_err==0)?data_reg:0;//只要校验没有问题时,才能给数据赋值
 
    reg done_rx;//判断数据是否有效
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
        done_rx<=0;
   
        else if(state==STATE0)
            done_rx<=0;
        else if(cnt==baud_1bit*10+(baud_1bit>>1))    
            done_rx<=1;
    end
    assign done=done_rx;
endmodule

该接收模块实现了完整的UART接收功能,包含以下关键设计:

  1. 四状态状态机
    • STATE0(空闲状态):等待起始位下降沿,检测到后进入STATE1
    • STATE1(起始位与数据位接收):接收起始位和8位数据位,在每个比特位的中间采样点读取数据
    • STATE2(校验位验证):接收并验证奇校验位,如果校验错误则直接返回空闲状态
    • STATE3(停止位处理):接收停止位,完成后返回空闲状态等待下一帧数据
  2. 数据采样逻辑:在STATE1状态下,分别在cnt == baud_1bit*1+(baud_1bit>>1)cnt == baud_1bit*8+(baud_1bit>>1)时刻采样8位数据,存储到data_rx寄存器
  3. 奇校验验证:在STATE2状态下,当cnt == baud_1bit*9 + (baud_1bit>>1)时采样校验位,并与计算得到的奇校验值比较:
    • 如果^data_rx == 1(数据中1的个数为奇数),则校验位应为0
    • 如果^data_rx == 0(数据中1的个数为偶数),则校验位应为1
    • 校验错误时设置parity_err = 1,状态机直接返回STATE0
  4. 数据输出控制只有在校验通过(parity_err == 0)时,才将data_reg赋值给输出data,否则输出0,确保错误数据不被传递。
  5. 完成信号生成:在STATE3状态下,当cnt == baud_1bit*10+(baud_1bit>>1)时,设置done_rx = 1,表示一帧数据接收完成。

模块工作流程

  1. 复位后,模块处于STATE0空闲状态,等待起始位下降沿
  2. 检测到下降沿后进入STATE1,开始接收起始位和8位数据
  3. 数据位接收完成后进入STATE2,接收并验证奇校验位
  4. 如果校验错误,直接返回STATE0;如果校验正确,进入STATE3接收停止位
  5. 停止位接收完成后,设置done信号有效(表示接收完成),并将有效数据输出到data
  6. 返回STATE0,等待下一帧数据的起始位

6. 发送模块代码实现

完整的UART发送模块Verilog代码:

module uart_tx(
    input wire clk,
    input wire rst_n,
    input wire [7:0] data,
    input wire start_tx,
    output wire TX,
    output wire busy          // 新增:忙状态指示
);
    parameter CLK=50000000;        // 系统时钟
    parameter baudrate=115200;     // 波特率

    parameter sent_1bit = CLK / baudrate;
    parameter sent_1Byte = sent_1bit * 11;   // 1起始 + 8数据 + 1校验 + 1停止

    reg [12:0] cnt_1bit;
    reg [1:0] state;
    parameter [1:0] STATE0 = 0,   // 空闲,等待启动
                    STATE1 = 1,   // 发送起始位、数据位、校验位、停止位
                    STATE2 = 2;   // 清空计数器,回到空闲

    // 状态机
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            state <= STATE0;
            cnt_1bit <= 0;
        end else begin
            case(state)
                STATE0: begin
                    if(start_tx) begin
                        state <= STATE1;
                        cnt_1bit <= 0;
                    end 
                    else
                        state <= STATE0;
                end
                STATE1: begin
                    if(cnt_1bit == sent_1Byte - 1)
                        state <= STATE2;
                    else
                        cnt_1bit <= cnt_1bit + 1;
                end
                STATE2: begin
                    cnt_1bit <= 0;
                    state <= STATE0;
                end
            endcase
        end
    end

    // 发送数据寄存器
    reg tx_p;
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            tx_p <= 1;
        else if(state == STATE1) begin
            if(cnt_1bit < sent_1bit * 1)          // 起始位(低电平)
                tx_p <= 0;
            else if(cnt_1bit < sent_1bit * 2)     // bit0
                tx_p <= data[0];
            else if(cnt_1bit < sent_1bit * 3)     // bit1
                tx_p <= data[1];
            else if(cnt_1bit < sent_1bit * 4)     // bit2
                tx_p <= data[2];
            else if(cnt_1bit < sent_1bit * 5)     // bit3
                tx_p <= data[3];
            else if(cnt_1bit < sent_1bit * 6)     // bit4
                tx_p <= data[4];
            else if(cnt_1bit < sent_1bit * 7)     // bit5
                tx_p <= data[5];
            else if(cnt_1bit < sent_1bit * 8)     // bit6
                tx_p <= data[6];
            else if(cnt_1bit < sent_1bit * 9)     // bit7
                tx_p <= data[7];
            else if(cnt_1bit < sent_1bit * 10)    // 奇校验位
                tx_p <= (^data == 1) ? 0 : 1;     // 数据中1为奇数则校验位0,否则1
            else if(cnt_1bit < sent_1bit * 11)    // 停止位(高电平)
                tx_p <= 1;
        end 
        else
            tx_p <= 1;    // 空闲时保持高电平
    end

    assign TX = tx_p;
    assign busy = (state != STATE0);   // 发送器忙指示
endmodule

该发送模块实现了以下功能:

  1. 三状态状态机:STATE0(空闲等待)、STATE1(发送数据)、STATE2(清空计数器)。
  2. 奇校验生成:在发送数据位后自动计算并发送奇校验位。
  3. 忙状态指示:通过 busy 信号指示发送器是否正在工作。
  4. 完整帧格式:按照起始位(0)+ 8位数据 + 奇校验位 + 停止位(1)的格式发送数据。

模块工作流程:当 start_tx 信号有效时,状态机从 STATE0 进入 STATE1,开始发送起始位、8位数据、奇校验位和停止位。发送完成后进入 STATE2 清空计数器,然后返回 STATE0 等待下一次发送。

7. 顶层模块设计

顶层模块整合发送、接收模块,实现回环测试功能:

module uart_loopback (
    input  wire clk,
    input  wire rst_n,
    input  wire RX,
    output wire TX
);
    // 接收模块实例
    wire [7:0] rx_data;
    wire       rx_done;
    uart_rx u_rx (
        .clk   (clk),
        .rst_n (rst_n),
        .RX    (RX),
        .data  (rx_data),
        .done  (rx_done)
    );
    
    // 发送模块实例
    wire       tx_busy;
    reg  [7:0] tx_data;
    reg        start_tx;
    uart_tx u_tx (
        .clk     (clk),
        .rst_n   (rst_n),
        .data    (tx_data),
        .start_tx(start_tx),
        .TX      (TX),
        .busy    (tx_busy)
    );
    
    reg [7:0] buf0, buf1;   // 两个存储单元,buf0 为最早数据
    reg [1:0] count;        // 当前缓存数量:0,1,2

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            tx_data  <= 8'd0;
            start_tx <= 1'b0;
            buf0     <= 8'd0;
            buf1     <= 8'd0;
            count    <= 2'd0;
        end 
        else begin
            start_tx <= 1'b0;   // 默认无发送启动

           
            // 如果发送空闲且缓存非空,则取出最早的数据发送
            if(!tx_busy && count > 0) begin
                tx_data <= buf0;          // 取 buf0(最早)
                start_tx <= 1'b1;

                // 更新缓存:移位
                if(count == 2) begin
                    buf0 <= buf1;         // 将 buf1 移到 buf0
                    buf1 <= 8'd0;         // 清空(可选)
                    count <= 1;
                end else begin            // count == 1
                    buf0 <= 8'd0;
                    count <= 0;
                end
            end
           
            // 如果接收到新数据且缓存未满,则存入缓存
            if(rx_done) begin
                if(count == 0) begin
                    buf0 <= rx_data;
                    count <= 1;
                end else if(count == 1) begin
                    buf1 <= rx_data;
                    count <= 2;
                end else begin
                    // count == 2,
                   
                end
            end
        end
    end
endmodule

该顶层模块实现了以下功能:

  1. 模块实例化:实例化接收模块 uart_rx 和发送模块 uart_tx,建立完整的串口通信链路。
  2. 双缓存设计:采用 buf0buf1 两个8位寄存器作为数据缓存,实现简单的先进先出(FIFO)的数据流控制。(使用数据缓存的目的防止数据的丢失,如果没有缓存,那么在数据接收完成后,马上开始发送,在数据发送期间,又接收到了数据,导致发送模块来不及处理,从而导致丢失这一帧数据)也可以直接调用一个fifoip核来处理

模块工作流程:

  1. 复位状态:所有寄存器和缓存清零,发送模块处于空闲状态。
  2. 数据接收:当接收模块完成数据接收(rx_done 有效)且缓存未满时,数据存入缓存。
  3. 数据发送:当发送模块空闲(tx_busy 为0)且缓存非空时,从 buf0 取出最早的数据启动发送。
  4. 缓存更新:发送完成后更新缓存,如果缓存中有两个数据,则将 buf1 移到 buf0;如果只有一个数据,则清空缓存。

8. 上板验证与测试

能够实现串口回环功能,且不漏掉数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值