news 2026/4/15 11:48:39

不靠 MCU,用 FPGA + DAC 实现可调信号源

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
不靠 MCU,用 FPGA + DAC 实现可调信号源

大多电子工程师都喜欢DIY,今天给大家分享一个不靠 MCU,用 FPGA + DAC 实现可调信号源的项目。利用板载 125MSPS 高速 DAC,从 DDS 原理出发,完整实现了一台可输出正弦波、三角波、方波的可调波形发生器。

项目介绍

1.通过板上的高速DAC(10bits/125Msps)配合FPGA内部DDS的逻辑,生成波形可调(正弦波、三角波、方波)、频率可调(DC-)、幅度可调的波形。 2.生成模拟信号的频率范围为DC-20MHz,调节精度为1Hz。 3.生成模拟信号的幅度为最大1Vpp,调节范围为0.1V-1V。 4.在OLED上显示当前波形的形状、波形的频率以及幅度。 5.利用板上旋转编码器和按键能够对波形进行切换、进行参数调节。

设计思路

本次实验中使用的模块包括OLED显示模块,DDS模块,旋钮模块,分频器模块,锁相环模块。

硬件介绍

本次使用的是硬禾学堂提供的基于小脚丫FPGA的电赛训练平台。使用的综合软件是Lattic Diamond。

小脚丫STEP企业店在售的便携示波器:

实现的功能以及图片展示:

主要代码片段及说明

1.顶层模块

通过顶层代码连接各个模块并传递参数。Verilog部分代码如下:

module TOP_1( input clk_in, //系统时钟 input rst_n_in, //系统复位,低有效 input key_a, //旋转编码器A管脚 input key_b, //旋转编码器B管脚 input change, //切换按钮 频率 幅度 input way, //切换按钮 波形图像 output oled_rst, //OLCD液晶屏复位 output oled_dcn, //OLCD数据指令控制 output oled_clk, //OLCD时钟信号 output oled_dat, //OLCD数据信号 output dac_clk, output [9:0] dac_data);//////略///dds_main u1( .clk(clkop), .dac_data(dac_data), .dac_clk(dac_clk), .check(check_1), .frequence(F), .range(range));posedge_check posedge_check_u5( .clk(clk_in), .rst_n(rst_n_in), .check(way), .pos_check(pos)); a120M a120M_u3 ( .CLKI(clk_in), .CLKOP(clkop) );OLED_12864 OLED_12864_u1 ( .clk (clk_in) , .rst_n (rst_n_in), .data(oleddata), .data1(data1), .state1(state1), .way(way_1), .oled_csn(oled_csn), .oled_rst(oled_rst), .oled_dcn(oled_dcn), .oled_clk(oled_clk), .oled_dat(oled_dat));TEMP_1 TEMP_1_u2 ( .clk(clk_in), .res(rst_n_in), .indata1(key1), .indata2(key2), .change(sum), .data(oleddata), .state1(state1), .data1(data1));XUANNIU_1 XUANNIU_1_u3( .clk(clk_in), //系统时钟 .rst_n(rst_n_in), //系统复位,低有效 .key_a(key_a), //旋转编码器A管脚 .key_b(key_b), //旋转编码器B管脚 .clk_500us(clk_500us), .key1(key1), .key2(key2), .L_pulse(L_pulse), .R_pulse(R_pulse));DIVIDE_1 #(.WIDTH(32),.N(6)) u4 ( .clk(clk_in), .rst_n(rst_n_in), .clkout(clk_500us) );endmodule

2.OLED模块

通过SPI协议向OLED屏幕书写数据,OLED模块中连接着ASCII码模块,可以通过旋钮模块进行动态调节数据,显示生成波形的频率和幅度。Verilog部分代码如下(部分代码参考电子森林开原代码):

module OLED_12864( input clk, //12MHz系统时钟 input rst_n, //系统复位,低有效 input [3:0] sw, input key_a, input key_b, input [63:0] data, input [7:0] data1, input state1, input [1:0] way, output reg oled_csn, //OLCD液晶屏使能 output reg oled_rst, //OLCD液晶屏复位 output reg oled_dcn, //OLCD数据指令控制 output reg oled_clk, //OLCD时钟信号 output reg oled_dat //OLCD数据信号);////// 略///MAIN:begin6'd0 : begin state <= INIT; end6'd1 : begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end6'd2 : begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end 6'd3 : begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end6'd4 : begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end6'd5 : begin y_p <= 8'hb4; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end6'd6 : begin y_p <= 8'hb5; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end6'd7 : begin y_p <= 8'hb6; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end6'd8 : begin y_p <= 8'hb7; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end 6'd9 : begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; mem_hanzi_num <= 8'd0; state <= CHINESE; end6'd10: begin y_p <= 8'hb0; x_ph <= 8'h11; x_pl <= 8'h00; mem_hanzi_num <= 8'd2; state <= CHINESE; end6'd11: begin y_p <= 8'hb0; x_ph <= 8'h12; x_pl <= 8'h00; mem_hanzi_num <= 8'd4; state <= CHINESE; end6'd12: begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; mem_hanzi_num <= 8'd6; state <= CHINESE; end6'd13: if(way == 2'b00) begin y_p <= 8'hb0; x_ph <= 8'h15; x_pl <= 8'h00; mem_sin_num <= 8'd0; state <= SIN; end else if(way == 2'b01) begin y_p <= 8'hb0; x_ph <= 8'h15; x_pl <= 8'h00; mem_sin_num <= 8'd8; state <= SIN; end else if(way == 2'b10) begin y_p <= 8'hb0; x_ph <= 8'h15; x_pl <= 8'h00; mem_sin_num <= 8'd16; state <= SIN; end else if(way == 2'b11) begin y_p <= 8'hb0; x_ph <= 8'h15; x_pl <= 8'h00; mem_sin_num <= 8'd24; state <= SIN; end 6'd14: begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; mem_hanzi_num <= 8'd8; state <= CHINESE; end6'd15: begin y_p <= 8'hb3; x_ph <= 8'h11; x_pl <= 8'h00; mem_hanzi_num <= 8'd10; state <= CHINESE; end6'd16: begin y_p <= 8'hb5; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd10; char <= {data,"HZ"};state <= SCAN; end 6'd17: begin y_p <= 8'hb6; x_ph <= 8'h10; x_pl <= 8'h00; mem_hanzi_num <= 8'd12; state <= CHINESE; end6'd18: begin y_p <= 8'hb6; x_ph <= 8'h11; x_pl <= 8'h00; mem_hanzi_num <= 8'd14; state <= CHINESE; end6'd19: if(state1 == 1) begin y_p <= 8'hb7; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd4; char <= {"0.",data1,"v"};state <= SCAN; end else if(state1 == 0)begin y_p <= 8'hb7; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd4; char <= {" ",data1,"v"};state <= SCAN; end6'd21: begin cnt_main <= 6'd9; endendcase

3.ASCII码转换模块

通过旋钮进行自加自减,最后通过assign进行拼接,完成OLED动态显示过程中的变量参数设计。Verilog部分代码如下:

module TEMP_1( clk, res, indata1, indata2, change, data, state1, data1);////// 略///always@(posedge clk or negedge res) begin if(!res)begin temp[0] <= 8'b0011_0000; temp[1] <= 8'b0011_0000; temp[2] <= 8'b0011_0001; temp[3] <= 8'b0011_0010; temp[4] <= 8'b0011_0000; temp[5] <= 8'b0011_0000; temp[6] <= 8'b0011_0000; temp[7] <= 8'b0011_0000; temp1 <= 8'b0011_0001; state1 <= 1; end else if (change == 1)begin if(indata2 == 1'b1)begin if(temp[7] > 8'b0011_0000)begin temp[7] <= temp[7] - 1'b1; end else if(temp[7] == 8'b0011_0000)begin temp[7] <= 8'b0011_1001; if(temp[6] > 8'b0011_0000)begin temp[6] <= temp[6] - 1'b1; end else if(temp[6] == 8'b0011_0000)begin temp[6] <= 8'b0011_1001; if(temp[5] > 8'b0011_0000)begin temp[5] <= temp[5] - 1'b1; end else if(temp[5] == 8'b0011_0000)begin temp[5] <= 8'b0011_1001; if(temp[4] > 8'b0011_0000)begin temp[4] <= temp[4] - 1'b1; end else if(temp[4] == 8'b0011_0000)begin temp[4] <=8'b0011_1001; if(temp[3] > 8'b0011_0000)begin temp[3] <= temp[3] - 1'b1; end else if(temp[3] == 8'b0011_0000)begin temp[3] <= 8'b0011_1001; if(temp[2] > 8'b0011_0000)begin temp[2] <= temp[2] - 1'b1; end else if(temp[2] == 8'b0011_0000)begin temp[2] <= 8'b0011_1001; if(temp[1] > 8'b0011_0000)begin temp[1] <= temp[1] - 1'b1; end else if(temp[1] == 8'b0011_0000)begin temp[1] <= 8'b0011_1001; if(temp[0] > 8'b0011_0000)begin temp[0] <= temp[0] - 1'b1; end end end end end end end end end////// 略///assign data1 = temp1;assign outdata1 = temp[0];assign outdata2 = temp[1];assign outdata3 = temp[2];assign outdata4 = temp[3];assign outdata5 = temp[4];assign outdata6 = temp[5];assign outdata7 = temp[6];assign outdata8 = temp[7];assign data = {outdata1,outdata2,outdata3,outdata4,outdata5,outdata6,outdata7,outdata8};

4.DDS模块

通过相位累加器完成频率的控制Verilog代码如下(部分参考电子森林开源代码):

module dds_main( clk, frequence, check, range, dac_data, dac_clk);input clk;input [1:0] check;output [9:0] dac_data; output dac_clk;input [31:0] frequence;input [3:0] range;wire [3:0] range_1;assign range_1 = range;wire [31:0] next_phase;wire [7:0] phase;reg [31:0] a;// 相位累加器assign next_phase = (32'h00000024 + frequence * 32'h24) + a;always@(posedge clk) a <= next_phase;assign phase = a[31:24];wire [9:0] sine_data;lookup_tables u_lookup_tables(phase,check,range_1,sine_data);assign dac_data = sine_data;assign dac_clk = ~clk;endmodule

5.正弦波 三角波 方波的波表以及幅度调节模块

通过制作的波表使DAC可以输出想要的波形,此模块中还含有波形幅度控制模块,可以配合旋钮进行波形幅度的控制。Verilog部分代码如下(部分参考电子森林开源代码):

module lookup_tables( phase, check, range, sin_out);////// 略///always @(sel or sine_table_out or phase)begin if(check == 2'b00) begin case(sel) 2'b00: begin sine_onecycle_amp = 9'h12C+sine_table_out[8:0]; address = phase[5:0]; end 2'b01: begin sine_onecycle_amp = 9'h12C+sine_table_out[8:0]; address = ~phase[5:0]; end 2'b10: begin sine_onecycle_amp = 9'h12C-sine_table_out[8:0]; address = phase[5:0]; end 2'b11: begin sine_onecycle_amp = 9'h12C-sine_table_out[8:0]; address = ~ phase[5:0]; end endcase end else if(check == 2'b01) begin case(sel) 2'b00: begin sine_onecycle_amp = sine_table_out[8:0]; address1 = phase[7:0]; end 2'b01: begin sine_onecycle_amp = sine_table_out[8:0]; address1 = phase[7:0]; end 2'b10: begin sine_onecycle_amp = 9'd315 + sine_table_out[8:0]; address1 = phase[7:0]; end 2'b11: begin sine_onecycle_amp = 9'd315 + sine_table_out[8:0]; address1 = phase[7:0]; end endcase end else if(check == 2'b10) begin case(sel) 2'b00: begin sine_onecycle_amp = sine_table_out[8:0]; address2 = phase[7:0]; end 2'b01: begin sine_onecycle_amp = 9'd315 + sine_table_out[8:0]; address2 = phase[7:0]; end 2'b10: begin sine_onecycle_amp = 9'd315 + sine_table_out[8:0]; address2 = phase[7:0]; end 2'b11: begin sine_onecycle_amp = sine_table_out[8:0]; address2 = phase[7:0]; end endcase endend////// 略///module sin_table(address,address1,address2,sin,check);output [8:0] sin;input [5:0] address;input [7:0] address1;input [7:0] address2;input [1:0] check;reg [9:0] state;reg [8:0] sin;localparam SIN = 10'h1, Triangle = 10'h2, Square = 10'h4;always @(address) begin if(check == 2'b00) state <= SIN; else if(check == 2'b01) state <= Square; else if(check == 2'b10) state <= Triangle; case(state) SIN:begin case(address) 6'd0: sin=9'd 0 ; 6'd1: sin=9'd 7 ; 6'd2: sin=9'd 15 ; 6'd3: sin=9'd 3 ; 6'd4: sin=9'd 29 ; 6'd5: sin=9'd 36 ; 6'd6: sin=9'd 44 ; 6'd7: sin=9'd 51 ;////// 略///

6.旋钮模块以及分频模块

借鉴电子森林开源代码,这里就不过多描述了。

资源占用

项目总结

遇到的主要难题及解决方法

在DDS模块中,频率的控制和幅度的控制需要统一,所以要同步控制三种波形,需要提前计算或者一步一步尝试。

在OLED模块部分,一开始没有什么思路,通过学习电子森林开源代码了解了SPI协议的书写,完善了动态输出过程中ASCII转换的问题,在后面的学习中发现可以通过左移加三算法进行快速计算,相比我自己写出来的部分要好很多,但由于时间较紧张就没有进行完善。

------------END------------

分享一个通用嵌入式软件架构分层设计思想!

分享一个提高MCU计算能力的神器!

实操,在单片机上移植CMSIS-NN神经网络库

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/7 19:08:01

GPT-SoVITS语音合成联合国多语言倡议:促进平等沟通

GPT-SoVITS语音合成&#xff1a;构建联合国多语言平等沟通的技术桥梁 在安理会一场紧急会议的录音档案中&#xff0c;某位已故外交官沉稳坚定的声音正通过广播回响——他用阿拉伯语发表的和平呼吁&#xff0c;如今以同样的音色被精准转换为中文、俄文和西班牙语&#xff0c;语气…

作者头像 李华
网站建设 2026/4/15 3:18:58

高效利用usb_burning_tool进行双分区烧录完整示例

用usb_burning_tool玩转双分区烧录&#xff1a;从零开始的实战指南你有没有遇到过这样的场景&#xff1f;新到一批开发板&#xff0c;要刷固件。以前是插SD卡、进系统、一条条敲命令&#xff0c;反复重启&#xff0c;一不小心就把bootloader写错了版本&#xff0c;设备直接“变…

作者头像 李华
网站建设 2026/4/15 1:53:48

Packet Tracer官网下载实战案例:模拟网络架构教学演示

用Packet Tracer搭建真实网络&#xff1a;从零开始的教学实战指南 你有没有遇到过这样的情况&#xff1f;讲了一整堂课的VLAN和子网划分&#xff0c;学生眼神依然迷茫。下课后他们问&#xff1a;“老师&#xff0c;你说的数据包到底在设备之间是怎么走的&#xff1f;”——那一…

作者头像 李华
网站建设 2026/4/13 4:11:49

GPT-SoVITS语音克隆可解释性研究:模型到底学到了什么?

GPT-SoVITS语音克隆可解释性研究&#xff1a;模型到底学到了什么&#xff1f; 在AI生成内容日益泛滥的今天&#xff0c;我们越来越难分辨一段声音是否来自真人。一条用明星音色朗读的广告、一个虚拟主播流畅讲述外语新闻——这些背后往往藏着像 GPT-SoVITS 这样的少样本语音克隆…

作者头像 李华
网站建设 2026/4/11 21:12:07

2025专科生必备!10个降AI率工具测评榜单

2025专科生必备&#xff01;10个降AI率工具测评榜单 专科生如何高效应对AI率检测&#xff1f;测评维度解析 随着人工智能技术的快速发展&#xff0c;AIGC&#xff08;AI生成内容&#xff09;检测系统在学术领域中的应用日益广泛。对于2025年的专科生而言&#xff0c;论文、报告…

作者头像 李华
网站建设 2026/4/6 14:11:34

STM32CubeMX安装包手把手安装步骤详解(小白适用)

从零开始手把手安装 STM32CubeMX&#xff08;新手避坑指南&#xff09; 你是不是刚接触嵌入式开发&#xff0c;想用STM32却卡在了第一步—— 连软件都装不上 &#xff1f; 别急。很多初学者以为“写代码”是最难的&#xff0c;结果真正拦住他们的&#xff0c;反而是环境搭建…

作者头像 李华