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

strongerHuang 2025-12-24 08:00

大多电子工程师都喜欢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模块,旋钮模块,分频器模块,锁相环模块。

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

硬件介绍

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

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

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

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

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

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

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

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

主要代码片段及说明

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.旋钮模块以及分频模块

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

资源占用

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

项目总结

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

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


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


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

声明:内容取材于网络,仅代表作者观点,如有内容违规问题,请联系处理。 
FPGA MCU
more
Altera重启征程:FPGA巨头的独立宣言与AI时代的“再编程”革命
fpga实现音频预加重(pre-emphasis)滤波器
基于FPGA的任意波形发生器(DDS)设计
国产FPGA,打入高端局
国际先进!中科亿海微国产嵌入式FPGA IP核及EDA系统设计技术通过科技成果评价
相控阵天线主要组件及FPGA应用
资料汇总|FPGA软件安装包、书籍、源码、技术文档…(2026.02.28更新)
FPGA漫谈:2K LUT能干点啥?
广东发力!GPU、FPGA、NPU 高端通用AI芯片!
FPGA/SoC+x86 AMD 技术峰会(12月16日 深圳)
Copyright © 2025 成都区角科技有限公司
蜀ICP备2025143415号-1
  
川公网安备51015602001305号