串口通信:接受数据(仿真task写法)
1.功能描述
设计一个串口数据接收模块。能够以设定的波特率(与发射端口速率匹配)接收数据,并输出保存到一个寄存器中。
2.过程描述
①边沿检测器,识别出起始位时让接收使能端有效。这里需要排除边沿脉冲的干扰,识别出的起始位不能是个瞬时脉冲。
②采样脉冲:区别于发射端,接收端需要对接收的数据进行采样。为保证接受到的数据的准确性,需要设定采样频率(奈奎斯特采样频率)。如下:
遵循uart协议的串口通信,每个数据为10位,包含8个数据位,1起始位,1结束位。在接收端接收时也要按照这个规律接收。具体思路是先判断起始位,然后逐个接收数据位,在结束位出现后把接收的数据赋给寄存器。
如上图所示 ,在采样时,我们把一位划分成16份,舍弃前5后4取中间七份(中心采样)(要求是奇数份,用来判断接收的数据是零还是一),在每一个1/16位中心产生一个采样脉冲,当此脉冲出现时,把它加到寄存器上,最终根据寄存器的值确定接收的数据大小(大于3为1,否则为0)。
编程的思路:此电路仍是时序电路,所以要确定用到的数据和时钟信号的关系。
①根据设定的波特率确定每一位的持续时间,进而确定每一位的计数值bit_tim。
②根据每一位的计数值确定每1/16位的计数值bit_tim_16,使用div_cnt寄存器计数到bit_tim_16-1.
③利用bit_tim_16产生采样脉冲bit16_pulse。
④利用bit_tim_16设计16*10个 状态,用bit16_cnt存储计数.
⑤设计一个寄存器edge_detect检测下降沿来判断起始位,产生接收脉冲,进而设置接收使能端receive_en。
⑥利用状态计数器bit16_cnt和采样脉冲bit16_pulse采样,保存采样值r_data。
⑦采样完成后设置结束信号rx_done。
⑧把储存的采样值r_data经过判决后存进输出寄存器data。
调试的经验:
①对design source 和 testbench 文件修改之后,不需要重新综合,可以直接relaunch stimulation 。这样子就不用重新添加变量,可以提高调试速度。
②Verilog在作运算时,结果取整数,舍弃小数位,因此在设置状态持续时间时,用运算得到的值可能会偏大/小,导致仿真时,误差使得结束位提前出现,tb中@检测不到,从而出错。在实 际中不会出错,但仍然有误差。具体看误差是否可以忽略。
③在仿真验证时,延时可以人为给定,波形出错的原因可能是跟design的时间对不上,不代表design source 设计的逻辑错误,可以对两者进行对比检查。
④注意多语句有无遗漏begin-end语句。
⑤开始检测起始位时,由于需要寄存器存值,出现一个时钟周期的误差,检测到边沿后再给receive_en赋值,又有一个时钟周期的误差。但由于采样取的是位中心,误差对本次设计的影响 可以忽略。
注意:
①采样脉冲的意义:让保存采样值的寄存器r_data在一个状态里只加一次。不然由于一个状态不止一个时钟周期,在代码设计上容易加很多次。
②保存到输出寄存器的时间可以在所有位都采样完之后,对所有位的采样寄存器进行判决即可。
③为保证接收到了起始位,要在边沿检测的基础上,对起始位进行判决,是0才对。
④用一个位宽为二的 寄存器作为边沿检测器,在整个过程中都会一直进行检测。但是不影响结果。
⑤每接受完一个数据后,要对保存采样的寄存器清零,不然会影响下一次接收。
⑥可以用位操作来简化代码:data[0]<=r_data[0][2] ;根据本次设计逻辑可以看出,也只适用于本设计。
新语法:
①设置二维寄存器格式:类型 [ 位宽 ] 名字 [ 寄存器个数]
位宽[2:0]指的是三个位宽,范围0~7。寄存器个数[2:0]是指三个寄存器,不是指7个寄存器。
引用时,格式为:名字【第几个寄存器】【哪个/哪几个数据】。注意,引用时不能直接对多个寄存器赋值,而定义时可以。
②case里面对于没用到的状态,无需动作,可以直接写default ; 。
③task系统任务。待补充。见语法书。可以在task中不止可以修改task中定义的变量值,而且可以修改task外,本文件里面定义的变量的值。注意task和function的区别,对比记忆。
④<=在always中可以是赋值语句,非阻塞赋值,也可以作为判断语句中,意为小于等于。
3.设计输入
接口: 1.输入端:
数据输入端口uart_tx,接受波特率设置端口baud_rate,时钟信号端口,复位信号端口。
2.输出端:
输出接收的数据端口data,接收完成信号端口rx_done。
4.代码
module uart_receive_1(
clk ,
reset ,
baud_rate ,
uart_tx,
data ,
rx_done
);
input clk ;
input reset ;
input [2:0]baud_rate ;
input uart_tx ;
output reg [7:0]data ;
output reg rx_done ; reg [2:0]r_data[7:0] ;//接收每一位数据
reg [2:0]sta_bit ;
reg [2:0]sto_bit ; reg [17:0]bit_tim ;//每一位持续的时间(计数)
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 wire [17:0]bit_tim_16 ;//每1/16位的持续时间(计数)
assign bit_tim_16 = bit_tim / 16; wire [8:0]bit16_mid ; //在中心点产生采样脉冲
assign bit16_mid = bit_tim_16 / 2 ; //边沿检测
reg [1:0]edge_detect ;
always @( posedge clk or negedge reset )
begin
if (!reset )
edge_detect <= 2'd0 ;
else
begin
edge_detect[0] <= uart_tx ;
edge_detect[1] <= edge_detect[0] ;
end
end wire byte_sta_neg ;
assign byte_sta_neg = ( edge_detect == 2'b10 ) ? 1 : 0 ;//输入的数据开始出现下降沿,说明出现了起始位(一直运行?) reg receive_en ;//接收使能端
reg [17:0]div_cnt ;//每1/16bit内的计数
reg [7:0]bit16_cnt ;//计数到了第几个状态(10位,每位分成16份,总共160个状态)
always @( posedge clk or negedge reset )
begin
if (!reset )
receive_en <= 1'd0 ;
else if ( byte_sta_neg ) //检测到下降沿,使能段有效(只要有下降沿就使能?)
receive_en <= 1'd1 ;
else if ( (rx_done) || (sta_bit >= 3'd4 ))
receive_en <= 1'd0 ; //检测到结束信号,使能端无效
else if ( ( bit16_cnt == 8'd159 ) && (div_cnt == bit_tim_16 - 1'd1 ) )//跑完159后re_en置零
receive_en <= 1'd0 ;
end always@( posedge clk or negedge reset )
begin
if ( ! reset )
div_cnt <= 18'd0 ;
else if (receive_en)
begin
if ( div_cnt == bit_tim_16 - 1'd1 )//计数,每1/16bit清零
div_cnt <= 18'd0 ;
else
div_cnt <= div_cnt + 1'b1 ;
end
else
div_cnt <= 18'd0 ;
end reg bit16_pulse ;//产生采样脉冲
always@( posedge clk or negedge reset )
begin
if ( ! reset )
bit16_pulse <= 18'd0 ;
else if (receive_en)
if ( div_cnt == bit16_mid )
bit16_pulse <= 1'd1 ;
else
bit16_pulse <= 1'd0 ;
else
bit16_pulse <= 1'd0 ;
end always@( posedge clk or negedge reset )
begin
if ( ! reset )
bit16_cnt <= 8'd0 ;
else if (receive_en)
begin
if (( bit16_cnt == 8'd159 ) && (div_cnt == bit_tim_16 - 1'd1 ))
bit16_cnt <= 8'd0 ;
else if ( div_cnt == bit_tim_16 - 1'd1 )
bit16_cnt <= bit16_cnt + 1'b1 ;
end
end always@(posedge clk or negedge reset)
begin
if(!reset)
begin
sta_bit <= 3'd0 ;
r_data[0] <= 3'd0 ;
r_data[1] <= 3'd0 ;
r_data[2] <= 3'd0 ;
r_data[3] <= 3'd0 ;
r_data[4] <= 3'd0 ;
r_data[5] <= 3'd0 ;
r_data[6] <= 3'd0 ;
r_data[7] <= 3'd0 ;
sto_bit <= 3'd0 ;
end
else if (bit16_pulse)//舍弃前5后4取中7
case(bit16_cnt)
0:
begin
sta_bit <= 3'd0 ;
r_data[0] <= 3'd0 ;
r_data[1] <= 3'd0 ;
r_data[2] <= 3'd0 ;
r_data[3] <= 3'd0 ;
r_data[4] <= 3'd0 ;
r_data[5] <= 3'd0 ;
r_data[6] <= 3'd0 ;
r_data[7] <= 3'd0 ;
sto_bit <= 3'd0 ;
end
5,6,7,8,9,10,11 : sta_bit <= sta_bit + uart_tx ;
21,22,23,24,25,26,27 : r_data[0] <= r_data[0] + uart_tx ;
37,38,39,41,42,43,44 : r_data[1] <= r_data[1] + uart_tx ;
53,54,55,56,57,58,59 : r_data[2] <= r_data[2] + uart_tx ;
69,70,71,72,73,74,75 : r_data[3] <= r_data[3] + uart_tx ;
85,86,87,88,89,90,91 : r_data[4] <= r_data[4] + uart_tx ;
101,102,103,104,105,106,107 : r_data[5] <= r_data[5] + uart_tx ;
117,118,119,120,121,122,123 : r_data[6] <= r_data[6] + uart_tx ;
133,134,135,136,137,138,139 : r_data[7] <= r_data[7] + uart_tx ;
149,150,151,152,153,154,155 : sto_bit <= sto_bit + uart_tx ;
default ;
endcase
end always@( posedge clk or negedge reset )
begin
if ( ! reset )
rx_done <= 8'd0 ;
else if ( ( bit16_cnt == 8'd159 ) && (div_cnt == bit_tim_16 - 1'd1 ) )//跑完159后产生一个rx_done信号
rx_done <= 8'd1 ;
else if (rx_done <= 8'd1 )
rx_done <= 8'd0 ;
end always@( posedge clk or negedge reset )//接收完数据发出rx_done后,把数据从r_data传递给data
begin
if ( ! reset )
data <= 8'd0 ;
else if ( rx_done )
begin
data[0] = ( r_data[0] >3 ) ? 1 : 0 ;
data[1] = ( r_data[1] >3 ) ? 1 : 0 ;
data[2] = ( r_data[2] >3 ) ? 1 : 0 ;
data[3] = ( r_data[3] >3 ) ? 1 : 0 ;
data[4] = ( r_data[4] >3 ) ? 1 : 0 ;
data[5] = ( r_data[5] >3 ) ? 1 : 0 ;
data[6] = ( r_data[6] >3 ) ? 1 : 0 ;
data[7] = ( r_data[7] >3 ) ? 1 : 0 ;
end
else if ( receive_en )
data <= 8'd0 ;
end endmodule
`timescale 1ns/1ns
module uart_receive_tb(); reg clk ;
reg reset ;
reg [2:0]baud_rate ;
reg uart_tx ;
wire [7:0]data ;
wire rx_done ; uart_receive_1 uart_receive_1(
clk ,
reset ,
baud_rate ,
uart_tx,
data ,
rx_done
); initial clk = 1 ;
always #10 clk = ! clk ;
initial
begin
reset = 0 ;
baud_rate = 0 ;
uart_tx = 1 ;
#201 ;
reset = 1 ;
baud_rate = 3'd5 ;
uart_input(8'h4f) ;
@(posedge rx_done) ;
#200 ;
uart_input(8'h3a) ;
@(posedge rx_done) ;
#200 ;
uart_input(8'hbc) ;
@(posedge rx_done) ;
#200;
$stop;
end task uart_input ;//设定一个任务uart_inpt,有一个输入端uart_tx_data_stm 。在这个task里可以对task外的变量进行赋值
input [7:0]uart_tx_data_stm ;//不返回值,所以不能用x=uart_input。而是直接uart_input。
begin //结构简单的begin-end
uart_tx = 1 ;
#20 ;
uart_tx = 0 ;
#8640 ;
uart_tx = uart_tx_data_stm[0] ;
#8640 ;
uart_tx = uart_tx_data_stm[1] ;
#8640 ;
uart_tx = uart_tx_data_stm[2] ;
#8640 ;
uart_tx = uart_tx_data_stm[3] ;
#8640 ;
uart_tx = uart_tx_data_stm[4] ;
#8640 ;
uart_tx = uart_tx_data_stm[5] ;
#8640 ;
uart_tx = uart_tx_data_stm[6] ;
#8640 ;
uart_tx = uart_tx_data_stm[7] ;
#8640 ;
uart_tx = 1 ;
#8640 ;
end
endtask
endmodule
串口通信:接受数据(仿真task写法)的更多相关文章
- C#串口通信及数据表格存储
1.开发环境 系统:win10 开发工具:Visual Studio 2017 2.界面设计 串口通信的界面大致如此,在此基础上添加项目所需的调试指令与数据存储功能,界面排布方面可参考其他教程. 3. ...
- Qt串口通信接收数据不完整的解决方法
在使用串口接收数据时,当数据量大的时候会出现数据接收不完整的情况.因为串口数据获取函数readAll()由readyRead()信号触发,但readyRead()信号在串口读到起始标志时立即发送,并不 ...
- Qt串口通信接收数据不完整的解决方法(传输图片)
在使用串口接收数据时,当数据量大的时候会出现数据接收不完整的情况.因为串口数据获取函数readAll()由readyRead()信号触发,但readyRead()信号在串口读到起始标志时立即发送,并不 ...
- 在Linux中如何使用命令进行RS-232串口通信和数据包解析
文章首发于浩瀚先森博客 1. 获取串口号 在Linux系统中一切皆为文件,所以串口端口号也不例外,都是以设备文件的形式出现.也就是说我们可以用访问文本文件的命令来访问它们. a. 一般串口都是以/de ...
- C#串口通信发送数据
1 发送数据 需要2个串口 http://www.openedv.com/thread-228847-1-1.html 下载源文件 File_Protocol_Test.rar
- CC2540开发板学习笔记(五)——串口通信
(一)串口发送 一.实验现象: 开发板实现功能发送 二.实验过程 1.PL2303 USB转串口电路图 2.串口发送 (1)查看用户手册有: UART0 对应的外部设备 IO 引脚关系为: P0_2 ...
- 张高兴的 .NET Core IoT 入门指南:(五)串口通信入门
在开始之前,首先要说明的是串口通信所用到的 SerialPort 类并不包含在 System.Device.Gpio NuGet 包中,而是在 System.IO.Ports NuGet 包中.之所以 ...
- Qt 串口通信 高速发送出错的解决方法总结
使用网上的qextserialport-1.2类,自行开发多线程串口通信.开发的过程中,出现两个问题: 问题1:我用信号槽跨线程调用串口类MyCom 发送和接收数据,中间运行的时候,会内存错误,Q ...
- C#上位机制作之串口接受数据(利用接受事件)
前面设计好了界面,现在就开始写代码了,首先定义一个串口对象.. SerialPort serialport = new SerialPort();//定义串口对象 添加串口扫描函数,扫描出来所有可用串 ...
随机推荐
- Spring Boot 启动源码解析结合Spring Bean生命周期分析
转载请注明出处: 1.SpringBoot 源码执行流程图 2. 创建SpringApplication 应用,在构造函数中推断启动应用类型,并进行spring boot自动装配 public sta ...
- 一次IOS通知推送问题排查全过程
原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 发现问题 在上周一个将要下班的夜晚,测试突然和我打招呼,说IOS推送的修复更新上线后存在问题,后台报错. 连忙跑到测试那 ...
- TCP 协议有哪些缺陷?
作者:小林coding 图解计算机基础网站:https://xiaolincoding.com 大家好,我是小林. 忽然思考一个问题,TCP 通过序列号.确认应答.超时重传.流量控制.拥塞控制等方式实 ...
- 零基础学Java第四节(字符串相关类)
本篇文章是<零基础学Java>专栏的第四篇文章,文章采用通俗易懂的文字.图示及代码实战,从零基础开始带大家走上高薪之路! String 本文章首发于公众号[编程攻略] 在Java中,我们经 ...
- 110_Power Pivot特殊结算日期及财年日期
博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 1.背景 前几天看到群里有朋友在搞特殊结算日期,主要不是按照正常日期里的整月,按照比如:上月21号至本月20号作为结算周期 ...
- ARM学习1
ARM相关概念 1.ARM的发展史 1. 1978年,CPU公司 Cambridge processing Unit 2. 1979年 Acorn 3. 1985年, 32位,8MHz, 使用的精简指 ...
- unittest自动化测试框架核心要素以及应用
1. unittest核心要素 unittest介绍 测试框架,不仅仅用于单元测试 python自动的测试包 用法和django.test.TestCase类似 1.1.unitest介绍和核心要素 ...
- npm init cabloy背后的故事
背景 我们知道许多框架会提供一个脚手架工具,我们先下载安装脚手架工具,然后再通过脚手架命令行来创建项目.在npm@6.1.0中引入了npm init <initializer>的语法.简单 ...
- Xmind头脑风暴
下图导出到excel后 上图对应的excel表格如下:
- 在Rally上,上传测试报告(文件)到每个Test Case方法
本文链接: https://www.cnblogs.com/hchengmx/p/how-to-upload-test-result-to-test-case-result-in-rally.html ...