FPGA之手把手教你做多路信号发生器(STM32与FPGA数据互传控制波形生成)

news2024/11/28 8:30:00

文章目录

  • 博主的念叨
  • 一、任务介绍
    • 1、本文目标
    • 2、设计思路
    • 3、设计注意事项
  • 二、设计代码
    • 1.顶层文件代码
    • 2.波形生成模块
    • 3.ROM例化
    • 4.PLL例化
    • 5.引脚分配
  • 总结


博主的念叨

博主建了一个技术资源分享的群,开源免费,欢迎进来唠嗑280730348

最近趁热打铁做了一个关于STM32与FPGA通信并且控制高速DA模块产生不同频率信号的正弦波、方波、三角波和锯齿波的项目,从中收获到了很多东西,也踩了一些雷和坑,将分为几篇文章将整个过程分享出来。

这一次准备分享的是将串口解析的出来的波形频率数据以及波形类型数据送入顶层文件中,通过调用不同的ROM核驱动高速DA模块产生对应的信号,通过调用IP核生成特定频率的时钟,使得正弦波能够贴近整数倍的输出频率。

本文参考正点原子EP4CE系列开发板的源码,做了部分修改

一、任务介绍

1、本文目标

实现STM32和FPGA的串口通信,并将STM32传输过来的频率信息和波形信息解析存入定义的reg变量中。通过调用ROM核内部的数据给高速DA模块发送数据,使得高速DA模块能够产生特定频率的波形

2、设计思路

根据设计需求,需要对串口数据进行接收解析,同时需要把解析的数据传送给内存单元。内存单元接收到数据以后传递到顶层模块,在顶层模块中作为其它子模块的输入条件,根据频率的不同改变计数次数,从而实现500KHz波形信号的生成以及其分频信号的产生。根据波形类型值的不同选择不同的波形单元

3、设计注意事项

1、串口通信属于异步通信方式,因此出现信号是不确定的,为了防止亚稳态的产生,需要对信号打拍子避免亚稳态的产生。

2、本文串口通信协议是 EA 频率高位 频率中位 频率低位 波形 AE,其中EA代表起始标志,AE代表停止标志。频率高位代表频率/65536,中位代表%65536/256,低位代表%65526%256。

3、串口波特率要对的上,否则无法进行正常通信

4、生成的波形模拟点数为128点,基频是锁相环产生的500KHz*128=64MHz信号

5、生成了多个ROM核,注意ROM核的调用

6、此设计与博主前面博文串口协议解析相关,请提前了解一下串口解析的过程

二、设计代码

1.顶层文件代码

module hs_da(
    input           sys_clk,       //时钟信号
	 input           sys_rst_n,     //复位信号
	 input           uart_rxd,      //串口接收
	 output          da_clk,        //da时钟
	 output [7:0]    da_data,       //da数据
	 output          uart_txd,       //串口发送
	 output          test_uart_txd       //串口发送

);
wire [6:0]   rd_addr;//ROM读地址
wire [7:0]   rd_data;//ROM读出的数据
wire [6:0]   rd_addr_ju;//ROM读地址
wire [7:0]   rd_data_ju;//ROM读出的数据
wire [6:0]   rd_addr_juchi;//ROM读地址
wire [7:0]   rd_data_juchi;//ROM读出的数据
wire [6:0]   rd_addr_sanjiao;//ROM读地址
wire [7:0]   rd_data_sanjiao;//ROM读出的数据
wire [23:0]  freq;
wire [7:0]   wave;
wire locked;
wire rst_n;
wire clk;
assign rst_n=sys_rst_n&locked;
assign test_uart_txd=uart_txd;
//例化串口接收数据和发送数据模块
uart_loopback_top u_uart_loopback_top(
    .sys_clk     (sys_clk),
	 .sys_rst_n   (sys_rst_n),
	 .uart_rxd    (uart_rxd),
	 .uart_txd    (uart_txd),
	 .freq        (freq),
    .wave        (wave)	 
);

da_wave_send u_da_wave_send(
    .clk               (clk),
	 .rst_n            (sys_rst_n),
	 .freq             (freq),
	 .wave             (wave),
	 .rd_data          (rd_data),
	 .rd_addr          (rd_addr),
	 .rd_data_ju       (rd_data_ju),
	 .rd_addr_ju       (rd_addr_ju),
	 .rd_data_juchi    (rd_data_juchi),
	 .rd_addr_juchi    (rd_addr_juchi),
	 .rd_data_sanjiao  (rd_data_sanjiao),
	 .rd_addr_sanjiao  (rd_addr_sanjiao), 
	 .da_clk           (da_clk),
	 .da_data          (da_data)
);
//正弦波例化
rom u_rom(
	.address ( rd_addr ),
	.clock   ( clk ),
	.q       ( rd_data )
);
//矩形波例化
rom_ju u_rom_ju(
	.address ( rd_addr_ju ),
	.clock   ( clk ),
	.q       ( rd_data_ju )
);

//锯齿波例化
rom_juchi u_rom_juchi(
	.address ( rd_addr_juchi ),
	.clock   ( clk ),
	.q       ( rd_data_juchi )
);

//三角波例化
rom_sanjiao u_rom_sanjiao(
	.address ( rd_addr_sanjiao ),
	.clock   ( clk ),
	.q       ( rd_data_sanjiao )
);
pll_clk u_pll_clk(
    .areset   (~sys_rst_n),
	 .inclk0   (sys_clk),
	 .c0       (clk),
	 .locked   (locked)
	 );
endmodule

输入总共定义三个变量,其中uart_rxd代表串口输入接入。sys_clk代表输入50MHz基准时钟,sys_rst_n代表复位信号。输出da_clk是提供给高速AD/DA模块的时钟,确保高速AD/DA模块能够与FPGA发送数据进行同步。da_data代表发送给DA模块的八位数据信号,uart_txd代表串口发送输出引脚,在其中还设置了test_uart_txd引脚,这个引脚可留下也可去除,主要目的是为了连接到FPGACH340的TX引脚,用于数据发送至电脑端进行数据的查看。

定义了许多wire变量。以正弦波为例,定义正弦波ROM地址为rd_addr,正弦波读出数据为rd_data。其中地址设置为七位,数据设置为八位。原因在于高速AD/DA模块可以接收0-255的数据,而我们整个周期只需要模拟出128个点即可,因此地址位为7位。以下带后缀的ju,juchi,sanjiao也都同理。另外freq和wave是由串口回环模块输出的变量,在顶层文件中被调用。locked,rst_n以及clk信号都是pll锁相环生成的。需要注意的是,我在设计中遇到了一个问题是在底层文件中进行PLL的时钟调用无效,但是到顶层中调用时钟就可行了,网上说PLL时钟的调用必须得放到顶层,然后赋给底层,如果有知道的小伙伴可以在评论区下方留言。

第一个assign语句的作用是生成锁相环输出时钟信号的复位信号,第二个assign语句的作用是连接串口线,也即前文提到的连接到ch340模块TX引脚的接口。

实例化da_wave_send模块,输入包括前面12路信号,输出为da_clk和da_data,其中da_clk直接与锁相环输出的64MHz信号连接起来。rom的例化都是按照指定格式进行例化的,大家只需要注意例化格式即可。

pll锁相环的例化也是如此,在这里博主只选择了一路锁相环的输出,因此输出信号也只有一个c0信号,若需要更改锁相环信号的输出可在ip核界面直接进行修正即可。

2.波形生成模块

module da_wave_send(
    input                 clk    ,  //时钟
    input                 rst_n  ,  //复位信号,低电平有效
    input        [23:0]   freq   ,  //频率信号
	 input        [7:0]    wave   ,  //波形信号
	 
    input        [7:0]    rd_data,  //ROM读出的数据
    output  reg  [6:0]    rd_addr,  //读ROM地址
    input        [7:0]    rd_data_ju,  //ROM读出的数据
    output  reg  [6:0]    rd_addr_ju,  //读ROM地址
    input        [7:0]    rd_data_juchi,  //ROM读出的数据
    output  reg  [6:0]    rd_addr_juchi,  //读ROM地址
    input        [7:0]    rd_data_sanjiao,  //ROM读出的数据
    output  reg  [6:0]    rd_addr_sanjiao,  //读ROM地址	 
    //DA芯片接口
    output                da_clk ,  //DA(AD9708)驱动时钟,最大支持125Mhz时钟
    output  reg  [7:0]    da_data   //输出给DA的数据  
    );

//频率调节控制
wire [23:0] FREQ_ADJ;               
assign FREQ_ADJ= 24'd500_000/freq-1'b1;//分频频率
//assign FREQ_ADJ = 8'd1;//分频频率
//reg define
reg    [19:0]    freq_cnt  ;  //频率调节计数器

//*****************************************************
//**                    main code
//*****************************************************

//数据rd_data是在clk的上升沿更新的,所以DA芯片在clk的下降沿锁存数据是稳定的时刻
//而DA实际上在da_clk的上升沿锁存数据,所以时钟取反,这样clk的下降沿相当于da_clk的上升沿
assign  da_clk = ~clk;       

//频率调节计数器
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        freq_cnt <= 20'd0;
    else if(freq_cnt == FREQ_ADJ)    
        freq_cnt <= 20'd0;
    else         
        freq_cnt <= freq_cnt + 20'd1;
end

always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        da_data <= 8'd0;
    else begin   
        case (wave)
		      8'd0:da_data = rd_data;
				8'd1:da_data = rd_data_ju;
				8'd2:da_data = rd_data_juchi;
				8'd3:da_data = rd_data_sanjiao;
				default da_data = 8'd0;
		  endcase
	 end 
end
//读ROM地址
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        rd_addr <= 7'd0;
    else begin
        if(freq_cnt == FREQ_ADJ) begin
          case (wave)
		        8'd0:rd_addr <= rd_addr + 7'd1;
				  8'd1:rd_addr_ju <= rd_addr_ju + 7'd1;
				  8'd2:rd_addr_juchi <= rd_addr_juchi + 7'd1;
				  8'd3:rd_addr_sanjiao <= rd_addr_sanjiao + 7'd1;
				  default rd_addr <= rd_addr + 7'd1;
		    endcase		  
        end    
    end            
end

endmodule

从代码中可见,定义了wire变量FREQ_ADJ,即我们当前的频率计数值。通过assign语句赋值,由于我们最高的频率为500KHz,因此分频计数值为500000/freq-1,假设我们接收到的频率数据为500000,那么计数值就为0。计数值为0的时候在程序内部每隔500KHz重新读一遍地址,从而也就实现了500KHz频率的输出,freq为250000的时候计数值为1就是250KHz频率输出。

第一个always语句是对频率进行计数,由于此时输入的时钟为64MHz,因此此always语句是每隔64MHz执行一次的。第二个always语句是对当前的da_data进行一个赋值,根据wave变量的不同将对应ROM里面的值赋值给da_data。第三个always语句是执行ROM的读取,当计数值等于设定的分频计数值时,就会对ROM进行一次数据读取。根据波形的不同对应的波形ROM地址就会加1,此时读取出来的ROM值就会对应不同的波形ROM。要注意的是,ROM只能够被读取而不能被写入,因此只需要改变读取的地址就能够读到不同地址对应的不同数据。

3.ROM例化

ROM的例化主要包括几个步骤:

1、生成mif文件。这个mif文件由对应的波形生成软件生成,我们可以设置波形的宽度和深度。其中深度就是波形一个完整周期的点数,宽度指的是波形的位宽。在这里我们需要128个点,和最大255的数据,就需要设置宽度为8位,深度为7位。

在这里插入图片描述
2、生成mif文件放入到doc文件夹里面,然后打开Quartus的ip核,搜索ROM
在这里插入图片描述
在这里插入图片描述
我这里由于是已经生成了,所以选择编辑已存在的
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
即可

4.PLL例化

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.引脚分配

在这里插入图片描述


总结

有什么不懂的可以在下方留言,只要学会了方法,实现信号发生器会比较简单的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/576909.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

pandas库的常用操作介绍

目录 1.1.Pandas概述2.Pandas索引结构3.groupby学习5.Pandas数值运算二元统计6.对象操作7.merge合并显示设置9.pivot操作10. 时间操作11.常用操作12.groupby操作13.字符串操作14.索引操作15.pandas绘图操作 1.1.Pandas概述 Python的pandas库是一个数据处理和数据分析库&#x…

javascript基础七:说说你对Javascript中作用域的理解?

一、作用域 作用域&#xff0c;即变量&#xff08;变量作用域又称上下文&#xff09;和函数生效&#xff08;能被访问&#xff09;的区域或集合 换句话说&#xff0c;作用域决定了代码区块中变量和其他资源的可见性 举个粟子 function myFunction(){let name小爱同学 } undef…

6.2:荷兰国旗问题

文章目录 实现key前面的数都小于等key&#xff0c;key后面的数都大于等于key1&#xff1a;前后指针法&#xff1a;2&#xff1a;挖坑法3&#xff1a;单指针法&#xff08;左神&#xff09; 辗转相除法求最大公约数 实现key前面的数都小于等key&#xff0c;key后面的数都大于等于…

【leetCode:剑指 Offer】20. 表示数值的字符串

1.题目描述 请实现一个函数用来判断字符串是否表示数值&#xff08;包括整数和小数&#xff09;。 数值&#xff08;按顺序&#xff09;可以分成以下几个部分&#xff1a; 若干空格 一个 小数 或者 整数 &#xff08;可选&#xff09;一个 e 或 E &#xff0c;后面跟着一个 …

深入篇【C++】类与对象:详解内部类+匿名对象+编译器对拷贝的优化

这里写目录标题 Ⅰ.内部类【特点】1.天生友元2.直接访问static成员3.访问限制符限制4.外部类的大小 Ⅱ.匿名对象【特点】1.一行生命域2.对象具有常性3.可强行续命 Ⅲ.拷贝对象时编译器的优化 Ⅰ.内部类 概念&#xff1a;一个类定义在另一个类内部&#xff0c;这个内部的类就叫做…

【Unity100个实用小技巧】如何修改UI上材质的Shader

☀️博客主页&#xff1a;CSDN博客主页&#x1f4a8;本文由 萌萌的小木屋 原创&#xff0c;首发于 CSDN&#x1f4a2;&#x1f525;学习专栏推荐&#xff1a;面试汇总❗️游戏框架专栏推荐&#xff1a;游戏实用框架专栏⛅️点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd;&#…

mysql基本操作1

库的基本操作 1.show variables like character_set_database 查看系统默认的字符集&#xff0c;若是指定数据库下使用该SQL&#xff0c;则查看的是该数据库对应的字符集。 2.show variables like collation_database 查看系统默认的字符集校验规则&#xff0c;指定数据库下使用…

Java 集合 - List 接口

文章目录 1.List 接口介绍2.List 接口常用 API3.ListIterator 迭代器4.ArrayList - 动态数组4.1 ArrayList 概述4.2 手撸动态数组 5.Vector - 动态数组6.LinkedList - 双向链表6.1 链表概述6.2 手撸双链表6.3 链表与动态数组的区别 7.Stack - 栈8.总结 1.List 接口介绍 在 Jav…

Linux:查看主机运行状态的一系列命令:top、df、iostat、sar

Linux&#xff1a;查看主机运行状态的一系列命令&#xff1a;top、df、iostat、sar 命令top监控系统资源&#xff1a; 使用top(回车)命令后&#xff0c;整个控制台会变成任务管理器的形式&#xff1a; 退出可以使用&#xff1a;ctrlc 或 q 第一行补充&#xff1a;表示正在执行的…

高程复习 欧几里得算法和扩展欧几里得算法考试前冲刺简约版

gcd(m,n)gcd(n,m%n) gcd欧几里得算法标准代码求最大公约数 #include <iostream>using namespace std;typedef long long LL; LL gcd(int a,int b) {if(b0)return a;return gcd(b,a%b); } int main() {LL a,b;cin>>a>>b;cout<<gcd(a,b)<<endl;re…

Linux基础:文件权限详细说明(全)

一、前提 我们要知道&#xff0c;Linux系统&#xff0c;一切皆文件的含义。 对于Linux来说&#xff0c;一切皆文件。 我们常涉及到的概念是目录和文件。 权限主要有三种&#xff1a;r(读)w(写)x(执行)。 二、正文 1、修改文件或者目录所属用户和所属组 chown [用户名[:组名…

规则网络构建

规则网络构建 文章目录 规则网络构建[toc]1 规则网络定义2 规则网络的构建3 代码实现 1 规则网络定义 常见规则网络包包括全局耦合网络、最近邻耦合网络和星型耦合网络&#xff0c;三种规则网络定义如下&#xff1a; (1)全局耦合网络&#xff1a;任意两个节点均存在连边的网络…

云原生 HTAP -- PolarDB-IMCI:A Cloud-Native HATP Database

文章目录 0 背景1 IMCI 架构1.1 架构演进的背景1.2 基本架构1.2 基本使用1.4 列索引存储 设计1.5 RW-RO 的数据同步实现1.5.1 CALS1.5.2 2P-COFFER 1.6 计算引擎实现1.7 性能 近期除了本职工作之外想要再跟进一下业界做讨论以及落地的事情&#xff0c;扩宽一下视野&#xff0c;…

算法7.从暴力递归到动态规划0

算法|7.从暴力递归到动态规划0 1.汉诺塔 题意&#xff1a;打印n层汉诺塔从最左边移动到最右边的全部过程 解题思路&#xff1a; 把字母抛掉&#xff0c;变成左中右三个盘子多个盘子能一下到吗&#xff1f;不能&#xff0c;把上边的拿走&#xff0c;最下边的才能放到指位置(…

java汉字转拼音pinyin4j-2.5.0.jar用法

要先下载哦&#xff0c; pinyin4j下载链接 可能会出现Cannot resolve symbol ‘net’&#xff0c;找到上面文件的下载路径&#xff0c;IDEA中File->Project Structure -> Modules->Dependencies import java.util.*; import net.sourceforge.pinyin4j.PinyinHelper;…

算法基础学习笔记——⑬质数\约数

✨博主&#xff1a;命运之光 ✨专栏&#xff1a;算法基础学习 目录 ✨质数 &#x1f353;&#xff08;1&#xff09;质数的判定——试除法 &#x1f353;&#xff08;2&#xff09;分解质因数——试除法 ✨约数 &#x1f353;&#xff08;1&#xff09;试除法求一个数的所…

算法基础学习笔记——⑪拓扑排序\最短路

✨博主&#xff1a;命运之光 ✨专栏&#xff1a;算法基础学习 目录 ✨拓扑排序 &#x1f353;朴素dijkstra算法&#xff1a; &#x1f353;堆优化版dijkstra : &#x1f353;Bellman-Ford算法 &#x1f353;spfa 算法&#xff08;队列优化的Bellman-Ford算法&#xff09; …

操作系统(2.8)--线程的实现

目录 线程的实现方式 1.内核支持线程(KST) 2.用户级线程(ULT) 3.组合方式 线程的实现 1.内核支持线程的实现 2.用户级线程的实现 线程的创建和终止 线程的实现方式 1.内核支持线程(KST) 内核支持线程&#xff0c;与进程相同&#xff0c;是在内核的支持下运行的&#x…

二叉树及其相关题目相关的功能的实现

前言&#xff1a;前面我们简单提及了二叉树的相关初级知识和顺序实现二叉树的相关操作详解&#xff0c;并且由完全二叉树延伸到了堆的相关知识&#xff0c;具体详见二叉树初阶和堆的详解&#xff0c;今天&#xff0c;我们展开二叉树的相关 的链式实现操作和经常考察的二叉树的相…

2023 华为 Datacom-HCIE 真题题库 07--含解析

多项选择题 1.[试题编号&#xff1a;190187] &#xff08;多选题&#xff09;如图所示的拓扑采用了VXLAN分布式网关&#xff0c;SW1上的VBDIF10配置了&#xff1a;arp-proxy local enable命令&#xff0c;则以下描述中正确的有哪些项&#xff1f; A、SW1收到PC1发往PC2的报文&…