【FPGA入门六】蜂鸣器播放两只老虎

本文介绍了如何使用FPGA通过蜂鸣器播放音乐《两只老虎》。首先讲解了音符振动频率与周期的关系,然后详细阐述了任务目标,即根据音符频率变化模拟音乐。接着,通过Verilog HDL编写了音频选择、音频产生和顶层文件,完成了编译、门级电路查看及硬件测试。总结中强调了乐理知识在音乐播放中的重要性,以及占空比和音符结束标志的控制技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文章目录

    • 一.音频音符知识
    • 二.任务
    • 三.工程项目
      • Verilog HDL编写
        • ①设计音频选择文件
        • ②设计音频产生文件
        • ③设计顶层文件
    • 四.总结
    • 五.参考链接

一.音频音符知识

不同的音符振动频率不同,周期T=1/频率f
在这里插入图片描述
根据上图可以计算出音符振动的周期,单位微秒。Cyclone IV开发板的晶振是50MHz,振动一次是20纳秒,使用周期时间除以20纳秒得出音符振动的次数。比如高音的DO计算方式如下公式所示。
在这里插入图片描述

二.任务

利用蜂鸣器的不同振动频率播放两只老虎。
每个音符持续时间:半拍300ms,一拍500ms
在这里插入图片描述

三.工程项目

Verilog HDL编写

①设计音频选择文件

freq_select.v

module freq_select(
	input wire clk,//时钟
	input wire rst_n,//复位信号
	
	output reg flag//脉宽调制信号
);

parameter CNT_DELAY = 24'd14_999_999;//300ms=0.3s,15_000_000次 半拍

parameter CNT_DELAY2 = 25'd24_999_999;//500ms=0.5s,25_000_000次 一拍

parameter NOTE_NUM = 6'd33;//音符数 34个
//各个音符对应振动次数
//高音
parameter MAX_DO = 16'd47750;
parameter MAX_RE = 16'd42550;
parameter MAX_MI = 16'd37900;
parameter MAX_FA = 16'd37550;
parameter MAX_SO = 16'd31850;
parameter MAX_LA = 16'd28400;
parameter MAX_SI = 16'd25400;
//中音
parameter MID_DO = 17'd95600;
parameter MID_RE = 17'd85150;
parameter MID_MI = 17'd75850;
parameter MID_FA = 17'd71600;
parameter MID_SO = 17'd63750;
parameter MID_LA = 17'd56800;
parameter MID_SI = 17'd50600;
//低音
parameter MIN_DO = 18'd190800;
parameter MIN_RE = 18'd170050;
parameter MIN_MI = 18'd151500;
parameter MIN_FA = 18'd143250;
parameter MIN_SO = 18'd127550;
parameter MIN_LA = 18'd113600;
parameter MIN_SI = 18'd101200;

reg  [24:0] cnt_delay	;//300ms或500ms计数器
reg  [ 5:0] cnt_note 	;//音符计数器
reg  [18:0] cnt_freq 	;//音符播放计数器
reg  [18:0] freq_data	;//音符数据寄存器

wire [17:0] duty_data	;//占空比
wire 			end_note		;//单个音符播放结束标志
wire 			end_spectrum;//所有音符结束标志

reg  [24:0] cnt_delay_r	;


//300ms计数功能
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)begin
		cnt_delay <= 25'd0;
	end
	else if(cnt_delay == cnt_delay_r)begin
		cnt_delay <= 25'd0;//计数器达到最大值,清空
	end
	else begin
		cnt_delay <= cnt_delay + 1'b1;
	end
end


//音符计数器
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)begin
		cnt_note <= 6'd0;
	end
	else if(end_spectrum)begin//音符数记到最大值
		cnt_note <= 6'd0;//清零
	end
	else if(cnt_delay == cnt_delay_r)begin//当前音符计时结束
		cnt_note <= cnt_note + 1'b1;
	end
	else begin
		cnt_note <= cnt_note;//保持
	end
end

//所有音符结束标志
assign end_spectrum = cnt_note == NOTE_NUM && cnt_delay == cnt_delay_r;//最后一个音符持续时间达到300ms,音谱结束

//单个音符振动周期
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)begin
		cnt_freq <= 16'd1;
	end
	else if(end_note)begin
		cnt_freq <= 16'd1;
	end
	else begin
		cnt_freq <= cnt_freq + 1'b1;
	end
end

//单个音符结束标志
assign end_note = cnt_freq == freq_data;//当音符计数器的值达到当前音符的震动次数,该音符就结束

//音符数据选择
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)begin
		freq_data <= MAX_DO;
	end
	else begin
		case(cnt_note)
			6'd0   : begin
							freq_data <= MID_DO;
							cnt_delay_r <= CNT_DELAY;
						end
			6'd1   : begin
							freq_data <= MID_RE;
							cnt_delay_r <= CNT_DELAY;
						end
			6'd2   : begin
							freq_data <= MID_MI;
							cnt_delay_r <= CNT_DELAY;
						end
			6'd3   : begin
							freq_data <= MID_DO;
							cnt_delay_r <= CNT_DELAY;
						end
			  
			  
			  
			6'd4   : begin
							freq_data <= MID_DO;
							cnt_delay_r <= CNT_DELAY;
						end
			6'd5   : begin
							freq_data <= MID_RE;
							cnt_delay_r <= CNT_DELAY;
						end
			6'd6   : begin
							freq_data <= MID_MI;
							cnt_delay_r <= CNT_DELAY;
						end
			6'd7   : begin
							freq_data <= MID_DO;
							cnt_delay_r <= CNT_DELAY;
						end
			  
			  
			  
			6'd8   : begin
							freq_data <= MID_MI;
							cnt_delay_r <= CNT_DELAY;
						end
			6'd9   : begin
							freq_data <= MID_FA;
							cnt_delay_r <= CNT_DELAY;
						end
			6'd10  : begin
							freq_data <= MID_SO;
							cnt_delay_r <= CNT_DELAY2;
						end
			
			
			
			6'd11	 : begin
							freq_data <= MID_MI;
							cnt_delay_r <= CNT_DELAY;
						end
			6'd12	 : begin
							freq_data <= MID_FA;
							cnt_delay_r <= CNT_DELAY;
						end
			6'd13	 : begin
							freq_data <= MID_SO;
							cnt_delay_r <= CNT_DELAY2;
						end
				 
				 
				 
			6'd14	 : begin
							freq_data <= MID_SO;
							cnt_delay_r <= CNT_DELAY;
						end
			6'd15	 : begin
							freq_data <= MID_LA;
							cnt_delay_r <= CNT_DELAY;
						end
			6'd16	 : begin
							freq_data <= MID_SO;
							cnt_delay_r <= CNT_DELAY;
						end
			6'd17	 : begin
							freq_data <= MID_FA;
							cnt_delay_r <= CNT_DELAY2;
						end
			6'd18	 : begin
							freq_data <= MID_MI;
							cnt_delay_r <= CNT_DELAY2;
						end
			6'd19	 : begin
							freq_data <= MID_DO;
							cnt_delay_r <= CNT_DELAY;
						end
				 
				 
				 
			6'd20	 : begin
							freq_data <= MID_SO;
							cnt_delay_r <= CNT_DELAY;
						end
			6'd21	 : begin
							freq_data <= MID_LA;
							cnt_delay_r <= CNT_DELAY;
						end
			6'd22	 : begin
							freq_data <= MID_SO;
							cnt_delay_r <= CNT_DELAY;
						end
			6'd23	 : begin
							freq_data <= MID_FA;
							cnt_delay_r <= CNT_DELAY2;
						end
			6'd24	 : begin
							freq_data <= MID_MI;
							cnt_delay_r <= CNT_DELAY2;
						end
			6'd25	 : begin
							freq_data <= MID_DO;
							cnt_delay_r <= CNT_DELAY2;
						end
				 
				 
				 
			6'd26	 : begin
							freq_data <= MID_RE;
							cnt_delay_r <= CNT_DELAY2;
						end
			6'd27	 : begin
							freq_data <= MID_SO;
							cnt_delay_r <= CNT_DELAY2;
						end
			6'd28	 : begin
							freq_data <= MID_DO;
							cnt_delay_r <= CNT_DELAY2;
						end
			6'd29	 : begin
							freq_data <= 1'b0;
							cnt_delay_r <= CNT_DELAY2;
						end
				 
				 
				 
			6'd30	 : begin
							freq_data <= MID_RE;
							cnt_delay_r <= CNT_DELAY2;
						end
			6'd31	 : begin
							freq_data <= MID_SO;
							cnt_delay_r <= CNT_DELAY2;
						end
			6'd32	 : begin
							freq_data <= MID_DO;
							cnt_delay_r <= CNT_DELAY2;
						end
			6'd33	 : begin
							freq_data <= 1'b0;
							cnt_delay_r <= CNT_DELAY2;
						end
						
						
			default:begin
							freq_data <= MID_DO;
							cnt_delay_r <= CNT_DELAY;
						end
		endcase
	end
end

//占空比
assign duty_data = freq_data >> 3;//移位越多,占空比越高

always@(posedge clk or negedge rst_n)begin
	if(!rst_n)begin
		flag <= 1'b0;
	end
	else begin
		flag <= (cnt_freq >= duty_data) ? 1'b1 : 1'b0;
	end
end

endmodule
②设计音频产生文件

gen_pwm.v

module gen_pwm(
	input wire clk,
	input wire rst_n,
	input wire flag,
	
	output reg beep
);

always@(posedge clk or negedge rst_n)begin
	if(!rst_n)begin
		beep <= 1'b0;
	end
	else if(flag)begin
		beep <= 1'b1;
	end
	else begin
		beep <= 1'b0;
	end
end

endmodule

③设计顶层文件
module top_pwm_beep(
	input wire clk,
	input wire rst_n,
	
	output wire beep
);

wire flag;

freq_select inst_freq_select(
.clk  (clk),
.rst_n(rst_n),

.flag (flag)
);


gen_pwm inst_gen_pwm(
.clk  (clk),
.rst_n(rst_n),
.flag (flag),

.beep (beep)
);
endmodule

编译:
在这里插入图片描述

查看RTL门级电路:
在这里插入图片描述

引脚绑定:

在这里插入图片描述

硬件测试:

四.总结

音乐播放这一块需要一点乐理知识,理解不同音符的特点,改变蜂鸣器的振动频率就可以模拟相应的音符音频。占空比的大小也会影响声音的清晰度,特别要注意的是音频音符的结束标志,音符根据时间是否延迟相应节拍的延迟时间判断,音谱根据是否计数到最后一个音符,且延迟时间是否达到该音符的节拍延迟时间。

五.参考链接

https://blog.youkuaiyun.com/weixin_43828944/article/details/122539335

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值