Verilog实现简易电子琴,(4*4+5)矩阵键盘控制的21个单音输出

一、实验目的与要求

用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

db590c47fb6b404595a127509e436a79.png

                      图2

57d8742517f048d0b06de8b8a1c7e0bc.png

 

<2> 乐曲演奏电路的设计

乐曲演奏的原理是这样的:组成乐曲的每个音符的频率值(音调)及其持续的时间(音长)是乐曲能够连续演奏所需的两个基本数据,因此,只要控制输出到扬声器的激励信号的频率高低和持续的时间,就可以使扬声器发出连续的乐曲声。

(1)音调的控制

频率的高低决定了音调的高低。音乐的十二平均律规定:每两个八度音之间的频率相差一倍。在两个八度音之间,又可分为12个半音,每两个半音的频率比为eq?%5Csqrt%5B12%5D%7B2%7D。简谱中从低音1至高音1之间每个音名对应的频率如表1所示。

                                                                表1

eeee6b205ac744a18961d82e007290e8.png

所有不同频率的信号都是从同一个基准频率分频得到的。为了减小输出的偶次谐波分量,最后输出到声音模块的波形应为对称方波,因此在到达声音模块之前,还需要一个二分频的分频器。如果可以,用正弦波代替方波来驱动声音模块将会有更好的效果。

(2)音长的控制

音符的持续时间根据乐曲的速度以及每个音符的节拍数来确定。如果将全音符的持续时间设置为1s的话,则四分音符的时长为4Hz,以此类推。

三、代码实现

注:代码采取Quartus prim模块化电路设计,生成电路元件,并在Block Diagram File中调用的设计方法,不清楚这种方法的读者可查看博主FPGA专栏:“Quartus prim 实现模块化.....的解决方案”这篇博客

<1>整体模块展示

5b67f068837142deac867fcb596cbf79.png

<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)按键扫描模块,以及消抖模块端口定义

其原理为:

2521e6e1af5d4ca4ad1a8f8ff15290bc.png

主板生成四位周期序列(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>硬件管脚配置

1bc58683039a49e9ad91193532b74e24.png

d35f553a8be94d9c9fcaf209f9fc5a0a.png

 <7>硬件下载效果展示

e2b10a71b7dd4c179bdac768469fe5a3.jpeg

 部分资料来源于实验指导书,侵权联系可删

 

  • 8
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值