基于Robei EDA--实现串口通信

2024-02-08 21:04
文章标签 实现 通信 串口 eda robei

本文主要是介绍基于Robei EDA--实现串口通信,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、串口简介

串口作为常用的三大低速总线(UART、SPI、IIC)之一,在设计众多通信接口和调试时占有重要地位。但UART和SPI、IIC不同的是,它是异步通信接口,异步通信中的接收方并不知道数据什么时候会到达,所以双方收发端都要有各自的时钟,在数据传输过程中是不需要时钟的,发送方发送的时间间隔可以不均匀,接受 方是在数据的起始位和停止位的帮助下实现信息同步的。而SPI、IIC是同步通信接口(后面的章节会做详细介绍),同步通信中双方使用频率一致的时钟,在数据传输过程中时钟伴随着数据一起传输,发送方和接收方使用的时钟都是由主机提供的。

UART通信只有两根信号线,一根是发送数据端口线叫tx(Transmitter),一根是接收数据端口线叫rx(Receiver)。可以实现全双工,即可以同时进行发送数据和接收数据。

RS232通信协议简介

串口的通信协议有很多,如RS232、RS499、RS423、RS422和RS485等接口,但是这里我们就使用最常见的通信协议RS232。

下面是数据帧结构

rx和tx的位宽都是1bit。串口数据的发送与接收是基于帧结构的,即一帧一帧的发送与接收数据。

每一帧数据共10bit,起始位为0,停止位为1,中间包含8bit的数据位。在不发送或者不接收数据的情况下,rx和tx处于空闲状 态,此时rx和tx线都保持高电平,即为空闲状态。

几个重要概念

波特率:在信息传输通道中,携带数据信息的信号单元叫码元(因为串口是1bit进行传输的,所以其码元就是代表一个二进制数),每秒钟通过信号传输的码元数称为码元的传输速率,简称波特率。串口常见的波特率有4800、9600、115200等。

由计算得串口发送或者接收1bit数据的时间为一个波特,即1/9600秒,如果用50MHz(周期为20ns)的系统时钟来计数,需要计数的个数为cnt = (1s * 10^9)ns / 9600bit)ns / 20ns ≈ 5208个系统时钟周期,即每个bit数据之间的间隔要在50MHz的时钟频率下计数5208次。

二、串口接收模块

先设计串口接收模块,该模块的功能是接收通过PC机上的串口调试助手发送的固定波特率的数据,串口接收模块按照串口的协议准确接收串行数据,解析提取有用数据后需将其转化为并行数据,因为并行数据在FPGA内部传输的效率更高,转化为并行数据后同时产生一个数据有效信号标志信号伴随着并行的有效数据一同输出。

采用WPS绘制框图--串口接收模块

  • rx:1bit  通过该模块将串行数据转换成8bit并行数据
  • 8bit并行数据有效的标志信号 po_flag
  • po_data 8bit数据并行输出
波形设计
思路1:减少亚稳态的影响对rx打两拍,两级寄存器
{signal: [{name: 'clk', wave: 'p.........|............'},{name: 'rerset', wave: 'lh......|..............'},{name: 'rx', wave: 'h......lhlhlhlhlh..'},{name: 'rx_reg1', wave: 'h.......lhlhlhlhlh..'},{name: 'rx_reg2', wave: 'h........lhlhlhlhlh..'},
],config: { hscale: 2,skin: "narrow"}}


思路二:采集下降沿

经过两级寄存器后rx_reg2相对稳定,rx_reg1是不稳定的,所以打算再打一拍,rx_reg3和rx_reg2用于采集下降沿

采集好下降沿后让start_negedge维持一个时钟周期

思路三:工作使能信号

为了使在过程中的下降沿不会影响串口的接收,我们设置一个工作使能信号,在work_en信号为高的时候,可以判断出现的start_negedge是不是我们想要的串口一帧的起始位下降沿,从而不影响后续操作

思路四:正式接收数据

正式开始接收一帧数据。我们使用的是9600bps的波特率和PC机进行串口通信,PC机的串口调试助手要将发送数据波特率调整为9600bps。而FPGA内部使用的系统时钟是50MHz,前面也进行过计算,得出1bit需要的时间约为5208个(因为一帧只有10bit,细微的近似计数差 别不会产生数据错误,但是如果计数值差的过大,则会产生接收数据的错误)系统时钟周期,那么我们就需要产生一个能计5208个数的计数器来依次接收10个比特的数据,计数器每计5208个数就接收一个新比特的数据。计数器名为baud_cnt,当work_en信号为高电平的时候就让计数器计数,当计数器计5208个数(从0到5207)或work_en信号为低电平时计数器清零。

思路五:读取数据

我们选择当baud_cnt计数器计数到2603,即中间位置时取数最稳定(其实只要b aud_cnt计数器在计数值不是在0和5207这两个最不稳定的时刻取数都可以,更为准确的是多次取值取概率最大的情况)。

思路六:rx何时拉高

串口的数据是基于帧的,所以每接收完一帧数据rx信号都要被拉高,即恢复到空闲状态重新判断串口帧起始下降沿,以等待下一帧数据的接收,且一帧数据中还包括了起始位和停止位这种无用的数据,而对我们有价值的数据只是中间的8bit数据,也就是说我们需要准确的知道我们此时此刻接收的是第几比特,当接收够10bit数据 后,我们就停止继续接收数据,等rx信号被拉高待恢复到空闲状态后再等待接收下一帧的数据。

bit_cnt用于记录目前接收到第几bit数据

可以利用我们已经产生的bit_flag取数标志信号,对该信号进行计数既可以知道此时我们接收的数据是第几个比 特了。这里我们只让bit_cnt计数器的计数值为8时再清零,虽然bit_cnt计数器的计数值从0计数到8只有9个bit,但这9个bit中已经包含的我们所需要的8bit有用的数据,最后的1bit停止位没有用,可以不用再进行计数了。

思路七:work_en何时拉低

前面讲了work_en什么时候拉高,下面思考什么时候拉低,当接受完第八位数据时:①bit_flag = 1

②bit_cnt = 8;如果只计数到8没有bit_flag = 1的条件的话会错失第8bit有小数据,读取位置偏前

思考八:串转并

串并转换就需要做移位,我们要考虑清楚什么时候开始移位,不能提前也不能推后,否则会将无用的 数据也移位进来,所以我们需要卡准时间。注意移位的条件,要在bit_cnt计数器的计数值为1到8区间内且bit_flag取数标志信号同时为高时才能移位,也就是移动7次即可,接收最后1bit有用数据时就不需要再进行移位了。当移位7次后1bit的串行数据已经变为8 bit的并行数据了,此时产生一个移位完成标志信号rx_flag。

思考九:输出po_data

程序编写

Robei EDA框图

//reg define
reg rx_reg1;
reg rx_reg2;
reg rx_reg3;
reg work_en;	//工作使能信号
reg start_negedge;//下降沿检测
reg [12:0]baud_cnt;	//比特计数器
reg bit_flag;
reg [3:0]bit_cnt;
reg [7:0]rx_data;
reg rx_flag;
parameter baud_cnt_MAX = 13'd5208;//三拍寄存器
always@(posedge clk or negedge reset)if(!reset)beginrx_reg1 <= 1'b0;
endelse beginrx_reg1 <= rx;
endalways@(posedge clk or negedge reset)if(!reset)beginrx_reg2 <= 1'b0;
endelse beginrx_reg2 <= rx_reg1;
endalways@(posedge clk or negedge reset)if(!reset)beginrx_reg3 <= 1'b0;
endelse beginrx_reg3 <= rx_reg2;
end//start_negedge
always@(posedge clk or negedge reset)
if(!reset)beginstart_negedge <= 1'b0;
end
else if((~rx_reg2) && (rx_reg3))
start_negedge <= 1'b1;
else
start_negedge <= 1'b0;//work_en
always@(posedge clk or negedge reset)
if(!reset)work_en <= 1'b0;
else if(start_negedge == 1'b1)
work_en <= 1'b1;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
work_en <= 1'b0;
else work_en <= work_en;//baud_cnt
always@(posedge clk or negedge reset)
if(!reset)baud_cnt<= 0;
else if(work_en == 1'b1)beginif(baud_cnt >= baud_cnt_MAX - 1'b1)baud_cnt <= 0;else baud_cnt <= baud_cnt + 1'b1;
end
elsebaud_cnt <= 0;//bit_flag
always@(posedge clk or negedge reset)
if(!reset)bit_flag <= 0;
else if(work_en == 1'b1)beginif(baud_cnt == baud_cnt_MAX/2)bit_flag <= 1;else bit_flag <= 0;
end
else bit_flag <= 0;//bit_cnt;
always@(posedge clk or negedge reset)
if(!reset)bit_cnt <= 4'd0;else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))bit_cnt <= 4'b0;else if(bit_flag ==1'b1)bit_cnt <= bit_cnt + 1'b1;//rx_data:输入数据进行移位always@(posedge clk or negedge reset)if(reset == 1'b0)rx_data <= 8'b0;else if((bit_cnt >= 4'd1)&&(bit_cnt <= 4'd8)&&(bit_flag == 1'b1))rx_data <= {rx_reg3, rx_data[7:1]};//rx_flag
always@(posedge clk or negedge reset)
if(!reset)rx_flag <= 1'b0;
else if(bit_cnt == 4'd8 &&(bit_flag == 1'b1))rx_flag <= 1'b1;
elserx_flag <= 1'b0;//po_data
always@(posedge clk or negedge reset)
if(!reset)po_data <= 8'b0;
else if(rx_flag)po_data <= rx_data;//po_flag
always@(posedge clk or negedge reset)
if(!reset)po_flag <= 1'b0;
else if(rx_flag)po_flag <= 1'b1;
elsepo_flag <= 1'b0;

仿真文件

 //初始化系统时钟、全局复位和输入信号initial beginclk = 1'b1;reset <= 1'b0;rx <= 1'b1;#20;reset <= 1'b1;
#1000_000_00;
$finish;end//模拟发送8次数据,分别为0~7initial begin#200rx_bit(8'd0); //任务的调用,任务名+括号中要传递进任务的参数rx_bit(8'd1);rx_bit(8'd2);rx_bit(8'd3);rx_bit(8'd4);rx_bit(8'd5);rx_bit(8'd6);rx_bit(8'd7);end//sys_clk:每10ns电平翻转一次,产生一个50MHz的时钟信号always #10 clk = ~clk;//定义一个名为rx_bit的任务,每次发送的数据有10位//data的值分别为0~7由j的值传递进来//任务以task开头,后面紧跟着的是任务名,调用时使用task rx_bit(//传递到任务中的参数,调用任务的时候从外部传进来一个8位的值input [7:0] data);integer i; //定义一个常量//用for循环产生一帧数据,for括号中最后执行的内容只能写i=i+1//不可以写成C语言i=i++的形式for(i=0; i<10; i=i+1) begincase(i)0: rx <= 1'b0;1: rx <= data[0];2: rx <= data[1];3: rx <= data[2];4: rx <= data[3];5: rx <= data[4];6: rx <= data[5];7: rx <= data[6];8: rx <= data[7];9: rx <= 1'b1;endcase#(5208*20); //每发送1位数据延时5208个时钟周期endendtask //任务以endtask结束

仿真文件里Verilog语法学习

task 任务名(
//此处写传递到任务里的参数
input xxx,
output xxx,
inout xxx
);endtask //任务以endtask结束

仿真波形

经过仿真,慢慢改正,成功完成。

三、串口发送模块

  • clk:时钟
  • reset:复位信号,低电平有效
  • pi_data:并行数据输入
  • pi_flag:并行数据输入有效信号
  • tx:发送端


波形设计

波形分析一:由于pi_flag的高电平只维持1个时钟周期,所以我们产生一个work_en工作使能信号

work_en=1 的条件下baud_cnt开始计数

波形分析二:选择发送数据的位置点

baund_cnt = 1

波形分析三:bit_cnt

一帧数据包括起始位、8位有效数据、停止位,需要一个4位的bit_cnt用于记录发了多少

波形分析四:

当bit_cnt计数到9就可以不再计数,因为停止位和空闲状态均为高电平


程序编写

//reg define
reg [12:0] baud_cnt;
reg [3:0] bit_cnt;
reg bit_flag;
reg work_en;parameter Baud_cnt_Max = 13'd5208;
//工作使能信号
always@(posedge clk or negedge reset)
if(!reset)work_en <= 1'b0;
else if(pi_flag == 1'b1)work_en <= 1'b1;
else if(bit_cnt == 4'd9 && bit_flag == 1)work_en <= 1'b0;
else work_en <= work_en;always@(posedge clk or negedge reset)
if(!reset)baud_cnt <= 13'b0;
else if(baud_cnt >= Baud_cnt_Max)baud_cnt <= 13'b0;
else if(work_en)baud_cnt <= baud_cnt + 1'b1;
else baud_cnt <= 13'b0;always@(posedge clk or negedge reset)
if(!reset)bit_flag <= 0;
else if(baud_cnt == 13'd1)bit_flag <= 1'b1;
else bit_flag <= 1'b0;always@(posedge clk or negedge reset)
if(!reset)bit_cnt <= 4'd0;
else if(bit_flag&&work_en)bit_cnt <= bit_cnt + 1'b1;
else if((work_en == 1'b0 )||(bit_cnt == 4'd9 && bit_flag ))bit_cnt <= 4'd0;
else bit_cnt <= bit_cnt;always@(posedge clk or negedge reset)
if(!reset)							tx <= 1'b1;//空闲状态为高电平
else if(bit_flag)
case(bit_cnt)0: tx <= 1'b0;1: tx <= pi_data[0];2: tx <= pi_data[1];3: tx <= pi_data[2];4: tx <= pi_data[3];5: tx <= pi_data[4];6: tx <= pi_data[5];7: tx <= pi_data[6];8: tx <= pi_data[7];9: tx <= 1'b1;default: tx <= 1'b1;
endcase
else tx <= tx;

四、数据回环

写在最后--串口学习(参考)可以应用在蓝牙模块以及一系列串口通信的外设,继续努力学习。

UART那么好用,为什么单片机还需要I2C和SPI?​​​​​​​

这篇关于基于Robei EDA--实现串口通信的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/692194

相关文章

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima

SpringBoot全局域名替换的实现

《SpringBoot全局域名替换的实现》本文主要介绍了SpringBoot全局域名替换的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录 项目结构⚙️ 配置文件application.yml️ 配置类AppProperties.Ja

Python实现批量CSV转Excel的高性能处理方案

《Python实现批量CSV转Excel的高性能处理方案》在日常办公中,我们经常需要将CSV格式的数据转换为Excel文件,本文将介绍一个基于Python的高性能解决方案,感兴趣的小伙伴可以跟随小编一... 目录一、场景需求二、技术方案三、核心代码四、批量处理方案五、性能优化六、使用示例完整代码七、小结一、

Java实现将HTML文件与字符串转换为图片

《Java实现将HTML文件与字符串转换为图片》在Java开发中,我们经常会遇到将HTML内容转换为图片的需求,本文小编就来和大家详细讲讲如何使用FreeSpire.DocforJava库来实现这一功... 目录前言核心实现:html 转图片完整代码场景 1:转换本地 HTML 文件为图片场景 2:转换 H

C#使用Spire.Doc for .NET实现HTML转Word的高效方案

《C#使用Spire.Docfor.NET实现HTML转Word的高效方案》在Web开发中,HTML内容的生成与处理是高频需求,然而,当用户需要将HTML页面或动态生成的HTML字符串转换为Wor... 目录引言一、html转Word的典型场景与挑战二、用 Spire.Doc 实现 HTML 转 Word1

C#实现一键批量合并PDF文档

《C#实现一键批量合并PDF文档》这篇文章主要为大家详细介绍了如何使用C#实现一键批量合并PDF文档功能,文中的示例代码简洁易懂,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言效果展示功能实现1、添加文件2、文件分组(书签)3、定义页码范围4、自定义显示5、定义页面尺寸6、PDF批量合并7、其他方法

SpringBoot实现不同接口指定上传文件大小的具体步骤

《SpringBoot实现不同接口指定上传文件大小的具体步骤》:本文主要介绍在SpringBoot中通过自定义注解、AOP拦截和配置文件实现不同接口上传文件大小限制的方法,强调需设置全局阈值远大于... 目录一  springboot实现不同接口指定文件大小1.1 思路说明1.2 工程启动说明二 具体实施2

Python实现精确小数计算的完全指南

《Python实现精确小数计算的完全指南》在金融计算、科学实验和工程领域,浮点数精度问题一直是开发者面临的重大挑战,本文将深入解析Python精确小数计算技术体系,感兴趣的小伙伴可以了解一下... 目录引言:小数精度问题的核心挑战一、浮点数精度问题分析1.1 浮点数精度陷阱1.2 浮点数误差来源二、基础解决

Java实现在Word文档中添加文本水印和图片水印的操作指南

《Java实现在Word文档中添加文本水印和图片水印的操作指南》在当今数字时代,文档的自动化处理与安全防护变得尤为重要,无论是为了保护版权、推广品牌,还是为了在文档中加入特定的标识,为Word文档添加... 目录引言Spire.Doc for Java:高效Word文档处理的利器代码实战:使用Java为Wo

Java实现远程执行Shell指令

《Java实现远程执行Shell指令》文章介绍使用JSch在SpringBoot项目中实现远程Shell操作,涵盖环境配置、依赖引入及工具类编写,详解分号和双与号执行多指令的区别... 目录软硬件环境说明编写执行Shell指令的工具类总结jsch(Java Secure Channel)是SSH2的一个纯J