1 Star 1 Fork 0

往月 / 基于DDS的李萨如图形生成器

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
README.md 48.13 KB
一键复制 编辑 原始数据 按行查看 历史
往月 提交于 2024-03-18 17:49 . 完成了技术文档

数电实验报告——基于DDS的李萨如图形生成器

[TOC]

一、任务要求

利用实验板上的DAC设计实现一个李萨如图形生成器。

  • 基础要求

    1. 在实验板上的 DAC(TLV5638)DA_A DA_B 分别输出频率和相位满足下图所示的正弦信号,将两路信号分别接到示波器的两个通道,则可以在示波器上(X-Y显示模式)观测到李萨如图形。
    2. 输出信号的峰峰值 ≥ 1 V,起始频率 ≥ 200 Hz(即频率比中1对应的频率),波形每个周期大于 100个点,波形无明显失真;
    3. SW7SW6 选择频率比,“00” 时为 1:1“01” 时为 1:2“10” 时为 1:3“11” 时为 2:3;并在数码管DISP7DISP6上显示频率比;
    4. SW2SW1SW0选择相位差,“000”时为 0°,“001” 时为 45°“010” 时为 90°“011” 时为 135°“100” 时为 180°;并在数码管 DISP2DISP1 和 **DISP0 **上显示相位差。
    image-20231116214013388
  • 提高要求

    1. 可通过 4*4 键盘任意设置频率比和相位差;
    2. 可显示其他波形如三角波、方波。

二、系统设计

1、设计思路

​ 各种李萨如图的产生取决于两路通道的参数能够被任意设计。本题目的核心在于实现输出两通道任意频率、任意相位的正弦波,这依赖于 DDS 模块所产生波形的频率和起始相位能够被准确地设置。

(1)DDS模块设计

​ 完成DDS模块的设计,它由特定的时钟驱动,频率、相位、可生成的波形和生成波形的幅度由外部输入确定。具有复位信号,用于及时清零内部的累加数据。这样顶层模块可以方便地调用它产生所需的波形数据,传入DAC模块中生成设定的信号。

(2)DAC模块设计

​ 完成DAC模块的设计,板载 DAC 即 TLV5638 ,由SPI时序驱动。这一步需要根据芯片手册的相关信息编写代码。通过调用这一模块,能够将DDS模块输出的波形数据转换成可供 TLV5638 识别的输入数据,最终将数据量输出,产生设定的信号。

(3)显示和控制模块设计

​ 包括数码管上的显示和开关、按钮以及矩阵键盘的控制。通过操控开关、按钮以及矩阵键盘,这部分模块能将相应操作转译成频率、相位或波形类型信息,并将数据传入DDS模块。数码管上的信息也会随着操作而改变。

(4)顶层模块设计

​ 调用以上三种模块,综合各模块间的时序关系,合理分配输入和输出关系。确保整个系统能够顺利运行。

2、总体框图

  • 主模块的输入信号包括 clkrst 以及由矩阵键盘、开关或按钮产生的控制信号。
  • 控制模块接收这些控制信号,一方面将输入的信息转译成信号参数传递给 DDS 模块,另一方面将当前设置的参数转化为数据并传递给数码管以进行显示。
  • DDS 模块接受传入的信号参数以生成特定的信号。生成的信号需要满足一个关键关系:在传输完两个通道的数据之前,该信号不应该发生改变。因此,DDS 模块的时钟需要经过精心设计,以实现 SPI 传输周期的二分频。
  • 在时序控制方面,DDS 模块将生成的信号按照 SPI 时序输出给 TLV5638。同时,它接收 TLV5638 的完成信号,以切换传输通道 A 和通道 B 的数据。完成信号的二分频也作为驱动时钟传递给 DDS 模块。
  • TLV5638 驱动满足 SPI 时序,完成一次传输后会返回一个上升沿,表示传输完成。这个信号可用作时序控制的周期。
总体模块图

3、分块设计

(1)DDS模块

管脚名称 管脚功能 说明
clk 时钟 在一个时钟周期内,DDS输出的数据不变。此时钟需满足传输的时序要求,即发送完A通道和B通道所花时间作为一个时钟周期。
rst 复位信号,清零相位累加值 在更改完信号参数后也需要进行复位,清空累加值
wave_en 使能信号,用于开启DDS 默认一直打开
wave_sel 波形选择信号,选择DDS要输出的波形 输入值为00、11时输出正弦波,输入值为01的时候输出方波,输入值为10的时候输出三角波
wave_amp 波形幅度控制信号,控制输出信号的幅度 00对应 dout ,01对应dout / 2,10对应dout / 4,11对应dout / 8
phase_init 设置初始相位 WIDTH_PHASE 对应相位位数
f_word 频率控制字 频率控制字用于设定输出信号的频率,根据具体的设计和应用选择相应的数值
dout 输出信号 DDS模块生成的具体信号,根据波形选择、频率等参数产生不同的波形
dout_en 输出使能信号 表明一次传输的完成,仿真时用于观察DDS是否正常工作

(2)查找表模块

管脚名称 管脚功能 说明
clk 驱动时钟 在时钟作用下更新输出的值
rst 复位信号 复位各寄存器的值
en 使能信号 控制模块的开启和关闭
sel 波形选择信号 选着对应波形的查找表
addr 地址信号 相当于DDS当前对应相位值
dout 输出信号 返回当前地址对应查找表中相应波形的值
dout_en 输出使能信号 表明一次传输的完成,仿真时用于观察DDS是否正常工作

(3)TLV5638

管脚名称 管脚功能 说明
clk 时钟 系统时钟输入
rst 复位信号 复位各寄存器的值
start_i 启动信号 拉高时结束传输,拉低时表示正在传输
data_i 输入信号 高四位为1000时传输通道A数据,高四位为0001时传输通道B数据
dclock_o 数据时钟 发送一位数据变换一次
data_o 发送的数据 输出给 DAC 芯片
cs_o 选通信号 拉高时结束传输,拉低时表示正在传输
eoc_o 结束信号 转换完成时拉高

(4)控制模块

管脚名称 管脚功能 说明
clk 时钟 系统时钟
rst 复位信号 复位各寄存器的值
switch_channel 选择改变某一通道参数 0时改变B通道,1时改变A通道
switch_parameter 选择改变某一通道的频率或相位 0时改变相位,1时改变频率
row 行信号 矩阵键盘行扫描
col 列信号 矩阵键盘列扫描
seg 数码管位选信号 数码管位选信号
cat 数码管位序信号 数码管位序信号
o_fre_1 通道A频率值 输出通道A的频率值
o_fre_2 通道B频率值 输出通道B的频率值
o_phase_1 通道A初始相位 精度为360°/2^WIDTH_PHASE^
o_phase_2 通道B初始相位 精度为360°/2^WIDTH_PHASE^

(5)显示模块

管脚名称 管脚功能 说明
clk 数码管时钟 为了利用视觉保留效果,它应当适当分频,以提供适宜的刷新频率
rst 复位信号 复位各寄存器的值
code [31:0] 待显示的数据 位宽为32,包含8个4位宽的8421BCD码,分别表示两通道频率比值、相位差,以及当前切换的参数
seg 数码管位选信号 数码管位选信号
cat 数码管位序信号 数码管位序信号

(6)矩阵键盘

管脚名称 管脚功能 说明
clk 用于控制矩阵键盘扫描 为了能够准确扫描到按下按键,该时钟应该越慢越好
rst 复位信号 复位各寄存器的值
row 行信号 矩阵键盘行扫描
col 列信号 矩阵键盘列扫描
data_number 按下键值 返回值为0~15
pressed 按下信号 矩阵键盘被按下拉高

三、工程代码

1、主模块

​ 主模块实现了一个功能丰富的DDS信号生成器。它可以通过控制信号动态地改变输出信号的频率、相位和波形,支持多通道数据生成,并与TLV5638进行集成以实现数字模拟转换。整个设计包括状态机、波形切换、频率和相位参数设置等模块,可用于生成多种类型的波形信号。

module MY_DDS_PRO
#(parameter WIDTH=12,
  parameter WIDTH_PHASE=10)
(
    input clk,       // 时钟信号
    input rst,     // 复位信号
    input switch_dds,  // DDS开关

    input btn_wave, //切换波形
    input switch_wave, //切换当前通道的波形
    input switch_channel, //切换两通道频率值
    input switch_parameter, //切换频率或者相位
    //矩阵键盘信号
    input [3:0] row, //行信号
    output [3:0] col,

    //tlv5638输出信号
    output wire cs_o,   // 选通信号
    output wire data_o, // 串行数字信号
    output wire dclock_o, // 串行时钟
    //数码管段选信号
    output [6:0] seg,
    //数码管位选信号
    output [7:0] cat,

    output [15:0] led //按下按键时显示按下次序LED
);

    localparam States_START= 1'b0,States_BUSY = 1'b1;

    // 内部信号声明
    reg start_da = 0; // 初始化为低电平
    wire eoc_da;

    //记录当前输入tlv5638的数据
    reg [15:0] data;
    reg [15:0] data_a;
    reg [15:0] data_b;
    
    reg [1:0] channel;
    reg [1:0] cnt;

    reg  state; // 表示当前传输状态
    reg state_ab; // 表示两位数据的传输状态

    wire [1:0] waveforms; //表示当前波形

    wire [7:0] o_fre_1;
    wire [7:0] o_fre_2;
    wire [WIDTH_PHASE-1:0] o_phase_1;
    wire [WIDTH_PHASE-1:0] o_phase_2;

    wire [WIDTH-1:0] dds_output1; // DDS输出数据1,对应A口
    wire [WIDTH-1:0] dds_output2; // DDS输出数据2,对应B口
    wire dds_output_enable; // 用于启用DDS输出
    
    wire clk_i; // 分频时钟,用于控制DAC
    reg clk_dds=1'b0; // DDS时钟,用于控制DDS
    wire rst_dds; // 用于改变参数时复位dds
    reg eoc_done; //延时记录eoc的状态

    //对输入时钟进行适当分频
    NDivider_Even n_divider_inst_0(.clk(clk) , .N(16'd2) , .reset(rst) , .clk_out(clk_i));

    //DDS模块
    dds #(WIDTH,WIDTH_PHASE) u_dds_1 (
        .clk(clk_dds), // 时钟信号
        .rstn(rst_dds | rst), // 复位信号
        .wave_en(switch_dds), // 启用DDS生成
        .wave_sel(waveforms & switch_wave), // 波形选择,这里选择合适的波形
        .wave_amp(2'b01), // 波形幅度控制,这里选择合适的幅度
        .phase_init(o_phase_1), // 初始相位,这里选择合适的初始相位
        .f_word(o_fre_1), // 频率控制字,这里选择合适的频率
        .dout(dds_output1), // DDS输出数据
        .dout_en(dds_output_enable) // 启用DDS输出
    );

    dds #(WIDTH,WIDTH_PHASE) u_dds_2 (
        .clk(clk_dds), // 时钟信号
        .rstn(rst_dds | rst), // 复位信号
        .wave_en(switch_dds), // 启用DDS生成
        .wave_sel(waveforms & ~switch_wave), // 波形选择,这里选择合适的波形
        .wave_amp(2'b01), // 波形幅度控制,这里选择合适的幅度
        .phase_init(o_phase_2), // 初始相位,这里选择合适的初始相位
        .f_word(o_fre_2), // 频率控制字,这里选择合适的频率
        .dout(dds_output2), // DDS输出数据
        .dout_en(dds_output_enable) // 启用DDS输出
    );

    // TLV5638
    tlv5638 u_tlv5638 (
        .cs_o(cs_o),
        .data_o(data_o),
        .dclock_o(dclock_o),
        .reset_i(rst),
        .clk_i(clk_i),
        .start_i(start_da),
        .eoc_o(eoc_da),
        .data_i(data)
    );

    //切换波形模块
    waveform_changed u_waveform_changed(
        .clk(clk),
        .rst(rst),
        .clicked(btn_wave),
        .waveform_type(waveforms)
    );


    parameter_wave #(WIDTH_PHASE) u_parameter_wave(
        .clk(clk),
        .rst(rst),
        .switch_channel(switch_channel), // 选择通道进行更改
        .switch_parameter(switch_parameter), // 选择频率或者相位进行更改
        //矩阵键盘控制信号
        .row(row),
        .col(col),

        //数码管相关信号
        .cat(cat),
        .seg(seg),

        .o_fre_1(o_fre_1),
        .o_fre_2(o_fre_2),
        .o_phase_1(o_phase_1),
        .o_phase_2(o_phase_2),
        .rst_dds(rst_dds),
        .led(led)
    );



    //标记eoc_da上升沿的位置
    always @(posedge clk_i or posedge rst)
    begin
        if(rst)
        begin
            eoc_done <= 1'b0;
        end
        else
            eoc_done <= eoc_da;
    end

    always @(posedge clk_i or posedge rst)
    begin
        if(rst)
        begin
            start_da <= 1'b0;
            state <= States_START;
            data_a <= 16'h0000;
            data_b <= 16'h0000;
            state_ab <= 1'b0;
        end
        //上一次数据发送完成时更换当前数据
        else if(eoc_done)
        begin
            data_a <= {4'b1000, dds_output1[11:0]}; // 写入DDS输出到 DAC A 缓冲区
            data_b <= {4'b0001, dds_output2[11:0]}; // 写入DDS输出到 DAC B 缓冲区
            case(state_ab)
                1'b0: // 传输a
                begin
                    data <= data_a;
                    state_ab <= 1'b1;
                end
                1'b1: // 传输b
                begin
                    data <= data_b;
                    state_ab <= 1'b0;
                end
            endcase;
            clk_dds <= ~clk_dds; // 产生DDS时钟
            start_da <= 1'b1; //拉高数据位准备下一次发送
        end
        else
        begin
            case(state)
                //初始化状态
                States_START:
                begin
                    data <= 16'b1101000000000010;
                    start_da <= 1'b1;
                    state <= States_BUSY; // 进入发送状态
                end
                //发送状态
                States_BUSY:
                    start_da <= 1'b0;
                default:
                    start_da <= 1'b1;
            endcase;
        end
    end

endmodule

2、DDS模块

​ DDS模块的主要目的是通过累加相位、计算地址、从 ROM 中获取波形数据,并根据选择的波形和幅度进行处理,生成相应的数字信号。

module dds
#(parameter WIDTH=12,
  parameter WIDTH_PHASE=8)
(
        input           clk,            // 参考时钟
        input           rstn ,          // 复位信号,低有效

        input           wave_en ,       // 启动波形生成
        input [1:0]     wave_sel ,      // 波形选择

        input [1:0]     wave_amp ,      // 波形幅度控制
        input [WIDTH_PHASE-1:0]     phase_init,     // 初始相位
        input [7:0]     f_word ,        // 频率控制字

        output [WIDTH-1:0]    dout,           // 输出数据,宽度为10位
        output          dout_en
);

   // 相位累加器
   reg [WIDTH_PHASE-1:0] phase_acc_r ;
   always @(posedge clk or posedge rstn) begin
      if (rstn) begin
         phase_acc_r    <= 'b0 ;
      end
      else if (wave_en) begin
         phase_acc_r    <= phase_acc_r + f_word ;
      end
      else begin
         phase_acc_r    <= 'b0 ;
      end
   end

   // ROM 地址
   reg [WIDTH_PHASE-1:0] mem_addr_r ;
   always @(posedge clk or posedge rstn) begin
      if (rstn) begin
         mem_addr_r     <= 'b0 ;
      end
      else if (wave_en) begin
         mem_addr_r     <= phase_acc_r + phase_init ;
      end
      else begin
         mem_addr_r     <= 'b0 ;
      end
   end

   // ROM 实例化
   wire [WIDTH-1:0]   dout_temp ;
   // mem的实例化在dds里
   mem  #(WIDTH,WIDTH_PHASE) u_mem_wave(
        .clk     (clk),                 // 参考时钟
        .rstn    (rstn),                // 复位信号,低有效
        .en      (wave_en),             // 启动波形生成
        .sel     (wave_sel[1:0]),       // 波形选择
        .addr    (mem_addr_r[WIDTH_PHASE-1:0]),
        .dout_en (dout_en),
        .dout    (dout_temp[WIDTH-1:0]));     // 输出数据,12位宽度

   // 幅度控制
   // 0 -> dout/1
   // 1 -> dout/2
   // 2 -> dout/4
   // 3 -> dout/8
   assign       dout = dout_temp >> wave_amp ;

endmodule
module mem
#(parameter WIDTH=12,
  parameter WIDTH_PHASE=8)
(
        input           clk,            // 参考时钟
        input           rstn ,          // 复位信号,低有效

        input           en ,            // 启动波形生成
        input [1:0]     sel ,           // 波形选择

        input [WIDTH_PHASE-1:0]     addr ,
        output          dout_en ,
        output [WIDTH-1:0]    dout          // 输出数据,10位宽度
);

   // 从 ROMs 获取的数据
   wire [WIDTH-1:0]           q_tri ;
   wire [WIDTH-1:0]           q_square ;
   wire [WIDTH-1:0]           q_cos ;

   // ROM 地址
   reg [1:0]            en_r ;
   always @(posedge clk or posedge rstn) begin
      if (rstn) begin
         en_r   <= 2'b0 ;
      end
      else begin
         en_r   <= {en_r[0], en} ;         // 延迟一个周期以适应 en
      end
   end
   assign dout      = en_r[1] ? (q_tri | q_square | q_cos) : 12'b0 ; // 数据输出选择
   assign dout_en   = en_r[1] ;

   // ROM 实例化
   cos_rom  #(WIDTH,WIDTH_PHASE)    u_cos_rom (
       .clk     (clk),
       .en      (en_r[0] & ((sel == 2'b0) || (sel == 2'b11)) ),    // sel = 0 或 3, 选择 cos 波形
       .addr    (addr[WIDTH_PHASE-1:0]),
       .q       (q_cos[WIDTH-1:0]));

   square_rom #(WIDTH,WIDTH_PHASE)  u_square_rom (
       .clk     (clk),
       .en      (en_r[0] & sel == 2'b01),    // sel = 1, 选择方波
       .addr    (addr[WIDTH_PHASE-1:0]),
       .q       (q_square[WIDTH-1:0]));

   tri_rom  #(WIDTH,WIDTH_PHASE)   u_tri_rom (
       .clk     (clk),
       .en      (en_r[0] & sel == 2'b10),    // sel = 2, 选择三角波
       .addr    (addr[WIDTH_PHASE-1:0]),
       .q       (q_tri[WIDTH-1:0]));

endmodule

module square_rom
#(parameter WIDTH1=12,
  parameter WIDTH_PHASE=8)
(
   input               clk,
   input               en,
   input [WIDTH_PHASE-1:0]         addr,
   output reg [WIDTH1-1:0]    q);

   initial begin
      q = 12'd0; // 初始值设置为0
   end

   reg [8:0] count = 9'd0;

   always @(posedge clk) begin
      if (en) begin
         if (count < 9'd200) begin
            count <= count + 9'd1;
            q <= 12'd4095;
         end
         else if(count < 9'd400)begin
            count <= count + 9'd1;
            q <= 12'd0;
         end
         else
            count <= 9'd0;
      end
      else begin
         q <= 12'd0;
         count <= 9'd0;
      end
   end
endmodule

module tri_rom
#(parameter WIDTH1=12,
  parameter WIDTH_PHASE=8)
(
   input               clk,
   input               en,
   input [WIDTH_PHASE-1:0]         addr,
   output reg [WIDTH1-1:0]    q);

   initial begin
      q = 12'd0; // 初始值设置为0
   end

   reg [8:0] count = 9'd0;

   always @(posedge clk) begin
      if (en) begin
         if (count < 9'd200) begin
            count <= count + 9'd1;
            q <= q + 12'd20;
         end
         else if(count < 9'd400) begin
            count <= count + 9'd1;
            q <= q - 12'd20;
         end
         else
            count <= 9'd0;
      end
      else begin
         q <= 12'd0;
         count <= 9'd0;
      end
   end
endmodule

module cos_rom

#(parameter WIDTH3=12,
  parameter WIDTH_PHASE=8)
(
   input               clk,
   input               en,
   input [WIDTH_PHASE-1:0]         addr,
   output reg [WIDTH3-1:0]    q);

   // 由于 cos 函数的对称性,只存储一个周期的 1/4 数据
   wire [WIDTH3-2:0] rom_t;
   wire [WIDTH_PHASE-3:0] selected_addr;

   initial begin
      q = 12'd0; // 初始值设置为0
   end

   always @(posedge clk) begin
      if (en) begin
         if (addr[WIDTH_PHASE-1:WIDTH_PHASE-2] == 2'b01 ) begin         // 第一象限,addr -> [0, 63]
            q <= rom_t + 12'd2048 ;
         end
         else if (addr[WIDTH_PHASE-1:WIDTH_PHASE-2] == 2'b10 ) begin    // 第二象限,addr -> [64, 127]
            q <= 12'd2048 +~ rom_t;
         end
         else if (addr[WIDTH_PHASE-1:WIDTH_PHASE-2] == 2'b11 ) begin    // 第三象限,addr -> [128, 192]
            q <= 12'd2048 +~ rom_t;
         end
         else begin     // 第四象限,addr -> [193, 256]
            q <= 12'd2048 + rom_t;
         end
      end
      else begin
         q <= 'b0 ;
      end
   end

   assign selected_addr = ~(addr[WIDTH_PHASE-1:WIDTH_PHASE-2] == 2'b10 || addr[WIDTH_PHASE-1:WIDTH_PHASE-2] == 2'b00) ? addr[WIDTH_PHASE-3:0] : ~addr[WIDTH_PHASE-3:0];
   cosine_mux cos_inst(.phase(selected_addr), .data(rom_t));

endmodule

3、TLV5638

​ TLV5638的主要目的是在接收到启动信号时,以状态机的方式控制数据的发送,并在完成数据传输后生成相应的控制信号和状态信号。

module tlv5638(
   output cs_o,        // 选通输出
   output data_o,      // 数据输出
   output dclock_o,    // 数据时钟输出
   input reset_i,      // 外部复位输入
   input clk_i,        // 系统时钟输入
   input start_i,      // 启动信号输入
   output eoc_o,       // 转换结束信号输出
   input [15:0] data_i // 数据输入
);

   // 内部信号声明
   parameter [1:0] States_IDEL = 2'b00, States_BUSY = 2'b01;
   reg [1:0] state;

   reg reset_int;
   reg [15:0] data_out = 16'd0;
   reg [3:0] data_out_addr;

   reg eoc_tmp;
   reg cs_o_tmp;
   reg dclock_o_tmp;
   reg data_o_tmp;
   reg [4:0] cnt_wr;
   reg [0:0] t;

   // 状态机
   always @(negedge clk_i or posedge reset_i or posedge eoc_tmp)//_tmp
   begin
      if (reset_i == 1'b1 | eoc_tmp == 1'b1)//_tmp
         state <= States_IDEL;
      else
      begin
         if (start_i == 1'b1 & state == States_IDEL)
         begin
            state <= States_BUSY;
            data_out <= data_i;
         end
      end
   end

   // 复位控制
   always @(negedge clk_i or posedge reset_i)
   begin
      if (reset_i == 1'b1)
         reset_int <= 1'b0;
      else
      begin
         if (eoc_tmp == 1'b1)//_tmp
            reset_int <= 1'b1;
         else
            reset_int <= 1'b0;
      end
   end

   // 数据发送
   always @(posedge clk_i)
   begin
      if (reset_i == 1'b1 | reset_int == 1'b1)
      begin
         cs_o_tmp <= 1'b1;
         cnt_wr = 5'b0;
         t = 1'b0;
         dclock_o_tmp <= 1'b0;
         eoc_tmp <= 1'b0;
         data_out_addr <= 4'b1111;
         data_o_tmp <= 1'b0;
      end
      else
      begin
         if (state == States_BUSY)
         begin
            if (cnt_wr < 5'b10000)
            begin
               eoc_tmp <= 1'b0;
               cs_o_tmp <= 1'b0;
               if (t == 1'b0)
               begin
                  data_o_tmp <= data_out[data_out_addr];
                  dclock_o_tmp <= 1'b1;
                  if (data_out_addr > 4'b0000)
                     data_out_addr <= data_out_addr - 1;
               end
               else
               begin
                  dclock_o_tmp <= 1'b0;
                  cnt_wr = cnt_wr + 1;
               end
               if (t < 1'b1)
                  t = t + 1'b1;
               else
                  t = 1'b0;
            end
            else
            begin
               cs_o_tmp <= 1'b1;
               eoc_tmp <= 1'b1;
            end
         end
      end
   end

   // 输出端口赋值
   assign eoc_o = eoc_tmp;
   assign cs_o = cs_o_tmp;
   assign dclock_o = dclock_o_tmp;
   assign data_o = data_o_tmp;

endmodule

4、控制模块

​ 用于控制 DDS 模块,具有通过矩阵键盘和按钮切换频率和相位参数的功能。

module parameter_wave
#(parameter WIDTH_PHASE=10)
(
    input clk , //时钟信号
    input rst , //复位信号
    input switch_channel , //需要修改的通道
    input switch_parameter, //切换参数
    input [3:0] row, //行信号
    output [3:0] col, // 列信号

    //数码管位序信号
    output [7:0] cat,
    //数码管位选信号
    output [6:0] seg,

    //输出波形参数
    output [7:0] o_fre_1,
    output [7:0] o_fre_2,
    output [WIDTH_PHASE-1:0] o_phase_1,
    output [WIDTH_PHASE-1:0] o_phase_2,

    output rst_dds, //用于改变参数后复位dds

    output [15:0] led //按下按键时显示按下次序LED
);

reg [15:0] led_temp;
assign led = led_temp;

localparam FREQUENCY_BASIC = 8'd1;//设置默认频率值

wire pressed; //用于记录是否按下按键
wire [3:0] number_pressed;

wire clk_i1; // 分频后的信号

// wire order_pressd; //切换相位位序
// wire data_pressd_add;  //切换相位当前位数字
// wire data_pressd_sub;  //切换相位当前位数字

reg [7:0] o_fre_1_temp;
reg [7:0] o_fre_2_temp;

//频率1十位
reg [3:0] fre_1_0x;
//频率1个位
reg [3:0] fre_1_x0;
//频率2十位
reg [3:0] fre_2_0x;
//频率2个位
reg [3:0] fre_2_x0;
//相位个位
reg [3:0] phase_xx0;
//相位十位
reg [3:0] phase_x0x;
//相位百位
reg [3:0] phase_0xx;
//相位位序状态
reg [1:0] num_state;
reg [3:0] parameter_state;

wire [31:0] code;//存储要显示的数据

wire [3:0] decoder_phase;//被译码后的相位输入值

//产生分频信号控制数码管显示
NDivider_Even n_divider_inst_2(.clk(clk) , .N(16'd500) , .reset(rst) , .clk_out(clk_i1));

Component_Binary_To_7Segment segment_inst(
    .clk(clk_i1),
    .rst_n(rst),
    .code(code),
    .cat(cat),
    .seg(seg)
);

//返回当前按下的矩阵键盘值
data_transport u_data_transport(    
    .clk(clk),
    .rst(rst),
    .row(row),
    .col(col),
    .pressed(pressed),
    .number_pressed(number_pressed)
    );

//返回被解码的相位值
ecoder_phase u_ecoder_phase(
    .number(number_pressed),
    .number_decoded(decoder_phase)
);


always@(posedge pressed or posedge rst) begin
    if(rst)
    begin
        //频率值复位
        fre_1_0x <= 4'd0;
        fre_1_x0 <= 4'd1;
        fre_2_0x <= 4'd0;
        fre_2_x0 <= 4'd1;
        o_fre_1_temp <= 8'd1;
        o_fre_2_temp <= 8'd1;
        //相位值复位
        phase_xx0 <= 4'd0;
        phase_x0x <= 4'd0;
        phase_0xx <= 4'd0;
        phase_data <= 4'd0;
        num_state <= 2'd0;
        parameter_state <= 4'hf;
        led_temp <= 16'd15;
    end
    //此时更改频率
    else if(switch_parameter)
    begin
        parameter_state <= 4'hf;
        //更改通道1
        led_temp <= 16'b1 << number_pressed;//记录按下LED的值
        if(switch_channel)
        begin
            case(number_pressed)
                0,1,2,3,4,5,6,7,8://1~9 
                begin
                    fre_1_0x <= 4'd0;
                    fre_1_x0 <= number_pressed + 4'd1;
                    o_fre_1_temp <= number_pressed+4'd1;
                end
                9,10,11,12,13,14,15://10~16
                begin
                    fre_1_0x <= 4'd1;
                    fre_1_x0 <= number_pressed - 4'd9;
                    o_fre_1_temp <= number_pressed+4'd1;
                end           
            endcase;
        end
        //更改通道2
        else
        begin
            case(number_pressed)
                0,1,2,3,4,5,6,7,8://1 
                begin
                    fre_2_0x <= 4'd0;
                    fre_2_x0 <= number_pressed+ 4'd1;
                    o_fre_2_temp <= number_pressed+4'd1;
                end
                9,10,11,12,13,14,15://11
                begin
                    fre_2_0x <= 4'd1;
                    fre_2_x0 <= number_pressed - 4'd9;
                    o_fre_2_temp <= number_pressed+4'd1;
                end
            endcase;
        end
    end
    //此时更改相位
    else
    begin
        led_temp <= 16'b1 << decoder_phase;
        case(decoder_phase)
            //此时更改当前位数据
            0,1,2,3,4,5,6,7,8,9:
            begin
                if(num_state == 2'd1)
                begin
                    phase_xx0 <= decoder_phase;
                end
                else if(num_state == 2'd2)
                begin
                    phase_x0x <= decoder_phase;
                end
                else if(num_state == 2'd3)
                begin
                    phase_0xx <= decoder_phase;
                end
                else
                begin
                    phase_xx0 <= 4'd9;
                    phase_x0x <= 4'd9;
                    phase_0xx <= 4'd9;
                end
            end
            //此时切换位序
            10,11,12:
            begin
                num_state <= decoder_phase - 4'd9;
            end
            default:
            begin
                phase_xx0 <= 4'd0;
                phase_x0x <= 4'd0;
                phase_0xx <= 4'd0;
            end
        endcase
        phase_data <= phase_0xx * 10'd100 + phase_x0x * 10'd10 + phase_xx0;
        parameter_state <= num_state;
    end
end

//拼凑要展示的数据
assign code = {fre_1_0x,fre_1_x0,fre_2_0x,fre_2_x0,phase_0xx,phase_x0x,phase_xx0,parameter_state};

reg [WIDTH_PHASE-1:0] phase_data; // 用于控制dds的相位

//连接dds频率端口
assign o_fre_1 = o_fre_1_temp;
assign o_fre_2 = o_fre_2_temp;

//连接dds相位端口
assign o_phase_1 = 8'd0;
assign o_phase_2 = phase_data;

assign rst_dds = pressed;//| (phase_data_add) | (phase_data_sub);

endmodule

5、矩阵键盘

module Keypad (
    input CLK,
    input RST,
    input [3:0] ROW,
    output reg [3:0] COL,
    
    output reg [3:0] DATA_NUMBER, 
    output reg PRESSED
);

    reg [15:0] DATA;
    reg [1:0] ColSel;

    // 列扫描计数
    always @(posedge CLK or posedge RST) begin
        if (RST) begin
            ColSel <= 2'd0;
            PRESSED <= 0;
        end
        else begin
            ColSel = ColSel + 2'd1;
            if (ColSel == 2'd0) begin
                PRESSED = (DATA != 16'd0);
            end
            else begin
                PRESSED = 0;
            end
        end
    end

    // 列扫描译码器
    always @(*) begin
        case (ColSel)
            2'd0: COL = 4'b1110;
            2'd1: COL = 4'b1101;
            2'd2: COL = 4'b1011;
            2'd3: COL = 4'b0111;
        endcase
    end

    // 行数据读取
    always @(negedge CLK or posedge RST) begin
        if (RST) begin
            DATA <= 16'd0;
        end
        else begin
            case (ColSel)
                2'd0: DATA[3:0] <= ~ROW;
                2'd1: DATA[7:4] <= ~ROW;
                2'd2: DATA[11:8] <= ~ROW;
                2'd3: DATA[15:12] <= ~ROW;
            endcase
        end
    end

  always @(*) begin
    if (RST) DATA_NUMBER <= 4'b0000;
    else if(DATA[15]==1)  DATA_NUMBER<=4'b1111;
    else if(DATA[14]==1)  DATA_NUMBER<=4'b1110;
    else if(DATA[13]==1)  DATA_NUMBER<=4'b1101;
    else if(DATA[12]==1)  DATA_NUMBER<=4'b1100;
    else if(DATA[11]==1)  DATA_NUMBER<=4'b1011;
    else if(DATA[10]==1)  DATA_NUMBER<=4'b1010;
    else if(DATA[ 9]==1)  DATA_NUMBER<=4'b1001;
    else if(DATA[ 8]==1)  DATA_NUMBER<=4'b1000;
    else if(DATA[ 7]==1)  DATA_NUMBER<=4'b0111;
    else if(DATA[ 6]==1)  DATA_NUMBER<=4'b0110;
    else if(DATA[ 5]==1)  DATA_NUMBER<=4'b0101;
    else if(DATA[ 4]==1)  DATA_NUMBER<=4'b0100;
    else if(DATA[ 3]==1)  DATA_NUMBER<=4'b0011;
    else if(DATA[ 2]==1)  DATA_NUMBER<=4'b0010;
    else if(DATA[ 1]==1)  DATA_NUMBER<=4'b0001;
    else if(DATA[ 0]==1)  DATA_NUMBER<=4'b0000;
    else DATA_NUMBER<=4'b0000;
    //没有按键按下,保持当前键值
  end
endmodule

6、数码管

module Component_Binary_To_7Segment(
    input             clk,    // 时钟信号
    input             rst_n,  // 复位信号(低电平有效)
    input      [31:0] code,   // 8421BCD码输入
    output     [6:0] seg,    // 段选信号输出(LEDA~LEDG)
    output reg [7:0] cat    // 管选信号输出(SEL0~SEL7)
);

  reg [2:0] cnt;  // 计数器
  reg [3:0] decoder_i;  // 数码管解码输入

  always @(posedge clk or posedge rst_n) begin
    if (rst_n) begin  // 复位信号为低电平时
      decoder_i <= 4'b0;  // 数码管解码输入置零
      cat      <= 8'b11111111;  // 管选信号置全高电平
      cnt       <= 3'b0;  // 计数器复位为0
    end else begin
      case (cnt)
        0: begin
          cat      <= 8'b11111110;  // 第一个管选信号(SEL0)为低电平,选择第一个数码管
          decoder_i <= code[3:0];  // 数码管解码输入为对应的8421BCD码
        end
        1: begin
          cat      <= 8'b11111101;  // 第二个管选信号(SEL1)为低电平,选择第二个数码管
          decoder_i <= code[7:4];  // 数码管解码输入为对应的8421BCD码
        end
        2: begin
          cat      <= 8'b11111011;  // 第三个管选信号(SEL2)为低电平,选择第三个数码管
          decoder_i <= code[11:8];  // 数码管解码输入为对应的8421BCD码
        end
        3: begin
          cat      <= 8'b11110111;  // 第四个管选信号(SEL3)为低电平,选择第四个数码管
          decoder_i <= code[15:12];  // 数码管解码输入为对应的8421BCD码
        end
        4: begin
          cat      <= 8'b11101111;  // 第五个管选信号(SEL4)为低电平,选择第五个数码管
          decoder_i <= code[19:16];  // 数码管解码输入为对应的8421BCD码
        end
        5: begin
          cat      <= 8'b11011111;  // 第六个管选信号(SEL5)为低电平,选择第六个数码管
          decoder_i <= code[23:20];  // 数码管解码输入为对应的8421BCD码
        end
        6: begin
          cat      <= 8'b10111111;  // 第七个管选信号(SEL6)为低电平,选择第七个数码管
          decoder_i <= code[27:24];  // 数码管解码输入为对应的8421BCD码
        end
        7: begin
          cat      <= 8'b01111111;  // 第八个管选信号(SEL7)为低电平,选择第八个数码管
          decoder_i <= code[31:28];  // 数码管解码输入为对应的8421BCD码
        end
      endcase

      if (cnt == 3'h7) cnt <= 3'b0;  // 计数器达到最大值时,复位为0
      else cnt <= cnt + 1'b1;  // 计数器递增
    end
  end

  nixie_cat_decoder unit1 (
      .decoder_i(decoder_i),  // 数码管解码输入
      .decoder_o(seg)         // 数码管段选信号输出
  );
endmodule


// 数码管解码模块
module nixie_cat_decoder (
    input      [3:0] decoder_i,  // 数码管解码输入
    output reg [6:0] decoder_o   // 数码管解码输出
);

  always @(decoder_i) begin
    case (decoder_i)
      4'h0: decoder_o <= 7'h3f;  // 8421BCD码为0时,输出数码管段选信号(LEDA~LEDG)使显示为数字0
      4'h1: decoder_o <= 7'h06;  // 8421BCD码为1时,输出数码管段选信号(LEDA~LEDG)使显示为数字1
      4'h2: decoder_o <= 7'h5b;  // 8421BCD码为2时,输出数码管段选信号(LEDA~LEDG)使显示为数字2
      4'h3: decoder_o <= 7'h4f;  // 8421BCD码为3时,输出数码管段选信号(LEDA~LEDG)使显示为数字3
      4'h4: decoder_o <= 7'h66;  // 8421BCD码为4时,输出数码管段选信号(LEDA~LEDG)使显示为数字4
      4'h5: decoder_o <= 7'h6d;  // 8421BCD码为5时,输出数码管段选信号(LEDA~LEDG)使显示为数字5
      4'h6: decoder_o <= 7'h7d;  // 8421BCD码为6时,输出数码管段选信号(LEDA~LEDG)使显示为数字6
      4'h7: decoder_o <= 7'h07;  // 8421BCD码为7时,输出数码管段选信号(LEDA~LEDG)使显示为数字7
      4'h8: decoder_o <= 7'h7f;  // 8421BCD码为8时,输出数码管段选信号(LEDA~LEDG)使显示为数字8
      4'h9: decoder_o <= 7'h6f;  // 8421BCD码为9时,输出数码管段选信号(LEDA~LEDG)使显示为数字9
      4'ha: decoder_o <= 7'h73;  // 8421BCD码为A时,输出数码管段选信号(LEDA~LEDG)使显示为字母A
      4'hb: decoder_o <= 7'h7c;  // 8421BCD码为B时,输出数码管段选信号(LEDA~LEDG)使显示为字母B
      4'hc: decoder_o <= 7'h39;  // 8421BCD码为C时,输出数码管段选信号(LEDA~LEDG)使显示为字母C
      4'hd: decoder_o <= 7'h5e;  // 8421BCD码为D时,输出数码管段选信号(LEDA~LEDG)使显示为字母D
      4'he: decoder_o <= 7'h79;  // 8421BCD码为E时,输出数码管段选信号(LEDA~LEDG)使显示为字母E
      4'hf: decoder_o <= 7'h71;  // 8421BCD码为F时,输出数码管段选信号(LEDA~LEDG)使显示为字母F
    endcase
  end
endmodule

7、分频器

module NDivider_Even(
    input clk,        // 输入时钟信号
    input [15:0] N,   // 分频比
    input reset,      // 复位信号
    output wire clk_out // 输出分频后的时钟信号
);
    reg [15:0] counter;// 32位计数器
    reg temp_out;//临时输出

    // 时钟分频逻辑
    always @(posedge clk or posedge reset)
    begin
        if(reset)
	        begin
                counter <= 0;
                temp_out <= 0;
            end
        else  if(counter == N / 2- 1)  
            begin 
                temp_out <= ~temp_out; 
                counter <= 0; 
            end
        else
                counter <= counter + 1'b1;
    end

    assign clk_out = temp_out;

endmodule

8、仿真文件

`include "MY_DDS_PRO.v"
`include "dds.v"
`include "mem.v"
`include "Keypad.v"
`include "key_press.v"
`include "parameter_wave.v"
`include "NDivider_Even.v"
`include "ratio.v"
`include "tlv5638.v"
`include "Counter_N.v"
`include "Component_Binary_To_7Segment.v"
`include "cosine_mux.v"
`include "data_transport.v"
`include "waveform_changed.v"
`include "ecoder_phase.v"
`timescale 1ps/1ps

module test;

    reg [3:0] row;//行信号
    wire [3:0] col;

    reg clk;       // 时钟信号
    reg rst;    // 复位信号
    reg switch_dds;  // DDS开关
    reg btn_wave; // 波形选择开关
    reg switch_channel; //频率选择
    reg switch_parameter; //参数选择
    reg switch_wave; //通道波形选择
    wire cs_o;   // 选通信号
    wire data_o; // 串行数字信号
    wire dclock_o; // 串行时钟
    
    //数码管位序信号
    wire [7:0] cat;
    //数码管位选信号
    wire [6:0] seg;
    wire [15:0] led;

MY_DDS_PRO dds_inst(
    .clk(clk),
    .rst(rst),
    .switch_dds(switch_dds),
    .cs_o(cs_o),
    .data_o(data_o),
    .dclock_o(dclock_o),
    .cat(cat),
    .seg(seg),

    .btn_wave(btn_wave),
    .switch_parameter(switch_parameter),
    .switch_channel(switch_channel),
    .switch_wave(switch_wave),
    .row(row),
    .col(col),
    .led(led)
);

//(1)clk, reset and other constant regs
   initial begin
      clk               = 1'b0 ;
      rst              = 1'b1 ;
      #100 ;
      rst      = 1'b0 ;
      #10 ;
      forever begin
         #5 ;
         clk = ~clk ;   //system clock, 1000Hz
      end
   end // initial begin

//(2)signal setup ;
//    integer      freq_dst        = 2000000 ;     //2MHz
//    integer      phase_coe       = 2;            //1/4 cycle, that is pi/2
   initial begin
      switch_dds = 1'b0;
      #100 ;
      switch_dds = 1'b1;
      #500000;
      row = 4'b1101;
      #300000;
      switch_dds = 1'b0;
   end // initial begin

   initial
   begin
      $dumpfile("test.vcd");
      $dumpvars(0, test);
      #1000000;
      $finish ;
   end

endmodule

四、仿真波形及波形分析

1、SPI时序分析

​ 在发送数据之前,选通信号首先被拉高,然后在下一个时钟上升沿时被拉低,表示进入发送状态。在发送状态中,数据将经过16个 dclock_o 周期,表示发送16位数据。发送完成后,eoc_o 会被拉高,表示发送完成。该信号可以触发下一次选通信号的拉高,进入下一轮数据传输。

image-20231116160328711

2、DDS驱动分析

eco_o的二分频作为DDS的时钟,故在一个时钟周期内,两个DDS模块的数据不会发生改变,发送的数据分别从中提取。

image-20231116164709474

mem_addr_r 的值从0变化到1023,根据当前的wave_sel从对应查找表中选取值,输出得到正弦波、方波、三角波。

image-20231116180729444

image-20231116181153233

3、控制逻辑分析

  • 改变频率值

image-20231116183228155

  • 改变相位值
image-20231116201026224

4、显示逻辑分析

​ 在时钟的驱动下,code的数据逐位被展示出来。

image-20231116201142794

五、功能说明及资源利用情况

1、实现输出任意频率比和相位的正弦波

​ 输出信号的峰峰值 ≥ 1 V,起始频率 ≥ 200 Hz,并且频率最高设置到 348 X 16 约 5 kHz,相位差可从 0 设置到 360 度,实际显示刻度 1 对应 0.35 度。

Frequency and Phase Setting

2、李萨如图展示

​ 能够正确显示题目要求的李萨如图,频率相位满足相应的指标。

Lissajous Figure

3、矩阵键盘和显示逻辑

​ 数码管显示的是 0101000F ,“0101”表示通道 A 和通道 B 的频率比值为 1:1,“000”表示通道相对于通道 A 的相位差是 0 度,最后一位是 F 表示的是当前正在设置频率。

​ 有两个开关用于参数设置,SW5 打时表示正在调整通道 A,反之则为通道 B,此时按下矩阵键盘的按键,能更改频率比值(变化范围为 1~16,超过 16 后一个周期内点数可能会不满足要求,故此处没有设置更高的数值);SW4 打开时表示正在改变频率,此时数码管最后一位显示为 “F”,反之表示正在改变相位——按下矩阵键盘左下角三位数后,数码管最后一位依次显示 “3”、“2”、“1”,表示当前在更改相位的某一位数值,再按下矩阵键盘左上角 3X3 的键值即可实现数值从 1—9 进行改变,按下其余按键改变为 0。

Matrix Keyboard Input

4、可切换波形

​ 设置了开关 SW6 用于切换两通道的波形,开关置高时改变通道 A,反之改变通道 B,此时再按下 BTN1 即可改变 DDS 输出的波形。

Waveform Selection Waveform Selection

5、资源利用情况

​ 如图所示,占用了 72% 的逻辑单元,占用了 42% 的引脚。

Resource Utilization

六、故障及问题分析

  1. DDS波形输出异常问题

    ​ 在仿真时观察到DDS有正常输出,但SPI时序初始时存在未定义现象。后面的观察正常,但在上板验证时却观察不到波形。这是因为在仿真中,部分寄存器没有初始值而被调用,导致传输数据异常。解决方法是在复位逻辑中添加对这些寄存器的赋初值操作,确保在启动时寄存器处于可控状态,从而解决未定义的问题。

  2. 矩阵键盘未响应问题

    ​ 按下矩阵键盘却没有响应的问题可能是由于编写代码时未考虑到扫描的连续性,没有添加优先译码功能。应当在矩阵键盘扫描逻辑中加入对按键的优先级判定,确保按键的响应。

  3. 烧录问题导致波形异常或通道消失

    ​ 在烧录代码后,板子未进行更新,甚至出现有一通道的波形消失的现象。可能是由于Quartus版本问题,建议添加辅助显示的代码,如某个LED灯被点亮,用来识别是否成功烧录。如果未出现预期的现象,建议重启电脑或Quartus软件。

  4. 矩阵键盘或数码管异常现象

    ​ 出现矩阵键盘或数码管无法识别键值或显示乱闪的现象可能是因为错误使用了系统时钟,导致模块出错。建议对时钟进行适当分频后再作为时钟输入,确保稳定运行。

  5. 波形相位乱飘问题

    ​ 输出的波形出现相位乱飘的现象可能是因为示波器触发源选择不得当。尝试切换另一个通道作为触发源。另一可能是改变参数时,没有对DDS模块进行复位清零,导致相位累加器的值错误。在每次改变参数后,传入复位信号到DDS模块,确保内部状态正确初始化。

七、总结和结论

​ 在本次实验中,我成功完成了一个简易的DDS信号生成器,实现了输出两路频率和相位有差别的正弦波,形成多样的李萨如图。这为我的数字电路设计能力提供了充分的锻炼和实践。

​ 首先,通过Verilog语言的使用,我更深刻地认识到其在数字电路设计中的强大和灵活性。合理规划顶层和底层模块,以及恰当处理它们之间的关系,使得代码更具可读性和可维护性。

​ 其次,通过合理设置仿真变量和编写完善的testbench文件,我能够有效验证设计的正确性。观察各个模块的变量情况,及时发现问题并进行追溯,对于确保数字电路的稳定性和可靠性起到了关键作用。

​ 在时序逻辑和状态机的设计方面,我更深刻地理解了其在数字电路中的重要性。在DDS信号生成器中,正确的时序控制是生成准确波形的关键。通过仔细的设计和调试,我成功实现了正弦波形的准确性和稳定性。

​ 总体而言,本次实验不仅为我提供了数字电路设计的全面训练,还深化了我对Verilog语言和时序控制的认识。这次经历不仅锻炼了我的编码和调试技能,也为未来更深入的硬件开发和数字电路设计奠定了坚实的基础。

1
https://gitee.com/previous-month/DDS.git
git@gitee.com:previous-month/DDS.git
previous-month
DDS
基于DDS的李萨如图形生成器
master

搜索帮助