串口应用:遵循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. ...
随机推荐
- [还不会搭建博客吗?]centos7系统部署hexo博客新手入门-进阶,看这一篇就够了
@ 目录 *本文说明 请大家务必查看 前言 首先介绍一下主角:Hexo 什么是 Hexo? 环境准备 详细版 入门:搭建步骤 安装git: 安装node: 安装Hexo: 进阶:hexo基本操作 发布 ...
- Prometheus监控实战应用
目录 1.Prometheus的主要特征及概述 2.普罗米修斯原理架构图 3.下载安装启动Prometheus 4.web客户端操作 5.默认图像 6.目录解释 7.配置文件 8.监控指标 8.1.监 ...
- 【Linux 网络编程】生动讲解 Reactor 模式与 Proactor 模式
五种 I/O 模型 先花费点时间了解这几种 I/O 模型,有助于后面的理解. 阻塞 I/O 与非阻塞 I/O 阻塞和非阻塞的概念能应用于所有的文件描述符,而不仅仅是 socket.我们称阻塞的文件描述 ...
- spring 拦截器流程 HandlerInterceptor AsyncHandlerInterceptor HandlerInterceptorAdapter
HandlerInterceptor源码 3种方法: preHandle:拦截于请求刚进入时,进行判断,需要boolean返回值,如果返回true将继续执行,如果返回false,将不进行执行.一般用于 ...
- Python-100-Days-master-第二周笔记
python100day学习第二周 # 通过enumerate函数处理列表之后再遍历可以同时获得元素索引和值 list1 = [1, 3, 5, 7, 100] for index, elem in ...
- 2022管家婆工贸版ERP T3 V22.0工厂管理软件单机网络版无限用户免狗软件可定制
管家婆工贸版是一款针对国内中小加工企业开发的管理软件,软件以财务管理为核心,集采购.销售.存货.生产.工资.固定资产.账务管理等模块于一体,对企业的信息进行监控,实现对企业物流.资金流.信息流和生产成 ...
- 分布式下Session一致性架构举例
一.问题及方案 见这篇文章:分布式下Session一致性问题 二.分布式环境搭建: 系统环境 [root@centos7 ~]# cat /etc/redhat-release CentOS Linu ...
- 关于『进击的Markdown』:第二弹
关于『进击的Markdown』:第二弹 建议缩放90%食用 众里寻他千百度,蓦然回首,Markdown却在灯火灿烂处 MarkdownYYDS! 各位早上好! 我果然鸽稿了 Markdown 语法 ...
- 微服务效率工具 goctl 深度解析(上)
前言 本文根据 安前松 的视频分享整理而来,视频回放地址如下: https://www.bilibili.com/video/BV1Hr4y1x7Ne goctl 的由来 1. goctl 的诞生 g ...
- sort是不稳定排序
今天才知道sort是不稳定的,WA了一个点. sort()排序不是稳定排序,sort是主要用到了快速排序(平均时间复杂度为O(nlogn)),还结合了插入排序(时间复杂度为O(n2))和堆排序(时间复 ...