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写法)的更多相关文章

  1. C#串口通信及数据表格存储

    1.开发环境 系统:win10 开发工具:Visual Studio 2017 2.界面设计 串口通信的界面大致如此,在此基础上添加项目所需的调试指令与数据存储功能,界面排布方面可参考其他教程. 3. ...

  2. Qt串口通信接收数据不完整的解决方法

    在使用串口接收数据时,当数据量大的时候会出现数据接收不完整的情况.因为串口数据获取函数readAll()由readyRead()信号触发,但readyRead()信号在串口读到起始标志时立即发送,并不 ...

  3. Qt串口通信接收数据不完整的解决方法(传输图片)

    在使用串口接收数据时,当数据量大的时候会出现数据接收不完整的情况.因为串口数据获取函数readAll()由readyRead()信号触发,但readyRead()信号在串口读到起始标志时立即发送,并不 ...

  4. 在Linux中如何使用命令进行RS-232串口通信和数据包解析

    文章首发于浩瀚先森博客 1. 获取串口号 在Linux系统中一切皆为文件,所以串口端口号也不例外,都是以设备文件的形式出现.也就是说我们可以用访问文本文件的命令来访问它们. a. 一般串口都是以/de ...

  5. C#串口通信发送数据

    1 发送数据 需要2个串口 http://www.openedv.com/thread-228847-1-1.html 下载源文件 File_Protocol_Test.rar

  6. CC2540开发板学习笔记(五)——串口通信

    (一)串口发送 一.实验现象: 开发板实现功能发送 二.实验过程 1.PL2303 USB转串口电路图 2.串口发送 (1)查看用户手册有: UART0 对应的外部设备 IO 引脚关系为: P0_2 ...

  7. 张高兴的 .NET Core IoT 入门指南:(五)串口通信入门

    在开始之前,首先要说明的是串口通信所用到的 SerialPort 类并不包含在 System.Device.Gpio NuGet 包中,而是在 System.IO.Ports NuGet 包中.之所以 ...

  8. Qt 串口通信 高速发送出错的解决方法总结

    使用网上的qextserialport-1.2类,自行开发多线程串口通信.开发的过程中,出现两个问题:   问题1:我用信号槽跨线程调用串口类MyCom 发送和接收数据,中间运行的时候,会内存错误,Q ...

  9. C#上位机制作之串口接受数据(利用接受事件)

    前面设计好了界面,现在就开始写代码了,首先定义一个串口对象.. SerialPort serialport = new SerialPort();//定义串口对象 添加串口扫描函数,扫描出来所有可用串 ...

随机推荐

  1. Spring Boot 启动源码解析结合Spring Bean生命周期分析

    转载请注明出处: 1.SpringBoot 源码执行流程图 2. 创建SpringApplication 应用,在构造函数中推断启动应用类型,并进行spring boot自动装配 public sta ...

  2. 一次IOS通知推送问题排查全过程

    原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 发现问题 在上周一个将要下班的夜晚,测试突然和我打招呼,说IOS推送的修复更新上线后存在问题,后台报错. 连忙跑到测试那 ...

  3. TCP 协议有哪些缺陷?

    作者:小林coding 图解计算机基础网站:https://xiaolincoding.com 大家好,我是小林. 忽然思考一个问题,TCP 通过序列号.确认应答.超时重传.流量控制.拥塞控制等方式实 ...

  4. 零基础学Java第四节(字符串相关类)

    本篇文章是<零基础学Java>专栏的第四篇文章,文章采用通俗易懂的文字.图示及代码实战,从零基础开始带大家走上高薪之路! String 本文章首发于公众号[编程攻略] 在Java中,我们经 ...

  5. 110_Power Pivot特殊结算日期及财年日期

    博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 1.背景 前几天看到群里有朋友在搞特殊结算日期,主要不是按照正常日期里的整月,按照比如:上月21号至本月20号作为结算周期 ...

  6. ARM学习1

    ARM相关概念 1.ARM的发展史 1. 1978年,CPU公司 Cambridge processing Unit 2. 1979年 Acorn 3. 1985年, 32位,8MHz, 使用的精简指 ...

  7. unittest自动化测试框架核心要素以及应用

    1. unittest核心要素 unittest介绍 测试框架,不仅仅用于单元测试 python自动的测试包 用法和django.test.TestCase类似 1.1.unitest介绍和核心要素 ...

  8. npm init cabloy背后的故事

    背景 我们知道许多框架会提供一个脚手架工具,我们先下载安装脚手架工具,然后再通过脚手架命令行来创建项目.在npm@6.1.0中引入了npm init <initializer>的语法.简单 ...

  9. Xmind头脑风暴

    下图导出到excel后 上图对应的excel表格如下:

  10. 在Rally上,上传测试报告(文件)到每个Test Case方法

    本文链接: https://www.cnblogs.com/hchengmx/p/how-to-upload-test-result-to-test-case-result-in-rally.html ...