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. Node.js 中的进程和线程

    线程和进程是计算机操作系统的基础概念,在程序员中属于高频词汇,那如何理解呢?Node.js 中的进程和线程又是怎样的呢? 一.进程和线程 1.1.专业性文字定义 进程(Process),进程是计算机中 ...

  2. Web3.0应用程序架构

    Web 3.0 应用程序(或"DApps")的架构与 Web 2.0 应用程序完全不同. 以博客园为例,这是一个简洁的博客网站,用户可以发布自己的内容并可以评论他人的内容进行互动. ...

  3. 如何形象简单地理解java中只有值传递,而没有引用传递?

    首先,java中只有值传递,没有引用传递.可以说是"传递的引用(地址)",而不能说是"按引用传递". 按值传递意味着当将一个参数传递给一个函数时,函数接收的是原 ...

  4. 初始 Django

    Python 知识点:函数,面向对象 前端开发:HTML,CSS,JavaScript,jQuery,BootStrap MySQL 数据库 Python 的 WEB 框架 Flask:轻量化,第三方 ...

  5. 561. Array Partition I - LeetCode

    Question 561. Array Partition I Solution 题目大意是,给的数组大小是2n,把数组分成n组,每组2个元素,每个组取最小值,这样就能得到n个值,怎样分组才能使这n个 ...

  6. chkconfig-配置系统服务

    管理Linux系统开机启动项. chkconfig命令默认在CentOS7+中不被使用了,由于系统服务管理都交给了systemctl托管. 语法 chkconfig [--list] [--type ...

  7. Prometheus普罗米修斯快速入门

    欢迎来到普罗米修斯! Prometheus是一个监控平台,通过从监控目标的抓取HTTP端点上获取指标. 本指南将展示如何使用和安装Promethues,配置和监视第一个资源.还将下载并安装导出器Exp ...

  8. 关于加密通道规范,你真正用的是TLS,而非SSL

    摘要:事实上我们现在用的都是TLS,但因为历史上习惯了SSL这个称呼,平常还是以SSL为多. 本文分享自华为云社区<SSL和TLS的联系及区别>,作者: HZDX. TLS/SSL是一种加 ...

  9. 羽夏笔记—— AT&T 与 GCC

    写在前面   本文是本人根据<AT&T 汇编语言与 GCC 内嵌汇编简介>进一步整理,修改了一些错误,并删除我并不能复现代码相关的部分.该文章一是我对 AT&T 的学习记录 ...

  10. [算法学习] dsu on tree

    简介 dsu on tree跟dsu没有关系,但是dsu on tree借鉴了dsu的启发式合并的思想. 它是用来解决一类树上的询问问题,一般这种问题有以下特征: \(1.\)只有对子树的查询: \( ...