文章目录
- 一.音频音符知识
- 二.任务
- 三.工程项目
- 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