串口应用:遵循uart协议发送N位数据(状态优化为3个,适用任意长度的输入数据,取寄存器中的一段(用变量作为边界))
上一节中成功实现了发送多个字节的数据。把需要发送的数据分成多段遵循uart协议的数据依次发送。上一节是使用状态机实现的,每发一次设定为一个状态,所以需要发送的数据越多,状态的个数越多,代码越长,因而冗长且适应范围不广 。
在这里,我通过优化代码,实现了把发送状态固定为3个,并且能适用任意长度的输入数据的功能。只需要修改一个参数即可实现。
学习:
1.error:cannot index into non-array type wire for 'dataN'
出现这个错误是因为dataN没有定义长度,添加定义【】即可。
2.想要修改/取出一个大的寄存器中的某几位,如要取dataN[10:18],我想用变量limits,limitx来作为上下限,这样子能实现取出不同段的值。按照这种想法我写成:data <= dataN [limitx:limits],会报错 : error:range must be bounded by constant expressions要求用常数来限制边界。
解决方法:写成 data <= dataN[(limitx)+:8] 意思是从limitx开始,向上取8位,limit可以是变量。
优化:我把下限写成寄存器变量limitx,在data赋值语句的begin-end内,即:
begin
limitx <= x *8 ;
data <= dataN[(limitx)+:8] ;
x <= x +1 ;
end
综合出来会有个limitx寄存器,data寄存器,一个clk上升沿到来,他们同时变化,limit会取上一个x的输出x0,data会取上一个limit的输出limit0,这样子data就延后了一个limit的输出;而我的目的是limitx变化后,再让data取变化后的limit1计算,这个代码无法实现这个目的。
解决:直接去掉limitx,写成
begin
data <= dataN[(x *8)+:8] ;
x <= x +1 ;
end
这样子就少了一个寄存器,节省资源,而且可以实现data取到前一个x的限定范围,不会出错。
观察波形可以发现这个问题,所以后面写代码时,能用计算式代替的尽量不要多设置一个寄存器。特别是运算都在同一个begin-end里面的情况。
3.底层的输出在顶层不可以被赋值,要被定义成wire。输入可以定义成reg,赋值。
4.always中的复位信号reset中应该复位的变量应包含所有本次always用到的变量,不复位的话仿真时会出现未知状态X。
5.再次确认,非阻塞赋值无论在begin-end中的多个语句顺序如何,综合出来的RTL级电路图是不变的。
6.用parameter定义一个常数,那么在后面他是不能被重写的。不过例化时可以重新定义一次。
7.从串口应用的学习中,要懂得,开始位,结束位的意义。这两个位可以作为指示与标志,为发送系统增添功能,改变适应范围。也要注意使能端的意义,可以让系统分为工作和空闲两种状态。也要懂得状态机的使用,由多状态固定到指定状态的优化。最最重要的是,通过观察仿真波形来修改某个信号的判断条件,从而达到优化/修正的目的。
`timescale 1ns / 1ns
module uart_6_tb(
);
reg clk ;
reg reset ;
reg [79:0]dataN ;
reg send_pulse ;
wire uart_tx ;
wire sign ; uart_6_optimization uart_6_sim(//隐式例化
clk,
reset,
send_pulse,
dataN,
sign,
uart_tx
); initial clk = 1;
always #10 clk = ! clk ;
initial begin
reset = 0 ;
dataN = 40'd0 ;
send_pulse = 1'b0 ;
#201 ;
reset = 1 ;
dataN = 80'h123456789 ;
#200 ;
send_pulse = 1'b1 ;
#20;
send_pulse = 1'b0 ;
@(negedge sign) ;
dataN = 80'ha98765432 ;
#200 ;
send_pulse = 1'b1 ;
#20;//设置为20ns时识别不出,会出错。设置21可以解决//多给200ns延迟后错误消失,用20ns也可以
send_pulse = 1'b0 ;
@(negedge sign) ;
#200 ;
$stop ;
end endmodule
module uart_6_optimization(//实现优化的目标,输入N位的数据,利用固定的三状态实现。
clk ,//想要改变输入的位数,只需要修改dataN的位数定义和parameter N 即可
reset ,
send_pulse ,
dataN ,
sign ,
uart_tx
);
input clk ;
input reset ;
input send_pulse ;
input [79:0]dataN ;
output sign ;
output uart_tx ; reg [7:0]data ;
reg send_en ;
wire baud_rate ;
wire tx_done ;
reg sign ;
reg tx_done_ctrl ; uart_1_2 uart_6_opti( //设计输入
clk,//时钟
reset,//复位
data,//数据
send_en,//使能
tx_done_ctrl,
baud_rate,//波特率
uart_tx,//串口输出
tx_done//结束信号
);
assign baud_rate = 3'd5 ; reg [1:0]state ;
reg [3:0]x ;
parameter N = 37 ;//输入的位数 always@(posedge clk or negedge reset)
if (!reset )
begin
data <= 8'd0 ;
send_en <= 1'b0 ;
state <= 2'd0 ;
sign <= 1'b0 ;
tx_done_ctrl <= 1'b0 ;
x <= 4'd0 ;
end
else if( send_pulse == 1'd1 )
begin
state <= 2'd1 ;
x <= 4'd0 ;
tx_done_ctrl <= 1'b1 ;
sign <= 1'b1 ;
end
else if(tx_done_ctrl == 1'b1 )
tx_done_ctrl <= 1'b0 ;
else if (( state == 2'd1 ) && ( (x) * 8 < N ) && (tx_done == 1'b1 ))
begin
send_en <= 1'b1 ;
x <= x + 1'b1 ;//说明在begin end里面,非阻塞赋值综合出来的还是对变量的综合设计寄存器是有先后顺序的,综合后对一个时钟沿到来,data先变,x再变。emm,也不是说谁先变,是时钟对寄存器同时作用,然后寄存器同时变化,只不过data根据x上一次的输出而变化。
data <= dataN[( x * 8 )+: 8 ] ;//确实是先取了data,然后x再自加1.只不过一开始我多设置了一个寄存器limit,使得data要延后一个 周期才赋值。逻辑出错。波形不对 end
else if (( (x) * 8 >= N )&& (tx_done == 1'b1 ))
begin
sign <= 1'b0 ;
state <= 1'b0 ;
data <= 8'd0 ;
end endmodule
module uart_1_2( //设计输入
clk,//时钟
reset,//复位
data,//数据
send_en,//使能
tx_done_ctrl,
baud_rate,//波特率
uart_tx,//串口输出
tx_done//结束信号
);
input clk;
input reset;
input [7:0]data;
input send_en;
input [2:0]baud_rate;
input tx_done_ctrl ;
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 if(counter2 == 4'd11)
counter2 <= 4'd0 ;
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 ;
else if (tx_done_ctrl == 1'b1)
tx_done <= 1'b1 ;
end
else if (tx_done_ctrl == 1'b1)
tx_done <= 1'b1 ;
else if (tx_done == 1'b1)
tx_done <= 1'b0 ;
end
endmodule
串口应用:遵循uart协议发送N位数据(状态优化为3个,适用任意长度的输入数据,取寄存器中的一段(用变量作为边界))的更多相关文章
- 串口应用:遵循uart协议,发送多个字节的数据(状态机)
上一节中,我们遵循uart协议,它发送一次只能发送6/7/8位数据,我们不能随意更改位数(虽然在代码上可行),不然就不遵循uart协议了,会造成接收端无法接收. 在现实生活中,我们有时候要发的数据不止 ...
- java-TCP协议发送和接收数据
TCP协议接收数据的步骤: A:创建接收数据的Socket对象 创建对象的时候要指定端口 B:监听客户端连接 等待客户端连接 C:获取Socket对象的输入流(字节流) D:读数据,并显示在控制台 E ...
- java实现http协议发送和接收数据
public void sendMessage() throws Exception { System.out.println("调用servlet开始=================&q ...
- IIC、SPI、UART协议总结
IIC 特点 1.Inter-Integrated Circuit,内部集成总线,半双工 2.短距离传输,有应答,速度较慢 3.SDA双向数据线,SCL时钟线 4.可以挂载多个设备,IIC设备有固化地 ...
- Java基础知识强化之网络编程笔记06:TCP之TCP协议发送数据 和 接收数据
1. TCP协议发送数据 和 接收数据 TCP协议接收数据:• 创建接收端的Socket对象• 监听客户端连接.返回一个对应的Socket对象• 获取输入流,读取数据显示在控制台• 释放资源 TCP协 ...
- python调用Moxa PCOMM Lite通过串口Ymodem协议发送文件
本文采用python 2.7编写. 经过长期搜寻,终于找到了Moxa PCOMM Lite.调用PCOMM.DLL可以非常方便的通过串口的Xmodem.Ymodem.Zmodem等协议传输文件,而无需 ...
- 基于STM32之UART串口通信协议(二)发送
一.前言 1.简介 在上一篇UART详解中,已经有了关于UART的详细介绍了,也有关于如何使用STM32CubeMX来配置UART的操作了,而在该篇博客,主要会讲解一下如何实现UART串口的发送功能. ...
- 基于FPGA的UART协议实现(通过线性序列机)
//////////////////2018/10/15 更新源代码: 实现uart这东西其实早就写了,不过不太完善,对于一个完美主义者来说,必须解决掉它. 1.什么是UART? 通用异 ...
- UART协议详解
UART(Universal Asynchronous Receiver/Transmitter)是一种异步全双工串行通信协议,由Tx和Rx两根数据线组成,因为没有参考时钟信号,所以通信的双方必须约定 ...
随机推荐
- 在Ubuntu安装eclipse环境
下载准备 1安装jdk,笔者安装的是jdk-8u121-linux-x64 2安装eclipse,下载地址:http://www.eclipse.org/downloads/packages/ecli ...
- Linux-文件查找-打包压缩-tar
1.文件查找工具locate,find 1.1 locate locate 查询系统上预建的文件索引数据库 /var/lib/mlocate/mlocate.db 索引的构建是在系统较为空闲时自动进 ...
- web安全之信息收集篇
信息收集 1.网络信息 网络信息就包括网站的厂商.运营商,网站的外网出口.后台.OA. 2.域名信息 通过域名可以查洵网站的所有人.注册商.邮箱等信息 --->Whois 第三方查询,查询子域网 ...
- NLP教程(5) - 语言模型、RNN、GRU与LSTM
作者:韩信子@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/36 本文地址:http://www.showmeai.tech/article-det ...
- Promise与async/await与Generator
Promise是什么: Promise是异步微任务(process.nextTick.Promise.then() catch() finally()等),用于解决异步多层嵌套回调的问题(回调地狱-- ...
- linux下nginx软件的学习
参考博客 1.nginx是什么 nginx是一个开源的,支持高性能,高并发的web服务和代理服务软件.它是开源的软件. nginx比它大哥apache性能改进许多,nginx占用的系统资源更少,支持更 ...
- ASP.NET Core + SaasKit + PostgreSQL + Citus 的多租户应用程序架构示例
在 确定分布策略 中, 我们讨论了在多租户用例中使用 Citus 所需的与框架无关的数据库更改. 当前部分研究如何构建与 Citus 存储后端一起使用的多租户 ASP.NET 应用程序. http:/ ...
- MyBatis 结果映射总结
前言 结果映射指的是将数据表中的字段与实体类中的属性关联起来,这样 MyBatis 就可以根据查询到的数据来填充实体对象的属性,帮助我们完成赋值操作.其实 MyBatis 的官方文档对映射规则的讲解还 ...
- 动态调试JS脚本文件:(JS源映射 - sourceURL)与 debugger
我们在进行js调试时经常会对js进行调试,chrome 对js提示对支持非常友好,只需要F12就可以打开chrome的调试器 在sources里面就是页面请求后加载的一些资源文件,我们可以找到我们的j ...
- ftp多文件压缩下载
@GetMapping(value = "/find") public String findfile(String filePath, String fileNames, Htt ...