串口应用:遵循uart协议,发送多个字节的数据(状态机)

2023-05-16

  上一节中,我们遵循uart协议,它发送一次只能发送6/7/8位数据,我们不能随意更改位数(虽然在代码上可行),不然就不遵循uart协议了,会造成接收端无法接收。

  在现实生活中,我们有时候要发的数据不止8位,这时候就得多次发送了。分多段发送,就是说发送一次数据的时间里发送系统有多个状态,这便是状态机。即有限状态自动机,通常体现为一张流程图。一般包含state(状态),event(事件),action(动作),transition(转换)四个要素。

如在此情景下,有以下几个状态:

像这种有多个状态的情景,我们可以设置状态变量state,使能端enable,结束位tx_done(一般在底层)来控制状态的转换。

所以关键的点在于,把问题逻辑抽象化。最好是画出一个流程图来,大部分问题便迎刃而解了。

注意:

1.仿真时给的脉冲20ns可能会使判断条件无效,设置为21ns即可。或者给个200ns的延迟后再给20ns的脉冲。

2.通过观察其他变量的波形,来设置tx_done变化的判定条件,从而优化了tx——done的持续时间。

3.可以加入一个signal信号来控制仿真合适结束(通过@(negedge signal))

4.隐式例化:例化时不改变端口的数量,顺序,便是隐式例化,只需要定义端口的reg类型即可。

不足:

1.状态太多,代码很长。

2.适应范围不广。

思考:

1.如何优化状态机,做到只用3个状态就能实现上述功能。

2.优化代码使得可以十分简单的修改代码从而达到发送任意字节的数据的功能(字节有上限)。


module uart_4_nbyte(//用ifelse的方法传送五个字节的数据
    clk,
    reset,
    data40,
    send_pulse,
    uart_tx,
    sign
    );
    input clk ;
    input reset ;
    input send_pulse ;
    input [39:0]data40 ;
    output uart_tx ;
    output sign ;
    
    reg send_en ;
    wire tx_done ;
    reg [7:0]data ;
    
    uart_1_1    uart_4_nbyte1(  //设计输入
    .clk(clk),//时钟
    .reset(reset),//复位
    .data(data),//数据
    .send_en(send_en),//使能
    .baud_rate(3'd5),//波特率
    .uart_tx(uart_tx),//串口输出
    .tx_done(tx_done)//结束信号
);

    reg [2:0]state ;
    reg sign ;

    
    always@(posedge clk or negedge reset)
    if (!reset)
        begin
        state <= 1'b0 ;
        sign <= 1'b0 ;
        data <= 0 ;
        end
     else if(send_pulse == 1'b1)
        begin
        state <= 1'b1 ;
        send_en <= 1'b1 ;
        sign <= 1 ;
        end
    else if (state == 1 )
        begin
        if(!tx_done)
            begin
            data <= data40[7:0] ;
            send_en <= 1 ;
            end
        else if(tx_done)
            begin
            state <= 2 ;
            send_en <= 0 ;
            end
        end     
    else if (state == 2 )
        begin
        if(!tx_done)
            begin
            data <= data40[15:8] ;
            send_en <= 1 ;
            end
        else if(tx_done)
            begin
            state <= 3 ;
            send_en <= 0 ;
            end
        end    
    else if (state == 3 )
        begin
        if(!tx_done)
            begin
            data <= data40[23:16] ;
            send_en <= 1 ;
            end
        else if(tx_done)
            begin
            state <= 4 ;
            send_en <= 0 ;
            end 
        end             
    else if (state == 4 )
        begin
        if(!tx_done)
            begin
            data <= data40[31:24] ;
            send_en <= 1 ;
            end
        else if(tx_done)
            begin
            state <= 5 ;
            send_en <= 0 ;
            end
        end    
    else if (state == 5 )
        begin
        if(!tx_done)
            begin
            data <= data40[39:32] ;
            send_en <= 1 ;
            end
        else if(tx_done)
            begin
            state <= 6 ;
            send_en <= 0 ;
            end
        end    
    else if (state == 6 )               
        begin
        state <= 1'b0 ;
        sign <= 1'b0 ;
        end

endmodule
  

module uart_1_1(  //设计输入
    clk,//时钟
    reset,//复位
    data,//数据
    send_en,//使能
    baud_rate,//波特率
    uart_tx,//串口输出
    tx_done//结束信号
);
    input clk;
    input reset;
    input [7:0]data;
    input send_en;
    input [2:0]baud_rate;
    output reg uart_tx;
    output reg tx_done;
    
     reg [17:0]bit_tim;
    
    //设计逻辑
    //把波特率转化为一位的持续时间  //单位时间内通过信道传输的码元数称为码元传输速率,即波特率,码元/s,一个码元可能由多个位组成。而比特率即 ‘位/s’
    always@(baud_rate)  //在这里一个 码元由一位组成,所以波特率=比特率
        begin
            case(baud_rate)         //常见的串口传输波特率
            3'd0 : bit_tim = 1000000000/300/20 ; //波特率为300
            3'd1 : bit_tim = 1000000000/1200/20 ; //波特率为1200
            3'd2 : bit_tim = 1000000000/2400/20 ; //波特率为2400
            3'd3 : bit_tim = 1000000000/9600/20 ; //波特率为9600
            3'd4 : bit_tim = 1000000000/19200/20 ; //波特率为19200
            3'd5 : bit_tim = 1000000000/115200/20 ; //波特率为115200
            default bit_tim = 1000000000/9600/20 ;   //多余的寄存器位置放什么:默认速率
            endcase
        end
    
    reg [17:0]counter1 ;//用来计数每一位的持续时间
    always@(posedge clk or negedge reset)
        begin
            if(!reset)//复位清零
                counter1 <=17'b0 ;
            else if (send_en )//使能端有效,计数
                begin
                if( counter1 == bit_tim - 1'b1 )//位持续时间到达时归零
                    counter1 <= 17'b0 ;
                else
                    counter1 <= counter1 + 1'b1 ;//位持续时间没达到时继续进行
                end
            else counter1 <= 17'b0 ;            //使能端无效时,清零
        end 
    
    reg [3:0]counter2 ; //输出第几位。如果忘了考虑归零,那么计数器会出现溢出归零,在这里是加到15然后归零
    always@(posedge clk or negedge reset)
        begin
            if(!reset)//复位
                counter2 <= 4'b0 ;
            else if ( send_en )//使能端有效
                begin
                if(counter2 == 0)//消耗20ns,进入起始位。这个挺重要的,没有这个的话得消耗一位的时间进入起始位
                    counter2 <= counter2 +1'b1 ;
                else if( counter1 == bit_tim - 1'b1 )//开始进行位移
                    counter2 <= counter2 + 4'b1 ;   
                else
                    counter2 <= counter2 ;
                end
            else//使能端无效,归零,进入空闲位
                counter2 <= 4'b0 ;    
        end                

    always@(posedge clk or negedge reset)
        begin
            if(!reset)//复位
                begin
                    uart_tx <= 4'b1 ; 
                end  
            else if ( send_en )//使能端有效,输出每一位
                    case(counter2)
                        0:begin uart_tx <= 1'b1 ; end//设定第一位为空闲位。没有空闲位的话,使能端无效时,counter停留在0,不能保持输出高电平(取决于要输出的数据),不符合要求。
                        1:uart_tx <= 1'b0 ;//起始位
                        2:uart_tx <= data[0] ;
                        3:uart_tx <= data[1] ;
                        4:uart_tx <= data[2] ;
                        5:uart_tx <= data[3] ;
                        6:uart_tx <= data[4] ;
                        7:uart_tx <= data[5] ;
                        8:uart_tx <= data[6] ;
                        9:uart_tx <= data[7] ;
                        10:uart_tx <= 1'b1 ;//结束位
                        11:begin uart_tx <= 1'b1 ;  end//为了让结束位跑满,设置11,作为第11个点,定第十位长度。
                        default uart_tx <= 1'b1 ;
                    endcase
           else 
                uart_tx <= 1'b1 ;         
        end 
        
        always@(posedge clk or negedge reset)
        begin
            if(!reset)//复位清零
                tx_done <= 1'b0 ;
            else if (send_en )//使能端有效
                begin
                if( counter2 == 0 )//
                     tx_done <= 1'b0 ;
                else if (( counter2 == 10 ) && ( counter1 == bit_tim - 1'b1 ))
                     tx_done <= 1'b1 ;
                else if (tx_done == 1'b1)
                     tx_done <= 1'b0 ;     
                end
            else if (tx_done == 1'b1)
                tx_done <= 1'b0 ;
        end 
endmodule  

`timescale 1ns / 1ns
module uart_4_tb(
    );
    reg clk ;
    reg reset ;
    reg [39:0]data40 ;
    reg send_pulse ;
    wire uart_tx ;
    wire sign ;
        
    uart_4_nbyte    uart_4_sim(//隐式例化
    clk,
    reset,
    data40,
    send_pulse,
    uart_tx,
    sign
    );

    initial clk = 1;
    always #10 clk = ! clk ;
    initial begin 
    reset = 0 ;
    data40  = 40'd0 ;
    send_pulse = 1'b0 ; 
    #201 ;
    reset = 1 ;
    data40  = 40'h123456789a ;
    #200 ;
    send_pulse = 1'b1 ;
    #20;
    send_pulse = 1'b0 ; 
    @(negedge sign) ;
    data40  = 40'ha987654321 ;
    #200 ;
    send_pulse = 1'b1 ;
    #20;//设置为20ns时识别不出,会出错。设置21可以解决//多给200ns延迟后错误消失,用20ns也可以
    send_pulse = 1'b0 ; 
    @(negedge sign) ;
    #200 ;
    $stop ;
    end
    
endmodule
  

module uart_5_nbyte(//用case的方法传送五个字节的数据
    clk,
    reset,
    data40,
    send_pulse,
    uart_tx,
    sign
    );
    input clk ;
    input reset ;
    input send_pulse ;
    input [39:0]data40 ;
    output uart_tx ;
    output sign ;
    
    reg send_en ;
    wire tx_done ;
    reg [7:0]data ;
    
    uart_1_1    uart_5_nbyte1(  //设计输入
    .clk(clk),//时钟
    .reset(reset),//复位
    .data(data),//数据
    .send_en(send_en),//使能
    .baud_rate(3'd5),//波特率
    .uart_tx(uart_tx),//串口输出
    .tx_done(tx_done)//结束信号
);

    reg [2:0]state ;
    reg sign ;

    
    always@(posedge clk or negedge reset)
    if (!reset)
        begin
        state <= 1'b0 ;
        sign <= 1'b0 ;
        data <= 0 ;
        end
     else if(send_pulse == 1'b1)
        begin
        state <= 1'b1 ;
        send_en <= 1'b1 ;
        sign <= 1 ;
        end
    else case(state)
     1:
        begin
        if(!tx_done)
            begin
            data <= data40[7:0] ;
            send_en <= 1 ;
            end
        else if(tx_done)
            begin
            state <= 2 ;
            send_en <= 0 ;
            end
        end     
    2:
        begin
        if(!tx_done)
            begin
            data <= data40[15:8] ;
            send_en <= 1 ;
            end
        else if(tx_done)
            begin
            state <= 3 ;
            send_en <= 0 ;
            end
        end    
    3:
        begin
        if(!tx_done)
            begin
            data <= data40[23:16] ;
            send_en <= 1 ;
            end
        else if(tx_done)
            begin
            state <= 4 ;
            send_en <= 0 ;
            end 
        end             
    4:
        begin
        if(!tx_done)
            begin
            data <= data40[31:24] ;
            send_en <= 1 ;
            end
        else if(tx_done)
            begin
            state <= 5 ;
            send_en <= 0 ;
            end
        end    
   5:
        begin
        if(!tx_done)
            begin
            data <= data40[39:32] ;
            send_en <= 1 ;
            end
        else if(tx_done)
            begin
            state <= 6 ;
            send_en <= 0 ;
            end
        end    
    6:               
        begin
        state <= 1'b0 ;
        sign <= 1'b0 ;
        end
    endcase

endmodule  

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

串口应用:遵循uart协议,发送多个字节的数据(状态机) 的相关文章

  • Linux:简单三步,教你解决ping:www.baidu.com:未知的名称或者服务

    Linux xff1a 简单三步 xff0c 教你解决ping www baidu com 未知的名称或者服务 1 在VMware Workstation中点开编辑 xff0c 找到虚拟网络编辑器2 直接点击更改设置3 点击还原默认设置 x
  • C++:从结构体开始理解this指针

    C 43 43 xff1a 从结构体开始理解this指针 span class token macro property span class token directive keyword include span span class
  • 原来直接插入排序这么简单(附完整代码)

    原来插入排序这么简单 附完整代码 xff09 基本思想带哨兵位的插入排序二分插入排序完整代码 基本思想 做一件是之前我们总是要先知道我们做这件的核心思想 xff0c 这样会让我们做事的效率得到有效的提高 xff1b 现在我们来看看插入排序算
  • 一张图带你了解c/c++的内存分布

    c c 43 43 的内存分布 对照这些代码查看对应内存分布 xff1a span class token keyword int span globalVar span class token operator 61 span span
  • 用一个例子理解希尔排序

    用一个例子理解希尔排序 思想代码 思想 希尔排序是把序列按下标的一定增量分组 xff0c 对每组使用直接插入排序算法排序 xff1b 随着增量的逐渐减少 xff0c 每组包含的关键词越来越多 xff0c 当增量减至1时 xff0c 整个序列
  • c++ pi

    C 43 43 中表示pi的方法有两种 xff08 1 xff09 math库中利用arctan函数算出 span class token function tan span span class token punctuation spa
  • 【非数值数据的编码】西文字符和汉字的编码表示 汉字国标码、机内码详细理解

    西文字符和汉字的编码表示 西文字符概念ASCII码表特点 西文字符特点西文字符表示 xff08 常用编码为7位ASCII码 xff09 西文字符操作 汉字字符编码形式输入码字符集与汉字内码汉字的区位码汉字的国标码汉字内码 汉字的字模点阵码和
  • 修改centos7系统用户最大线程数和最大文件数限制

    修改centos7系统用户最大线程数和最大文件数限制 需要注意 xff0c 不同版本的Linux系统所对应的修改方法不同 ulimit 的作用 ulimit xff1a 显示 xff08 或设置 xff09 用户可以使用的资源的限制 xff
  • (已全部解决)ubantu18运行vins遇到的问题

    1 sudo rosdep init时报错 xff1a 打开hosts文件 sudo gedit etc hosts 在文件末尾添加 151 101 84 133 raw githubusercontent com 保存后退出再尝试 sud
  • ROS只使用思岚A1激光雷达进行slam建图

    使用思岚A1激光雷达 A1的ros功能包下载地址 xff1a https github com slamtec rplidar ros 因为只有激光雷达 xff0c 需要其做SLAM的话 xff0c 就需要有一个laser scan mat
  • STM32 四轴无人机的设计——基于HCSR04超声波模块的距离检测与警报设计

    1 系列总述 从现在开始将会进入四轴无人机的制作 xff0c 我是第一次制作四旋翼 xff0c 从前没有接触过这个方面 xff0c 手边的参考资料只有一本四轴的设计书和正点原子F405飞控的源码 xff0c 所以代码逻辑设计方面肯定有所欠缺
  • 【C++基础】inline与内联函数

    目录 引入 inline 关键字inline使用限制类中的成员函数与inline 引入 inline 关键字 为了解决一些频繁调用的小函数大量消耗栈空间 xff08 栈内存 xff09 的问题 xff0c 特别的引入了 inline 修饰符
  • 串口通信的基本原理详解

    目录 串口通信 串口通信的两种基本方式 异步数据的数据发送过程 异步通信的数据接收过程 9针串口 xff08 DB9 xff09 TTL与RS232区别 TTL RS232 xff1a 串口通信的数据格式 通讯方式 偶校验与奇校验 停止位
  • jeston nano安装Ubuntu镜像时启动遇到问题

    A start job is running for End user configuration after initial OEM installation 开始我跑了一下午 43 一晚上 xff0c 都没成功 xff0c 第二天 xf
  • cmake 常用变量、常用环境变量、常用语法总结

    一 cmake 变量引用的方式 前面我们已经提到了 使用 进行变量的引用 在 IF 等语句中 是直接使用变量名而不通过 取值 二 cmake 自定义变量的方式 主要有隐式定义和显式定义两种 隐式定义的例子 xff1a PROJECT 指令
  • Java基础篇:Iterator迭代器

    一 什么是Iterator xff1a 迭代器 Iterator 是一个对象 xff0c 它的工作是遍历并目标序列中的对象 xff0c 它提供了一种访问一个容器 container 对象中的各个元素的方法 xff0c 把访问逻辑从不同类型的
  • 2022-2-19 ros环境变量

    学习时间及标题 xff1a 2022 2 19 ros环境变量 学习内容 xff1a 1 添加环境变量 xff1a source span class token operator span span class token operato
  • EGO-Planner: An ESDF-free Gradient-based Local Planner for Quadrotors(论文学习)

    EGO规划器 xff1a 一种基于ESDF自由梯度的四转子局部规划器 摘要 ESDF地图被广泛运用在局部地图的梯度方向和大小估计之中 xff0c 但是由于我们在进行轨迹优化的过程中 xff0c 只用到了ESDF地图中很小的一部分 xff0c
  • cmake "undefined reference to"

    main函数在调用其他 c或 cpp文件的函数时 xff0c 有以下几种情况 函数名写错 没有将其他 c或 cpp文件链接到main o xff0c 导致main函数在执行时找不到需要调用的函数 的解决方法 修改CMakeLists txt
  • STM32串口详解

    实验一 xff1a 简单的利用串口接收中断回调函数实现数据的返回 关于串口调试助手 xff0c 还应知道 xff1a 发送英文字符需要用一个字符即8位 xff0c 发送汉字需要两个字符即16位 xff0c 如上图 xff0c 发送汉字 姜

随机推荐