注意:本项目基于Altera开发版开发
项目要求:
如下图模块所示,输入时钟为板载的50Mhz时钟信号,key1~key4为板卡输入的按键信号,程序实现当任意1个按键按下后,蜂鸣器顺序演奏7个音符,每个音符持续时间为0.5秒,7音符演奏完成后停止演奏,如果继续按下任意按键,再次开始演奏,演奏完成就停止演奏。要求按键消抖模块与蜂鸣器控制的模块均使用锁相环输出的100Mhz时钟工作,所以设计锁相环IP核来满足这个要求
接下来是拿到项目要求后进行思路分析:
理清楚思路后开始创建工程进行编写代码:
对于pll锁相环部分简单设置成输入为50MHZ(板卡频率时钟)输出为100MHZ即可:
按键消抖模块:
按键消抖模块思路前面已经写过,这里不在叙述,主要是留意计数器的计数值和位宽即可
module key_xd(
input wire clk ,
input wire rst_n ,
input wire key_in ,
output reg flag_key
);
//---------------------------------------------------------------------------------------
// 基于100Mhz的计数器20ms
//---------------------------------------------------------------------------------------
parameter MAX_20ms = 'd1_999_999;
reg [ 20: 0] ms20_cnt ;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
ms20_cnt <= 'd0 ;
else if(key_in)
ms20_cnt <= 0;
else if(ms20_cnt >= MAX_20ms )
ms20_cnt <= MAX_20ms ;
else
ms20_cnt <= ms20_cnt + 1'b1;
end
//---------------------------------------------------------------------------------------
// 在按键输入时,生成一个flag
//---------------------------------------------------------------------------------------
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
flag_key <= 0;
else if( !key_in && ms20_cnt == MAX_20ms -1 )
flag_key <= 1;
else
flag_key <= 0;
end
endmodule
蜂鸣器控制发声模块:
个人建议先把蜂鸣器发声模块理清思路后再加入按键进行控制发声。我们会发现,把按键加在计数7个音符的代码处是最合适的,因为按键控制的就是音符发声顺序,DO--RE--..--XI,所以我们只需要当按键信号有效时,将音符计数器清零,让蜂鸣器发声,当音符响过后,保持没有声音即可(这个方法有很多,比如给按键到来时加一个使能信号,使能信号有效时,计数器才工作,才让蜂鸣器发声),这里我采用代码更加简洁的办法。
在7个音符计数器的基本上再加一个音符:空音符,当按键按下时,音符计数器清零,并且会自加,达到最大值时,让其保持最大值(空音符)即可。这样就实现了蜂鸣器不发声的情况。当然此时为了避免板卡上电后蜂鸣器会直接发声,所以我们要把音符计数器的初值设为最大值即可。代码如下:
module beep_music(
input wire clk ,
input wire rst_n ,
input wire [ 3: 0] flag_key ,
output reg beep_out
);
//---------------------------------------------------------------------------------------
// 基于100Mhz计数500ms
//---------------------------------------------------------------------------------------
parameter MAX_500ms = 49_999_999;
reg [ 25: 0] ms500_cnt ;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
ms500_cnt <= 'd0 ;
else if(ms500_cnt >= MAX_500ms )
ms500_cnt <= 0 ;
else
ms500_cnt <= ms500_cnt + 1'b1;
end
//---------------------------------------------------------------------------------------
// 7个音符频率
//---------------------------------------------------------------------------------------
parameter DO = 100_000_000/262;
parameter RE = 100_000_000/294;
parameter MI = 100_000_000/330;
parameter FA = 100_000_000/349;
parameter SO = 100_000_000/392;
parameter LA = 100_000_000/440;
parameter XI = 100_000_000/494;
//---------------------------------------------------------------------------------------
// 计数7个数(题目要求只响一次就停止)那就计数9个--第一个和最后一个为空,计数到最大值后保持--等待下次再次触发
//---------------------------------------------------------------------------------------
reg [ 3: 0] g_cnt ;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
g_cnt <= 'd8 ;
else if(flag_key)
g_cnt <= 0;
else if(g_cnt >= 'd8 && ms500_cnt >= MAX_500ms)
g_cnt <= 'd8 ;
else if(ms500_cnt >= MAX_500ms)
g_cnt <= g_cnt + 1'b1;
end
//---------------------------------------------------------------------------------------
// 用case语句进行赋值
//---------------------------------------------------------------------------------------
reg [ 18: 0] frq ;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
frq <= 0;
else
case (g_cnt)
0 :
frq <= 0;
1 :
frq <= DO;
2 :
frq <= RE;
3 :
frq <= MI;
4 :
frq <= FA;
5 :
frq <= SO;
6 :
frq <= LA;
7 :
frq <= XI;
8 :
frq <= 0;
default:
frq <= 0;
endcase
end
//---------------------------------------------------------------------------------------
// 占空比50%
//---------------------------------------------------------------------------------------
wire [ 18: 0] duty ;
assign duty = frq >>1;
//---------------------------------------------------------------------------------------
// 进行频率计数
//---------------------------------------------------------------------------------------
reg [ 18: 0] frq_cnt ;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
frq_cnt <= 'd0 ;
else if(frq_cnt >= frq || ms500_cnt >= MAX_500ms )
frq_cnt <= 0 ;
else
frq_cnt <= frq_cnt + 1'b1;
end
//---------------------------------------------------------------------------------------
// 让蜂鸣器发声
//---------------------------------------------------------------------------------------
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
beep_out <= 0;
else if(frq_cnt >= duty )
beep_out <= 1;
else
beep_out <= 0;
end
endmodule
#### 接下来就是顶层模块的编写:
顶层模块由一个pll锁相环,4个按键消抖和一个蜂鸣器发声。只需要将其用先连接起开即可
module top(
input wire clk_50M ,
input wire key_in1 ,
input wire key_in2 ,
input wire key_in3 ,
input wire key_in4 ,
output beep
);
wire clk ;
wire [ 3: 0] flag_key ;
wire locked ;
pll_key_beep pll_key_beep_inst (
.inclk0 (clk_50M ),
.c0 (clk ),
.locked (locked )
);
key_xd u_key_xd_1(
.clk (clk ),
.rst_n (locked ),
.key_in (key_in1 ),
.flag_key (flag_key[0] )
);
key_xd u_key_xd_2(
.clk (clk ),
.rst_n (locked ),
.key_in (key_in2 ),
.flag_key (flag_key[1] )
);
key_xd u_key_xd_3(
.clk (clk ),
.rst_n (locked ),
.key_in (key_in3 ),
.flag_key (flag_key[2] )
);
key_xd u_key_xd_4(
.clk (clk ),
.rst_n (locked ),
.key_in (key_in4 ),
.flag_key (flag_key[3] )
);
beep_music u_beep_music(
.clk (clk ),
.rst_n (locked ),
.flag_key (flag_key ),
.beep_out (beep )
);
endmodule
其中clk_50M是板卡50MHZ,clk在代码内是100MHZ。