一.功能和原理:
本项目主要实现以下功能,当使能en按键按下时开始播放存在rom里的乐谱,当en按键再次按下时暂停播放。
无源蜂鸣器根据输入方波信号频率的不同可以发出不同音调的声音。因此我们可以把乐谱中对应的音符信息存储在ROM里,然后使用一个分频器以一定的速率去取出ROM中的音符频率信息,并将该信息送至另外一个分频器中转换成方波信号输出到蜂鸣器,综上就可以实现按照一定节拍演奏乐曲的功能。更多详细信息请参考该链接。
二.代码实现:
根据上述的功能要求,本次项目总共需要一个按键检测模块来判断en按键是否被按下,两个分频模块,一个用来实现那一定速率从ROM中取出音符频率信息,另一个用来将音符信息转换成特定频率的方波输出到无源蜂鸣器,此外还需要一个ROM 模块用来存储音符信息。
1.按键检测模块
先将其实的50Mhz时钟分频成1Mhz的时钟clk1,在ckl1的控制下对en信号进行两次采样,采样结果分别放置在寄存器en_buff1和en_buff2里。en_buff2里的数据相较与en_buff1延迟了一个clk1时钟。因此当en出现下降沿后,en_buff1会先与en_buff2跳转到低电平,因此当en_buff1为0,en_buff1为1时就可以认为按键已经被按下。之所以采用1Mhz是时钟进行采样(按键去抖),是因为当按键按下时由于触点之间的接触存在不稳定,这导致了在按键按下后会出现多次的高低电平跳变,如果用50Mhz的时钟直接进行采样操作可能出现误把按键的抖动也检测成按键按下操作从而导致多次错误触发。
当检测到en按下后对start信号进行一次翻转操作。
reg [4:0]cnt1;
reg clk1;
always @(posedge clk or negedge rstn)
begin
if(!rstn)
begin
cnt1<=0;
clk1<=0;
end
else
begin
cnt1<= cnt1==5'd24 ? 1'b0 : cnt1+1'b1;
clk1<= cnt1==5'd24 ? ~clk1: clk1;
end
end
reg en_buff1,en_buff2;
always @(posedge clk1 or negedge rstn)
begin
if (!rstn)
begin
en_buff1<=1'b1;
en_buff2<=1'b1;
end
else
begin
en_buff1<=en;
en_buff2<=en_buff1;
end
end
wire down_edge;
assign down_edge= en_buff2&~en_buff1;
reg start;
always @(posedge clk1 or negedge rstn )
begin
if (!rstn)
begin
start<=1'b0;
end
else
begin
start<= down_edge ? ~start : start;
end
end
仿真结果如下:
2.分频器模块----beat
根据模块传入参数cnt的值来生成计数器,当cnt=1时,代表每秒读出一个音符。模块传入参数len_sheet代表乐曲音符的个数,len_sheet等于48代表总共有48个音符。输出的时钟o_clk作为ROM模块的读出时钟,address为读出数据的地址,由于ROM模块是在读出时钟为上升沿时进行数据读出的,所以地址应该在读出时钟上沿来临之前准备好,因此在clk时钟频率下对o_clk时钟进行采样检测,当检测到o_clk出现下降沿时将地址加1。
module beat #(parameter cnt=1,len_sheet=48)
(
input clk,rstn,start,
output o_clk,
output [5:0]address);
localparam div_cnt= 50_000_000/2/cnt-1;
reg [24:0]cnt_reg;
reg out;
always @(posedge clk or negedge rstn)
begin
if (!rstn)
begin
cnt_reg<=25'd0;
out<=1'b1;
end
else if (start)
begin
cnt_reg<=cnt_reg==div_cnt ? 25'd0 : cnt_reg+ 25'd1;
out<= cnt_reg==div_cnt ? ~out :out;
end
else
begin
cnt_reg<=cnt_reg;
out<=out;
end
end
assign o_clk=out;
reg clk_buff1,clk_buff2;
always @(posedge clk or negedge rstn)
begin
if(!rstn)
begin
clk_buff1<=1'b1;
clk_buff2<=1'b1;
end
else
begin
clk_buff1<=o_clk;
clk_buff2<=clk_buff1;
end
end
wire flag;
assign flag=clk_buff2&~clk_buff1;
reg [5:0]addr;
always @(posedge clk or negedge rstn)
begin
if(!rstn)
begin
addr<=len_sheet;
end
else if (flag)
begin
addr<=addr==len_sheet ? 6'd0 : addr+6'd1;
end
end
assign address=addr;
endmodule
3.ROM模块 sheet
使用quartus13.1的rom IP核生成。rom ip核使用方法可以查看该贴进行了解:quartus ii ROM ip的使用
4.分频器模块---tone
当en信号有效时模块开始工作。首先在50Mhz的时钟下对从ROM模块传来的音符信息进行两次寄存。当两次寄存的值不同时将目前对应的信息存入tone_reg寄存器,然后根据tone_reg寄存器的值进行分频器计数值的选择,从而实现根据输入音符信息输出不同方波信号给无源蜂鸣器的功能。
module tone (
input clk,rstn,en,
input [2:0]tone_num,
output bee
);
reg [2:0] buff1,buff2;
always @(posedge clk or negedge rstn)
begin
if(!rstn)
begin
buff1<=3'd0;
buff2<=3'd0;
end
else
begin
buff1<=tone_num;
buff2<=buff1;
end
end
wire en_flag;
assign en_flag=buff1 != buff2;
reg[2:0] tone_reg;
always @(posedge clk or negedge rstn)
begin
if(!rstn)
begin
tone_reg<=3'd0;
end
else if (en_flag)
begin
tone_reg<=buff1;
end
else
begin
tone_reg<=tone_reg;
end
end
reg en_cnt;
reg [16:0]cnt_buff;
always @(posedge clk or negedge rstn)
begin
if(!rstn)
begin
en_cnt<=1'b0;
cnt_buff<=17'd0;
end
else
begin
case (tone_reg)
0:begin
en_cnt<=1'b0;
end
1:begin
en_cnt<=1'b1;
cnt_buff<=17'd47801;
end
2:begin
en_cnt<=1'b1;
cnt_buff<=17'd43253;
end
3:begin
en_cnt<=1'b1;
cnt_buff<=17'd37936;
end
4:begin
en_cnt<=1'b1;
cnt_buff<=17'd35817;
end
5:begin
en_cnt<=1'b1;
cnt_buff<=17'd31888;
end
6:begin
en_cnt<=1'b1;
cnt_buff<=17'd28409;
end
7:begin
en_cnt<=1'b1;
cnt_buff<=17'd25050;
end
endcase
end
end
reg out;
reg [16:0]cnt;
always @(posedge clk or negedge rstn)
begin
if (!rstn)
begin
out<=1'b0;
cnt<=17'd0;
end
else if(en_cnt&en)
begin
cnt<=cnt==cnt_buff ? 17'd0 : cnt+17'd1;
out<=cnt==cnt_buff ? ~out : out;
end
else
begin
out<=1'b0;
cnt<=17'd0;
end
end
assign bee=out;
endmodule
5.例化代码:
工程文件已经上传至https://download.youkuaiyun.com/download/qq_54843159/88788475?spm=1001.2014.3001.5503
module music (
input clk,
input rstn,
input en,
output bee);
reg [4:0]cnt1;
reg clk1;
always @(posedge clk or negedge rstn)
begin
if(!rstn)
begin
cnt1<=0;
clk1<=0;
end
else
begin
cnt1<= cnt1==5'd24 ? 1'b0 : cnt1+1'b1;
clk1<= cnt1==5'd24 ? ~clk1: clk1;
end
end
reg en_buff1,en_buff2;
always @(posedge clk1 or negedge rstn)
begin
if (!rstn)
begin
en_buff1<=1'b1;
en_buff2<=1'b1;
end
else
begin
en_buff1<=en;
en_buff2<=en_buff1;
end
end
wire down_edge;
assign down_edge= en_buff2&~en_buff1;
reg start;
always @(posedge clk1 or negedge rstn )
begin
if (!rstn)
begin
start<=1'b0;
end
else
begin
start<= down_edge ? ~start : start;
end
end
wire clk_sheet;
wire [5:0] addr;
beat #(.cnt(4),.len_sheet(48))
u0(
.clk(clk),.rstn(rstn),.start(start),
.o_clk(clk_sheet),
.address(addr));
wire [2:0]data;
Sheet u1
(
.address (addr),
.clock (clk_sheet),
.q (data)
);
tone u2(
.clk(clk),.rstn(rstn),.en(start),
.tone_num(data),
.bee(bee)
);
endmodule