串口应用:遵循uart协议,发送多个字节的数据(状态机)
上一节中,我们遵循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协议,发送多个字节的数据(状态机)的更多相关文章
- 串口应用:遵循uart协议发送N位数据(状态优化为3个,适用任意长度的输入数据,取寄存器中的一段(用变量作为边界))
上一节中成功实现了发送多个字节的数据.把需要发送的数据分成多段遵循uart协议的数据依次发送.上一节是使用状态机实现的,每发一次设定为一个状态,所以需要发送的数据越多,状态的个数越多,代码越长,因而冗 ...
- 基于STM32之UART串口通信协议(二)发送
一.前言 1.简介 在上一篇UART详解中,已经有了关于UART的详细介绍了,也有关于如何使用STM32CubeMX来配置UART的操作了,而在该篇博客,主要会讲解一下如何实现UART串口的发送功能. ...
- C# 串口操作系列(3) -- 协议篇,二进制协议数据解析
原文地址:http://blog.csdn.net/wuyazhe/article/details/5627253 我们的串口程序,除了通用的,进行串口监听收发的简单工具,大多都和下位机有关,这就需要 ...
- 基于FPGA的UART协议实现(通过线性序列机)
//////////////////2018/10/15 更新源代码: 实现uart这东西其实早就写了,不过不太完善,对于一个完美主义者来说,必须解决掉它. 1.什么是UART? 通用异 ...
- UART协议详解
UART(Universal Asynchronous Receiver/Transmitter)是一种异步全双工串行通信协议,由Tx和Rx两根数据线组成,因为没有参考时钟信号,所以通信的双方必须约定 ...
- stm32实现DMX512协议发送与接收(非标)
最近把玩了一下485,期间也接触了dmx512通信协议,该协议主要用于各种舞台灯光的控制当中,进而实现各种光效以及色彩变化.根据标准的512协议,其物理连接与传统上的RS485是完全一致的,并没有什么 ...
- C#使用SMTP协议发送验证码到QQ邮箱
C#使用SMTP协议发送验证码到QQ邮箱 在程序设计中,发送验证码是常见的一个功能,用户在注册账号时或忘记密码后,通常需要发送验证码到手机短信或邮箱来验证身份,此篇博客介绍在C#中如何使用SMTP协议 ...
- stm32f103c8串口USART1发送多一字节
用UART写了一段Bootloader代码,遇到了一个很奇怪的现象. 代码如下:简单介绍一下就是先统一配置MCU的IO端口,然后配置串口参数,然后循环发送‘0’和'\r’.16进制是0x30 0x0d ...
- Android(java)学习笔记80:UDP协议发送数据
UDP协议发送数据:我们总是先运行接收端,再运行发送端发送端: 1 package cn.itcast_02; import java.io.IOException; import java.net. ...
随机推荐
- Hadoop(四)C#操作Hbase
Hbase Hbase是一种NoSql模式的数据库,采用了列式存储.而采用了列存储天然具备以下优势: 可只查涉及的列,且列可作为索引,相对高效 针对某一列的聚合及其方便 同一列的数据类型一致,方便压缩 ...
- systemd进程管理工具实战教程
关注「开源Linux」,选择"设为星标" 回复「学习」,有我为您特别筛选的学习资料~ 1. systemd介绍 systemd是目前Linux系统上主要的系统守护进程管理工具,由于 ...
- Azure DevOps (十二) 通过Azure Devops部署一个SpringBoot应用
文章配套视频专栏: https://space.bilibili.com/38649342/channel/seriesdetail?sid=2267536 视频正在努力更新. 上一篇文章中,我们通过 ...
- p2p-tunnel 打洞内网穿透系列(三)TCP转发访问内网web服务
系列文章 p2p-tunnel 打洞内网穿透系列(一)客户端配置及打洞 p2p-tunnel 打洞内网穿透系列(二)TCP转发访问远程共享文件夹 p2p-tunnel 打洞内网穿透系列(三)TCP转发 ...
- DOM操作标签,事件绑定,jQuery框架
DOM操作标签 ''' 在起变量名的时候 如果该变量指向的是一个标签 那么建议使用 xxxEle eg:aEle\pEle\divEle\spanEle ''' 基本使用 动态创建一个标签 var 变 ...
- 【HarmonyOS学习笔记】Slider组件实现图形可调旋转
哈喽大家好我是厚脸皮的小威 之前刚刚用华为的IDE跑通"HELLO,WORLD" 趁热又想去试试看跑一下基于TS拓展API接口的Slider组件,去实现图片的放大和缩小 凭借着大学 ...
- 力扣算法:125-验证回文串,131-分割回文串---js
LC 125-验证回文串 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写. 说明:本题中,我们将空字符串定义为有效的回文串. 注:回文串是正着读和反着读都一样的字符串. ...
- 学习Java的第十六天——随机数
学习内容:随机数 1.GetEvenNum()方法 实例代码: package 数字处理类; public class MathRondom {public static int GetEvenNum ...
- Vue出现Component template should ...
当运行vue出现错误Component template should contain exactly one root element. If you ...的时候,我们只需要将<templa ...
- STC8H开发(十一): GPIO单线驱动多个DS18B20数字温度计
目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) ST ...