jjzjj

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

彼方云城 2024-03-01 原文

文章目录


前言

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


一、数据帧结构


  含校验位的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状态。


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状态,等待下一个有效数据使能信号。

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,校验位为奇校验,发送后返回数据与输入数据相同,可知模块设计无误。

总结

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

有关【FPGA学习】状态机实现UART通信的更多相关文章

  1. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  2. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  3. ruby-on-rails - 跳过状态机方法的所有验证 - 2

    当我的预订模型通过rake任务在状态机上转换时,我试图找出如何跳过对ActiveRecord对象的特定实例的验证。我想在reservation.close时跳过所有验证!叫做。希望调用reservation.close!(:validate=>false)之类的东西。仅供引用,我们正在使用https://github.com/pluginaweek/state_machine用于状态机。这是我的预订模型的示例。classReservation["requested","negotiating","approved"])}state_machine:initial=>'requested

  4. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  5. ruby - 字符串文字中的转义状态作为 `String#tr` 的参数 - 2

    对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一

  6. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  7. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  8. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  9. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  10. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

随机推荐