内容介绍
一.功能描述
- 低功耗互连与计算 —— 运用于智能家居、智能工厂和智慧城市的各类系统正变得日趋复杂,而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来制作一个音乐播放器实现要求的相关功能,设计过程中主要参考了卞维智同学之前的设计及相关思路,设计主要可分为五大模块来对其设计:top模块、oled模块、beeper模块、key(按键消抖)模块、music模块。每个模块的都有其独特的作用,从而实现所要求的音乐播放器功能。下面对每个模块进行一一分析:
1.top模块
顶层top模块的设计和应用的编写方式就像C++语言中的头文件和函数一样,它使得很多开源的代码能够直接应用到一个很大的项目中去。该top模块包含了music音乐模块、beeper模块、key模块和oled模块。该模块代码参考卞维智同学案例中使用的代码。
module top(
input clk_in, //12MHz时钟
input rst_n_in,
input k2,
output beep, //蜂鸣器
output oled_csn, //OLED_CS
output oled_rst, //OLED_reset
output oled_dcn, //OLED_D/C
output oled_clk, //OLED_CLK
output oled_dat //OLCD_DATA
);
wire [1:0] key_value;
wire tone_en;
Music music( //音乐模块,其中调用了Beeper模块
.clk_in(clk_in),
.rst_n_in(rst_n_in),
.beep(beep),
.key_value(key_value),
.tone_en(tone_en)
);
key filter( //按键消抖模块
.clk(clk_in),
.rst(rst_n_in),
.key(k2),
.key_value(key_value),
.tone_en(tone_en)
);
OLED12832 OLED( //OLED显示模块
.clk_in(clk_in),
.rst_n_in(rst_n_in),
.key_value(key_value),
.oled_csn(oled_csn),
.oled_rst(oled_rst),
.oled_dcn(oled_dcn),
.oled_clk(oled_clk),
.oled_dat(oled_dat)
);
endmodule
2.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'd1)
music_data=64'h06_07_08_00_00_00_00_00; //东方红
else if(key_value==2'd2)
music_data=64'h09_0A_09_0B_0C_0D_00_00; //我和我的祖国
else if(key_value==2'd3)
music_data=64'h01_02_03_04_05_00_00_00; //森林幻想曲
else
music_data=64'h05_0F_00_00_00_00_00_00; //项目二
end
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
MAIN:begin
if(cnt_main == 5'd8) cnt_main <= 5'd1;
else cnt_main <= cnt_main + 1'b1;
case(cnt_main)
5'd0: begin state <= INIT; end
5'd1: begin x_ph <= 8'h7f; x_pl <= 8'h00; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= music_data[63:56]<<3;state <= SCAN; end
5'd2: begin x_ph <= 8'h7f; x_pl <= 8'h10; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= music_data[55:48]<<3;state <= SCAN; end
5'd3: begin x_ph <= 8'h7f; x_pl <= 8'h20; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= music_data[47:40]<<3;state <= SCAN; end
5'd4: begin x_ph <= 8'h7f; x_pl <= 8'h30; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= music_data[39:32]<<3;state <= SCAN; end
5'd5: begin x_ph <= 8'h7f; x_pl <= 8'h40; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= music_data[31:24]<<3;state <= SCAN; end
5'd6: begin x_ph <= 8'h7f; x_pl <= 8'h50; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= music_data[23:16]<<3;state <= SCAN; end
5'd7: begin x_ph <= 8'h7f; x_pl <= 8'h60; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= music_data[15:8]<<3;state <= SCAN; end
5'd8: begin x_ph <= 8'h7f; x_pl <= 8'h70; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= music_data[7:0]<<3;state <= SCAN; end
default: state <= IDLE;
endcase
end
INIT:begin
case(cnt_init)
5'd0: begin oled_rst <= LOW; cnt_init <= cnt_init + 1'b1; end
5'd1: begin num1_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end
5'd2: begin oled_rst <= HIGH; cnt_init <= cnt_init + 1'b1; end
5'd3: begin num1_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end
5'd4: begin
if(cnt>=INIT_DEPTH) begin
cnt <= 1'b0;
cnt_init <= cnt_init + 1'b1;
end else begin
cnt <= cnt + 1'b1; num1_delay <= 16'd5;
oled_dcn <= CMD; char_reg <= cmd[cnt]; state <= WRITE; state_back <= INIT;
end
end
5'd5: begin cnt_init <= 1'b0; state <= MAIN; end
default: state <= IDLE;
endcase
end
SCAN:begin
if(cnt_scan == 5'd7) begin
cnt_scan <= cnt_scan + 1'b1; //32*16
end
else if(cnt_scan == 5'd16) begin
if(num1) cnt_scan <= 5'd8;
else cnt_scan <= cnt_scan + 1'b1;
end
else if(cnt_scan == 5'd17) cnt_scan <= 1'b0;
else cnt_scan <= cnt_scan + 1'b1;
case(cnt_scan)
5'd0: begin oled_dcn <= CMD; char_reg <= 8'h20; state <= WRITE; state_back <= SCAN; end
5'd1: begin oled_dcn <= CMD; char_reg <= 8'h01; state <= WRITE; state_back <= SCAN; end
5'd2: begin oled_dcn <= CMD; char_reg <= 8'h21; state <= WRITE; state_back <= SCAN; end
5'd3: begin oled_dcn <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= SCAN; end
5'd4: begin oled_dcn <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= SCAN; end
5'd5: begin oled_dcn <= CMD; char_reg <= 8'h22; state <= WRITE; state_back <= SCAN; end
5'd6: begin oled_dcn <= CMD; char_reg <= y_pl; state <= WRITE; state_back <= SCAN; end
5'd7: begin oled_dcn <= CMD; char_reg <= y_ph; state <= WRITE; state_back <= SCAN; end
5'd8: begin num1 <= num1 - 1'b1;end
5'd9: begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][63:56]; state <= WRITE; state_back <= SCAN; end
5'd10: begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][55:48]; state <= WRITE; state_back <= SCAN; end
5'd11: begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][47:40]; state <= WRITE; state_back <= SCAN; end
5'd12: begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][39:32]; state <= WRITE; state_back <= SCAN; end
5'd13: begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][31:24]; state <= WRITE; state_back <= SCAN; end
5'd14: begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][23:16]; state <= WRITE; state_back <= SCAN; end
5'd15: begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][15: 8]; state <= WRITE; state_back <= SCAN; end
5'd16: begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][ 7: 0]; state <= WRITE; state_back <= SCAN; end
5'd17: begin state <= MAIN; end
default: state <= IDLE;
endcase
end
WRITE:begin
if(cnt_write >= 5'd17) cnt_write <= 1'b0;
else cnt_write <= cnt_write + 1'b1;
case(cnt_write)
5'd0: begin oled_csn <= LOW; end
5'd1: begin oled_clk <= LOW; oled_dat <= char_reg[7]; end
5'd2: begin oled_clk <= HIGH; end
5'd3: begin oled_clk <= LOW; oled_dat <= char_reg[6]; end
5'd4: begin oled_clk <= HIGH; end
5'd5: begin oled_clk <= LOW; oled_dat <= char_reg[5]; end
5'd6: begin oled_clk <= HIGH; end
5'd7: begin oled_clk <= LOW; oled_dat <= char_reg[4]; end
5'd8: begin oled_clk <= HIGH; end
5'd9: begin oled_clk <= LOW; oled_dat <= char_reg[3]; end
5'd10: begin oled_clk <= HIGH; end
5'd11: begin oled_clk <= LOW; oled_dat <= char_reg[2]; end
5'd12: begin oled_clk <= HIGH; end
5'd13: begin oled_clk <= LOW; oled_dat <= char_reg[1]; end
5'd14: begin oled_clk <= HIGH; end
5'd15: begin oled_clk <= LOW; oled_dat <= char_reg[0]; end
5'd16: begin oled_clk <= HIGH; end
5'd17: begin oled_csn <= HIGH; state <= DELAY; end //
default: state <= IDLE;
endcase
end
DELAY:begin
if(cnt_delay >= num1_delay) begin
cnt_delay <= 16'd0; state <= state_back;
end else cnt_delay <= cnt_delay + 1'b1;
end
default:state <= IDLE;
endcase
end
end
//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
//OLED字库
always@(posedge rst_n_in)
begin
//0
mem1[0]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
mem1[1]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
mem1[2]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
mem1[3]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
mem1[4]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
mem1[5]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
mem1[6]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
mem1[7]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};/*空白,0*/
//1
mem1[8]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h05,8'h0C};
mem1[9]={8'h40,8'h80,8'h04,8'h03,8'h40,8'h60,8'hE4,8'h00};
mem1[10]={8'h40,8'h90,8'hFF,8'h7F,8'h40,8'h8C,8'h24,8'h00};
mem1[11]={8'hC0,8'h03,8'hC6,8'h19,8'hF4,8'hFF,8'h05,8'h04};
mem1[12]={8'h48,8'h00,8'h84,8'h03,8'hC0,8'h03,8'h74,8'h00};
mem1[13]={8'h40,8'hCC,8'hFF,8'h7F,8'h40,8'h98,8'h3C,8'h00};
mem1[14]={8'h60,8'h70,8'hC4,8'h01,8'h60,8'hE0,8'h06,8'h06};
mem1[15]={8'h00,8'h00,8'h04,8'h04,8'h00,8'h00,8'h00,8'h00};/*"森",1*/
//2
mem1[16]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h02,8'hE0,8'h00};
mem1[17]={8'h00,8'h02,8'h1C,8'h00,8'h00,8'hC2,8'h03,8'h00};
mem1[18]={8'hFC,8'hFF,8'hFF,8'h7F,8'h00,8'hC2,8'h00,8'h00};
mem1[19]={8'h00,8'h82,8'hC7,8'h00,8'h00,8'h02,8'h38,8'h00};
mem1[20]={8'h00,8'h02,8'h07,8'h00,8'h00,8'hFA,8'h00,8'h00};
mem1[21]={8'hFC,8'hFF,8'hFF,8'h7F,8'h00,8'hE2,8'h01,8'h00};
mem1[22]={8'h00,8'h02,8'h0E,8'h00,8'h00,8'h03,8'h70,8'h00};
mem1[23]={8'h00,8'h02,8'hC0,8'h00,8'h00,8'h00,8'h00,8'h00};/*"林",2*/
//3
mem1[24]={8'h00,8'h00,8'h00,8'h00,8'h10,8'h10,8'h40,8'h00};
mem1[25]={8'h20,8'h08,8'h30,8'h10,8'hC0,8'h06,8'h0E,8'h70};
mem1[26]={8'hC0,8'hFF,8'h01,8'h3C,8'h38,8'hC0,8'hFF,8'h03};
mem1[27]={8'h50,8'h00,8'h00,8'h18,8'h40,8'h00,8'h01,8'h08};
mem1[28]={8'h40,8'h00,8'h01,8'h08,8'h40,8'h00,8'h01,8'h08};
mem1[29]={8'hC0,8'hFF,8'hFF,8'h0F,8'h40,8'h00,8'h01,8'h08};
mem1[30]={8'h40,8'h00,8'h01,8'h08,8'h60,8'h80,8'h01,8'h08};
mem1[31]={8'h40,8'h00,8'h00,8'h08,8'h00,8'h00,8'h00,8'h00};/*"狂",3*/
//4
mem1[32]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h01,8'h06,8'h00};
mem1[33]={8'h00,8'h81,8'h01,8'h1E,8'h00,8'h71,8'h00,8'h01};
mem1[34]={8'hFC,8'hFF,8'h0F,8'h00,8'h00,8'h09,8'hC0,8'h3F};
mem1[35]={8'h80,8'hF1,8'h00,8'h30,8'h00,8'h01,8'h10,8'h30};
mem1[36]={8'hF0,8'hFF,8'hEF,8'h31,8'h20,8'h22,8'h02,8'h30};
mem1[37]={8'h20,8'h22,8'h02,8'h30,8'h20,8'h22,8'h82,8'h3F};
mem1[38]={8'h20,8'h22,8'h42,8'h00,8'hF0,8'hFF,8'h8F,8'h03};
mem1[39]={8'h00,8'h00,8'h00,8'h0E,8'h00,8'h00,8'h00,8'h00};/*"想",4*/
//5
mem1[40]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
mem1[41]={8'h00,8'hFD,8'hFD,8'h77,8'h00,8'h02,8'h02,8'h08};
mem1[42]={8'h00,8'h02,8'h02,8'h08,8'h00,8'h02,8'h02,8'h08};
mem1[43]={8'hFC,8'hFF,8'hFF,8'h0F,8'h00,8'h02,8'h02,8'h08};
mem1[44]={8'h00,8'h02,8'h02,8'h08,8'hFC,8'hFF,8'hFF,8'h0F};
mem1[45]={8'h08,8'h02,8'h02,8'h08,8'h00,8'h02,8'h02,8'h08};
mem1[46]={8'h00,8'h02,8'h02,8'h08,8'h00,8'hFF,8'hFF,8'h1F};
mem1[47]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};/*"曲",5*/
//6
mem1[48]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h08};
mem1[49]={8'h80,8'h00,8'h00,8'h04,8'h80,8'h80,8'h03,8'h03};
mem1[50]={8'h80,8'hE0,8'hC1,8'h00,8'h80,8'h1C,8'h39,8'h00};
mem1[51]={8'hE0,8'h03,8'h11,8'h10,8'hBC,8'h00,8'h01,8'h70};
mem1[52]={8'h80,8'hF8,8'hFF,8'h3F,8'h80,8'h00,8'h01,8'h00};
mem1[53]={8'h80,8'h00,8'h09,8'h00,8'h80,8'h00,8'h31,8'h00};
mem1[54]={8'h80,8'hC0,8'hC1,8'h00,8'hC0,8'h00,8'h81,8'h07};
mem1[55]={8'h00,8'h00,8'h00,8'h04,8'h00,8'h00,8'h00,8'h00};/*"东",6*/
//7
mem1[56]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h01,8'h00,8'h40};
mem1[57]={8'h00,8'h01,8'h00,8'h30,8'h00,8'h01,8'h00,8'h08};
mem1[58]={8'h00,8'h01,8'h00,8'h07,8'h00,8'h01,8'hE0,8'h00};
mem1[59]={8'h00,8'hE1,8'h1F,8'h00,8'h18,8'h9F,8'h00,8'h08};
mem1[60]={8'hF0,8'h81,8'h00,8'h10,8'h00,8'h81,8'h00,8'h70};
mem1[61]={8'h00,8'h81,8'h00,8'h38,8'h00,8'hC1,8'hFF,8'h0F};
mem1[62]={8'h00,8'hC1,8'h00,8'h00,8'h00,8'h01,8'h00,8'h00};
mem1[63]={8'h80,8'h01,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};/*"方",7*/
//8
mem1[64]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h70,8'h00,8'h08};
mem1[65]={8'h00,8'h6C,8'h78,8'h18,8'hC0,8'h23,8'h36,8'h0C};
mem1[66]={8'h38,8'hE0,8'h11,8'h06,8'h00,8'h38,8'h08,8'h02};
mem1[67]={8'h00,8'h06,8'h08,8'h11,8'h80,8'h00,8'h00,8'h10};
mem1[68]={8'h80,8'h00,8'h00,8'h10,8'h80,8'h00,8'h00,8'h10};
mem1[69]={8'h80,8'hFF,8'hFF,8'h0F,8'h80,8'h00,8'h00,8'h10};
mem1[70]={8'h80,8'h00,8'h00,8'h10,8'hC0,8'h00,8'h00,8'h08};
mem1[71]={8'h00,8'h00,8'h00,8'h08,8'h00,8'h00,8'h00,8'h00};/*"红",8*/
//9
mem1[72]={8'h00,8'h00,8'h00,8'h00,8'h40,8'h10,8'h70,8'h00};
mem1[73]={8'h40,8'h10,8'h30,8'h00,8'h20,8'h10,8'h18,8'h10};
mem1[74]={8'hE0,8'hFF,8'hFF,8'h3F,8'h30,8'h10,8'h04,8'h00};
mem1[75]={8'h18,8'h10,8'h02,8'h08,8'h10,8'h10,8'h02,8'h04};
mem1[76]={8'h04,8'h10,8'h00,8'h03,8'hF8,8'hFF,8'hBF,8'h01};
mem1[77]={8'h00,8'h10,8'hF0,8'h01,8'h10,8'h10,8'h0C,8'h0E};
mem1[78]={8'hE0,8'hD0,8'h03,8'h18,8'h80,8'h8D,8'h00,8'h30};
mem1[79]={8'h00,8'h08,8'h80,8'h7F,8'h00,8'h00,8'h00,8'h00};/*"我",9*/
//0A
mem1[80]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h08,8'hC0,8'h00};
mem1[81]={8'h40,8'h08,8'h30,8'h00,8'h20,8'h08,8'h0E,8'h00};
mem1[82]={8'h20,8'hF8,8'h01,8'h00,8'hF0,8'hFF,8'hFF,8'h3F};
mem1[83]={8'h10,8'h08,8'h01,8'h00,8'h18,8'h08,8'h07,8'h00};
mem1[84]={8'h00,8'h08,8'h00,8'h00,8'hC0,8'hFF,8'hFF,8'h07};
mem1[85]={8'h80,8'h00,8'h80,8'h00,8'h80,8'h00,8'h80,8'h00};
mem1[86]={8'h80,8'h00,8'h80,8'h00,8'hC0,8'hFF,8'hFF,8'h03};
mem1[87]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};/*"和",0A*/
//0B
mem1[88]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
mem1[89]={8'h00,8'hFF,8'hFF,8'h3F,8'h80,8'h01,8'h02,8'h08};
mem1[90]={8'h7C,8'h01,8'h02,8'h08,8'h00,8'h01,8'h02,8'h08};
mem1[91]={8'h80,8'hFF,8'hFF,8'h3F,8'h00,8'h60,8'h00,8'h00};
mem1[92]={8'h00,8'h1C,8'h00,8'h00,8'hE0,8'h83,8'h01,8'h00};
mem1[93]={8'h38,8'h02,8'h1E,8'h08,8'h00,8'h02,8'h00,8'h10};
mem1[94]={8'h00,8'h02,8'h00,8'h30,8'h00,8'hFF,8'hFF,8'h1F};
mem1[95]={8'h00,8'h02,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};/*"的",0B*/
//0C
mem1[96]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h02,8'h18,8'h00};
mem1[97]={8'h00,8'h02,8'h06,8'h00,8'h38,8'hC2,8'hFF,8'h7F};
mem1[98]={8'h60,8'hBE,8'h00,8'h00,8'h80,8'h03,8'h07,8'h10};
mem1[99]={8'h00,8'h00,8'h00,8'h10,8'hF0,8'hFF,8'hFF,8'h1F};
mem1[100]={8'h20,8'h10,8'h10,8'h10,8'h20,8'h10,8'h10,8'h10};
mem1[101]={8'h20,8'h10,8'h10,8'h10,8'h20,8'h10,8'h10,8'h10};
mem1[102]={8'hF0,8'hFF,8'hFF,8'h1F,8'h00,8'h00,8'h00,8'h10};
mem1[103]={8'h00,8'h00,8'h00,8'h18,8'h00,8'h00,8'h00,8'h00};/*"祖",0C*/
//0D
mem1[104]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
mem1[105]={8'hFC,8'hFF,8'hFF,8'h3F,8'h08,8'h00,8'h40,8'h08};
mem1[106]={8'h88,8'h00,8'h80,8'h08,8'h88,8'h40,8'h80,8'h08};
mem1[107]={8'h88,8'h40,8'h80,8'h08,8'h88,8'hFF,8'hFF,8'h08};
mem1[108]={8'h88,8'h40,8'h80,8'h08,8'h88,8'h40,8'h81,8'h08};
mem1[109]={8'h88,8'h60,8'h9E,8'h08,8'hC8,8'h40,8'hE0,8'h08};
mem1[110]={8'h08,8'h01,8'h80,8'h08,8'hFC,8'hFF,8'hFF,8'h3F};
mem1[111]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};/*"国",0D*/
//0E
mem1[112]={8'h00,8'h00,8'h00,8'h00,8'h80,8'h00,8'h80,8'h01};
mem1[113]={8'h80,8'h00,8'hC0,8'h00,8'h80,8'hFF,8'h7F,8'h00};
mem1[114]={8'h80,8'h00,8'h20,8'h00,8'hE0,8'h00,8'h10,8'h40};
mem1[115]={8'h90,8'h00,8'h08,8'h20,8'h10,8'hFF,8'hFF,8'h30};
mem1[116]={8'h10,8'h02,8'h00,8'h18,8'h90,8'h03,8'h00,8'h07};
mem1[117]={8'h70,8'hF2,8'hFF,8'h00,8'h10,8'h22,8'h80,8'h00};
mem1[118]={8'h10,8'h02,8'h00,8'h03,8'h08,8'hFF,8'h7F,8'h0E};
mem1[119]={8'h08,8'h00,8'h00,8'h38,8'h00,8'h00,8'h00,8'h00};/*"项",0E*/
//0F
mem1[120]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
mem1[121]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
mem1[122]={8'hF0,8'hFF,8'hFF,8'h3F,8'h20,8'h10,8'h08,8'h04};
mem1[123]={8'h20,8'h10,8'h08,8'h04,8'h20,8'h10,8'h08,8'h04};
mem1[124]={8'h20,8'h10,8'h08,8'h04,8'h20,8'h10,8'h08,8'h04};
mem1[125]={8'h20,8'h10,8'h08,8'h04,8'hF0,8'hFF,8'hFF,8'h7F};
mem1[126]={8'h30,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
mem1[127]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};/*"目",0F*/
//10
mem1[128]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h04};
mem1[129]={8'h00,8'h01,8'h00,8'h04,8'h00,8'h01,8'h00,8'h04};
mem1[130]={8'h00,8'h01,8'h00,8'h04,8'h00,8'h01,8'h00,8'h04};
mem1[131]={8'h00,8'h01,8'h00,8'h04,8'h00,8'h01,8'h00,8'h04};
mem1[132]={8'h00,8'h01,8'h00,8'h04,8'h00,8'h01,8'h00,8'h04};
mem1[133]={8'h00,8'h01,8'h00,8'h04,8'h00,8'h01,8'h00,8'h04};
mem1[134]={8'h80,8'h01,8'h00,8'h04,8'h00,8'h00,8'h00,8'h07};
mem1[135]={8'h00,8'h00,8'h00,8'h06,8'h00,8'h00,8'h00,8'h00};/*"二",10*/
end
endmodule
3.music模块
该模块内部调用了电子森林提供的蜂鸣器模块,该模块的设计也主要参考了卞维智同学之前案例中的思路与代码。当音调传入Beeper模块之后,Beeper模块通过pwm将对应频率的声音输出,从而达到播放音乐的目的。此外,该模块还通过传入的键值不同来播放不同的音乐,以达到切换音乐的目的。
module Music
(
input clk_in, //系统时钟
input rst_n_in, //系统复位,低有效
output beep, //蜂鸣器控制输出
input [1:0] key_value, //按键键值
input tone_en
);
localparam nummax1 = 6'd63; //森林狂想曲音乐的长度
localparam nummax2 = 6'd63; //东方红音乐的长度
localparam nummax3 = 7'd95; //我和我的祖国音乐的长度
reg [6:0] nummax; //当前音乐播放长度
reg [63:0] delay_cnt = 64'd0; //延时计数器
reg [4:0] mem0 = 5'd0; //蜂鸣器静止
reg [4:0] mem1 [nummax1:0]; //森林狂想曲乐码
reg [4:0] mem2 [nummax2:0]; //东方红乐码
reg [4:0] mem3 [nummax3:0]; //我和我的祖国乐码
reg [4:0] tone;
reg [6:0] num = 7'd0; //播放音乐索引
Beeper Beep
(
.clk_in(clk_in), //系统时钟
.rst_n_in(rst_n_in), //系统复位,低有效
.tone_en(tone_en), //蜂鸣器使能信号
.tone(tone), //蜂鸣器音节控制
.piano_out(beep) //蜂鸣器控制输出
);
always@(posedge clk_in or negedge rst_n_in) begin
case(key_value)
2'd3: begin nummax <= nummax1; end
2'd1: begin nummax <= nummax2; end
2'd2: begin nummax <= nummax3; end
default:;
endcase
if(!rst_n_in) begin
num <= 6'd0;
delay_cnt<=64'd0;
end
else if(num <= nummax) begin //如果未播放完音乐,继续计数延时
if(delay_cnt <= 64'd1500000) begin //该音调还未播放完全
delay_cnt <= delay_cnt + 1'd1;
case(key_value)
2'd0: tone <= mem0; //静止不响
2'd3: tone <= mem1[num]; //森林幻想曲
2'd1: tone <= mem2[num]; //东方红
2'd2: tone <= mem3[num]; //我和我的祖国
default: ; //默认播放第一首
endcase
end
else begin
delay_cnt <= 64'd0;
num <= num + 1'd1;
end
end
else begin //音乐播放完,num=0重新播放
num <= 7'd0;
end
end
always@(negedge rst_n_in) begin
//森林狂想曲
mem1[0]<=5'd6;
mem1[1]<=5'd8;
mem1[2]<=5'd10;
mem1[3]<=5'd12;
mem1[4]<=5'd10;
mem1[5]<=5'd10;
mem1[6]<=5'd10;
mem1[7]<=5'd9;
mem1[8]<=5'd10;
mem1[9]<=5'd10;
mem1[10]<=5'd10;
mem1[11]<=5'd9;
mem1[12]<=5'd10;
mem1[13]<=5'd10;
mem1[14]<=5'd6;
mem1[15]<=5'd7;
mem1[16]<=5'd8;
mem1[17]<=5'd10;
mem1[18]<=5'd9;
mem1[19]<=5'd8;
mem1[20]<=5'd6;
mem1[21]<=5'd6;
mem1[22]<=5'd5;
mem1[23]<=5'd5;
mem1[24]<=5'd3;
mem1[25]<=5'd3;
mem1[26]<=5'd3;
mem1[27]<=5'd3;
mem1[28]<=5'd3;
mem1[29]<=5'd3;
mem1[30]<=5'd3;
mem1[31]<=5'd3;
mem1[32]<=5'd6;
mem1[33]<=5'd8;
mem1[34]<=5'd10;
mem1[35]<=5'd12;
mem1[36]<=5'd10;
mem1[37]<=5'd10;
mem1[38]<=5'd10;
mem1[39]<=5'd9;
mem1[40]<=5'd10;
mem1[41]<=5'd10;
mem1[42]<=5'd10;
mem1[43]<=5'd9;
mem1[44]<=5'd10;
mem1[45]<=5'd10;
mem1[46]<=5'd6;
mem1[47]<=5'd7;
mem1[48]<=5'd8;
mem1[49]<=5'd10;
mem1[50]<=5'd9;
mem1[51]<=5'd8;
mem1[52]<=5'd6;
mem1[53]<=5'd6;
mem1[54]<=5'd5;
mem1[55]<=5'd5;
mem1[56]<=5'd6;
mem1[57]<=5'd6;
mem1[58]<=5'd6;
mem1[59]<=5'd6;
mem1[60]<=5'd6;
mem1[61]<=5'd6;
mem1[62]<=5'd6;
mem1[63]<=5'd7;
//东方红
mem2[0]<=5'd12;
mem2[1]<=5'd12;
mem2[2]<=5'd12;
mem2[3]<=5'd13;
mem2[4]<=5'd9;
mem2[5]<=5'd9;
mem2[6]<=5'd9;
mem2[7]<=5'd9;
mem2[8]<=5'd8;
mem2[9]<=5'd8;
mem2[10]<=5'd8;
mem2[11]<=5'd6;
mem2[12]<=5'd9;
mem2[13]<=5'd9;
mem2[14]<=5'd9;
mem2[15]<=5'd9;
mem2[16]<=5'd12;
mem2[17]<=5'd12;
mem2[18]<=5'd12;
mem2[19]<=5'd12;
mem2[20]<=5'd13;
mem2[21]<=5'd15;
mem2[22]<=5'd13;
mem2[23]<=5'd12;
mem2[24]<=5'd8;
mem2[25]<=5'd8;
mem2[26]<=5'd8;
mem2[27]<=5'd6;
mem2[28]<=5'd9;
mem2[29]<=5'd9;
mem2[30]<=5'd9;
mem2[31]<=5'd9;
mem2[32]<=5'd12;
mem2[33]<=5'd12;
mem2[34]<=5'd9;
mem2[35]<=5'd9;
mem2[36]<=5'd8;
mem2[37]<=5'd8;
mem2[38]<=5'd7;
mem2[39]<=5'd6;
mem2[40]<=5'd5;
mem2[41]<=5'd5;
mem2[42]<=5'd12;
mem2[43]<=5'd12;
mem2[44]<=5'd9;
mem2[45]<=5'd9;
mem2[46]<=5'd10;
mem2[47]<=5'd9;
mem2[48]<=5'd8;
mem2[49]<=5'd8;
mem2[50]<=5'd8;
mem2[51]<=5'd6;
mem2[52]<=5'd9;
mem2[53]<=5'd10;
mem2[54]<=5'd9;
mem2[55]<=5'd8;
mem2[56]<=5'd9;
mem2[57]<=5'd8;
mem2[58]<=5'd7;
mem2[59]<=5'd6;
mem2[60]<=5'd5;
mem2[61]<=5'd5;
mem2[62]<=5'd5;
mem2[63]<=5'd5;
//我和我的祖国
mem3[0]<=5'd12;
mem3[1]<=5'd12;
mem3[2]<=5'd13;
mem3[3]<=5'd13;
mem3[4]<=5'd12;
mem3[5]<=5'd12;
mem3[6]<=5'd11;
mem3[7]<=5'd11;
mem3[8]<=5'd10;
mem3[9]<=5'd10;
mem3[10]<=5'd9;
mem3[11]<=5'd9;
mem3[12]<=5'd8;
mem3[13]<=5'd8;
mem3[14]<=5'd8;
mem3[15]<=5'd8;
mem3[16]<=5'd8;
mem3[17]<=5'd8;
mem3[18]<=5'd5;
mem3[19]<=5'd5;
mem3[20]<=5'd5;
mem3[21]<=5'd5;
mem3[22]<=5'd5;
mem3[23]<=5'd5;
mem3[24]<=5'd8;
mem3[25]<=5'd8;
mem3[26]<=5'd10;
mem3[27]<=5'd10;
mem3[28]<=5'd15;
mem3[29]<=5'd15;
mem3[30]<=5'd14;
mem3[31]<=5'd14;
mem3[32]<=5'd13;
mem3[33]<=5'd13;
mem3[34]<=5'd13;
mem3[35]<=5'd10;
mem3[36]<=5'd12;
mem3[37]<=5'd12;
mem3[38]<=5'd12;
mem3[39]<=5'd12;
mem3[40]<=5'd12;
mem3[41]<=5'd12;
mem3[42]<=5'd12;
mem3[43]<=5'd12;
mem3[44]<=5'd12;
mem3[45]<=5'd12;
mem3[46]<=5'd12;
mem3[47]<=5'd12;
mem3[48]<=5'd13;
mem3[49]<=5'd13;
mem3[50]<=5'd14;
mem3[51]<=5'd14;
mem3[52]<=5'd13;
mem3[53]<=5'd13;
mem3[54]<=5'd12;
mem3[55]<=5'd12;
mem3[56]<=5'd11;
mem3[57]<=5'd11;
mem3[58]<=5'd10;
mem3[59]<=5'd10;
mem3[60]<=5'd9;
mem3[61]<=5'd9;
mem3[62]<=5'd9;
mem3[63]<=5'd9;
mem3[64]<=5'd9;
mem3[65]<=5'd9;
mem3[66]<=5'd6;
mem3[67]<=5'd6;
mem3[68]<=5'd6;
mem3[69]<=5'd6;
mem3[70]<=5'd6;
mem3[71]<=5'd6;
mem3[72]<=5'd7;
mem3[73]<=5'd7;
mem3[74]<=5'd6;
mem3[75]<=5'd6;
mem3[76]<=5'd5;
mem3[77]<=5'd5;
mem3[78]<=5'd12;
mem3[79]<=5'd12;
mem3[80]<=5'd8;
mem3[81]<=5'd8;
mem3[82]<=5'd8;
mem3[83]<=5'd9;
mem3[84]<=5'd10;
mem3[85]<=5'd10;
mem3[86]<=5'd10;
mem3[87]<=5'd10;
mem3[88]<=5'd10;
mem3[89]<=5'd10;
mem3[90]<=5'd10;
mem3[91]<=5'd10;
mem3[92]<=5'd10;
mem3[93]<=5'd10;
mem3[94]<=5'd10;
mem3[95]<=5'd10;
end
endmodule
beeper:
// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module: Beeper
//
// Author: Step
//
// Description: Beeper
//
// Web: www.stepfapga.com
//
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date: |Changes Made:
// V1.0 |2016/04/20 |Initial ver
// --------------------------------------------------------------------
module Beeper
(
input clk_in, //系统时钟
input rst_n_in, //系统复位,低有效
input tone_en, //蜂鸣器使能信号
input [4:0] tone, //蜂鸣器音节控制
output reg piano_out //蜂鸣器控制输出
);
/*
无源蜂鸣器可以发出不同的音节,与蜂鸣器震动的频率(等于蜂鸣器控制信号的频率)相关,
为了让蜂鸣器控制信号产生不同的频率,我们使用计数器计数(分频)实现,不同的音节控制对应不同的计数终值(分频系数)
计数器根据计数终值计数并分频,产生蜂鸣器控制信号
*/
reg [19:0] time_end;
//根据不同的音节控制,选择对应的计数终值(分频系数)
//低音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 = 20'd1048575; //让蜂鸣器不响
endcase
end
reg [17:0] time_cnt;
//当蜂鸣器使能时,计数器按照计数终值(分频系数)计数
always@(posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) begin
time_cnt <= 1'b0;
end else if(!tone_en) begin
time_cnt <= 1'b0;
end else if(time_cnt>=time_end) begin
time_cnt <= 1'b0;
end else begin
time_cnt <= time_cnt + 1'b1;
end
end
//根据计数器的周期,翻转蜂鸣器控制信号
always@(posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) begin
piano_out <= 1'b0;
end else if(time_cnt==time_end) begin
piano_out <= ~piano_out; //蜂鸣器控制输出翻转,两次翻转为1Hz
end else begin
piano_out <= piano_out;
end
end
endmodule
4.key模块(按键消抖)
该模块的设计思路与设计方法也主要参考了卞维智同学与电子森林提供的开源代码。
module key( //按键消抖模块
input clk,
input rst,
input key,
output reg [1:0] key_value, //键值,即按下几次
output reg tone_en //蜂鸣器使能
);
reg key_reg ;
reg [22:0] delay_cnt;
reg [63:0] delay_1s_cnt;
reg [7:0] delay_1m_cnt;
always @(posedge clk or negedge rst) begin
if (!rst) begin
key_reg <= 1'b1;
end
else begin
key_reg <= key; //非阻塞赋值
if (key_reg != key) //判断按键是否在变化(抖动或按下),此时key_reg还是上一个时钟沿的key_reg
delay_cnt <= 23'd2400000; //设置20ms的延时
else
if(delay_cnt > 23'd0)
delay_cnt <= delay_cnt - 1'b1;
else
delay_cnt <= 23'd0;
end
end
always @(posedge clk or negedge rst) begin
if (!rst) begin
tone_en <= 1; //使能蜂鸣器
key_value <= 1'b0;
delay_1s_cnt <= 64'd0; //重新计时
delay_1m_cnt <= 6'd0;
end
else begin
if (delay_cnt == 23'd1)begin
//若按键发生变化,则延时1分钟后关闭音乐
tone_en <= 1; //使能蜂鸣器
delay_1s_cnt <= 64'd0; //重新计时
delay_1m_cnt <= 6'd0;
key_value <= key_value + 1'b1;
if(key_value >= 3)
key_value <= 2'b00;
end
else begin //按键没有发生变化
key_value <= key_value;
//1分钟后停止播放
delay_1s_cnt <= delay_1s_cnt + 1'd1;
if(delay_1s_cnt >= 12000000) begin //计时到达1s
delay_1m_cnt <= delay_1m_cnt + 1'd1; //秒数加1
delay_1s_cnt <= 64'd0;
if(delay_1m_cnt >= 60) begin //但秒数加到60,即计时已到达1分钟
tone_en <= 1'd0; //计时到1分钟音乐停止播放
delay_1m_cnt <= 6'd0;
end
end
end
end
end
endmodule
四.完成功能
参考项目需求部分,满足了全部需求。
五.遇到的难题及解决方法
本次设计是我第一次接触FPGA,在安装完软件后我对项目的实现思路并无思路,这成了完成此次寒假在家练项目的第一个障碍。之后在电子森林中观看了一些相关内容的视频和有关本次设计的相关基础知识后并阅读了卞维智同学在2021年的利用PWM实现音乐播放器的项目后,我开始有了思路,这种分开模块特别是top模块让我对这次项目设计的思路变得格外清晰。
有了设计思路后,下一步便是学习代码。我首先通过电子森林提供的很多基础代码知识学习相关代码的使用与功能,在对相关代码有了一定认识后,我开始阅读之前同学所写的代码以及github上提供的开源代码,这个过程很漫长但提升也是真的大。通过读懂代码然和说明书,我开始了本次的设计。
在设计的过程中也遇到了很多问题,其中oled显示是设计过程中比较突出的一个难题,但通过读电子森林平台所提供的的开源代码并查询相关资料,我解决了这一难题。由于第一次接触FPGA的相关知识,此次设计在学习和读之前同学的过程时效率较低,但卞维智同学的设计思路与相关案例的代码给了我很大的帮助,最后完成了本次设计。
六、未来计划及建议
进一步学习FPGA的相关知识,在之后的学习中多参加像这次寒假在家练类似的活动,通过这样的活动让我学习了很多,也充分利用好了寒假的时间,收获很多。在未来的学习中,我们应该多去参考和理解一些优秀同学和老师提供的开源代码,通过越多的阅读可以更高效地解决很多自己之前迷惑的问题。