串口收发,115200波特率,奇校验,1位停止位(附有源码)

一、UART 串口基本概念

  1. 什么是 UART?
    UART(Universal Asynchronous Receiver/Transmitter)是一种“异步”串行通信协议,用来在两个设备间逐位发送数据。

  2. 数据帧结构
    每发送一个“字节”(8 位数据),会在它前后加上固定的控制位,常见框架叫做 “8N1”

    • 起始位:始终是 0,告诉接收端“马上有数据了”。

    • 数据位:实际要传输的 8 个二进制位。

    • 校验位:可选,用于简单检测数据是否出错(本例中用了“奇校验”)。

    • 停止位:始终是 1,告诉接收端“这一帧结束了,恢复空闲”。


二、接收模块 uart_rx(uart_rx)

1. 同步与消抖

  • 三级寄存器rx_reg1rx_reg2rx_reg3)把外部的异步串口信号 rx 先在 FPGA 时钟域里稳定下来,避免亚稳态。

  • 然后用 rx_reg2rx_reg3 比较,检测到“高→低”的下降沿(即起始位到来),把 start_nedge 拉高一个时钟周期,开启接收流程。

2. 波特率(BAUD)产生

  • 先根据系统时钟(50 MHz)和目标波特率(115200 bps)计算出一个计数上限:

    
    
  • baud_cnt 计数,让它从 0 数到 BAUD_CNT_MAX – 1,每数一次就相当于过了一个“串口比特时间”。

  • 当计数到一半(BAUD_CNT_MAX/2)时,把 bit_flag 拉高,表示“现在是采样时刻,数据最稳定”,这时读一次当前电平。

3. 数据移位与计数

  • bit_cnt 记录当前是第几位:

    • 0:起始位

    • 1~8:8 位数据

    • 9:校验位

    • 10:停止位(本例中接收到第 9 次采样就结束,因此 stop 在计数到 9 时结束)

  • bit_flag 为高的时候:

    • 对于数据位(1~8),把采到的 rx_reg3 依次移入 rx_data(移位寄存器)。

    • 在第 9 位采样时,把它当作 parity_bit(奇校验位)保存。

  • bit_cnt 数满(接收完起始、8 数据、1 校验、1 停止)后,拉高 rx_flag 一个时钟脉冲,表示“这一字节收完了,可以输出了”。

4. 输出与错误检测

  • 并行输出:在 rx_flag 之后一个时钟周期,把 rx_data 送到 po_data,同时 po_flag 拉高一个时钟,告诉外部“新数据来了”。

  • 奇校验错误:收到完整字节后,用归约异或 ^rx_data ^ parity_bit 检查“1 的个数是否为奇数”,若不符则 parity_error 拉高。


三、发送模块 uart_tx(uart_tx)

1. 发送使能

  • 当外部把一个新字节(pi_data)及其有效信号 pi_flag 拉高时,模块把 work_en1,开始发送。

  • 发送完一个完整帧(1 起始 + 8 数据 + 1 校验 + 1 停止=共 11 比特的输出)后,work_en0,恢复空闲。

2. 波特率与节拍

  • 同样用 baud_cnt 按目标波特率计时,每当计数到 1(即一个比特周期到来),拉高 bit_flag,让下一位数据准备好输出。

  • bit_cnt 跟踪当前发送到哪一比特:0 是起始位,1~8 是数据位,9 是校验位,10 是停止位。

3. 校验位计算

  • 使用“奇校验”规则:先对 8 位数据做归约异或 ^pi_data,再取反 ~^pi_data,得到要发送的 parity_bit

4. 一位一位发送

在每个 bit_flag 到来时,根据 bit_cnt 来决定 tx 信号应该输出什么:


四、总结与核心要点

  • “异步”意思:没有单独的时钟线,靠双方事先约定同一波特率,用起始位和停止位来区分帧。

  • 接收模块

    1. 先把异步信号“同步”到本地时钟域;

    2. 检测到起始位;

    3. 按波特率节拍,依次采样数据、校验、停止;

    4. 移位、输出并做奇校验。

  • 发送模块

    1. 外部拉起发送使能;

    2. 按波特率节拍,一位一位输出:起始→数据→校验→停止;

    3. 结束后恢复空闲。

  • 奇校验:保证“1 的总数为奇数”,简单检测“哪怕有一位翻转”也能发现错误。

最后贴上源码:

`timescale  1ns/1ps

module  uart_rx
#(
    parameter   UART_BPS    =   'd115200,         //串口波特率
    parameter   CLK_FREQ    =   'd50_000_000    //时钟频率
)
(
    input   wire            sys_clk     ,   //系统时钟50MHz
    input   wire            sys_rst_n   ,   //全局复位
    input   wire            rx          ,   //串口接收数据

    output  reg     [7:0]   po_data     ,   //串转并后的8bit数据
    output  reg             po_flag     ,   //串转并后的数据有效标志信号
    output  reg             parity_error    // 奇校验错误标志
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//localparam    define
localparam  BAUD_CNT_MAX    =   CLK_FREQ/UART_BPS   ;

//reg   define
reg         rx_reg1     ;
reg         rx_reg2     ;
reg         rx_reg3     ;
reg         start_nedge ;
reg         work_en     ;
reg [12:0]  baud_cnt    ;
reg         bit_flag    ;
reg [3:0]   bit_cnt     ;
reg [7:0]   rx_data     ;
reg         rx_flag     ;
reg         parity_bit  ; // 新增:保存接收到的校验位

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//插入两级寄存器进行数据同步,用来消除亚稳态
//rx_reg1:第一级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk)
    if(sys_rst_n == 1'b1)
        rx_reg1 <= 1'b1;
    else
        rx_reg1 <= rx;

//rx_reg2:第二级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk)
    if(sys_rst_n == 1'b1)
        rx_reg2 <= 1'b1;
    else
        rx_reg2 <= rx_reg1;

//rx_reg3:第三级寄存器和第二级寄存器共同构成下降沿检测
always@(posedge sys_clk)
    if(sys_rst_n == 1'b1)
        rx_reg3 <= 1'b1;
    else
        rx_reg3 <= rx_reg2;

//start_nedge:检测到下降沿时start_nedge产生一个时钟的高电平
always@(posedge sys_clk)
    if(sys_rst_n == 1'b1)
        start_nedge <= 1'b0;
    else    if((~rx_reg2) && (rx_reg3))
        start_nedge <= 1'b1;
    else
        start_nedge <= 1'b0;

//work_en:接收数据工作使能信号
always@(posedge sys_clk)
    if(sys_rst_n == 1'b1)
        work_en <= 1'b0;
    else    if(start_nedge == 1'b1)
        work_en <= 1'b1;
    else    if((bit_cnt == 4'd9) && (bit_flag == 1'b1))
        work_en <= 1'b0;

//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
always@(posedge sys_clk)
    if(sys_rst_n == 1'b1)
        baud_cnt <= 13'b0;
    else    if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
        baud_cnt <= 13'b0;
    else    if(work_en == 1'b1)
        baud_cnt <= baud_cnt + 1'b1;

//bit_flag:当baud_cnt计数器计数到中间数时采样的数据最稳定,
//此时拉高一个标志信号表示数据可以被取走
always@(posedge sys_clk)
    if(sys_rst_n == 1'b1)
        bit_flag <= 1'b0;
    else    if(baud_cnt == BAUD_CNT_MAX/2 - 1)
        bit_flag <= 1'b1;
    else
        bit_flag <= 1'b0;

//bit_cnt:有效数据个数计数器,当8个有效数据(不含起始位和停止位)
//都接收完成后计数器清零
always@(posedge sys_clk)
    if(sys_rst_n == 1'b1)
        bit_cnt <= 4'b0;
    else    if((bit_cnt == 4'd9) && (bit_flag == 1'b1)) // 计数到9
        bit_cnt <= 4'b0;
     else    if(bit_flag ==1'b1)
         bit_cnt <= bit_cnt + 1'b1;

//rx_data:输入数据进行移位
always@(posedge sys_clk)
    if(sys_rst_n == 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]};
//parity_bit:接收第9位校验位
always@(posedge sys_clk)
    if(sys_rst_n == 1'b1)
        parity_bit <= 1'b0;
    else if((bit_cnt == 4'd9)&&(bit_flag == 1'b1))
        parity_bit <= rx_reg3;

//rx_flag:输入数据移位完成时rx_flag拉高一个时钟的高电平
always@(posedge sys_clk)
    if(sys_rst_n == 1'b1)
        rx_flag <= 1'b0;
    else    if((bit_cnt == 4'd9) && (bit_flag == 1'b1)) // 9位后拉高
        rx_flag <= 1'b1;
    else
        rx_flag <= 1'b0;

//po_data:输出完整的8位有效数据
always@(posedge sys_clk)
    if(sys_rst_n == 1'b1)
        po_data <= 8'b0;
    else    if(rx_flag == 1'b1)
        po_data <= rx_data;

//po_flag:输出数据有效标志(比rx_flag延后一个时钟周期,为了和po_data同步)
always@(posedge sys_clk)
    if(sys_rst_n == 1'b1)
        po_flag <= 1'b0;
    else
        po_flag <= rx_flag;

//parity_error: 奇校验错误标志
always@(posedge sys_clk)
    if(sys_rst_n == 1'b1)
        parity_error <= 1'b0;
    else if(rx_flag == 1'b1) begin
        // 奇校验:数据位+校验位总1的个数应为奇数
        if(^rx_data ^ parity_bit) // ^为奇偶校验归约异或
            parity_error <= 1'b0; // 正确
        else
            parity_error <= 1'b1; // 错误
    end

endmodule
`timescale  1ns/1ps

module  uart_tx
#(
    parameter   UART_BPS    =   'd115200,         //串口波特率
    parameter   CLK_FREQ    =   'd50_000_000    //时钟频率
)
(
     input   wire            sys_clk     ,   //系统时钟50MHz
     input   wire            sys_rst_n   ,   //全局复位
     input   wire    [7:0]   pi_data     ,   //模块输入的8bit数据
     input   wire            pi_flag     ,   //并行数据有效标志信号
 
     output  reg             tx              //串转并后的1bit数据
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//localparam    define
localparam  BAUD_CNT_MAX    =   CLK_FREQ/UART_BPS   ;

//reg   define
reg [12:0]  baud_cnt;
reg         bit_flag;
reg [3:0]   bit_cnt ;
reg         work_en ;
reg         parity_bit; // 新增:奇校验位

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//work_en:接收数据工作使能信号
always@(posedge sys_clk)
        if(sys_rst_n == 1'b1)
            work_en <= 1'b0;
        else    if(pi_flag == 1'b1)
            work_en <= 1'b1;
        else    if((bit_flag == 1'b1) && (bit_cnt == 4'd10)) // 计数到10
            work_en <= 1'b0;

//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
always@(posedge sys_clk)
        if(sys_rst_n == 1'b1)
            baud_cnt <= 13'b0;
        else    if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
            baud_cnt <= 13'b0;
        else    if(work_en == 1'b1)
            baud_cnt <= baud_cnt + 1'b1;

//bit_flag:当baud_cnt计数器计数到1时让bit_flag拉高一个时钟的高电平
always@(posedge sys_clk)
        if(sys_rst_n == 1'b1)
            bit_flag <= 1'b0;
        else    if(baud_cnt == 13'd1)
            bit_flag <= 1'b1;
        else
            bit_flag <= 1'b0;

//bit_cnt:数据位数个数计数,10个有效数据(含起始位和停止位)到来后计数器清零
always@(posedge sys_clk)
    if(sys_rst_n == 1'b1)
        bit_cnt <= 4'b0;
    else    if((bit_flag == 1'b1) && (bit_cnt == 4'd10)) // 计数到10
        bit_cnt <= 4'b0;
    else    if((bit_flag == 1'b1) && (work_en == 1'b1))
        bit_cnt <= bit_cnt + 1'b1;

//parity_bit:计算奇校验位
always@(posedge sys_clk)
    if(sys_rst_n == 1'b1)
        parity_bit <= 1'b0;
    else if(pi_flag == 1'b1)
        parity_bit <= ~^pi_data; // 奇校验:数据位异或后取反

//tx:输出数据在满足rs422协议(起始位为0,停止位为1)的情况下一位一位输出
always@(posedge sys_clk)
        if(sys_rst_n == 1'b1)
            tx <= 1'b1; //空闲状态时为高电平
        else    if(bit_flag == 1'b1)
            case(bit_cnt)
                0       : tx <= 1'b0;    // 起始位
                1       : tx <= pi_data[0];
                2       : tx <= pi_data[1];
                3       : tx <= pi_data[2];
                4       : tx <= pi_data[3];
                5       : tx <= pi_data[4];
                6       : tx <= pi_data[5];
                7       : tx <= pi_data[6];
                8       : tx <= pi_data[7];
                9       : tx <= parity_bit; // 奇校验位
                10      : tx <= 1'b1;    // 停止位
                default : tx <= 1'b1;
            endcase

endmodule

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值