基于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

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现