【FPGA学习】状态机实现UART通信

这篇具有很好参考价值的文章主要介绍了【FPGA学习】状态机实现UART通信。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


前言

  在之前的文章中【FPGA学习】实例一、Cyclone IV串口通信(RS232)我们已经能够采用波形图的方法,实现9600bps的Uart通信。近期笔者在整理了状态机和计数器组合的设计方法以后,对状态机的设计又有了新的感悟和体会,所以又把经典的RS232协议拉出来当状态机的例子练手了哈哈哈。数据有效位为8bit,功能上增加了奇校验,并将波特率设置为115200bps,并借助这篇文章梳理一下状态机和计数器组合设计的思路和设计要点,文章奉上:


一、数据帧结构

【FPGA学习】状态机实现UART通信
  含校验位的RS232数据帧结构如上图所示,其中三个单元格为一个数据帧,数据有效为8bit 空闲状态下,rx信号为高电平,rx拉低一帧作为起始位。接下来按照数据位由低到高每一帧输出一位数据,发送到最高位Data[7]以后,有效数据发送完毕。
  接着PARITY为数据校验位。本文采用奇校验,即判断是否数据位和校验位的1的数量之和是否为奇数,若为奇数则满足奇校验,否则就认为数据在传输过程中出现了错误,从而将本次的8bit 数据丢弃。
  奇校验帧结束后为停止位,将rx拉高一帧作为结束信号,等待下一组数据传入。

二、接收模块

  接收模块是将接收到的串行数据转换为并行数据的模块,并同步输出一个数据有效使能信号。
  首先因为系统晶振为50MHz,串口的波特率为115200bps,因此每一帧数据所需时钟周期数为N = (1/115200)/(1/50000000) = 434,所以每一帧数据需要借助一个计数器判断当前的位数,该计数器计数到433后清零,采用组合逻辑赋值baud_max,当计数到433计满清零时拉高。
  接下来结合时序图介绍笔者的状态机部分设计思路:

2.1 状态设置

  接收模块状态机状态共包含IDLE、START、DATA、PARITY、STOP、WAIT六个状态,前五个状态望文生义即可,而WAIT状态是在校验位校验错误以及停止位没有读取到高电平时进入到的状态,意味着本次读取的8bit 数据是失效的,当在此状态下识别到停止位后直接进入IDLE状态,不进行数据的输出。

2.1 状态跳转

  状态机采用三段式的写法,比特率计数器baud_cntnext_state == DATA或者next_state == PARITY时进行循环计数(见时序图)。由于计数器慢一拍,所以进入状态DATA以后,baud_cnt从1开始计数,计数一帧即434个周期后,计数值为0,因此baud_cnt==0作为每一个状态跳转的必要条件(也保证了未开始计数时IDLE状态能在识别到起始位后直接进入到START状态)。
  START状态满足一帧时间就跳转到DATA状态,为了表征当前是数据的第几帧,引入帧计数器bit_cnt,该计数器在next_state == DATA或者next_state == PARITY时进行循环计数(见时序图),识别到帧结束信号baud_max==1以后,自加1。其他状态下,帧计数器清零。

2.2 奇校验

  由于帧计数器也慢实际数据一拍,和状态变化同步,因此判断校验位时,校验位相对于实际数据在第9帧。帧计数器慢一拍,所以当bit_cnt==8 && baud_max==1时对校验位进行判断。本文的奇数校验方式为判断parity == ~(^po_data) 是否成立,若成立,则校验位和数据位中1的个数之和为奇数(见时序图)。其中po_data为进行移位拼接后,准备并行输出的8位宽数据。
  满足校验位则进入校验位状态PARITY1帧,识别到停止位就进入到STOP状态。

【FPGA学习】状态机实现UART通信
【FPGA学习】状态机实现UART通信

2.3 数据输出

  接收模块的数据即为拼接结束以后的并行输出数据,并同步输出一个使能信号,在使能信号拉高时的数据信号才有效。因为是要把数据拼接为8位宽的数据,要保证移位8次,并且每次移位时数据正确,设置了移位标志信号shiftshift信号当波特率计数器计数到一帧的一半时state == DATA && baud_cnt == BAUD_CNT_MAX / 2拉高信号(见代码数据移位输出部分),此时数据稳定,并且在校验位前就可将八位数据移位完成,同时满足奇校验要求。又由于STOP状态只要发生错误就不会进入,保证了数据的正确性,所以选择当state==STOP时输出数据有效使能信号,并输出拼接完成的并行数据po_data。完整代码如下:

// *****************************************************************************************************************************
// ** 作者 : 彼方云城                                                  
// ** 邮箱 : zwr2059341302@163.com
// ** 博客 : https://blog.csdn.net/weixin_45451974?spm=1000.2115.3001.5343
// ** 日期 : 2022/10/10
// ** 功能 : 1、串口接收模块,接收串行数据,转换成并行数据输出
//            2、8bit数据(先接受低字节数据,再接收高字节数据),奇校验(不可在宏定义处删除奇校验)
// *****************************************************************************************************************************
module uart_rx_fsm
#(
    parameter   BAUD        =   115200   ,  //串口波特率
                CLK_FREQ    =   50_000_000, //时钟周期
                BAUD_CNT_MAX = CLK_FREQ / BAUD  //每帧数据对应的时钟周期
)
(
    //系统接口
    input   wire            sys_clk     ,
    input   wire            sys_rst_n   ,

    //输入数据
    input   wire            in_data     ,

    //输出数据
    output  reg     [7:0]   po_data     ,
    output                  po_flag     
);

//in_data_reg同步数据,in_data_reg1和in_data_reg2打两拍减小亚稳态
//in_data_reg2为稳定数据
reg     in_data_reg     ;
reg     in_data_reg1    ;
reg     in_data_reg2    ;

always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        in_data_reg <=  1'b1;
        in_data_reg1 <= 1'b1;
        in_data_reg2 <= 1'b1;
    end
    else begin
        in_data_reg  <= in_data;
        in_data_reg1 <= in_data_reg;
        in_data_reg2 <= in_data_reg1;
    end
end

//**********************状态机********************//

parameter   IDLE = 3'd0,START = 3'd1,DATA = 3'd2;
parameter   PARITY = 3'd3,WAIT = 3'd4,STOP = 3'd5;

reg [2:0]   state,next_state;

//状态机状态转换
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        state <=    IDLE;
    end
    else begin
        state <=    next_state;
    end
end

//波特率计数器及计满脉冲信号
reg     [9:0]   baud_cnt;
wire            baud_max;

always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        baud_cnt <= 10'd0;
    end
    else if(baud_cnt == BAUD_CNT_MAX - 1'b1)begin
        baud_cnt <= 10'd0;
    end
    else if(next_state==START || next_state==DATA 
            || next_state==PARITY || next_state==STOP)begin
        baud_cnt <= baud_cnt + 1'b1;
    end
end

assign baud_max = (baud_cnt == 10'd0);

//起始位及结束位标志信号
wire        start;
wire        stop;

assign start    = (in_data_reg2 == 1'b0) & baud_max;
assign stop     = (in_data_reg2 == 1'b1) & baud_max;

//bit位计数器
reg     [3:0]   bit_cnt;
wire            bit_done;//计数到8时拉高

always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        bit_cnt <= 4'd0;
    end
    else if(next_state == DATA & bit_cnt == 4'd9 & baud_max)begin
        bit_cnt <= 4'd0;
    end
    else if((next_state == DATA || next_state == PARITY)&& baud_max)begin
        bit_cnt <= bit_cnt + 1'b1;
    end
    else if(next_state == DATA)begin
        bit_cnt <= bit_cnt;
    end
    else begin
        bit_cnt <= 4'd0;
    end
end

assign bit_done = (bit_cnt == 4'd8);

//奇校验位
wire    parity;

assign  parity  =  baud_max && bit_done && in_data_reg2 == ~(^po_data);
//*******当奇校验位与数据奇数相反时数据正确

//状态机下一状态描述
always@(*)begin
    case(state)
        IDLE    :   next_state = start?START:IDLE;
        START   :   next_state = baud_max?DATA:START;
        DATA    :   next_state = (bit_done&baud_max)?(parity?PARITY:WAIT):DATA;
        PARITY  :   next_state = baud_max?(stop?STOP:WAIT):PARITY;
        WAIT    :   next_state = baud_max?(stop?STOP:WAIT):WAIT;
        STOP    :   next_state = baud_max?(start?START:IDLE):STOP;
        default :   next_state = IDLE;
    endcase
end

//数据移位标志信号
wire    shift;

assign  shift   = (next_state == DATA && baud_cnt == BAUD_CNT_MAX/2);
//当数据在波特率计数器满值一半时提取

//状态机输出及数据有效使能信号
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        po_data <= 8'd0;
    end
    else if(shift)begin
        po_data <= {in_data_reg2,po_data[7:1]};
    end
    else begin
        po_data <= po_data;
    end
end

//输出同步使能信号
assign  po_flag = (state==STOP);

endmodule

三、发送模块

  发送模块主要是将接受到的并行多位宽数据以串行形式RS232的协议发出,接下来结合时序图和状态转移图介绍发送模块状态机实现的设计思路:

3.1 状态跳转

  发送模块的状态去除了WAIT状态,同样采用一个波特率计数器对一帧对应的434个时钟周期进行计数,baud_cnt以及baud_max作为每帧的时钟计数信号和跳转信号,初始状态为IDLE,接收到pi_flag后跳转为START状态,经过一帧跳转到DATA状态,进入到DATA状态后,用计数器bit_cnt来计数数据帧数,计满8位数据后跳转到校验位状态PARITY,校验位采用parity_data = (state==PARITY)?(~(^pi_data)):1'b0;进行赋值,仅在校验位状态下将校验位数据赋值为原数据缩减异或运算的取反。一帧后进入STOP状态,输出1帧高电平。随后判断是否有输入使能,有就跳转到START状态,否则进入IDLE状态,等待下一个有效数据使能信号。
【FPGA学习】状态机实现UART通信
【FPGA学习】状态机实现UART通信

3.2 数据输出

  发送模块数据的输出采用组合逻辑输出的方式,因为延迟next_state一拍的帧计数器bit_cnt和当前状态state同步,所以可以在DATA状态下用计数器来读取并输出采集数据的由低到高的不同位,而在START状态输出低电平,STOP状态输出高电平,其他状态均为空闲状态的高电平。完整代码如下:

// *****************************************************************************************************************************
// ** 作者 : 彼方云城                                                  
// ** 邮箱 : zwr2059341302@163.com
// ** 博客 : https://blog.csdn.net/weixin_45451974?spm=1000.2115.3001.5343
// ** 日期 : 2022/10/13
// ** 功能 : 1、串口发送模块,接收并行数据,读取数据后串行输出
//            2、8bit数据(先接受低字节数据,再接收高字节数据),奇校验(不可在宏定义处删除奇校验)
//            3、波特率115200
// *****************************************************************************************************************************
module  uart_tx_fsm
#(
    parameter   BAUD        =   115200   ,  //串口波特率
                CLK_FREQ    =   50_000_000, //时钟周期
                BAUD_CNT_MAX = CLK_FREQ / BAUD  //每帧数据对应的时钟周期
)
(
    //系统接口
    input   wire            sys_clk     ,
    input   wire            sys_rst_n   ,

    //输入数据及同步信号
    input   wire    [7:0]   pi_data     ,
    input   wire            pi_flag     ,
    
    //输出串行数据
    output  reg             out_data     
);

//**********************状态机********************//

parameter   IDLE = 3'd0,START = 3'd1,DATA = 3'd2;
parameter   PARITY = 3'd3,STOP = 3'd4;

reg [2:0]   state,next_state;

//状态机状态转换
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        state <=    IDLE;
    end
    else begin
        state <=    next_state;
    end
end

//波特率计数器及计满脉冲信号
reg     [9:0]   baud_cnt;
wire            baud_max;

always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        baud_cnt <= 10'd0;
    end
    else if(baud_cnt == BAUD_CNT_MAX - 1'b1)begin
        baud_cnt <= 10'd0;
    end
    else if(next_state==START || next_state==DATA 
            || next_state==PARITY || next_state==STOP)begin
        baud_cnt <= baud_cnt + 1'b1;
    end
end

assign baud_max = (baud_cnt == 10'd0);

//起始位及结束位标志信号
wire        start;//进入start状态

assign start    =   pi_flag;

//bit位计数器
reg     [3:0]   bit_cnt;

always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        bit_cnt <= 4'd0;
    end
    else if((next_state == DATA || next_state == PARITY)&& baud_max)begin
        bit_cnt <= bit_cnt + 1'b1;
    end
    else if(next_state == DATA || next_state == PARITY)begin
        bit_cnt <= bit_cnt;
    end
    else begin
        bit_cnt <= 4'd0;
    end
end

//奇校验位数据
wire    parity;
wire    parity_data;

assign  parity  =   (bit_cnt==4'd8) && baud_max;
assign  parity_data  =  (state==PARITY)?(~(^pi_data)):1'b0;
//*******仅在PARITY状态下有效

//状态机下一状态描述
always@(*)begin
    case(state)
        IDLE    :   next_state  =   start?START:IDLE;
        START   :   next_state  =   baud_max?DATA:START;
        DATA    :   next_state  =   parity?PARITY:DATA;
        PARITY  :   next_state  =   baud_max?STOP:PARITY;
        STOP    :   next_state  =   baud_max?(start?START:IDLE):STOP;
        default :   next_state  =   IDLE;
    endcase
end

//状态机输出
always@(*)begin
    case(state)
        START   :   out_data    =   1'b0;
        DATA    :   out_data    =   pi_data[bit_cnt - 1'b1];
        PARITY  :   out_data    =   parity_data;
        STOP    :   out_data    =   1'b1;
        default :   out_data    =   1'b1;
    endcase
end

endmodule

四、顶层模块

实例化连接两个模块,代码如下:

// *****************************************************************************************************************************
// ** 作者 : 彼方云城                                                  
// ** 邮箱 : zwr2059341302@163.com
// ** 博客 : https://blog.csdn.net/weixin_45451974?spm=1000.2115.3001.5343
// ** 日期 : 2022/10/14
// ** 功能 : 1、RS232协议顶层模块
//            2、实例化接收模块和发送模块,并组成串口回环
//            3、波特率115200
// *****************************************************************************************************************************

module  rs232_fsm(

//系统接口
    input   sys_clk     ,
    input   sys_rst_n   ,
    
//输入串行数据
    input   in_data     ,

//输出串行数据
    output  out_data    
);

wire    [7:0]   po_data;
wire            po_flag;

uart_rx_fsm
#(
    .BAUD        (115200    ),  //串口波特率
    .CLK_FREQ    (50_000_000) //时钟周期
)
uart_rx_fsm_inst
(
    //系统接口
    .sys_clk     (sys_clk  ),
    .sys_rst_n   (sys_rst_n),

    //输入数据
    .in_data     (in_data),

    //输出数据
    .po_data     (po_data),
    .po_flag     (po_flag)
);

uart_tx_fsm
#(
    .BAUD        (115200    ),  //串口波特率
    .CLK_FREQ    (50_000_000) //时钟周期
)
uart_tx_fsm_inst
(
    //系统接口
    .sys_clk     (sys_clk  ),
    .sys_rst_n   (sys_rst_n),

    //输入数据及同步信号
    .pi_data     (po_data),
    .pi_flag     (po_flag),
    
    //输出串行数据
    .out_data     (out_data)
);

endmodule

  将串口接收模块和发送模块直接相连,形成一个串口回环,并上板进行验证,验证结果如下,输入数据"112256478ff",设置波特率为115200,数据位8,校验位为奇校验,发送后返回数据与输入数据相同,可知模块设计无误。
【FPGA学习】状态机实现UART通信

总结

  本文采用状态机和计数器组合的方式实现了RS232协议,在代码中笔者也采用了将时序逻辑中较为复杂的条件判断条件以组合逻辑的形式单独列出来的方式,的确使得状态机的转变条理更加清晰,也易于修改拓展了,笔者能力尚浅,还请读者多多指正!文章来源地址https://www.toymoban.com/news/detail-410701.html

到了这里,关于【FPGA学习】状态机实现UART通信的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 详解UART通信协议以及FPGA实现

      从《浅谈UART,TTL,RS-232,RS-485的区别》这篇文章,我们知道了UART是一种串行、异步、全双工的通信协议,属于协议层;传输过程一般采用RS-232,RS-485电平标准,将所需传输的数据一位接一位地传输;整体传输框架如下:   串口通信由发送端和接收端构成,两根信号线

    2024年04月28日
    浏览(27)
  • FPGA协议篇:UART通信及Verilog最易懂实现方式/通用于任何工程/带握手信号 ----UART_TX

            UART(Universal Asynchronous Receiver/Transmitter)是一种通用的 异步收发传输协议 ,用于在计算机系统和外部设备之间进行串行数据传输。UART 协议定义了数据的传输格式和通信规则,使得不同设备之间能够进行可靠的数据交换。 首先先把设计代码放到这里: UART_TX完整代

    2024年03月27日
    浏览(37)
  • 【FPGA协议篇】UART通信及其verilog实现(代码采用传参实现模块通用性,适用于快速开发)

    ​ 即通用异步收发器(Universal Asynchronous Receiver/Transmitter),是一种 串行、异步、全双工 的通信协议。特点是通信线路简单,适用于远距离通信,但传输速度慢。 数据传输速率:波特率(单位:baud,波特) 常见波特率有:1200、2400、4800、19200、38400、57600等,最常用的是9600和11520

    2024年02月05日
    浏览(33)
  • FPGA以状态机实现串口通信

    UART通信只有两根信号线,一-根是发送数据端口线叫tx ,一 根是接收数据端口线叫rx ,对于上位机来说它的tx要和对于FPGA来说的rx连接,同样上位机的rx要和FPGA的tx连接,如果是两个tx或者两个rx连接那数据就不能正常被发送出去和接收到。UART可以实现全双工,即可以同时进行

    2024年02月03日
    浏览(31)
  • FPGA入门 —— FPGA UART 串口通信

    UART 通用异步收发传输器( Universal Asynchronous Receiver/Transmitter) ,通常称作 UART。 UART 是一种通用的数据通信协议,也是异步串行通信口(串口)的总称,它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。 它包括了ch340、 RS232、

    2024年02月09日
    浏览(31)
  • 【FPGA】UART串口通信---基于FIFO

    我们在上一章完成了UART串口通信的收发模块,这一章我们将FIFO引入进来,使用FIFO进行缓存数据,来连接串口通信的收发模块 FIFO即First In First Out,是一种先进先出数据存储、缓冲器,我们知道一般的存储器是用外部的读写地址来进行读写,而FIFO这种存储器的结构并不需要外

    2023年04月14日
    浏览(70)
  • FPGA串行通信(UART,IIC,SPI)

    此篇为学习正点原子FPGA课程总结 串行/并行通信 串行通信即收发双方通过单根线进行数据传输,发送方有并转串逻辑,接收方有串转并逻辑。优点是占用IO少,成本低,缺点是速率低。 并行通信一次用多根数据线传输。优点是速度快,缺点是占用IO多,成本高。 单工/半双工

    2024年02月04日
    浏览(33)
  • FPGA与STM32之间的UART通信实验

    目录 1.UART串口介绍 2.实验任务 3.FPGA代码 4.STM32代码 5.总结         UART是一种采用异步串行方式的通用异步收发传输器,在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。         UART串口通信需要两根信号线来实现,

    2024年02月13日
    浏览(31)
  • FPGA 串口通信(uart)初探篇(含源代码)

    一、UART 串口通信,说实话是日常生活中很常见的一种9针型的主机和显示屏之间的通信模式。本次博客,只为自己复盘相关知识,初学者,错误较多,请多指教。所以本文参考(一)FPGA之串口通信(UART)_fpga uart-CSDN博客 而编著的。 FPGA是一种可编程的逻辑器件,用于各种数字电

    2024年04月14日
    浏览(25)
  • (五)零基础学懂FPGA中的串口通信(UART)

    此篇为专栏 《FPGA学习笔记》 的第五篇,记录我的学习FPGA的一些开发过程和心得感悟,刚接触FPGA的朋友们可以先去此专栏置顶 《FPGA零基础入门学习路线》来做最基础的扫盲。 本篇内容基于笔者实际开发过程和正点原子资料撰写,将会详细讲解此FPGA实验的全流程, 诚挚 地

    2024年02月04日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包