一、实验目的与要求
用Verilog设计一个简易电子琴,可通过敲击不同的按键发出相应的音调,同时将音符显示在数码管上。
二、实验内容
<1> 4*4矩阵键盘扫描电路的设计
矩阵键盘又称为行列式键盘,它是由4条列线组成的键盘,其原理如图1所示。在行线和列线的每一个交叉点上设置一个按键,按键的个数是4*4,按键排列如图1所示。按下某个键后,为了辨别和读取键值信息,一般可以采用如下方法:向A端口扫描输入一组只含一个0的4位数据,如1110、1101、1011、0111,若有按键按下,则B端口一定会输出对应的数据,因此,只要结合A、B端口的数据,就能判断按键的位置。比如,在图2中,S1按键的位置编码是{A,B}=1110_0111。
图1
图2
<2> 乐曲演奏电路的设计
乐曲演奏的原理是这样的:组成乐曲的每个音符的频率值(音调)及其持续的时间(音长)是乐曲能够连续演奏所需的两个基本数据,因此,只要控制输出到扬声器的激励信号的频率高低和持续的时间,就可以使扬声器发出连续的乐曲声。
(1)音调的控制
频率的高低决定了音调的高低。音乐的十二平均律规定:每两个八度音之间的频率相差一倍。在两个八度音之间,又可分为12个半音,每两个半音的频率比为。简谱中从低音1至高音1之间每个音名对应的频率如表1所示。
表1
所有不同频率的信号都是从同一个基准频率分频得到的。为了减小输出的偶次谐波分量,最后输出到声音模块的波形应为对称方波,因此在到达声音模块之前,还需要一个二分频的分频器。如果可以,用正弦波代替方波来驱动声音模块将会有更好的效果。
(2)音长的控制
音符的持续时间根据乐曲的速度以及每个音符的节拍数来确定。如果将全音符的持续时间设置为1s的话,则四分音符的时长为4Hz,以此类推。
三、代码实现
注:代码采取Quartus prim模块化电路设计,生成电路元件,并在Block Diagram File中调用的设计方法,不清楚这种方法的读者可查看博主FPGA专栏:“Quartus prim 实现模块化.....的解决方案”这篇博客
<1>整体模块展示
<2>divider分频模块代码展示
系统输入时钟为50MHZ,利用计数器,通过系统时钟不断分频,得到所需的21个不同的音调,同时生成数码管显示所需的2ms始终和1ms按键扫描时钟(时钟大小读者可根据实际情况调整)
module divider(clkin,rst,clkout_2ms,clkout_1ms,key_value,tone);
input clkin,rst;
//音调端口定义
input [4:0]key_value;//按键值输入
output reg tone;//输出音调
reg[16:0] cnt,counter;//cnt分频计数 counter,半周期计数上限制
reg flip;//时钟翻转标志
//2ms 数码管、按键扫描显示工作时钟
output reg clkout_2ms,clkout_1ms;
reg [31:0] cnt_2ms,cnt_1ms;
always @ (posedge clkin or posedge rst)
if (rst==1)
cnt_2ms <= 0;
else if (cnt_2ms==49999)
cnt_2ms <= 0;
else
cnt_2ms <= cnt_2ms + 1;
always @ (posedge clkin or posedge rst)
if (rst==1)
clkout_2ms <= 1;
else if (cnt_2ms <= 24999)
clkout_2ms <= 1;
else
clkout_2ms <= 0;
//1ms
always @ (posedge clkin or posedge rst)
if (rst==1)
cnt_1ms <= 0;
else if (cnt_1ms==24999)
cnt_1ms <= 0;
else
cnt_1ms <= cnt_1ms + 1;
always @ (posedge clkin or posedge rst)
if (rst==1)
clkout_1ms <= 1;
else if (cnt_1ms <= 12499)
clkout_1ms <= 1;
else
clkout_1ms <= 0;
//弹奏音调分频模块
always@(posedge clkin or posedge rst)
begin
if(rst)
begin
cnt<=0;
flip<=1;
end
else
begin
if(cnt==counter)
begin
cnt<=0; flip<=1;
end
else
begin
cnt<=cnt+1;
flip<=0;
end
end
end
always@(posedge flip)
begin
if(key_value)
tone<=~tone;//输出时钟翻转
else
tone<=0;
end
always@(key_value,rst)
begin
if(rst)
counter<=0;
else
begin
case(key_value)
1:counter<=95785;//低音1 (50M/261.63)/2
2:counter<=85324;
3:counter<=75987;
4:counter<=71633;
5:counter<=63938;
6:counter<=56818;
7:counter<=50709;
8:counter<=46992;
9:counter<=42589;
10:counter<=37936;
11:counter<=35816;
12:counter<=31888;
13:counter<=28409;
14:counter<=25303;
15:counter<=23900;
16:counter<=21294;
17:counter<=18968;
18:counter<=17908;
19:counter<=15954;
20:counter<=14204;
21:counter<=12658;
default:counter<=0;
endcase
end
end
endmodule
<3>:KEY按键扫描模块电路以及消抖模块代码展示
(1)按键扫描模块,以及消抖模块端口定义
其原理为:
主板生成四位周期序列(1110,1101,1011,0111),并将四位数据通过IO口送入矩阵键盘,当A端为1110时,如果按键KEY0按下(key_column_0,1,2,3)输出分别为0111,如果KEY1按下(key_column_0,1,2,3)输出分别为1011;如果此时KEY4按下(key_column_0,1,2,3)输出分别为1111;按键不按下是2脚悬空,为1;以此为确定哪个按键按下。
module KEY(rst,A,B,clk_2ms,keyvaule,switch);
input rst;
input [4:0] switch;//5个高音拨档开关
input clk_2ms;
input [3:0] B;//按键输出信号
output reg[3:0] A;//产生A端信号扫描信号
output reg[4:0] keyvaule;
reg [5:0]cnt,cnt1;
reg x,n; //x为16个按键开关开关标识,n为另外5个高音拨档开关开关标识
reg [8:0]reg_1;//reg_1 reg_2为按键输入值寄存参数
reg [4:0]reg_2;
reg [1:0] Q;
always@(posedge clk_2ms)
begin //产生按键A端口扫描信号
Q<=Q+1;
case (Q)
0: A<=4'b1110;
1: A<=4'b1101;
2: A<=4'b1011;
3: A<=4'b0111;
default: A<=4'b0000;
endcase
if(0)
keyvaule <=5'd16;
else
begin
case({A,B})
8'b1110_0111:begin
keyvaule <=4'h1;
x<=1; //x=1时判断为按键按下
reg_1<=8'b1110_0111;end //按键扫描A、B端口的8位值
8'b1110_1011:begin
keyvaule <=4'h2;
x<=1;reg_1<=8'b1110_1011;end
8'b1110_1101:begin
keyvaule <=4'h3;
x<=1;reg_1<=8'b1110_1101;end
8'b1110_1110:begin
keyvaule <=4'h4;
x<=1;reg_1<=8'b1110_1110;end
8'b1101_0111:begin
keyvaule <=4'h5;
x<=1;reg_1<=8'b1101_0111;end
8'b1101_1011:begin
keyvaule <=4'h6;
x<=1;reg_1<=8'b1101_1011;end
8'b1101_1101:begin
keyvaule <=4'h7;
x<=1;reg_1<=8'b1101_1101;end
8'b1101_1110:begin
keyvaule <=4'h8;
x<=1;reg_1<=8'b1101_1110;end
8'b1011_0111:begin
keyvaule <=4'h9;
x<=1;reg_1<=8'b1011_0111;end
8'b1011_1011:begin
keyvaule <=4'ha;
x<=1;reg_1<=8'b1011_1011;end
8'b1011_1101:begin
keyvaule <=4'hb;
x<=1;reg_1<=8'b1011_1101;end
8'b1011_1110:begin
keyvaule <=4'hc;
x<=1;reg_1<=8'b1011_1110;end
8'b0111_0111:begin keyvaule <=4'hd;
x<=1;reg_1<=8'b0111_0111;end
8'b0111_1011:begin keyvaule <=4'he;
x<=1;reg_1<=8'b0111_1011;end
8'b0111_1101:begin keyvaule <=4'hf;
x<=1;reg_1<=8'b0111_1101;end
8'b0111_1110:begin keyvaule <=5'd16;
x<=1;reg_1<=8'b0111_1110;end
8'b0000_1111:begin keyvaule <=5'd0;end//A端没有输入时B端口为悬空为1
endcase
//另外五个按键开关
case(switch)//高音
5'b00001:begin keyvaule <=5'd17;
n<=1;reg_2<=5'b00001; end
5'b00010:begin keyvaule <=5'd18;
n<=1;reg_2<=5'b00010; end
5'b00100:begin keyvaule <=5'd19;
n<=1;reg_2<=5'b00100; end
5'b01000:begin keyvaule <=5'd20;
n<=1;reg_2<=5'b01000; end
5'b10000:begin keyvaule <=5'd21;
n<=1;reg_2<=5'b10000; end
endcase
end
(2)消抖模块电路代码展示
其原理为:当按键按下时把此时按键的值送入寄存变量reg_1延时15s后再将按键此时的值与寄存量中的值进行比较,如果二者相同按键值则为输入值,否者为0;
//按键开关消抖
if(x) //按键按下 触发延时
begin
if(cnt==15)begin
cnt<=0;
if({A,B}==reg_1)
keyvaule<=keyvaule;//判断是否按下keyvaule<=keyvaule;
else begin keyvaule<=5'd0;x<=0; end
end
else cnt<=cnt+1;
end
else cnt<=0; //按键按下前不计数
//另外五个按键开关消抖
if(n)
begin
if(cnt1==15) begin
cnt1<=0;
if(switch==reg_2)
keyvaule<=keyvaule;
else begin keyvaule<=5'd0;n<=0; end
end
else cnt1<=cnt1+1; //计数 延时
end
else cnt1<=0; //按键按下前就不会开始计数
end
endmodule
<4>:Estimate数码管显示内容控制模块
module Estimate(
input clkin,
input rst,
input [4:0] keyvaule,
output reg [4:0] qout_0,qout_1
);
always @ (posedge clkin or posedge rst)
begin
if(rst)
begin
qout_1 <= 0;//音高中低标志的显示,低:数码管d段亮 中:g段亮 高:a段亮
qout_0 <= 0;//音的1-7显示:1哆2来3咪4发5嗦6啦7西
end
else
begin
if(keyvaule==0)
begin
qout_1 <= 0;
qout_0 <= keyvaule; //按键未按下
end
else if(keyvaule<=7&&keyvaule>0)
begin
qout_1 <= 16;
qout_0 <= keyvaule;
end
else if(keyvaule>7&&keyvaule<=14)
begin
qout_1 <= 17;
qout_0 <= keyvaule-7; //低、中、高音按键数值上依次相差7
end
else if(keyvaule>14&&keyvaule<=21)
begin
qout_1 <= 18;
qout_0 <= keyvaule-14; //高音的1-7
end
end
end
endmodule
<5>:数码管显示模块
(模式和歌标模块没有放在这篇博客里面,不实现该功能,所以目前我们只用到前两位数码管)
module Display(clkout_2ms,rst,qout_0,qout_1,G,seg);
input clkout_2ms,rst;
input [4:0] qout_0,qout_1; //四个数码管分别对应(中高低)(1-7) (模式)(歌标)
output reg [3:0] G;//四位数码管使能
output reg [7:0] seg;//7位数码管显示
reg [4:0] qout;
reg [1:0] cnt;
always @ (posedge clkout_2ms or posedge rst)
if(rst)
cnt<=0;
else
cnt<=cnt+1;
always @ (posedge clkout_2ms or posedge rst)
if(rst)
G<=4'b0000;
else
begin
case(cnt)
0:G<=4'b0111;
1:G<=4'b1011;
2:G<=4'b1101;
3:G<=4'b1110;
default:G<=4'b0000;
endcase
end
always @ (posedge clkout_2ms or posedge rst)
if(rst)
qout<=0;
else
begin
case(cnt)
0:qout<=0;
1:qout<=0;
2:qout<=qout_0;
3:qout<=qout_1;
default:qout<=0;
endcase
end
always@(*)
if(rst)
seg=8'b11111100;
else
begin
case(qout)
0:seg<=8'b11111100; //0
1:seg<=8'b01100000; //1
2:seg<=8'b11011010; //2
3:seg<=8'b11110010; //3
4:seg<=8'b01100110; //4
5:seg<=8'b10110110; //5
6:seg<=8'b10111110; //6
7:seg<=8'b11100000; //7
8:seg<=8'b11111110; //8
9:seg<=8'b11100110; //9
10:seg<=8'b11101110; //A
11:seg<=8'b00111110; //b
12:seg<=8'b10011100; //C
13:seg<=8'b01111010; //d
14:seg<=8'b10011110; //E
15:seg<=8'b10001110;//F
16:seg<=8'b00010000; //_
17:seg<=8'b00000010; //中音
18:seg<=8'b10000000; //-
default:seg<=8'b11111100;
endcase
end
endmodule
<6>硬件管脚配置
<7>硬件下载效果展示
部分资料来源于实验指导书,侵权联系可删