上一节中成功实现了发送多个字节的数据。把需要发送的数据分成多段遵循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个,适用任意长度的输入数据,取寄存器中的一段(用变量作为边界))的更多相关文章

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

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

  2. java-TCP协议发送和接收数据

    TCP协议接收数据的步骤: A:创建接收数据的Socket对象 创建对象的时候要指定端口 B:监听客户端连接 等待客户端连接 C:获取Socket对象的输入流(字节流) D:读数据,并显示在控制台 E ...

  3. java实现http协议发送和接收数据

    public void sendMessage() throws Exception { System.out.println("调用servlet开始=================&q ...

  4. IIC、SPI、UART协议总结

    IIC 特点 1.Inter-Integrated Circuit,内部集成总线,半双工 2.短距离传输,有应答,速度较慢 3.SDA双向数据线,SCL时钟线 4.可以挂载多个设备,IIC设备有固化地 ...

  5. Java基础知识强化之网络编程笔记06:TCP之TCP协议发送数据 和 接收数据

    1. TCP协议发送数据 和 接收数据 TCP协议接收数据:• 创建接收端的Socket对象• 监听客户端连接.返回一个对应的Socket对象• 获取输入流,读取数据显示在控制台• 释放资源 TCP协 ...

  6. python调用Moxa PCOMM Lite通过串口Ymodem协议发送文件

    本文采用python 2.7编写. 经过长期搜寻,终于找到了Moxa PCOMM Lite.调用PCOMM.DLL可以非常方便的通过串口的Xmodem.Ymodem.Zmodem等协议传输文件,而无需 ...

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

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

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

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

  9. UART协议详解

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

随机推荐

  1. SpringData JPA接口总结

    1 JPA 1.1 整体概念 JPA:Java Persistence API,就是java持久化api,是SUN公司推出的一套基于ORM的规范. ORM呢:Object-Relational Map ...

  2. [笔记] Slope Trick:解决一类凸代价函数的DP优化问题

    原理 当序列 DP 的转移代价函数满足 连续: 凸函数: 分段线性函数. 时,可以通过记录分段函数的最右一段 \(f_r(x)\) 以及其分段点 \(L\) 实现快速维护代价的效果. 如:$ f(x) ...

  3. MySQL8新增降序索引

    MySQL8新增降序索引 桃花坞里桃花庵,桃花庵里桃花仙.桃花仙人种桃树,又摘桃花卖酒钱. 一.MySQL5.7 降序索引 MySQL在语法上很早就已经支持降序索引,但实际上创建的却仍然是升序索引,如 ...

  4. 深入C++06:深入掌握OOP最强大的机制

    深入掌握OOP最强大的机制 1. 继承的基本意义 类与类之间的关系:①组合:a part of ... 一部分的关系:②继承: a kind of ... 属于同一种的关系: 继承的本质:a. 代码的 ...

  5. 109_Power Pivot客户ABC(帕累托)分析度量值写法(非计算列)

    博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 1.背景 客户ABC分析,一般的套路是在计算列中把客户ABC分类,便于后续维度使用.今天用度量值的方式写一个ABC的分类. ...

  6. 个人冲刺(五)——体温上报app(二阶段)

    冲刺任务:完成用户登录和随机验证码功能 loginActivity.java package com.example.helloworld; /** * 纯粹实现登录注册功能,其它功能都被注释掉了 * ...

  7. uniapp设置竖屏

    //在APP.vue中的onLaunch钩子写入plus.screen.lockOrientation('portrait-primary');

  8. RabbitMQ消息可靠性、死信交换机、消息堆积问题

    目录 消息可靠性 生产者消息确认 示例 消费者消息确认 示例 死信交换机 例子 高可用问题 消息堆积问题 惰性队列 参考 消息可靠性 确保消息至少被消费了一次(不丢失) 消息丢失的几种情况: 消息在网 ...

  9. GoF的23种设计模式的功能

    GoF的23种设计模式的功能 前面说明了 GoF 的 23 种设计模式的分类,现在对各个模式的功能进行介绍. 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取 ...

  10. 《Effective C++》阅读总结(三):资源管理

    C++中的资源管理非常重要,在将资源加载到内存后,便可以使用资源了,当我们不再需要资源时,我们要保证其正确的释放,才能将其占用的内存空间归还给操作系统,不正确的释放很容易造成内存泄漏.本章以资源管理类 ...