1 项目需求
利用PWM生成不同频率,持续时间不同的音调,驱动蜂鸣器播放音乐,程序中制作一个标准的音符库,播放音乐的时候通过查找表的方式把该音符对应的频率的信号播放出来。
2 环境配置
1、thonny:
作为官方推荐的开发软件,thonny页面简洁,基础功能齐全,简单易上手,非常适合初学者(比如我)。安装的教程网上比较多,我个人由于参与了直播课,主讲老师在评论区给了下载链接和在线演示了安装过程,并且硬禾平台所提供的树莓派pico板子预置了thonny,所以在环境配置方面并未遇到困难。
一点小启发:很多时候新手配置环境,第一反应都是直接百度,然后找各种帖子照着做,但就如直播课老师所说,直接百度由于信息过于冗杂或许会浪费更多时间,不如一开始就去官方网站上寻找教程来得更方便快捷。
2、硬禾学堂树莓派pico平台:
硬禾学堂为“暑期一起练”制作了一个平台,这平台正是我视频中演示用到的板子他的原理图如下
- 低功耗互连与计算 —— 运用于智能家居、智能工厂和智慧城市的各类系统正变得日趋复杂,而iCE40 UltraPlus则能有效解决互连难题,通过各类广泛的接口和协议,提供低功耗的计算资源实现更高级别的智能。
- 网络边缘智能FPGA —— 拥有5K LUT的iCE40 UltraPlus FPGA可实现网络边缘实时在线的智能应用所需的神经网络模式匹配。其功耗优化遥遥领先,并且设计人员消除了云端智能应用带来的延迟,降低了整个系统解决方案的成本。
- 灵活的封装选择 —— 为满足各类应用的需求,可提供多种封装选项,从专为电子消费品和IoT设备优化的超小尺寸2.15 mm x 2.50 mm x 0.45 mm WLCSP封装到低成本应用的0.5mm间距7x7mm QFN封装,不一而足。
特性
- 灵活的逻辑架构,拥有2800或5280个4输入LUT、自定义I/O、多达80 Kb和1Mb的嵌入式存储器
- 超低功耗的先进工艺,睡眠电流低至75 uA,工作电流仅为1-10mA
- 使用DSP模块实现高性能信号处理,支持乘法和累加功能
- 神经网络软IP和编译器实现灵活的机器学习/人工智能应用
三、 实现思路
设计思路简述如下:由于频率的高低可以发出不同高低的声音,因此可以考虑用pwm波来控制蜂鸣器的发声,从而实现播放歌曲。
首先设定一个音符所占的时间,在0.2秒内通过改变频率从而达到发出不同的声音,在把各种音符的频率换算成为count的数量,从而实现歌曲的编写,其次,在关于oled方面,我参考了例程的写法,在此基础上进行修改达到我所需要的效果。
我的项目主要用到的是两个模块:oled模块,蜂鸣器模块,下图为二者之间的关系
四、程序实现
项目要求是通过PWM产生不同的音调,并驱动板上蜂鸣器将音调输出,
程序均使用micropython编写。
首先利用在线网音序器将歌曲编码,并复制所有编码到micropython中,新建一个以歌曲命名的文件,注意去掉复制过来的最后两个分号,并按照环境要求加上单引号。
其次,本次设计是利用PWM来制作一个音乐播放器实现要求的相关功能,设计过程中主要参考了各位同学之前的设计及相关思路,设计主要可分为几大模块来对其设计:oled模块、music模块。每个模块的都有其独特的作用,从而实现所要求的音乐播放器功能。
1、OLED模块
oled模块主要就是用来显示播放曲目的名字,该部分主要参考了硬禾学堂提供在github的开源代码。代码和部分注释如下:
module OLED12832(
input clk_in, //12MHz
input rst_n_in, //复位
input [1:0] key_value, //按键键值,控制显示
output reg oled_csn, //OLCD_CS
output reg oled_rst, //OLCD_RESET
output reg oled_dcn, //OLCD_DC
output reg oled_clk, //OLCD_CLK
output reg oled_dat //OLCD_DAT
);
reg [63:0] music_data;
//通过改变music_data可以改变OLED显示文字,即已封装完成
localparam INIT_DEPTH = 6'd29; //LCD初始化深度
localparam IDLE = 6'h1, MAIN = 6'h2, INIT = 6'h4, SCAN = 6'h8, WRITE = 6'h10, DELAY = 6'h20;
localparam HIGH = 1'b1, LOW = 1'b0;
localparam DATA = 1'b1, CMD = 1'b0;
reg [7:0] cmd [28:0];
reg [63:0] mem1 [135:0];
reg [7:0] x_ph, x_pl;
reg [7:0] y_ph, y_pl;
reg [7:0] char;
reg [7:0] num1, char_reg;
reg [4:0] cnt_main, cnt_init, cnt_scan, cnt_write;
reg [15:0] num1_delay, cnt_delay, cnt;
reg [5:0] state, state_back;
always@(posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in)
music_data=64'h05_0F_00_00_00_00_00_00; //我的项目
else if(key_value == 0)
music_data=64'h05_0F_00_00_00_00_00_00; //我的项目
else if(key_value==2'd2)
music_data=64'h09_0A_09_0B_0C_0D_00_00; //我和我的祖国
always@(posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) begin
cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 5'b0; cnt_write <= 1'b0;
x_ph <= 8'h7f; x_pl <= 8'h00;
y_ph <= 8'h03; y_pl <= 8'h00;
num1 <= 8'b0; char <= 8'b0; char_reg <= 8'b0;
num1_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;
oled_csn <= HIGH; oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
state <= IDLE; state_back <= IDLE;
end else begin
case(state)
IDLE:begin
cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 5'b0; cnt_write <= 1'b0;
x_ph <= 8'h7f; x_pl <= 8'h00;
y_ph <= 8'h03; y_pl <= 8'h00;
num1 <= 8'b0; char <= 8'b0; char_reg <= 1'b0;
num1_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;
oled_csn <= HIGH; oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
state <= MAIN; state_back <= MAIN;
end
oled初始化
//OLED初始化命令
always@(posedge rst_n_in)
begin
cmd[ 0] = {8'hae};
cmd[ 1] = {8'h20};
cmd[ 2] = {8'h01};
cmd[ 3] = {8'h21};
cmd[ 4] = {8'h00};
cmd[ 5] = {8'h7f};
cmd[ 6] = {8'h22};
cmd[ 7] = {8'h00};
cmd[ 8] = {8'h03};
cmd[ 9] = {8'h81};
cmd[10] = {8'hff};
cmd[11] = {8'ha1};
cmd[12] = {8'ha6};
cmd[13] = {8'ha8};
cmd[14] = {8'h1f};
cmd[15] = {8'hc8};
cmd[16] = {8'hd3};
cmd[17] = {8'h00};
cmd[18] = {8'hd5};
cmd[19] = {8'h80};
cmd[20] = {8'hd9};
cmd[21] = {8'h1f};
cmd[22] = {8'hda};
cmd[23] = {8'h00};
cmd[24] = {8'hdb};
cmd[25] = {8'h40};
cmd[26] = {8'h8d};
cmd[27] = {8'h14};
cmd[28] = {8'haf};
end
2、蜂鸣器模块
蜂鸣器,主要就是计算各种参数,然后把曲子按照顺序排列上去一个一个写即可
本fpga的时钟频率为12MHZ,所以0.2S也就是 12 * 10^6*0.2 = 2400000,又因为从0开始计数
所以最大的数字为2399999,相应的其他频率代表的数字,就用12m去除就行。
该模块内部调用了电子森林提供的蜂鸣器模块,该模块的设计也主要参考了以前案例中的思路与代码。当音调传入Beeper模块之后,Beeper模块通过pwm将对应频率的声音输出,从而达到播放音乐的目的。
各种参数是根据板子的时钟频率以及各种音符的频率相除得到的,这些都需要事先计算
首先,我规定,一个音符的时间为0.2s所以设置一个cnt变量,每到一个0.2就重新技术,再每个0.2s之间,通过改变freqdata的数值,达到频率不同的目的,从而达到发出不同的声音的目的。由于空间关系,只展示一部分的代码,关于具体歌曲是如何写的,我这里由于空间关系只给出大致流程与思路,基本做法就是对照着谱子,按照顺序写出每个音符即可,我这里是一个八分音符为一个单位,四分音符就用连续的两个单位来代替。
#(
parameter cnt_max = 23'd2399999,
parameter do1 = 16'd45871,
parameter re1 = 16'd40857,
parameter mi1 = 16'd36406,
parameter fa1 = 16'd34363,
parameter so1 = 16'd30612,
parameter la1 = 16'd27271,
parameter xi1 = 16'd24295,
parameter do = 16'd22927,
parameter re = 16'd20230,
parameter mi = 16'd18200,
parameter fa = 16'd17177,
parameter so = 16'd15303,
parameter doh = 16'd11463,
parameter sos= 16'd14446,
parameter la = 16'd13633,
parameter xi = 16'd12145,
parameter reh = 16'd10214,
parameter mih = 16'd9298,
parameter fah = 16'd8589,
parameter soh = 16'd7651,
parameter xih = 16'd6073,
parameter no = 16'd600
)
五.遇到的难题及解决方法
本次设计是我第一次接触FPGA,在安装完软件后我对项目的实现思路并无思路,这对0基础的我来说是一个大的障碍,于是我开始从头仔细学习平台所提供的课程,在此之后开始参考平台所提供的开源代码和以前同学的思路,逐渐有了眉目。
在设计的过程中也遇到了很多问题,其中oled显示是设计过程中比较突出的一个难题,但通过读电子森林平台所提供的的开源代码并查询相关资料,我解决了这一难题。
六、未来计划及建议
进一步学习树莓派的相关知识,在之后的学习中多参加像这次寒假在家练类似的活动,通过这样的活动让我学习了很多,也充分利用好了寒假的时间,收获很多。在未来的学习中,我们应该多去参考和理解一些优秀同学和老师提供的开源代码,通过越多的阅读可以更高效地解决很多自己之前迷惑的问题。