首页 > 其他分享 >FPGA之串口接收数据(看注释)

FPGA之串口接收数据(看注释)

时间:2024-03-19 16:14:53浏览次数:26  
标签:FPGA clk rx sys flag 串口 rst bit 接收数据

兜兜转转看了好多家视频和好几本书,明白了FPGA难学的原因之一是因为讲的好(我觉得就是很详细,告诉你为什么这么来写代码)的视频比较少,之前看到的那本书其实也很好,只是没有说为什么这么写,以及某些步骤的用意,这次看了野火的视频,发现挺符合我的需求,他们视频和我借的那本书思路是相同的,野火的视频能将那些东西详细讲解我觉得挺好,接下来就开始更新

整体代码思路:rx每次接收一位数据,先打一拍,从而与系统时钟同步----->再多打两拍消除亚稳态------>开始提取数据(包含起始位和终止位)------->使能信号置为高电平,抽效数据--------> 提取完八位有效数据后结束计数,并且关闭使能信号------>拼接数据并传输----->结束

附上代码

/*
串口重要的参数有波特率、数据位、起始位和停止位、奇偶校验位
波特率bps:一秒可以传输的bit位数,所以与时间有关(有时序逻辑)
数据位通常是8位,起始位是第一位,停止位通常是最后一位
起始位是低电平0,停止位是高电平1
因为是用于与pc的通信,所以还需要数据,所以输入有数据,clk,重置(感觉好像每个目前每个设计都有外部重置)
还要有一个rx信号,是因为他是传入fpga的信号(他应该就是接收数据的状态的意思空闲为1,起始为0结尾为1)
还要有一个的reg1打一拍,因为串行数据与时钟不同步,所以进行打一拍来进行信号的同步,然而rx作为一个异步信号被同步会进入亚稳态
因为实际上rx信号变化并不是理想的瞬间阶跃信号,而是一个斜坡信号,消除亚稳态要靠多级寄存器,所以用多打几拍来消除
单比特信号从高速时钟到低速时钟,通常打两个时钟拍就好,所以使用打拍一次后调整为同步,后面再打两拍就是为了消除亚稳态
消除亚稳态以后要清楚在什么位置提取有效数据,所以又要有一个标志信号(start_flag)表示开始提取数据,因为空闲状态和停止位都是高电平
起始位是低电平,所以可以利用下降沿来作为提取标志位的开始,保持一个时钟周期的高电平再回到低电平,直到下一次下降沿出现
还要有一个使能信号,表示数据提取开始的范围,他是在start_flag变为高电平以后他才开始变,因为是时序逻辑,所以他要落后一个时钟周期
接下来是重要的计数器,记录一个波特所占用的时间,来判断什么时候完成,50m时钟一个时钟周期为20ns,9600bps是一秒9600个数据,所以一个数据为
1/9600s,再除以20ns即可得到计数器满多少完成一个数据传输
使能信号为1时计数器才开始工作!!!
还要保证提取数据时数据是在最稳定的状态下提取,那么就是在bode计数器的一半时候这个时候数据最为稳定令为bit标志位
然而我们还是要注意,十个数据我们只需要中间八个,所以还要需要一个bit计数器因为总共是0-9,所以我们只需要1-8位,在使能端为1的时候,bit标志位每次拉高一个时钟周期
就自加1,提取完有效数据以后使能端便可变为低电平,此时比特计数器是要在使能端和bit标志位都为1的时候才开始计数,所以是一个与条件
最后要有一个数据拼接,由于rs232是lsb至msb,所以是最低位先输入,所以数据拼接是 rx_data = {reg3,rx_data[7:1]},所以相当于左移(相对来说是收集到的数据自己向左移)
收集数据完毕要有一个标志位,标志位为高电平以后,把收集数据传递给输出,,同时一个新的flag就可以诞生了

发送部分
重置和时钟都要有
,接收的并行数据(此时变为pi_data)以及flag确定是否可以发送

他的输出就是tx
*/

module uart_new

(

parameter	UART_BPS	=	'd9600,
			CLK_FREQ	=	'd50_000_000

)//宏定义

(
input wire sys_clk,
input wire sys_rst_n,
input wire rx,
output reg [7:0] po_data,
output reg po_flag
);

parameter BAUD_CNT_MAX = CLK_FREQ / UART_BPS;

reg rx_reg1;
reg rx_reg2;
reg rx_reg3;
reg start_flag;
reg word_en;
reg [12:0] baud_cnt;
reg bit_flag;
reg [3:0] bit_cnt;
reg [7:0] rx_data;
reg rx_flag;

//先打拍
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
rx_reg1 <= 1'b1;//因为初始值是高电平
else
rx_reg1 <= rx;
end

always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
rx_reg2 <= 1'b1;//因为初始值是高电平
else
rx_reg2 <= rx_reg1;
end

always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
rx_reg3 <= 1'b1;//因为初始值是高电平
else
rx_reg3 <= rx_reg2;
end

//接下来是开始标志
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
start_flag <= 1'b0;
else if ((rx_reg2 == 1'b0) && (rx_reg3 == 1'b1) && (word_en == 1'b0))//注意画波形图以后来写判断条件,同时由于是时序电路所以被产生的东西会比生其者慢一个时钟周期,
//所以写条件在start_flag上升沿前面半个时钟周期来判定,有点投机取巧,但是感觉应该可以,为什么还要有后面的条件是因为如果传输过程中reg2和3碰巧出现该情况则会出错,所以
//要记得相互制约
start_flag <= 1'b1;
else
start_flag <= 1'b0;//其他时候为低电平
end

//接下来是使能信号
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
word_en <= 1'b0;
else if(start_flag == 1'b1)
word_en <= 1'b1;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))//还是老规矩,变为0看前半个时钟周期其他的变量来写条件
word_en <= 1'b0;
else
word_en <= word_en;//我的理解是如果能有一个状态保持多个时钟周期,那么就要这么写
end

//baud计数器

always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
baud_cnt <= 13'b0;
else if ((baud_cnt == BAUD_CNT_MAX - 1'b1) || (word_en == 1'b0))//还有没有使能信号也要清零
baud_cnt <= 13'b0;
else if (word_en == 1'b1)
baud_cnt <= baud_cnt + 1'b1;
end

//bit_flag
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
bit_flag <= 1'b0;
else if(baud_cnt == BAUD_CNT_MAX / 2 - 1'b1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;//只保持一个时钟周期就这么写
end

//bit_cnt
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
bit_cnt <= 4'd0;//这个变量是多少位那么就要写多少
else if ((bit_cnt == 4'd8) && (bit_flag == 1'b1))//出现bug:是将4'd8写为了d0
bit_cnt <= 4'd0;
else if (bit_flag == 1'b1)
bit_cnt <= bit_cnt + 1'b1;
end

//rx_data
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
rx_data <= 8'd0;
else if ((bit_cnt >= 4'd1) && (bit_cnt <= 4'd8) && (bit_flag == 1'b1))//多一个条件来约束便于稳定
rx_data <= {rx_reg3,rx_data[7:1]};
end
//rx_flag
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
rx_flag <= 1'b0;
else if ((bit_cnt == 4'd8) && (bit_flag == 1'b1))
rx_flag <= 1'b1;
else
rx_flag <= 1'b0;
end

//po_data
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
po_data <= 8'd0;
else if ((rx_flag == 1'b1))
po_data <= rx_data;
end

always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
po_flag <= 1'b0;
else
po_flag <= rx_flag;
end

endmodule

仿真文件
`timescale 1ns / 1ns

module uart_new_tb();
reg sys_clk;
reg sys_rst_n;
reg rx;

wire [7:0] po_data;
wire po_flag;

initial begin//先是模拟产生两路信号,时钟加复位信号
sys_clk = 1'b1;
sys_rst_n = 1'b0;
rx = 1'b1;//这一步是在两路信号写完以后再写的,因为是空闲状态,所以得是高电平
#20
sys_rst_n = 1'b1;
end

always #10 sys_clk = ~sys_clk;//产生时间信号

initial begin//用initial对任务函数进行调用,直接使用任务名进行调用
#201
rx_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

//任务函数 关键词task 名称(端口列表,我感觉相当于形参) 最后要有endtask
task rx_bit
(
input [7:0] data
);

integer i;//定义一个常量用于循环

for (i = 0; i < 10; i = i + 1)
begin
case(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
#(520820);//模拟传输一个数据因为是5208个时钟周期,所以要等待520820

end

endtask

uart_new

(

			.UART_BPS(9600),
			.CLK_FREQ(50_000_000)

)//宏定义

uart_new_inst
(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.rx(rx),
//输入输出分开来
.po_data(po_data),
.po_flag(po_flag)
);

endmodule

仿真时要记得自己对照着仿真波形验证是否出现了想要的结果

标签:FPGA,clk,rx,sys,flag,串口,rst,bit,接收数据
From: https://www.cnblogs.com/cccofHIT/p/18083124

相关文章

  • FPGA入门笔记008——数码管动态扫描设计与验证
    #FPGA入门笔记008——数码管动态扫描设计与验证1、数码管动态扫描原理​ 8段数码管的结构图如图1所示:图1——8段数码管结构图(a为共阴极,b为共阳极)​ 对于共阴数码管需要给对应段以高电平才会使其点亮,而对于共阳极数码管则需要给低电平才会点亮。AC620上板载的是共阳极数......
  • FPGA通过I2C控制AT24C64
    文章目录前言一、代码设计框图二、IIC_drive模块设计2.1、模块接口:2.2、代码功能描述:2.3、IIC协议实现过程:三、EEPROM_ctrl模块设计3.1、模块接口:3.2、代码功能描述四、EEPROM_drive模块五、iic_top模块前言继上一篇FPGA学习_I2C总线协议内容,本文将基于FPGA通过I2......
  • 锁相环技术原理及FPGA实现(第四章4.1)
            经过前面几章的学习,我们已积累了设计锁相环电路的一些基本技能。根据作者的学习经验,这个阶段最期望的一定不是再去理解什么原理公式,学习什么方法思路。好比初次接触到羽毛球时,在网上看了一段中规中矩的教学视频,又刚好买回一支炫丽的球拍,走进球场,实在没有心情......
  • FPGA设计优化(3.7)
            设计规则1:对综合后的设计就要开始进行扇出分析,以尽早发现高扇出的网线,并评估其可能对设计造成的影响。report_high_fanout_nets的具体用法如Tcl代码9-1所示。代码第3行的选项-load_types生成的报告样例如图9-1所示。从此报告中可以看到网线rectify_reset的扇出......
  • FPGA静态时序分析与约束(二)、时序分析
    系列文章目录FPGA静态时序分析与约束(一)、理解亚稳态FPGA静态时序分析与约束(三)、读懂vivado时序报告文章目录系列文章目录前言一、时序分析基本概念1.1时钟抖动1.2时钟偏斜1.3时钟不确定性Uncertainty1.4建立时间和保持时间1.5启动沿和锁存沿二、时序分析基本步......
  • FPGA的VGA显示驱动部分知识点
    vga显示这边的的的知识点不难,在我写代码的时候却没能显示成功,现在重新设计一遍设计思路。根据下面的这个时序图,可以用计数器的方式来设计,在不同时间段选择显示情况。目前我电脑的副屏是一个1440*900的显示器,在网上找到了他的VGA时序图。根据这个与时序表对应。完成项目代码,最......
  • 初出茅庐的小李博客之串口屏开发一个音乐控制器UI
    串口屏介绍串口屏通常指的是一种带有串口接口的显示屏,可以通过串口与其他设备进行通信和控制。这种屏幕通常具有独立的控制器和显示功能,可以直接接入主控系统,实现信息的显示和交互。开发步骤准备UI素材准备了100张音量的图标,这里面还遇到了个小问题,这么多图片如何批量......
  • FPGA开发工具安装
    FPGA从零开始学习第一章工欲善其事必先利其器–各类工具安装FPGA开发工具安装软件配置和可能遇到的问题软件配置和可能遇到的问题FPGA从零开始学习前言一、软件安装中的问题1.Vivado的安装过程中的可能问题2.Modelsim的安装过程中的可能问题二、软件配置1.Mod......
  • 基于EP4CE6F17C8的FPGA数码管动态显示实例
    一、电路模块1、数码管开发板板载了6个数码管,全部为共阳型,原理图如下图所示,段码端引脚为DIG[0]~DIG[7]共8位(包含小数点),位选端引脚为SEL[0]~SEL[5]共6位。端口均为低电平有效。其实物图如下所示。数码管引脚分配见下表。2、时钟晶振开发板板载了一个50MHz的有源晶振,为系统......
  • FPGA常用通信协议——I2C(二)
    一、时序 上一篇中我们已经了解了I2C的基本时序,这里我们只考虑I2C的写数据,具体时序见下图 二、信号列表sta_flag开始信号,拉高时开始一次读或写sda_en三态门控制信号,控制SDA是输出还是输入ready_flag结束标志,传入下一个模块,提醒下一模块可继续发送write总线正在工作信号,......