一、项目要求
二、项目设计思路、实现功能和资源占用
1.设计基本思路
使用PWM驱动无源蜂鸣器发出不同的音调,然后对音调的节拍时间进行统计,然后用SPI的方式驱动0.96英寸的oled,把中文曲名和时间显示在上面,然后通过按键K1复位,K2进行切歌。
2.设计框图
3.实现功能分析
三、主要功能实现
1.演奏单元
用pwm驱动蜂鸣器发出不同音调,如下图,输入不同的频率,就会产生不同的音调。
//根据不同的音节控制,选择对应的计数终值(分频系数)
//低音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
然后就根据乐谱,就可以写出连续的音符,这里用第一首曲子举例。
always@(posedge rst_n_in) begin//音符表
//漠河舞厅
//第一小节
note[0] = {5'd3};
note[1] = {5'd6};
note[2] = {5'd6};
note[3] = {5'd7};
note[4] = {5'd8};
note[5] = {5'd8};
note[6] = {5'd9};
note[7] = {5'd10};
note[8] = {5'd8};
//第二小节
note[9] = {5'd10};
note[10] = {5'd10};
note[11] = {5'd10};
note[12] = {5'd12};
note[13] = {5'd10};
note[14] = {5'd10};
note[15] = {5'd0};
note[16] = {5'd10};
note[17] = {5'd10};
//第三小节
note[18] = {5'd9};
note[19] = {5'd9};
note[20] = {5'd9};
note[21] = {5'd10};
note[22] = {5'd11};
note[23] = {5'd11};
note[24] = {5'd0};
note[25] = {5'd5};
//第四小节
note[26] = {5'd10};
note[27] = {5'd10};
note[28] = {5'd10};
note[29] = {5'd9};
note[30] = {5'd14};
note[31] = {5'd7};
note[32] = {5'd0};
//第五小节
note[33] = {5'd6};
note[34] = {5'd7};
note[35] = {5'd8};
note[36] = {5'd9};
note[37] = {5'd10};
note[38] = {5'd10};
note[39] = {5'd8};
note[40] = {5'd10};
note[41] = {5'd8};
//第六小节
note[42] = {5'd10};
note[43] = {5'd10};
note[44] = {5'd10};
note[45] = {5'd12};
note[46] = {5'd10};
note[47] = {5'd10};
note[48] = {5'd0};
note[49] = {5'd10};
note[50] = {5'd10};
//第七小节
note[51] = {5'd9};
note[52] = {5'd9};
note[53] = {5'd9};
note[54] = {5'd10};
note[55] = {5'd11};
note[56] = {5'd0};
//第八小节
note[57] = {5'd5};
note[58] = {5'd10};
note[59] = {5'd10};
note[60] = {5'd9};
note[61] = {5'd14};
note[62] = {5'd0};
//第九小节
note[63] = {5'd13};
note[64] = {5'd10};
note[65] = {5'd6};
note[66] = {5'd0};
note[67] = {5'd6};
note[68] = {5'd12};
//第十小节
note[69] = {5'd11};
note[70] = {5'd10};
note[71] = {5'd11};
note[72] = {5'd11};
note[73] = {5'd0};
//十一小节
note[74] = {5'd12};
note[75] = {5'd9};
note[76] = {5'd5};
note[77] = {5'd0};
note[78] = {5'd5};
note[79] = {5'd12};
//十二小节
note[80] = {5'd10};
note[81] = {5'd11};
note[82] = {5'd10};
note[83] = {5'd11};
note[84] = {5'd10};
note[85] = {5'd0};
//十三小节
note[86] = {5'd13};
note[87] = {5'd10};
note[88] = {5'd6};
note[89] = {5'd0};
note[90] = {5'd6};
note[91] = {5'd12};
//十四小节
note[92] = {5'd11};
note[93] = {5'd10};
note[94] = {5'd11};
note[95] = {5'd11};
note[96] = {5'd0};
note[97] = {5'd11};
//十五小节
note[98] = {5'd10};
note[99] = {5'd11};
note[100] = {5'd10};
note[101] = {5'd11};
note[102] = {5'd11};
note[103] = {5'd10};
note[104] = {5'd11};
note[105] = {5'd10};
note[106] = {5'd11};
//十六小节
note[107] = {5'd10};
note[108] = {5'd0};
note[109] = {5'd0};
note[110] = {5'd0};
//十七小节
note[111] = {5'd5};
note[112] = {5'd5};
note[113] = {5'd5};
note[114] = {5'd6};
note[115] = {5'd7};
note[116] = {5'd0};
note[117] = {5'd3};
note[118] = {5'd10};
note[119] = {5'd7};
//十八小节
note[120] = {5'd9};
note[121] = {5'd8};
note[122] = {5'd8};
note[123] = {5'd7};
note[124] = {5'd8};
note[125] = {5'd0};
note[126] = {5'd8};
note[127] = {5'd8};
note[128] = {5'd8};
//十九小节
note[129] = {5'd9};
note[130] = {5'd5};
note[131] = {5'd9};
note[132] = {5'd5};
note[133] = {5'd9};
note[134] = {5'd0};
note[135] = {5'd9};
note[136] = {5'd9};
note[137] = {5'd11};
//二十小节
note[138] = {5'd11};
note[139] = {5'd10};
note[140] = {5'd10};
note[141] = {5'd9};
note[142] = {5'd10};
note[143] = {5'd0};
note[144] = {5'd10};
note[145] = {5'd10};
note[146] = {5'd10};
//二十一小节
note[147] = {5'd9};
note[148] = {5'd9};
note[149] = {5'd9};
note[150] = {5'd10};
note[151] = {5'd11};
note[152] = {5'd0};
note[153] = {5'd11};
note[154] = {5'd11};
note[155] = {5'd12};
//二十二小节
note[156] = {5'd8};
note[157] = {5'd8};
note[158] = {5'd8};
note[159] = {5'd9};
note[160] = {5'd10};
note[161] = {5'd10};
note[162] = {5'd0};
note[163] = {5'd10};
note[164] = {5'd10};
//二十三小节
note[165] = {5'd9};
note[166] = {5'd9};
note[167] = {5'd10};
note[168] = {5'd10};
note[169] = {5'd10};
note[170] = {5'd13};
note[171] = {5'd10};
note[172] = {5'd9};
note[173] = {5'd9};
note[174] = {5'd8};
note[175] = {5'd14};
//二十四小节
note[176] = {5'd8};
note[177] = {5'd6};
note[178] = {5'd0};
note[179] = {5'd0};
note[180] = {5'd7};
note[181] = {5'd8};
一个一个写太慢了,我写了一个简单的Python脚本来帮助我快速写音符表
# Python转换脚本
tone_dic = {
'L1':'d1',
'L2':'d2',
'L3':'d3',
'L4':'d4',
'L5':'d5',
'L6':'d6',
'L7':'d7',
'M1':'d8',
'M2':'d9',
'M3':'d10',
'M4':'d11',
'M5':'d12',
'M6':'d13',
'M7':'d14',
'H1':'d15',
'H2':'d16',
'H3':'d17',
'H4':'d18',
'H5':'d19',
'H6':'d20',
'H7':'d21',
'L0':'d0',
'M0':'d0',
'H0':'d0'
}
Str1 = input("输入开始标号: ")
flag = int(Str1) # 把Str1转为数字型
Str2 = input("请输入音符: ")
# 字符串以2个字符为单位切割, 切割后组成列表
list = []
for i in range(0, len(Str2), 2):
list.append(Str2[i:i+2])
# 输出
for i in range(len(list)):
a = list[i]
print(f" note[{flag+i}] = " + "{" + f"5'{tone_dic[a]}" + "};")
给音调分配节拍
因为我没有学过乐理,在开始的时候,我给每个音调分配相同的时间,演奏出来的效果很差,根本就不像一首歌。
然后我才知道每个音调分配的节拍是不一样的,节拍就是给每个音调分配演奏的时间。
比如乐谱上面标注 J60,就是说1分钟演奏60拍,一拍也就是1秒,而1秒在我们12Mhz晶振的板子上就是计数12000000次。
always@(tcon or flag) begin
if(flag == 3'd1) begin
case(tcon)//漠河舞厅,一小节四拍,拍速为80拍/分钟
5'd 1: time_delay = 28'd2250000;//四分之一拍
5'd 2: time_delay = 28'd4500000;//半拍
5'd 3: time_delay = 28'd6750000;//四分之三拍
5'd 4: time_delay = 28'd9_000_000;//一拍
5'd 6: time_delay = 28'd13500000;//一拍半
5'd 8: time_delay = 28'd18000000;//两拍
5'd12: time_delay = 28'd27000000;//三拍
5'd16: time_delay = 28'd36000000;//四拍
5'd24: time_delay = 28'd72000000;//八拍
default:time_delay = 28'd0;
endcase
end
else if(flag == 3'd2) begin
case(tcon)//漂洋过海来看你,一小节四拍,拍速为60拍/分钟
5'd 1: time_delay = 28'd3_000_000;//四分之一拍
5'd 2: time_delay = 28'd6_000_000;//半拍
5'd 3: time_delay = 28'd9_000_000;//四分之三拍
5'd 4: time_delay = 28'd12_000_000;//一拍
5'd 6: time_delay = 28'd18_000_000;//一拍半
5'd 8: time_delay = 28'd24_000_000;//两拍
5'd12: time_delay = 28'd36_000_000;//三拍
5'd16: time_delay = 28'd48_000_000;//四拍
5'd24: time_delay = 28'd96_000_000;//八拍
default:time_delay = 28'd0;
endcase
end
else if(flag == 3'd3) begin
case(tcon)//青花瓷,一小节四拍,拍速为80拍/分钟
5'd 1: time_delay = 28'd2250000;//四分之一拍
5'd 2: time_delay = 28'd4500000;//半拍
5'd 3: time_delay = 28'd6750000;//四分之三拍
5'd 4: time_delay = 28'd9_000_000;//一拍
5'd 6: time_delay = 28'd13500000;//一拍半
5'd 8: time_delay = 28'd18000000;//两拍
5'd12: time_delay = 28'd27000000;//三拍
5'd16: time_delay = 28'd36000000;//四拍
5'd24: time_delay = 28'd72000000;//八拍
default:time_delay = 28'd0;
endcase
end
end
使用flag标志位信号来确定播放曲目
always@(posedge clk_in or negedge rst_n_in) begin //时间控制,上一首或者下一首切换歌曲(手动或者自动)
if(rst_n_in == 1'b0) begin //复位
cnt_delay <= 1'b0; num <= 1'b0; flag <= 3'd1; end
else if(tone_en == 1'b0) begin
cnt_delay <= cnt_delay; end
else if(chose_down) begin//按键消抖后会输出一个相反的脉冲,不用!chose_up,下面chose_down同理
case(flag)
3'd1: begin flag <= 3'd2;num <= 10'd184; end
3'd2: begin flag <= 3'd3;num <= 10'd374; end
3'd3: begin flag <= 3'd1;num <= 1'b0; end
default:begin flag <= 3'd1;num <= 1'b0; end
endcase
end
else if(cnt_delay >= time_delay) begin //自动切换
cnt_delay <= 1'b0;
case(num)
10'd 0: begin flag <= 3'd1;num <= num+1'b1; end
10'd184: begin flag <= 3'd2;num <= num+1'b1; end
10'd374: begin flag <= 3'd3;num <= num+1'b1; end
10'd609: begin flag <= 3'd1;num <= 1'b0; end
default: begin num <= num+1'b1; end
endcase
end
else begin cnt_delay <= cnt_delay + 1'b1; end
end
2.显示单元
使用0.96英寸的OLED屏幕,这部分的代码参考电子森林上的项目,做了修改以满足我的需求。
使用PCtoLCD软件进行取字模。
//16*16点阵字库数据
always@(posedge rst_n)
begin
mem[ 0] = {8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};//空
mem[ 1] = {8'h10,8'h60,8'h02,8'h0C,8'hC0,8'h04,8'hE4,8'hAF};//漠
mem[ 2] = {8'hA4,8'hA4,8'hA4,8'hAF,8'hE4,8'h04,8'h00,8'h00};
mem[ 3] = {8'h04,8'h04,8'h7C,8'h03,8'h80,8'h88,8'h4B,8'h2A};
mem[ 4] = {8'h1A,8'h0E,8'h1A,8'h2A,8'h4B,8'h88,8'h80,8'h00};
mem[ 5] = {8'h10,8'h60,8'h02,8'h8C,8'h00,8'h04,8'hE4,8'h24};//河
mem[ 6] = {8'h24,8'hE4,8'h04,8'h04,8'hFC,8'h04,8'h04,8'h00};
mem[ 7] = {8'h04,8'h04,8'h7E,8'h01,8'h00,8'h00,8'h0F,8'h04};
mem[ 8] = {8'h04,8'h0F,8'h40,8'h80,8'h7F,8'h00,8'h00,8'h00};
mem[ 9] = {8'h00,8'h94,8'h92,8'h93,8'hFE,8'h92,8'hFE,8'h92};//舞
mem[ 10] = {8'hFE,8'h92,8'hFE,8'h92,8'h92,8'h92,8'h00,8'h00};
mem[ 11] = {8'h80,8'h88,8'h44,8'h57,8'h24,8'h14,8'h0C,8'h00};
mem[ 12] = {8'h34,8'h24,8'h24,8'hFF,8'h24,8'h24,8'h20,8'h00};
mem[ 13] = {8'h00,8'h00,8'hFE,8'h02,8'h22,8'h22,8'h22,8'h22};//厅
mem[ 14] = {8'h22,8'hE2,8'h22,8'h22,8'h22,8'h22,8'h22,8'h00};
mem[ 15] = {8'h80,8'h60,8'h1F,8'h00,8'h00,8'h00,8'h00,8'h40};
mem[ 16] = {8'h80,8'h7F,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
mem[ 17] = {8'h7A,8'h4A,8'h7E,8'h4A,8'h7E,8'h4A,8'h7A,8'h00};//飘
mem[ 18] = {8'hFE,8'h42,8'h82,8'h62,8'hFE,8'h00,8'h00,8'h00};
mem[ 19] = {8'h24,8'h55,8'h85,8'h7D,8'h05,8'h15,8'hA4,8'h60};
mem[ 20] = {8'h1F,8'h04,8'h03,8'h04,8'h3F,8'h40,8'hF8,8'h00};
mem[ 21] = {8'h10,8'h60,8'h02,8'h8C,8'h00,8'h10,8'h91,8'h96};//洋
mem[ 22] = {8'h90,8'hF0,8'h90,8'h94,8'h93,8'h10,8'h00,8'h00};
mem[ 23] = {8'h04,8'h04,8'h7E,8'h01,8'h00,8'h04,8'h04,8'h04};
mem[ 24] = {8'h04,8'hFF,8'h04,8'h04,8'h04,8'h04,8'h04,8'h00};
mem[ 25] = {8'h40,8'h40,8'h42,8'hCC,8'h00,8'h08,8'h48,8'h88};//过
mem[ 26] = {8'h08,8'h08,8'h08,8'hFF,8'h08,8'h08,8'h08,8'h00};
mem[ 27] = {8'h00,8'h40,8'h20,8'h1F,8'h20,8'h40,8'h40,8'h41};
mem[ 28] = {8'h40,8'h48,8'h50,8'h4F,8'h40,8'h40,8'h40,8'h00};
mem[ 29] = {8'h10,8'h60,8'h02,8'h0C,8'hC0,8'h10,8'h08,8'hF7};//海
mem[ 30] = {8'h14,8'h54,8'h94,8'h14,8'hF4,8'h04,8'h00,8'h00};
mem[ 31] = {8'h04,8'h04,8'h7C,8'h03,8'h00,8'h01,8'h1D,8'h13};
mem[ 32] = {8'h11,8'h55,8'h99,8'h51,8'h3F,8'h11,8'h01,8'h00};
mem[ 33] = {8'h00,8'h08,8'h08,8'h28,8'hC8,8'h08,8'h08,8'hFF};//来
mem[ 34] = {8'h08,8'h08,8'h88,8'h68,8'h08,8'h08,8'h00,8'h00};
mem[ 35] = {8'h21,8'h21,8'h11,8'h11,8'h09,8'h05,8'h03,8'hFF};
mem[ 36] = {8'h03,8'h05,8'h09,8'h11,8'h11,8'h21,8'h21,8'h00};
mem[ 37] = {8'h20,8'h22,8'h2A,8'h2A,8'hAA,8'h6A,8'h3A,8'h2E};//看
mem[ 38] = {8'h29,8'h29,8'h29,8'h29,8'h29,8'h20,8'h20,8'h00};
mem[ 39] = {8'h08,8'h04,8'h02,8'h01,8'hFF,8'h55,8'h55,8'h55};
mem[ 40] = {8'h55,8'h55,8'h55,8'hFF,8'h00,8'h00,8'h00,8'h00};
mem[ 41] = {8'h00,8'h80,8'h60,8'hF8,8'h07,8'h40,8'h20,8'h18};//你
mem[ 42] = {8'h0F,8'h08,8'hC8,8'h08,8'h08,8'h28,8'h18,8'h00};
mem[ 43] = {8'h01,8'h00,8'h00,8'hFF,8'h00,8'h10,8'h0C,8'h03};
mem[ 44] = {8'h40,8'h80,8'h7F,8'h00,8'h01,8'h06,8'h18,8'h00};
mem[ 45] = {8'h40,8'h44,8'h54,8'h54,8'h54,8'h54,8'h54,8'h7F};//青
mem[ 46] = {8'h54,8'h54,8'h54,8'h54,8'h54,8'h44,8'h40,8'h00};
mem[ 47] = {8'h00,8'h00,8'h00,8'hFF,8'h15,8'h15,8'h15,8'h15};
mem[ 48] = {8'h15,8'h55,8'h95,8'h7F,8'h00,8'h00,8'h00,8'h00};
mem[ 49] = {8'h04,8'h04,8'h04,8'h84,8'h6F,8'h04,8'h04,8'h04};//花
mem[ 50] = {8'hE4,8'h04,8'h8F,8'h44,8'h24,8'h04,8'h04,8'h00};
mem[ 51] = {8'h04,8'h02,8'h01,8'hFF,8'h00,8'h10,8'h08,8'h04};
mem[ 52] = {8'h3F,8'h41,8'h40,8'h40,8'h40,8'h40,8'h78,8'h00};
mem[ 53] = {8'h20,8'h22,8'hE4,8'h10,8'h08,8'h90,8'h88,8'h47};//瓷
mem[ 54] = {8'h24,8'h1C,8'h24,8'h44,8'h94,8'h8C,8'h00,8'h00};
mem[ 55] = {8'h00,8'h02,8'h02,8'hE2,8'h9E,8'h4A,8'h4A,8'h1A};
mem[ 56] = {8'h2A,8'h0A,8'h7A,8'h82,8'h82,8'hE2,8'h00,8'h00};
mem[ 57] = {8'h00,8'hE0,8'h10,8'h08,8'h08,8'h10,8'hE0,8'h00};//0
mem[ 58] = {8'h00,8'h0F,8'h10,8'h20,8'h20,8'h10,8'h0F,8'h00};
mem[ 59] = {8'h00,8'h00,8'h10,8'h10,8'hF8,8'h00,8'h00,8'h00};//1
mem[ 60] = {8'h00,8'h00,8'h20,8'h20,8'h3F,8'h20,8'h20,8'h00};
mem[ 61] = {8'h00,8'h70,8'h08,8'h08,8'h08,8'h08,8'hF0,8'h00};//2
mem[ 62] = {8'h00,8'h30,8'h28,8'h24,8'h22,8'h21,8'h30,8'h00};
mem[ 63] = {8'h00,8'h30,8'h08,8'h08,8'h08,8'h88,8'h70,8'h00};//3
mem[ 64] = {8'h00,8'h18,8'h20,8'h21,8'h21,8'h22,8'h1C,8'h00};
mem[ 65] = {8'h00,8'h00,8'h80,8'h40,8'h30,8'hF8,8'h00,8'h00};//4
mem[ 66] = {8'h00,8'h06,8'h05,8'h24,8'h24,8'h3F,8'h24,8'h24};
mem[ 67] = {8'h00,8'hF8,8'h88,8'h88,8'h88,8'h08,8'h08,8'h00};//5
mem[ 68] = {8'h00,8'h19,8'h20,8'h20,8'h20,8'h11,8'h0E,8'h00};
mem[ 69] = {8'h00,8'hE0,8'h10,8'h88,8'h88,8'h90,8'h00,8'h00};//6
mem[ 70] = {8'h00,8'h0F,8'h11,8'h20,8'h20,8'h20,8'h1F,8'h00};
mem[ 71] = {8'h00,8'h18,8'h08,8'h08,8'h88,8'h68,8'h18,8'h00};//7
mem[ 72] = {8'h00,8'h00,8'h00,8'h3E,8'h01,8'h00,8'h00,8'h00};
mem[ 73] = {8'h00,8'h70,8'h88,8'h08,8'h08,8'h88,8'h70,8'h00};//8
mem[ 74] = {8'h00,8'h1C,8'h22,8'h21,8'h21,8'h22,8'h1C,8'h00};
mem[ 75] = {8'h00,8'hF0,8'h08,8'h08,8'h08,8'h10,8'hE0,8'h00};//9
mem[ 76] = {8'h00,8'h01,8'h12,8'h22,8'h22,8'h11,8'h0F,8'h00};
mem[ 77] = {8'h00,8'h00,8'h00,8'h00,8'hC0,8'h38,8'h04,8'h00};// /
mem[ 78] = {8'h00,8'h60,8'h18,8'h07,8'h00,8'h00,8'h00,8'h00};
mem[ 79] = {8'h00,8'h00,8'h00,8'hC0,8'hC0,8'h00,8'h00,8'h00};//:
mem[ 80] = {8'h00,8'h00,8'h00,8'h30,8'h30,8'h00,8'h00,8'h00};
end
然后是屏幕上显示的字幕显示位置进行选择,然后这里使用中间量flag来判断,然后显示出当前的曲名。
always@(posedge clk or negedge rst_n)//字幕选择数据
begin
if(!rst_n) begin
choice[0] = 128'h0102_0000_0506_0000_090a_0000_0d0e_0000;
choice[1] = 128'h0304_0000_0708_0000_0b0c_0000_0f10_0000;
choice[2] = time_1;
choice[3] = time_2; end
else if(!tone_en) 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
else if(flag==3'd1) begin
choice[0] = 128'h0102_0000_0506_0000_090a_0000_0d0e_0000;
choice[1] = 128'h0304_0000_0708_0000_0b0c_0000_0f10_0000;
choice[2] = time_1;
choice[3] = time_2; end
else if(flag==3'd2) begin
choice[0] = 128'h1112_1516_191a_1d1e_2122_2526_292a_0000;
choice[1] = 128'h1314_1718_1b1c_1f20_2324_2728_2b2c_0000;
choice[2] = time_1;
choice[3] = time_2; end
else if(flag==3'd3) begin
choice[0] = 128'h2d2e_0000_3132_0000_3536_0000_0000_0000;
choice[1] = 128'h2f30_0000_3334_0000_3738_0000_0000_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
3.消抖单元
按键抖动会项目产生干扰,所以我们要进行按键消抖,下面模块参考了电子森林的案例
module debounce (clk,rst,key,key_pulse);
parameter N = 1; //要消除的按键的数量
input clk;
input rst;
input [N-1:0] key; //输入的按键
output [N-1:0] key_pulse; //按键动作产生的脉冲
reg [N-1:0] key_rst_pre; //定义一个寄存器型变量存储上一个触发时的按键值
reg [N-1:0] key_rst; //定义一个寄存器变量储存储当前时刻触发的按键值
wire [N-1:0] key_edge; //检测到按键由高到低变化是产生一个高脉冲
//利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中
always @(posedge clk or negedge rst)
begin
if (!rst) begin
key_rst <= {N{1'b1}}; //初始化时给key_rst赋值全为1,{}中表示N个1
key_rst_pre <= {N{1'b1}};
end
else begin
key_rst <= key; //第一个时钟上升沿触发之后key的值赋给key_rst,同时key_rst的值赋给key_rst_pre
key_rst_pre <= key_rst; //非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值
end
end
assign key_edge = key_rst_pre & (~key_rst);//脉冲边沿检测。当key检测到下降沿时,key_edge产生一个时钟周期的高电平
reg [17:0] cnt; //产生延时所用的计数器,系统时钟12MHz,要延时20ms左右时间,至少需要18位计数器
//产生20ms延时,当检测到key_edge有效是计数器清零开始计数
always @(posedge clk or negedge rst)
begin
if(!rst)
cnt <= 18'h0;
else if(key_edge)
cnt <= 18'h0;
else
cnt <= cnt + 1'h1;
end
reg [N-1:0] key_sec_pre; //延时后检测电平寄存器变量
reg [N-1:0] key_sec;
//延时后检测key,如果按键状态变低产生一个时钟的高脉冲。如果按键状态是高的话说明按键无效
always @(posedge clk or negedge rst)
begin
if (!rst)
key_sec <= {N{1'b1}};
else if (cnt==18'h3ffff)
key_sec <= key;
end
always @(posedge clk or negedge rst)
begin
if (!rst)
key_sec_pre <= {N{1'b1}};
else
key_sec_pre <= key_sec;
end
assign key_pulse = key_sec_pre & (~key_sec);
endmodule
4.计时单元
把时间显示到OLED下半部分,这里的逻辑和上面显示汉字的逻辑是一样的。
always@(posedge clk) begin
case(minute)
2'd0: begin out_1[127:120] <= 8'h39;
out_2[127:120] <= 8'h3a; end
2'd1: begin out_1[127:120] <= 8'h3b;
out_2[127:120] <= 8'h3c; end
2'd2: begin out_1[127:120] <= 8'h3d;
out_2[127:120] <= 8'h3e; end
default:begin out_1[127:120] <= 8'h00;
out_2[127:120] <= 8'h00; end
endcase
out_1[119:112] <= 8'h4f;
out_2[119:112] <= 8'h50;
case(second_shi)
4'd0: begin out_1[111:104] <= 8'h39;
out_2[111:104] <= 8'h3a; end
4'd1: begin out_1[111:104] <= 8'h3b;
out_2[111:104] <= 8'h3c; end
4'd2: begin out_1[111:104] <= 8'h3d;
out_2[111:104] <= 8'h3e; end
4'd3: begin out_1[111:104] <= 8'h3f;
out_2[111:104] <= 8'h40; end
4'd4: begin out_1[111:104] <= 8'h41;
out_2[111:104] <= 8'h42; end
4'd5: begin out_1[111:104] <= 8'h43;
out_2[111:104] <= 8'h44; end
default:begin out_1[111:104] <= 8'h00;
out_2[111:104] <= 8'h00; end
endcase
case(second_ge)
4'd0: begin out_1[103:96] <= 8'h39;
out_2[103:96] <= 8'h3a; end
4'd1: begin out_1[103:96] <= 8'h3b;
out_2[103:96] <= 8'h3c; end
4'd2: begin out_1[103:96] <= 8'h3d;
out_2[103:96] <= 8'h3e; end
4'd3: begin out_1[103:96] <= 8'h3f;
out_2[103:96] <= 8'h40; end
4'd4: begin out_1[103:96] <= 8'h41;
out_2[103:96] <= 8'h42; end
4'd5: begin out_1[103:96] <= 8'h43;
out_2[103:96] <= 8'h44; end
4'd6: begin out_1[103:96] <= 8'h45;
out_2[103:96] <= 8'h46; end
4'd7: begin out_1[103:96] <= 8'h47;
out_2[103:96] <= 8'h48; end
4'd8: begin out_1[103:96] <= 8'h49;
out_2[103:96] <= 8'h4a; end
4'd9: begin out_1[103:96] <= 8'h4b;
out_2[103:96] <= 8'h4c; end
default:begin out_1[103:96] <= 8'h00;
out_2[103:96] <= 8'h00; end
endcase
out_1[95:0] <= 96'h0000_0000_0000_0000_0000_0000;
out_2[95:0] <= 96'h0000_0000_0000_0000_0000_0000;
end
处理时间的变化,并设定了播放的时间
always@(posedge clk or negedge rst) begin
if(!rst) begin
minute <= 2'd1;
second_ge <= 1'b0;
second_shi <= 1'b0;
flag_pre <= 3'd1;
count <= 1'b0; end
else if(!tone_en) begin
count <= count;
end
else if(flag_pre != flag) begin
case(flag)
3'd1: begin second_ge <= 1'b0;second_shi <= 1'b0;minute <= 2'd1; end
3'd2: begin second_ge <= 1'b0;second_shi <= 1'b0;minute <= 2'd1; end
3'd3: begin second_ge <= 1'b0;second_shi <= 1'b0;minute <= 2'd1; end
endcase
flag_pre <= flag;
count <= 1'b0;
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
四、项目总结
在本次项目中,我实现了结合音符和节拍演奏曲子,使用中间变量flag来切换不同的曲子,然后显示不同的内容,进行了按键消抖,基本完成了项目的所有要求。
在这一个寒假中,我通过参加“2022寒假在家练”活动,学会了FPGA的开发语言Verilog的基本语法,重温了很多数电的知识,掌握了FPGA的基本开发流程。平常做一些东西,我使用过uno、esp8266和stm32这些开发板,开发它们和开发FPGA的差别很大,我一开始很不习惯,但是为了能完成项目,我去b站找了一个FPGA的入门教程,因为在群里苏老师说使用FPGA和开车是一样的,原理都是相同的,看的教程除了晶振是50Mhz的,和我们用的不一样,基本一致,很好移植。
会遇到很多问题,但是只要耐心去看代码,不会的去参考搜索内容琢磨,只要花时间,都可以解决。这个项目是我大学以来做过最有意思的一个项目,从0开始,最终实现,虽然这个项目对很多人不难,但是完成它,增强了我继续学习下去的信心!
02/27/2022