找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3579

积分

0

好友

474

主题
发表于 11 小时前 | 查看: 2| 回复: 0

本项目基于 Lattice LCMXO2 系列 FPGA,结合高速 DAC 模块,实现了一台 频率、幅值、波形类型均可调的 DDS 任意波形发生器。系统支持正弦波、三角波、锯齿波和方波输出,最高频率可达 20MHz,最小调节精度 1Hz,并通过 OLED 实时显示参数,按键与旋转编码器完成交互控制。

这是一个覆盖 FPGA 架构设计、DDS 原理、数模转换、外设驱动与系统调试 的完整工程实践。

项目介绍

本设计主要实现了以下功能:

  1. 波形类型可调(正弦波、锯齿波、三角波、方波)。
  2. 频率可调0-20MHz,最低调节精度1Hz,可选调节累加值。
  3. 幅度可调0-1V,累加值为0.1v。
  4. 通过OLED显示当前输出波形的类型、频率、幅度;显示频率调节累加值;显示当前可调模式。
  5. 通过按键实现三种调节模式切换,通过旋转编码器调节波形、频率、频率累加值、幅度。

使用说明

*1.通过按键K1控制模式切换(调节波形、频率、幅值),OLED的用于指示当前选择的调节模式。**

2.OLED第一行显示波形类型,第二行显示幅值,第三行显示频率,第四行显示频率调节累加值。

3.通过旋转编码器左右转实现波形切换、幅值改变(每次0.1V)、频率改变。

4.当处在修改频率模式时,可通过按下旋转编码器修改频率累加值(ACC),可按照1Hz、10Hz、100Hz、1KHZ、10KHZ、100KHZ、1MHZ修改频率。

OLED显示DDS参数

硬件介绍

  • FPGA:Lattice LCMXO2-4000HC-4MG132
  • 基于3Peaks的3PD5651制作的高速DAC模块
  • OLED显示屏168*64,SPI三线通信
  • 按键和旋转编码器

设计思路

本次设计采用了自上而下的设计方法,首先定义顶层模块功能,然后将需要实现的功能设计成多个子模块,分别对多个子模块进行设计验证,验证无误后将子模块汇总,最终实现整个设计。

DDS系统顶层模块框图

  • 其中时钟模块通过IP核生成,输出120MHz时钟;
  • 按键消抖和编码器驱动模块移植官方参考例程,未作修改;
  • DDS生成和OLED显示模块基于官方例程进行了修改;
  • 参数控制模块独立编写。

DDS系统详细连接框图

主要代码片段

1.DDS参数控制模块

module dds_ctrl
(
    input               clk,        //时钟
    input               rst,        //复位
    input               Left_pulse, //旋转编码器
    input               Right_pulse,//旋转编码器
    input               OK_pulse,   //旋转编码器
    input               key_pulse,  //按键

    output reg [1:0]    fun_sel,    //功能选择,0调类型,1调幅值,2调频率,3调原始波形

    output reg [1:0]    wave_sel,   //波形类型
    output reg [2:0]    pulse_cnt,  //频率调节步进
    output reg [31:0]   phase_fo,   //频率
    output reg [3:0]    amp_sel,    //幅度可调范围 0.1-1  
    output reg          raw_sel     //调节波形输出,0:经调幅之后的波形 ,1:原始波形
);
localparam WAVE = 2'h0, AMP = 2'h1, FRE = 2'h2,SEL = 2'h3;
always @(posedge key_pulse  or  negedge rst )//功能选择
begin
    if (!rst)
begin
            fun_sel <= 2'd0;
        end
    else if(fun_sel>=4)
        begin
            fun_sel <= 2'd0;
end
else 
begin
            fun_sel <= fun_sel + 2'd1;   
        end             
end
reg [19:0]  phase_add [6:0]; //频率调节范围
initial
begin // 调节宽度
    phase_add[0] = 20'd1;           //1Hz
    phase_add[1] = 20'd10;          //10Hz
    phase_add[2] = 20'd100;         //100Hz
    phase_add[3] = 20'd1_000;       //1KHZ
    phase_add[4] = 20'd10_000;      //10KHZ
    phase_add[5] = 20'd100_000;     //100KHZ
    phase_add[6] = 20'd1_000_000;   //1MHZ
end
always@(posedge clk or negedge rst) 
begin
    if(!rst)
begin
            phase_fo <= 32'd5;
            pulse_cnt<= 3'd0;
            wave_sel <= 2'd0;
            amp_sel  <= 3'd5;
            raw_sel  <= 1'd1;
        end 
    else 
        begin
            case (fun_sel)
                WAVE: begin //调节波形
                        if(Right_pulse)
                            wave_sel <= wave_sel + 1'b1;
else if(Left_pulse)                     
                            wave_sel <= wave_sel - 1'b1;
                        else
                            wave_sel <= wave_sel;
                end
                AMP: begin//调节幅值
                        if(Right_pulse&&(amp_sel<4'd10))     //限制幅度调节范围
                            amp_sel <= amp_sel + 1'b1;
                        else if(Left_pulse&&(amp_sel>4'd1))      //限制幅度调节范围              
                            amp_sel <= amp_sel - 1'b1;
                        else
                            amp_sel <= amp_sel;
                end
                FRE: begin//调节频率
                    if(Right_pulse) 
                    begin

                        if(phase_fo>=32'd20_000_000) phase_fo<=32'd20_000_000; 
                        //限制频率调节范围
                            else  phase_fo <= phase_fo + phase_add[pulse_cnt];

                    end 
                    else if(Left_pulse&&(phase_fo>phase_add[pulse_cnt]))         
                    //限制频率调节范围,防止过零
                    begin                                   
                        phase_fo <= phase_fo - phase_add[pulse_cnt];
                    end 
                    else if(OK_pulse)
                    begin
                        pulse_cnt <= pulse_cnt + 3'd1;
                        if(pulse_cnt>=3'd7)pulse_cnt <= 3'd0;
end
else
begin
                        phase_fo <= phase_fo;
end
end
                SEL:begin
                    if(Right_pulse)
                            raw_sel <= ~raw_sel;
else if(Left_pulse)                     
                            raw_sel <= ~raw_sel;
else
                            raw_sel <= raw_sel;
end
default: begin
                        if(Right_pulse)
                            wave_sel <= wave_sel + 1'b1;
                        else if(Left_pulse)                     
                            wave_sel <= wave_sel - 1'b1;
else
                            wave_sel <= wave_sel;
end
            endcase
end
end
endmodule

2.DDS生成模块-频率控制原理

通过修改累加值控制实际波形输出频率,phase_fo 即为实际输出的波形频率,phase_fo 已知求 phase_m 公式为:

*m = fo2^32/fc**

其中 fc 为时钟频率 120MHz,因此公式可以简化为:

*m = fo35.791394125**

在 FPGA 中进行浮点运算会出现问题,此处使用右移的方式来实现,35.791394125 转化为二进制位 100011.11001010100110001101

/*dds_wave中 主要的修改部分,实现频率调节*/
reg [31:0] phase_acc;       //32位相位累加器 频率可调范围:0.028Hz ~ 20MHz
reg [31:0] phase_m;         //频率累加值
//reg   [31:0] phase_fo = 2;    //可调频率范围0-20Mhz
always @(posedge clk_120m) 
begin   
    //phase_m <= phase_fo * 35.791394125;
    phase_m <= (phase_fo<<5)+(phase_fo<<1)+(phase_fo)+(phase_fo>> 1)
    +(phase_fo>> 1)+(phase_fo>> 2)+(phase_fo>> 5)+(phase_fo>> 7)+(phase_fo>> 9)
    +(phase_fo>> 12)+(phase_fo>> 13)+(phase_fo>> 17)+(phase_fo>> 18)+(phase_fo>> 20);     //fo = (m*fc) / (2^32)// 35.791394125  //100011.11001010100110001101
    phase_acc <= phase_acc + phase_m;        //m = fo/fc * 2^32                  
end

3.DDS生成模块-幅度控制原理

此处用于调幅和控制波形输出为原始波形还是调幅波形,已知波形输出原始幅值为 2.7V 左右,结合公式:

*OUT = 2.7X/K**

需要控制 OUT 输出范围为 0.1-1VX 为输入,K 为固定系数,可以设定 X 输入范围为 1-10,当 X=1 时,OUT=0.1VX=10OUT=1V,则 K=1/27=0.037109375

//控制波形输出:0经调幅之后的波形 ,1原始波形
always @(*) begin
    case(raw_sel)
        1'b0: dac_data = amp_sel*((dac_dat>>5)+(dac_dat>>8)+(dac_dat>>9));//*0.037109375  二进制 0.000010011
        1'b1: dac_data  = dac_dat;
        default: dac_data = dac_dat;
    endcase
end

4.OLED显示模块

OLED主要修改部分如下,8421BCD码提取频率和幅值的值。通过case进行判断,然后进行字符串拼接。

//显示波形类型
    always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
            type_buff <= "Type:Sin";
end else begin
case(wave_sel)
                2'b00: type_buff <= "Type:Sin";
2'b01: type_buff <= "Type:Saw";
2'b10: type_buff <= "Type:Trg";
2'b11: type_buff <= "Type:Squ";
            endcase
        end
    end

//显示输出频率
    wire [3:0]fre_unit,fre_ten,fre_hun,fre_tho,fre_t_tho,fre_h_hun,fre_m,fre_mm;
bcd_8421 u1_bcd_8421
    (
        .sys_clk    (clk), 
        .sys_rst_n  (rst_n), 
        .data       (phase_fo),

        .unit       (fre_unit), //个位
        .ten        (fre_ten),  //十位
        .hun        (fre_hun),  //百位
        .tho        (fre_tho),  //千位
        .t_tho      (fre_t_tho),//万位
        .h_hun      (fre_h_hun) //十万
    );  
bcd_8421 u2_bcd_8421
    (
        .sys_clk    (clk), 
        .sys_rst_n  (rst_n), 
        .data       (phase_fo/1000_000),

        .unit       (fre_m), //百万位
        .ten        (fre_mm) //千万位
    );
    always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
            fre_buff <= "Fre:   1Hz";
end else begin
if(phase_fo<32'd1000) 
                fre_buff <= {"Fre:",4'd0,fre_hun,4'd0,fre_ten,4'd0,fre_unit,"Hz "};
else if(phase_fo>32'd1_000_000) 
                fre_buff <= {"Fre:",4'd0,fre_mm,4'd0,fre_m,"MHz "};//此处显示有bug

else fre_buff <= {"Fre:",4'd0,fre_h_hun,4'd0,fre_t_tho,4'd0,fre_tho,"KHz"};
        end
    end

//显示输出幅度峰峰值vpp
    wire [3:0]amp_unit,amp_ten;
bcd_8421 u3_bcd_8421
    (
        .sys_clk    (clk), 
        .sys_rst_n  (rst_n), 
        .data       (amp_sel),

        .unit       (amp_unit), //个位
        .ten        (amp_ten)   //十位
    );
    always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
            amp_buff <= "Amp:0.5v";
        end else begin
            amp_buff <= {"Amp:",4'd0,amp_ten,".",4'd0,amp_unit,"v"};
        end
    end
//显示频率调节步进值
    always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
            cnt_buff <= "Acc:  1Hz";
end else begin
case(pulse_cnt)
                5'd0: cnt_buff <= "Acc:1Hz  ";
5'd1: cnt_buff <= "Acc:10Hz ";
5'd2: cnt_buff <= "Acc:100Hz";
5'd3: cnt_buff <= "Acc:1KHz ";
5'd4: cnt_buff <= "Acc:10KHz";
5'd5: cnt_buff <= "Acc:100K ";
5'd6: cnt_buff <= "Acc:1MHz ";
            endcase
        end
    end
/*此处省略官方案例代码,以下仅列出修改的代码片段*/
5'd13:  begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd8;  char <= type_buff;state <= SCAN; end
5'd14:  begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd8;  char <= amp_buff;state <= SCAN; end   
5'd15:  begin y_p <= 8'hb4; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd10; char <= fre_buff;state <= SCAN; end       
5'd16:  begin y_p <= 8'hb6; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd9;  char <= cnt_buff;state <= SCAN; end

5'd17 : begin y_p <= 8'hb0; x_ph <= 8'h16; x_pl <= 8'h04; num <= 5'd1;  char <= " ";state <= SCAN; end
5'd18:  begin y_p <= 8'hb2; x_ph <= 8'h16; x_pl <= 8'h04; num <= 5'd1;  char <= " ";state <= SCAN; end    
5'd19:  begin y_p <= 8'hb4; x_ph <= 8'h16; x_pl <= 8'h04; num <= 5'd1;  char <= " ";state <= SCAN; end        

5'd20:  begin y_p <= {4'hb,(fun_sel<<1)}; x_ph <= 8'h16; x_pl <= 8'h04; num <= 5'd1;  char <= "*";state <= SCAN; end
5'd21:  begin y_p <= 8'hb6; x_ph <= 8'h16; x_pl <= 8'h04; num <= 5'd1;  char <= " ";state <= SCAN; end                                
default: state <= IDLE;   //如果你需要动态刷新一些信息,此行应该取消注释

实验结果

1.四种类型波形

DDS输出的四种标准波形

2.不同频率的正弦波信号

不同频率的正弦波输出

3.实物图片

DDS信号发生器实物连接图

4.资源使用情况:

Design Summary
Number of registers: 551 out of 4635 (12%)
PFU registers: 551 out of 4320 (13%)
PIO registers: 0 out of 315 (0%)
Number of SLICEs: 1225 out of 2160 (57%)
SLICEs as Logic/ROM: 1225 out of 2160 (57%)
SLICEs as RAM: 0 out of 1620 (0%)
SLICEs as Carry: 500 out of 2160 (23%)
Number of LUT4s: 2441 out of 4320 (57%)
Number used as logic LUTs: 1441
Number used as distributed RAM: 0
Number used as ripple logic: 1000
Number used as shift registers: 0
Number of PIO sites used: 39 + 4(JTAG) out of 105 (41%)
Number of block RAMs: 0 out of 10 (0%)
Number of GSRs: 1 out of 1 (100%)
EFB used : No
JTAG used : No
Readback used : No
Oscillator used : No
Startup used : No
POR : On
Bandgap : On
Number of Power Controller: 0 out of 1 (0%)
Number of Dynamic Bank Controller (BCINRD): 0 out of 6 (0%)
Number of Dynamic Bank Controller (BCLVDSO): 0 out of 1 (0%)
Number of DCCA: 0 out of 8 (0%)
Number of DCMA: 0 out of 2 (0%)
Number of PLLs: 1 out of 2 (50%)
Number of DQSDLLs: 0 out of 2 (0%)
Number of CLKDIVC: 0 out of 4 (0%)
Number of ECLKSYNCA: 0 out of 4 (0%)
Number of ECLKBRIDGECS: 0 out of 2 (0%)

设计心得

第一次使用 FPGA 完成的项目,本次项目耗时大约一周的时间。因为具有STM32的软硬件开发经验,Verilog语言从语法规则上和C语言十分相似,所以使用 Verilog 编写代码上手很快。但也带来一些坏处,总是跳不出用软件编程的思想编写 Verilog 代码。

在整个设计过程中主要出现了以下几个问题:

1.提示旋转编码器三个输入接口未连接问题
ERROR - Port 'encoder_a' is unconnected.
问题分析:没有在顶层指定 dds_ctrl 子模块输出位宽,导致生成的硬件链路出现问题。
解决方法:顶层模块调用子模块时一定要声明输出数据的位宽,否则默认当作1bit。

2.DAC模块CLK时钟短路
问题分析:使用探针测量CLK引脚时不小心将焊盘附近的绝缘层划开,铜箔露出导致CLK与GND短路。
解决方法:使用烙铁将露出铜箔稍微刮几下。

3.训练板DAC输出引脚无信号输出
问题分析:刚开始测量时习惯将示波器探针插到Aout的中间位置,次数多了导致导线断路。
解决方法:从DAC模块输出引脚飞线。

以上问题解决起来都很简单,但是找到问题所在需要不断试错。当代码找不出bug但是还是没有输出时就不要死磕软件了,看看硬件是不是出现了什么问题。

本次设计还有几个问题没有解决:

  • OLED显示问题,调节频率到MHz以上时OLED显示的频率出现bug。
  • 波形失真问题,当进行调幅时输出的波形有明显的失真。频率调整到10MHz左右,不进行调幅也会失真(此处提高PLL频率应该可以解决)。调幅失真还没有找到好的解决办法。

分享一个简单的脚本,实现自动将 .jed 文件复制到小脚丫开发板,能自动化解决的事情就绝不自己动手。注意:需要修改磁盘编号

copy *.jed K:\
pause
::https://zhidao.baidu.com/question/513072753.html?qbl=relate_question_3&word=%C5%FA%B4%A6%C0%ED%CE%C4%BC%FE%D0%DE%B8%C4%CA%B1%BC%E4

DDS 和信号源类项目,是 FPGA 学习过程中非常典型、也非常有“含金量”的一类综合实战。相比只跑算法或点灯,这类项目更贴近真实仪器设计,对时序、资源、硬件细节的要求也更高。

适合用于 FPGA 入门进阶、课程设计、毕业设计以及数字信号处理方向的实战训练。通过真实硬件 + 完整工程,你可以把 DDS、DAC、时钟与接口这些抽象概念,真正“变成波形”。如果你想寻找更多类似的嵌入式及 FPGA 开源实战项目,可以到 云栈社区 的技术论坛板块交流探索。




上一篇:AMD以1美分股权协议绑定Meta与OpenAI,深度布局AI算力市场
下一篇:裁员后,我教会了狗用Claude开发游戏:一套Prompt与硬件搭建实录
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-26 17:40 , Processed in 0.772412 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表