上一节中,我们遵循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

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

  1. 串口应用:遵循uart协议发送N位数据(状态优化为3个,适用任意长度的输入数据,取寄存器中的一段(用变量作为边界))

    上一节中成功实现了发送多个字节的数据.把需要发送的数据分成多段遵循uart协议的数据依次发送.上一节是使用状态机实现的,每发一次设定为一个状态,所以需要发送的数据越多,状态的个数越多,代码越长,因而冗 ...

  2. 基于STM32之UART串口通信协议(二)发送

    一.前言 1.简介 在上一篇UART详解中,已经有了关于UART的详细介绍了,也有关于如何使用STM32CubeMX来配置UART的操作了,而在该篇博客,主要会讲解一下如何实现UART串口的发送功能. ...

  3. C# 串口操作系列(3) -- 协议篇,二进制协议数据解析

    原文地址:http://blog.csdn.net/wuyazhe/article/details/5627253 我们的串口程序,除了通用的,进行串口监听收发的简单工具,大多都和下位机有关,这就需要 ...

  4. 基于FPGA的UART协议实现(通过线性序列机)

    //////////////////2018/10/15 更新源代码: 实现uart这东西其实早就写了,不过不太完善,对于一个完美主义者来说,必须解决掉它. 1.什么是UART?        通用异 ...

  5. UART协议详解

    UART(Universal Asynchronous Receiver/Transmitter)是一种异步全双工串行通信协议,由Tx和Rx两根数据线组成,因为没有参考时钟信号,所以通信的双方必须约定 ...

  6. stm32实现DMX512协议发送与接收(非标)

    最近把玩了一下485,期间也接触了dmx512通信协议,该协议主要用于各种舞台灯光的控制当中,进而实现各种光效以及色彩变化.根据标准的512协议,其物理连接与传统上的RS485是完全一致的,并没有什么 ...

  7. C#使用SMTP协议发送验证码到QQ邮箱

    C#使用SMTP协议发送验证码到QQ邮箱 在程序设计中,发送验证码是常见的一个功能,用户在注册账号时或忘记密码后,通常需要发送验证码到手机短信或邮箱来验证身份,此篇博客介绍在C#中如何使用SMTP协议 ...

  8. stm32f103c8串口USART1发送多一字节

    用UART写了一段Bootloader代码,遇到了一个很奇怪的现象. 代码如下:简单介绍一下就是先统一配置MCU的IO端口,然后配置串口参数,然后循环发送‘0’和'\r’.16进制是0x30 0x0d ...

  9. Android(java)学习笔记80:UDP协议发送数据

    UDP协议发送数据:我们总是先运行接收端,再运行发送端发送端: 1 package cn.itcast_02; import java.io.IOException; import java.net. ...

随机推荐

  1. Centos 7.4_64位系统安装指南

    小土豆Linux学习随笔 -- 清听凌雪慕忆 目录 1. 范围 1.1标识 1.2 文档概述 2. 安装环境 3. 安装步骤 4. 注意事项 1. 范围 1.1标识 CentOS 7.4 64位系统安 ...

  2. 使用本地自签名证书为 React 项目启用 https 支持

    简介 现在是大前端的时代,我们在本地开发 React 项目非常方便.这不是本文的重点,今天要分享一个话题是,如何为这些本地的项目,添加 https 的支持.为什么要考虑这个问题呢?主要有几个原因 如果 ...

  3. 在SpringBoot中使用logback优化异常堆栈的输出

    一.背景 在我们在编写程序的过程中,无法保证自己的代码不抛出异常.当我们抛出异常的时候,通常会将整个异常堆栈的信息使用日志记录下来.通常一整个异常堆栈的信息是比较多的,而且存在一些没用的信息.那么我们 ...

  4. 好客租房30-事件绑定this指向(箭头函数)

    1箭头函数 利用箭头函数自身不绑定this的特点 //导入react     import React from 'react'           import ReactDOM from 'rea ...

  5. 148_赠送300家门店260亿销售额的零售企业Power BI实战示例数据

    焦棚子的文章目录 一背景 2022年即将到来之际,笔者准备在Power BI中做一个实战专题,作为实战专题最基础的就是demo数据,于是我们赠送大家一个300家门店,260亿+销售额,360万行+的零 ...

  6. 一条更新SQL的内部执行及日志模块

    一条更新SQL的内部执行 学习MySQL实战45讲,非常推荐学 还是老图: 上文复习 在执行查询语句的时候,会执行连接器(总要连上才能搞事情),然后去查询缓存(MySQL8+删除了),有数据返回,没数 ...

  7. MySQL 事务常见面试题总结 | JavaGuide 审核中

    <Java 面试指北>来啦!这是一份教你如何更高效地准备面试的小册,涵盖常见八股文(系统设计.常见框架.分布式.高并发 ......).优质面经等内容. 本文原发于 MySQL知识点&am ...

  8. 【可视化分析案例】用python分析B站Top100排行榜数据

    一.数据源 之前,我分享过一期爬虫,用python爬取Top100排行榜: 最终数据结果,是这样的: 在此数据基础上,做python可视化分析. 二.数据读取 首先,读取数据源: # 读取csv数据 ...

  9. AT32F415 修改时钟和晶振方法(原创)

    1. 简介 我们几乎是国内第一批使用AT32F415芯片的客户,那个时候芯片还没涨价,岁月一切静好.使用AT32F415 做了几个小产品,也在持续出货.后来大家都知道,涨价缺货愈演愈烈.好在我们提前囤 ...

  10. 【clickhouse专栏】单机版的安装与验证

    <clickhouse专栏>第三节内容,先安装一个单机版的clickhouse,是后续学习多副本或者分布式集群安装的基础内容.但基本的clickhouse是不依赖于zookeeper的,只 ...