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

1422

积分

0

好友

204

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

如果不借助现成的示波器软件,仅使用一块FPGA,如何独立完成一个真正可用的信号源设计与实现?

本文基于小脚丫FPGA电赛训练平台,利用其板载的125MSPS高速DAC,从直接数字频率合成(DDS)的基本原理出发,完整实现了一台可输出正弦波、三角波、方波的可调波形发生器。

项目介绍

本项目旨在实现以下功能:

  1. 通过板载高速DAC(10位分辨率,125Msps采样率)配合FPGA内部的DDS逻辑,生成波形形状(正弦波、三角波、方波)、频率、幅度均可调节的信号。
  2. 生成模拟信号的频率范围为DC-20MHz,调节精度可达1Hz。
  3. 生成模拟信号的幅度最大为1Vpp,调节范围为0.1V至1V。
  4. 在OLED屏幕上实时显示当前波形类型、频率以及幅度值。
  5. 利用板上的旋转编码器和按键实现波形切换与参数调节。

设计思路

本次实验主要涉及以下几个核心模块:OLED显示模块、DDS信号生成模块、旋转编码器(旋钮)输入模块、分频器模块以及锁相环(PLL)模块。这些模块协同工作,构成了整个信号发生器的系统架构,其中锁相环分频器对于生成稳定的系统时钟至关重要,这属于底层系统与时钟管理的范畴。

系统模块框图

硬件平台

本次开发使用的是硬禾学堂提供的基于小脚丫FPGA的电赛训练平台,综合开发工具为Lattice Diamond。

小脚丫FPGA电赛训练平台

功能实现与效果展示

项目成功实现了预设的所有功能,以下为实物运行效果图:

波形发生器运行效果1
波形发生器运行效果2

主要代码片段及说明

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屏幕,并包含字符显示逻辑。能够动态显示由旋钮调节的波形频率和幅度参数。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:begin
6'd0 :    begin state <= INIT; end
6'd1 :    begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16;  char <= "                "; state <= SCAN; end
6'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; end
6'd4 :    begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                "; state <= SCAN; end
6'd5 :    begin y_p <= 8'hb4; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                "; state <= SCAN; end
6'd6 :    begin y_p <= 8'hb5; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                "; state <= SCAN; end
6'd7 :    begin y_p <= 8'hb6; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                "; state <= SCAN; end
6'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; end
6'd10:    begin y_p <= 8'hb0; x_ph <= 8'h11; x_pl <= 8'h00; mem_hanzi_num <= 8'd2; state <= CHINESE; end
6'd11:    begin y_p <= 8'hb0; x_ph <= 8'h12; x_pl <= 8'h00; mem_hanzi_num <= 8'd4; state <= CHINESE; end
6'd12:    begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; mem_hanzi_num <= 8'd6; state <= CHINESE; end
6'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; end
6'd15:  begin y_p <= 8'hb3; x_ph <= 8'h11; x_pl <= 8'h00; mem_hanzi_num <= 8'd10; state <= CHINESE; end
6'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; end
6'd18:  begin y_p <= 8'hb6; x_ph <= 8'h11; x_pl <= 8'h00; mem_hanzi_num <= 8'd14; state <= CHINESE; end
6'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;
        end
6'd21:    begin cnt_main <= 6'd9; end
endcase
3. ASCII码转换模块

该模块处理旋钮输入,实现数值的增减,并通过赋值语句拼接成可用于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. 波形表与幅度调节模块

该模块预存了正弦波、三角波、方波的数字波形表(Look-Up Table),并集成了幅度控制逻辑,配合旋钮实现输出幅度的调节。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
    end
end
////// 略///

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. 旋钮编码器与分频模块

旋钮编码器模块用于检测用户旋转操作,分频模块用于产生系统所需的各种时钟。这部分设计借鉴了成熟的开源代码。

项目总结与思考

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

在DDS模块设计中,如何统一、同步地控制三种不同波形的频率和幅度是主要挑战。这需要精确的理论计算或通过实验进行反复调试来确定参数。

在OLED显示部分,初期对SPI协议驱动和动态内容刷新缺乏思路。通过研究学习相关的开源代码,掌握了SPI通信的实现方法,并解决了动态输出过程中数值到ASCII码实时转换的难题。后续了解到更高效的算法(如左移加三算法)可用于此类转换,但因时间关系未在本项目中实施优化。

2. 经验与展望

通过本项目,对Verilog硬件描述语言在系统级设计中的应用有了更深的体会。未来希望参与更多类似的FPGA实战项目,以积累设计经验、拓宽思路,并探索如何利用更少的逻辑资源实现更复杂的功能,这对于深入理解数字系统设计精髓很有帮助。




上一篇:Python构建开源漏洞监控平台:实时追踪GitHub、微软与CNNVD威胁
下一篇:归并排序链表实战:LeetCode148迭代O(1)空间解法
您需要登录后才可以回帖 登录 | 立即注册

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

GMT+8, 2025-12-24 17:19 , Processed in 0.157799 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

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