时隔两年重新开始写博客,以后也会经常更新,感谢大家的支持!!
DDS信号发生器采用直接数字频率合成(Direct Digital Synthesis,简称DDS)技术,把信号发生器的频率稳定度、准确度提高到与基准频率相同的水平,并且可以在很宽的频率范围内进行精细的频率调节。采用这种方法设计的信号源可工作于调制状态,可对输出电平进行调节,也可输出各种波形。在某一次课程设计中,需要设计一个DDS信号发生器,以下是题目要求:
基于DDS技术利用硬件描述语言设计并制作一个数字式移相信号发生器。
(1)基本要求:
a.频率范围:1Hz~4kHz,频率步进为1Hz,输出频率可预置。
b.A、B两路正弦信号输出,10位输出数据宽度。
c.相位差范围为0~359°,步进为1.0°,相位差值可预置。
d.数字显示预置的频率(10进制)、相位差值。
(2)发挥部分
a.修改设计,增加幅度控制电路(如可以用一乘法器控制输出幅度)。
b.输出幅度峰峰值0.1~3.0V,步距0.1V。
首先介绍DDS的原理。如果想要采样一个正弦波,首先需要对正弦波数据进行采样,每周期为 M 个采样点,分别记为 1~M。 对应每次参考时钟 fc , 输出一个采样点,输出图中所示的一个周期的正弦,需要 M 个时钟周期,则输出的波形频率为 fout =fc/M。对于这种情况, 每次时钟到来时,相位累加器加 1 ,则就会在第 i 个时钟周期输出 第 i 个采样点( i = 1~M) ,第 M + 1 个时钟输出第 1 个采样点,以此循坏,这时的相位累加器实 际上是步进为 1 的模 M 计数器。 如果每次时钟到来时, 总是间隔一个采样点输出,即相位累 加器的步进为 2 ,这时在第 i 个周期输出第 2i 个采样点,输出波形如图 1. 2 的波形 b,显然波 形 b 的频率是 a 的 2 倍,即 f b = 2 f a 。
本次采用查表法进行波形数据的读取,调用ROM IP核,ROM IP核调用方法如下:
- 首先打开Quartus,新建一个工程(新建工程不过多赘述),左上角Tools->MegaWizard Plug-In Manager
选择第一个Creat a new custom....,在Memory Compiler 中选择 ROM:1-PORT,右边选择自己的输出文件,名称为rom,文件语言默认选择Verilog,这里建议所有的IP核尽量存放在一个单独的文件夹内,方便后续管理
选择next,输出位宽选择10bits,1024个words,后续一路next后选择ROM的波形数据(.mif),波形数据选择可以使用波形数据生成器(资源链接分享在文章末尾)。然后一路next直到finish。之后可以在模块中例化ROM IP即可。
本次DDS信号发生器的为模块化,自顶向下的层次化设计,具体RTL视图如下:
整体为4个模块,系统时钟分频模块,矩阵键盘模块,DDS信号数据模块,数码管驱动模块。为了将DAC抛出的数据能够有两个不同相位的输出,DA模块有DAch的通道选择,可以使用二选一数据选择器,当DAch为低电平时,抛出未进行相移的波形,当DAch为高电平时,抛出进行相移后的波形。伪代码设计如下,其中Pword为相位控制字,ROMADDR为ROM的寻址信号。
always @(posedge clk or negedge rst) begin
if(!rst)
ROMADDR <= 10'd0;
else if(DAch == 1'b0) //相位控制
ROMADDR <= frechange[31:22] + Pword;
else
ROMADDR <= frechange[31:22];
end
矩阵键盘扫描以及按下一个数字数码管自动移位的代码设计思路,矩阵键盘扫描原理不过多赘述,请参考其他优秀的文章,可以指定一个较大的位宽,比如八位数码管,每位数码管显示的最大数据为9,而显示9则需要4个位宽,则八位数码管需要32个位宽的寄存器,才能保证每一位数据的正常显示,可以使用一个计数器进行按键按下次数的计数,按键按下一下,计数器加1,再按下,计数器再加1,以次类推,对按键计数器进行状态机代码编写,当cnt == 1时,显示第一位数字,当cnt==2时,将之前的数字进行左移,然后将本次输入的数字放到最低位。具体伪代码设计如下:
always@(posedge sys_clk or negedge sys_rstn) begin // 处理按键事件
if(!sys_rstn)
;// todo
else
case(key_count)
4'd1:value = {4'd0,4'd0,4'd0,keyboard_val};
4'd2:begin value[7:4] <= value[3:0]; value[3:0] <= keyboard_val;end
4'd3:begin value[11:8] <= value[7:4]; value[7:4] <= value[3:0];value[3:0] <= keyboard_val;end
4'd4:begin value[15:12] <= value[11:8];value[11:8] <= value[7:4]; value[7:4] <= value[3:0];value[3:0] <= keyboard_val;end
4'd5:begin value[19:16] <= value[15:12];value[15:12] <= value[11:8];value[11:8] <= value[7:4]; value[7:4] <= value[3:0];value[3:0] <= keyboard_val;end
default value <= value;
endcase
end
第二个较难点就是如何将输入的数字转换成DDS的频率控制、相位控制以及幅度控制。其实这个点不难,熟悉C语言的应该知道,如何取多位数的指定的个位十位百位,这里进行逆运算就可以了,将上述存储在value寄存器中的数据进行运算,具体伪代码如下,具体看对应控制字的赋值。
always @(posedge sys_clk or negedge sys_rstn) begin // 状态机选择
if (!sys_rstn) begin //默认开始调节频率
Fword <= 22'd3436426;
Pword <= 10'd0;
Aword <= 10'd1023;
LED_OUT <= 1'b0;
end
else
case (mode_select)
FwordState:begin // 频率控制
if(key_pressed_flag && keyboard_val == KEY_SURE)begin // 按下确认按键,将对应控制字进行输出
LED_OUT <= 1'b1;
Fword <= (value[19:16]*10000 + value[15:12]*1000 + value[11:8] * 100 + value[7:4]*10 + value[3:0])*100000/1164;
end
else
LED_OUT <=1'b0;
end
PwordState:begin // 相位控制
if(key_pressed_flag && keyboard_val == KEY_SURE)begin // 按下确认按键,将对应控制字进行输出
LED_OUT <= 1'b1;
Pword <= (value[11:8] * 100 + value[7:4]*10 + value[3:0])*1023/360;
end
else
LED_OUT <=1'b0;
end
AwordState:begin
if(key_pressed_flag && keyboard_val == KEY_SURE)begin // 按下确认按键,将对应控制字进行输出
LED_OUT <= 1'b1;
Aword <= (value[7:4]*10 + value[3:0])*1023/33;
end
else
LED_OUT <=1'b0;
end
default :
;
endcase
end
整体代码如下,觉得有用的可以三连支持哟,感谢!!!
/*
Author : Junhan Lv
Email Address : 1322069095@qq.com
Filename : top.v
Data : 2024-9-1
Description : DDS TOP Connection.
Modification History :
Data Author Version Change Description
=======================================================
24/9/1 Junhan Lv 1.0 Original
*/
module top (
input wire sys_clk, // 系统时钟
input wire sys_rstn, // 系统复位
input wire [1:0] mode_select, // 模式选择,通过拨码开关
input wire [1:0] wave_selevt,
input [3:0] row, // 矩阵键盘 行
output wire LED_OUT,
output wire [3:0] col, // 矩阵键盘 列
output wire [2:0] sel,
output wire [7:0] seg,
output wire DAch, // 通道选择
output wire DAclk,
output wire DAwr,
output wire [9:0] data_out // 波形数据输出
);
wire [21:0]value ; // 按键键值
wire [3:0] key_count; // 按键按下次数
wire nedge ;
wire [9:0] ROMADDR ; // ROM 地址查找
wire [21:0]Fword ;
wire [9:0] Pword ;
wire [9:0] Aword ;
keyboard keyboard_1 (
.sys_clk ( sys_clk ),
.sys_rstn ( sys_rstn ),
.row ( row ),
.col ( col ),
.mode_select ( mode_select ),
.nedge ( nedge ),
.value ( value ),
.key_count ( key_count ),
.LED_OUT ( LED_OUT ),
.Fword ( Fword ),
.Pword ( Pword ),
.Aword ( Aword )
);
seg_driver seg_driver_1 (
.clk ( sys_clk ),
.rst_n ( sys_rstn ),
.value ( value ),
.key_count ( key_count ),
.nedge ( nedge ),
.sel ( sel ),
.seg ( seg ),
.mode_select ( mode_select )
);
dds dds_1(
.clk ( sys_clk ),
.rst ( sys_rstn ),
.DAch ( DAch ),
.wave_select ( wave_selevt ),
.Fword ( Fword ),
.Pword ( Pword ),
.Aword ( Aword ),
.ROMADDR ( ROMADDR ),
.dataout ( data_out )
);
div_freq div_freq_1 (
.clk ( sys_clk ),
.DAch ( DAch ),
.DAclk ( DAclk ),
.DAwr ( DAwr )
);
endmodule
module div_freq (
input clk,
output wire DAch,
output wire DAclk,
output wire DAwr
);
wire[15:0] DOUT;
reg [15:0] cnt = 0;
always @(posedge clk) begin
if (cnt == 16'b1111_1111_1111_1111)
cnt <= 0;
else
cnt <= cnt+1'b1;
end
assign DOUT = cnt;
assign DAclk = DOUT[1];
assign DAwr = DOUT[1];
assign DAch = DOUT[2];
endmodule
/*
Author : Junhan Lv
Email Address : 1322069095@qq.com
Filename : keyboard.v
Data : 2024-9-1
Description : keyboard
Modification History :
Data Author Version Change Description
=======================================================
24/9/1 Junhan Lv 1.0 Original
*/
module keyboard(
input sys_clk,
input sys_rstn,
input [3:0] row, // 矩阵键盘 行
input wire[1:0] mode_select, // 模式选择
output reg [3:0] col, // 矩阵键盘 列
output reg [21:0] value, // 键盘值
output reg [3:0] key_count, // 在确定按钮按下前,进行按键按下计数操作
output wire nedge,
output reg LED_OUT,
output reg [21:0] Fword,
output reg [9:0] Pword,
output reg [9:0] Aword
);
//++++++++++++++++++++++++++++++++++++++
// 分频部分 开始
//++++++++++++++++++++++++++++++++++++++
parameter FwordState = 2'b01; // 频率控制字模式
parameter PwordState = 2'b10; // 相位控制字模式
parameter AwordState = 2'b11; // 幅度控制字模式
reg [17:0] cnt; // 去抖动计数器
reg [3:0] keyboard_val;
reg key_r0; //同步信号(滤波作用,滤除小于一个周期的抖动)
reg key_r1; //打拍
//同步
always @(posedge sys_clk) begin
key_r0<=key_pressed_flag;
end
//打拍
always @(posedge sys_clk) begin
key_r1<=key_r0;
end
assign nedge = ~key_r0 & key_r1; //检测到下降沿拉高
always @ (posedge sys_clk, negedge sys_rstn)
if (!sys_rstn)
cnt <= 0;
else
cnt <= cnt + 1'b1;
wire key_clk = cnt[17]; // T =(2^20/50M = 20.97152)ms
//--------------------------------------
// 分频部分 结束
//--------------------------------------
//++++++++++++++++++++++++++++++++++++++
// 状态机部分 开始
//++++++++++++++++++++++++++++++++++++++
// 状态数较少,独热码编码
parameter NO_KEY_PRESSED = 6'b000_001; // 没有按键按下
parameter SCAN_COL0 = 6'b000_010; // 扫描第0列
parameter SCAN_COL1 = 6'b000_100; // 扫描第1列
parameter SCAN_COL2 = 6'b001_000; // 扫描第2列
parameter SCAN_COL3 = 6'b010_000; // 扫描第3列
parameter KEY_PRESSED = 6'b100_000; // 有按键按下
parameter KEY_SURE = 4'hE ; // 确认按键
parameter KEYCLEAR = 4'hF ; // 清楚按键
parameter KEYMODE = 4'hD ; // 模式选择按键
reg [5:0] current_state, next_state; // 现态、次态
always @ (posedge key_clk, negedge sys_rstn)
if (!sys_rstn)
current_state <= NO_KEY_PRESSED;
else
current_state <= next_state;
// 根据条件转移状态
always @(*)
case (current_state)
NO_KEY_PRESSED : // 没有按键按下
if (row != 4'hF)
next_state = SCAN_COL0;
else
next_state = NO_KEY_PRESSED;
SCAN_COL0 : // 扫描第0列
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = SCAN_COL1;
SCAN_COL1 : // 扫描第1列
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = SCAN_COL2;
SCAN_COL2 : // 扫描第2列
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = SCAN_COL3;
SCAN_COL3 : // 扫描第3列
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = NO_KEY_PRESSED;
KEY_PRESSED : // 有按键按下
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = NO_KEY_PRESSED;
endcase
reg key_pressed_flag; // 键盘按下标志
reg [3:0] col_val, row_val; // 列值、行值
// 根据次态,给相应寄存器赋值
always @ (posedge key_clk, negedge sys_rstn)
if (!sys_rstn)
begin
col <= 4'h0;
key_pressed_flag <= 0;
end
else
case (next_state)
NO_KEY_PRESSED : // 没有按键按下
begin
col <= 4'h0;
key_pressed_flag <= 0; // 清键盘按下标志
end
SCAN_COL0 : // 扫描第0列
col <= 4'b1110;
SCAN_COL1 : // 扫描第1列
col <= 4'b1101;
SCAN_COL2 : // 扫描第2列
col <= 4'b1011;
SCAN_COL3 : // 扫描第3列
col <= 4'b0111;
KEY_PRESSED : // 有按键按下
begin
col_val <= col; // 锁存列值
row_val <= row; // 锁存行值
key_pressed_flag <= 1; // 置键盘按下标志
end
endcase
//--------------------------------------
// 状态机部分 结束
//--------------------------------------
//++++++++++++++++++++++++++++++++++++++
// 扫描行列值部分 开始
//++++++++++++++++++++++++++++++++++++++
always @ (posedge key_clk, negedge sys_rstn)
if (!sys_rstn)begin
keyboard_val <= 4'h0;
end
else
if (key_pressed_flag) begin
case ({col_val, row_val})
8'b1110_1110 : keyboard_val <= KEYMODE;
8'b1110_1101 : keyboard_val <= KEY_SURE; //4'hE // 确定按键
8'b1110_1011 : keyboard_val <= 4'h0;
8'b1110_0111 : keyboard_val <= KEYCLEAR; // 清除按键
8'b1101_1110 : keyboard_val <= 4'hC;
8'b1101_1101 : keyboard_val <= 4'h9;
8'b1101_1011 : keyboard_val <= 4'h8;
8'b1101_0111 : keyboard_val <= 4'h7;
8'b1011_1110 : keyboard_val <= 4'hB;
8'b1011_1101 : keyboard_val <= 4'h6;
8'b1011_1011 : keyboard_val <= 4'h5;
8'b1011_0111 : keyboard_val <= 4'h4;
8'b0111_1110 : keyboard_val <= 4'hA;
8'b0111_1101 : keyboard_val <= 4'h3;
8'b0111_1011 : keyboard_val <= 4'h2;
8'b0111_0111 : keyboard_val <= 4'h1;
endcase
end
//--------------------------------------
// 扫描行列值部分 结束
//--------------------------------------
always @(posedge sys_clk or negedge sys_rstn ) begin
if(!sys_rstn)
key_count <= 0;
else if(nedge)
key_count <= key_count + 1'b1;
else if (key_count >= 9 || keyboard_val == KEYCLEAR)
key_count <= 0;
else
key_count <= key_count;
end
always@(posedge sys_clk or negedge sys_rstn) begin // 处理按键事件
if(!sys_rstn)begin
value <= 22'b0;
end
else if(nedge && keyboard_val != KEY_SURE && keyboard_val != KEYCLEAR && keyboard_val != KEYMODE)begin // 判断是否按键按下,除开确认按键和清除按键
case(key_count)
4'd1:value = {4'd0,4'd0,4'd0,keyboard_val};
4'd2:begin value[7:4] <= value[3:0]; value[3:0] <= keyboard_val;end
4'd3:begin value[11:8] <= value[7:4]; value[7:4] <= value[3:0];value[3:0] <= keyboard_val;end
4'd4:begin value[15:12] <= value[11:8];value[11:8] <= value[7:4]; value[7:4] <= value[3:0];value[3:0] <= keyboard_val;end
4'd5:begin value[19:16] <= value[15:12];value[15:12] <= value[11:8];value[11:8] <= value[7:4]; value[7:4] <= value[3:0];value[3:0] <= keyboard_val;end
default value <= value;
endcase
end
else if (nedge && keyboard_val == KEYCLEAR) begin // 清除按键,将所有value清除
value <= 22'b0;
end
else
value[21:20] <= mode_select; // 模式赋值
end
always @(posedge sys_clk or negedge sys_rstn) begin // 状态机选择
if (!sys_rstn) begin //默认开始调节频率
Fword <= 22'd3436426;
Pword <= 10'd0;
Aword <= 10'd1023;
LED_OUT <= 1'b0;
end
else
case (mode_select)
FwordState:begin // 频率控制
if(key_pressed_flag && keyboard_val == KEY_SURE)begin // 按下确认按键,将对应控制字进行输出
LED_OUT <= 1'b1;
Fword <= (value[19:16]*10000 + value[15:12]*1000 + value[11:8] * 100 + value[7:4]*10 + value[3:0])*100000/1164;
end
else
LED_OUT <=1'b0;
end
PwordState:begin // 相位控制
if(key_pressed_flag && keyboard_val == KEY_SURE)begin // 按下确认按键,将对应控制字进行输出
LED_OUT <= 1'b1;
Pword <= (value[11:8] * 100 + value[7:4]*10 + value[3:0])*1023/360;
end
else
LED_OUT <=1'b0;
end
AwordState:begin
if(key_pressed_flag && keyboard_val == KEY_SURE)begin // 按下确认按键,将对应控制字进行输出
LED_OUT <= 1'b1;
Aword <= (value[7:4]*10 + value[3:0])*1023/33;
end
else
LED_OUT <=1'b0;
end
default :
;
endcase
end
endmodule
module seg_driver (
input wire clk,
input wire rst_n,
input wire [21:0] value,
input wire [3:0] key_count,
input wire nedge,
input wire [1:0] mode_select,
output reg [2:0] sel,
output reg [7:0] seg
);
reg [3:0] seg_flag;
reg dot;
//10ms计时器---用来切换数码管位选,以达到轮流显示时间的各位(肉眼可以看到动态的时间计数)
reg [19:0] cnt;
wire add_cnt;
wire end_cnt;
parameter MAX_CNT =50_000 ,
ZERO =7'b011_1111,
ONE =7'b000_0110,
TWO =7'b101_1011,
THREE =7'b100_1111,
FOUR =7'b110_0110,
FIVE =7'b110_1101,
SIX =7'b111_1101,
SEVEN =7'b000_0111,
EIGHT =7'b111_1111,
NINE =7'b110_1111,
KEY_SURE = 4'hE;
//计时器
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt<=0;
end
else if(add_cnt) begin
if (end_cnt) begin
cnt<=0;
end
else
cnt<=cnt+1'b1;
end
end
assign add_cnt=1'b1;
assign end_cnt=add_cnt&&cnt==MAX_CNT-1;
//切换数码管位选
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sel<=3'b000;
end
else if(cnt==MAX_CNT-1) begin
sel<=sel+1'b1; // 动态切换数码管
end
end
//切换数码管段选
always @(*) begin
begin
case (sel)
3'b111: begin seg_flag<=value[3:0]; dot <=0; end
3'b110: begin seg_flag<=value[7:4]; if(mode_select == 2'b11) dot <=1; else dot <= 0; end
3'b101: begin seg_flag<=value[11:8]; dot <=0; end
3'b100: begin seg_flag<=value[15:12]; dot <=0; end
3'b011: begin seg_flag<=value[19:16]; dot <=0; end
3'b001: begin seg_flag<=value[21:20]; dot <=0; end
3'b010: begin seg_flag<=0; dot <=0; end
3'b000: begin seg_flag<=key_count; dot <=0; end
default :seg_flag<=0;
endcase
end
end
//段选译码
always @(*) begin
begin
case (seg_flag)
0: seg<={dot,ZERO} ;
1: seg<={dot,ONE} ;
2: seg<={dot,TWO} ;
3: seg<={dot,THREE} ;
4: seg<={dot,FOUR} ;
5: seg<={dot,FIVE} ;
6: seg<={dot,SIX} ;
7: seg<={dot,SEVEN} ;
8: seg<={dot,EIGHT} ;
9: seg<={dot,NINE} ;
default: seg<=8'b0000_0000;
endcase
end
end
endmodule //seg_driver
module dds(
input clk,
input rst,
input wire [1:0] wave_select, // 波形切换
input wire DAch, // 波形通道选择
input wire [21:0]Fword,
input wire [9:0] Pword,
input wire [9:0] Aword,
input wire [7:0] CNT8 ,
output reg [9:0] ROMADDR, // 输出ROM地址
output wire [9:0] dataout // 输出对应波形
);
parameter SINWAVE = 2'b00; // 正弦波
parameter SAWTOOTHWAVE = 2'b01; // 锯齿波
parameter SQUAREWAVE = 2'b10; //方波
parameter TRIANGLEWAVE = 2'b11; // 三角波
reg [9:0] wavedata;
wire[9:0] sindata;
wire[9:0] sawtoothdata;
wire[9:0] squaredata;
wire[9:0] triangledata;
rom rom_inst0 ( // 正弦波数据
.address ( ROMADDR ),
.clock ( clk ),
.q ( sindata )
);
rom2 rom_inst1 ( // 三角波数据
.address ( ROMADDR ),
.clock ( clk ),
.q ( triangledata )
);
rom3 rom_inst2 ( // 方波数据
.address ( ROMADDR ),
.clock ( clk ),
.q ( squaredata )
);
rom4 rom_inst3 ( // 锯齿波数据
.address ( ROMADDR ),
.clock ( clk ),
.q ( sawtoothdata )
);
always @(*) begin // 波形选择
case (wave_select)
SINWAVE: wavedata <= sindata;
SAWTOOTHWAVE: wavedata <= sawtoothdata;
SQUAREWAVE: wavedata <= squaredata;
TRIANGLEWAVE: wavedata <= triangledata;
default:
wavedata <= triangledata;
endcase
end
//相位寄存器:
reg [31:0]frechange;
always @(posedge clk or negedge rst) begin
if(!rst)
frechange <= 32'd0;
else
frechange <= frechange + Fword;
end
always @(posedge clk or negedge rst) begin
if(!rst)
ROMADDR <= 10'd0;
else if(DAch == 1'b0) //相位控制
ROMADDR <= frechange[31:22] + Pword;
else
ROMADDR <= frechange[31:22];
end
wire [9:0] data;
assign data = wavedata;
//调幅
reg [19:0] AMdata;
always@(posedge clk)
if(!rst)
AMdata <= 1'd0;
else
AMdata <= data * Aword;
assign dataout = AMdata[19:10];
endmodule