1 项目需求
- 通过PWM产生不同的音调,并驱动板上蜂鸣器将音调输出
- 能够播放三首不同的曲子,每个曲子的时间长度为1分钟,可以切换播放
- 曲子的切换使用扩展板的按键,需要有按键消抖的功能
- 播放的曲子的名字在OLED屏幕上显示出来(汉字显示)
2 完成的功能及达到的性能
2.1歌曲及时长的显示
将文件录入后,进入初始页面,如下图所示:
按下"RUN”按键,执行“reset”指令,即可在64x128的OLED屏幕上显示出歌曲的汉字名称以及时长(距离歌曲结束所剩余的时间)。初始的歌曲为火影忍者的主题曲青鸟,如下图所示:
2.2歌曲的切换
“K1”按键可执行“下一曲”的指令,按下“K1”键,由第一首歌曲切换到第二首歌曲“党歌”(即国际歌),其歌曲名及时长如下图所示:
再次按下“K1”键,由第二首歌曲切换到第三首歌曲——陈奕迅的“好久不见”,其歌曲名及时长如下图所示:
2.3歌曲的播放与暂停
“K2”按键可执行歌曲的播放与暂停功能,按下“K2”键,播放第一首歌曲“青鸟”一段时间后,如下图所示:
经过一段时间后,再次按下“K2”键,即可实现暂停,如下图所示:
3 实现思路
1. 分别设计"beeper"(蜂鸣器)、"oled"(OLED屏幕)、"debounce"(消抖)、"timer"(计时器)四个模块,再通过"top"(顶层)模块将它们连接起来。
2. "beeper"(蜂鸣器)模块用于实现歌曲的播放功能。运用PWM技术对占空比进行调制,从而产生不同的频率,并将其分为三个大段的七个音阶,实现对音符的调节。再通过音符表与节拍表的一一对应,即可实现播放功能。
3. "oled"(OLED屏幕)模块用于实现歌曲名称的显示(汉字名称),以及歌曲时长的显示功能。其规格为64x128,运用小脚丫的基本程序代码,通过排列8x8的屏幕块,实现16x16的汉字、16x8的数字输入及显示。
4. "debounce"(消抖)模块用于实现按键的消抖功能。这一部分可以直接运用小脚丫的程序代码,实现“K1”和“K2”两个按键的消抖。
5. "timer"(计时器)模块用于实现计时功能。将时钟分为分、秒(十位)和秒(个位),初始时间需要计算并录入,之后每秒减1,实现对歌曲剩余时间的记录。
4 实现过程
4.1PWM产生音阶
根据不同的音节控制,选择对应的计数终值(分频系数)。低音1的频率为261.6Hz,蜂鸣器控制信号周期应为12MHz/261.6Hz = 45871.5,因为本设计中蜂鸣器控制信号是按计数器周期翻转的,所以几种终值 = 45871.5/2 = 22936,需要计数22936个,计数范围为0 ~ (22936-1),所以time_end = 22935
always@(tone) begin
case(tone)
5'd1: time_end = 16'd22935; //L1,
5'd2: time_end = 16'd20428; //L2,
5'd3: time_end = 16'd18203; //L3,
5'd4: time_end = 16'd17181; //L4,
5'd5: time_end = 16'd15305; //L5,
5'd6: time_end = 16'd13635; //L6,
5'd7: time_end = 16'd12147; //L7,
5'd8: time_end = 16'd11464; //M1,
5'd9: time_end = 16'd10215; //M2,
5'd10: time_end = 16'd9100; //M3,
5'd11: time_end = 16'd8589; //M4,
5'd12: time_end = 16'd7652; //M5,
5'd13: time_end = 16'd6817; //M6,
5'd14: time_end = 16'd6073; //M7,
5'd15: time_end = 16'd5740; //H1,
5'd16: time_end = 16'd5107; //H2,
5'd17: time_end = 16'd4549; //H3,
5'd18: time_end = 16'd4294; //H4,
5'd19: time_end = 16'd3825; //H5,
5'd20: time_end = 16'd3408; //H6,
5'd21: time_end = 16'd3036; //H7,
default:time_end = 16'd65535;
endcase
end
4.2字幕选择数据
将汉字与数字通过choice分成8x8的小块排列输入并显示
always@(posedge clk or negedge rst_n)//字幕选择数据
begin
if(!rst_n) begin
choice[0] = 128'h0102_0000_0506_0000_0000_0000_0000_0000;
choice[1] = 128'h0304_0000_0708_0000_0000_0000_0000_0000;
choice[2] = time_1;
choice[3] = time_2; end
else if(flag==3'd1) begin
choice[0] = 128'h0102_0000_0506_0000_0000_0000_0000_0000;
choice[1] = 128'h0304_0000_0708_0000_0000_0000_0000_0000;
choice[2] = time_1;
choice[3] = time_2; end
else if(flag==3'd2) begin
choice[0] = 128'h090a_0000_0d0e_0000_0000_0000_0000_0000;
choice[1] = 128'h0b0c_0000_0f10_0000_0000_0000_0000_0000;
choice[2] = time_1;
choice[3] = time_2; end
else if(flag==3'd3) begin
choice[0] = 128'h1112_0000_1516_0000_191a_0000_1d1e_0000;
choice[1] = 128'h1314_0000_1718_0000_1b1c_0000_1f20_0000;
choice[2] = time_1;
choice[3] = time_2; end
else begin
choice[0] = 128'h0000_0000_0000_0000_0000_0000_0000_0000;
choice[1] = 128'h0000_0000_0000_0000_0000_0000_0000_0000;
choice[2] = 128'h0000_0000_0000_0000_0000_0000_0000_0000;
choice[3] = 128'h0000_0000_0000_0000_0000_0000_0000_0000; end
end
4.3设置初始时间并实现计时
将时钟分为分、秒(十位)和秒(个位),每一首歌曲的总时长需要提前计时,并对每一位分别录入,设置flag_pre来存储前一步的flag状态,与现在flag相比较,如果发生变化则切换计时,计时过程中,由秒(个位)开始减1,以此类推。
always@(posedge clk or negedge rst) begin
if(!rst) begin
minute <= 2'd1;
second_ge <= 4'd4;
second_shi <= 1'b0;
flag_pre <= 3'd1;
count <= 1'b0; end
else if(flag_pre != flag) begin
case(flag)
3'd1: begin second_ge <= 4'd4;second_shi <= 1'b0;minute <= 2'd1; end
3'd2: begin second_ge <= 4'd5;second_shi <= 1'b1;minute <= 2'd1; end
3'd3: begin second_ge <= 4'd1;second_shi <= 4'd4;minute <= 2'd1; end
endcase
flag_pre <= flag;
count <= 1'b0;
end
else if(!ss) begin
count <= count;
end
else if((count >= 24'd12000000)&&(second_ge == 4'd0)&&(second_shi != 4'd0)) begin
second_ge <= 4'd9;
second_shi <= second_shi - 1'b1;
count <= 1'b0;
end
else if((count >= 24'd12000000)&&(second_ge == 4'd0)&&(second_shi == 4'd0)&&(minute !=2'd0)) begin
second_ge <= 4'd9;
second_shi <= 4'd5;
minute <= minute - 1'b1;
count <= 1'b0;
end
else if((count >= 24'd12000000)&&(second_ge == 4'd0)&&(second_shi == 4'd0)&&(minute ==2'd0)) begin
second_ge <= 1'b0;
second_shi <= 1'b0;
minute <= 1'b0;
count <= 1'b0; end
else if(count >= 24'd12000000) begin
count <= 1'b0;
second_ge <= second_ge - 1'b1; end
else begin count <= count + 1'b1; end
end
5 遇到的主要难题
1. PWM实现音阶的输出
2. 歌曲节拍的计算
3. 汉字和数字在OLED屏幕上的输入与显示
4. 歌曲计时初值的设定
6 未来的计划建议
该项目已经成功实现了音乐播放器的功能,并达到了预期指标。然而通过更换硬件,还有许多可以提升与扩展的地方:
1. 歌曲数目较少,可添加更多歌曲
2. 可进行按键的调节,实现倍速播放
3. 运用彩灯功能,实现播放歌曲的同时再增加彩灯效果