一、数字简谱转换成FPGA二进制代码的思路:
step1 随便百度一下,找一个简谱,再根据简谱制定对应的音符规则。
step2 高中低三类音,可以用2位的二进制表示;00表示低音,01表示中音,10表示高音
step3 音符有1-7,共7个,同样用三位二进制表示,对应关系如下,这种要提前写下来,写代码对应的时候会快很多
000-----1
001-----2
010-----3
011-----4
100-----5
101-----6
110-----7
step4 这首歌是4/4拍,规定每一拍持续时间为0.25s,可以用00表示1拍,01表示2拍,10表示3拍,11表示4拍。
step5 将音调、音符、节拍由高到低拼接起来,可以得到一个7位的data(如:10_101_00),每一个这样的data,表示曲谱中的一个音符。注意:虽然7位的二进制可以表示一个完整的音符,但是由于我写的音符数量达到了143个,导致rom中的地址数超过了2^7,所以可以将节拍改成000表示1拍,001表示2拍,010表示3拍,011表示四拍,将data扩充至8位。
step6 将乐谱一一对应后,储存进对应的mif文件,再封装进rom
二、搭建流程框图
step1 地址控制。复位后依次输出地址读取rom中的音符即可;触发“读取地址”动作的还有一个条件就是音符的切换,当读到一个音符后再去读取下一个,所以addr_ctrl中,需要一个脉冲信号flag,当检测到flag=1时,系统读取下一个地址;
step2 在rom读取音符。按地址输出rom中的8位音符文件,将其中控制节拍长度的低3位传递给beat_ctrl用于控制时间,控制音调、和音符的高5位传递给freq_ctrl,直接控制输出的频率音色
step3 时间控制。用1个26位的cnt计数器计数1s,最大值计数到max;beat_ctrl模块根据低三位的节拍长度,用case语句控制他们对应的时间,cnt检测计数时间,抵达对应时间时,脉冲flag信号拉高到1
step4 频率控制。freq模块接收到的高5位中,包含了音调、音符信息,均有对应的频率,其中最低音频率为262hz,据此计算出他们对应的频率值,命名为freq_max(18位),再建立一个18位计数器cnt,因为蜂鸣器低电平有效,分频一个占空比40%(60%单位时间播放)频率,控制蜂鸣器。
三、播放器代码
第一部分:节拍控制(接受rom中的低三位数据,传递脉冲信号,用于addr控制)
module beat_ctrl(
input rst_n,
input clk,
input [1:0] rom_data,
output reg flag
);
reg [25:0] cnt;
parameter max = 50_000_000; // 先定义1s 的计数器
always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
begin
cnt <= 26'd 0;
flag <= 1'b 0;
end
else
case(rom_data)
2'b 00 : begin //0.25s
flag <= (cnt == max/4 - 26'd 4)?1'b 1:1'b 0;
if(cnt < max/4 - 26'd 1)
cnt <= cnt +26'd 1;
else
cnt <= 26'd 0;
end
2'b 01 : begin //0.5s
flag <= (cnt == max/2 - 26'd 4)?1'b 1:1'b 0;
if(cnt < max/2 - 26'd 1)
cnt <= cnt +26'd 1;
else
cnt <= 26'd 0;
end
2'b 10 : begin //0.75s
flag <= (cnt == 3*max/4 - 26'd 4)?1'b 1:1'b 0;
if(cnt < 3*max/4 - 26'd 1)
cnt <= cnt +26'd 1;
else
cnt <= 26'd 0;
end
2'b 11 : begin //1s
flag <= (cnt == max - 26'd 4)?1'b 1:1'b 0;
if(cnt < max - 26'd 1)
cnt <= cnt +26'd 1;
else
cnt <= 26'd 0;
end
default : begin flag <= 1'b 0; cnt <= 26'd 0;end
endcase
end
endmodule
第二部分:地址控制(复位后,根据脉冲信号flag控制地址读取)
module addr_ctrl(
input rst_n,
input clk,
input flag,
output reg [6:0] addr
);
always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
addr <= 7'd 0;
else
if(flag == 1)
if(addr < 7'd 142)
addr <= addr + 7'd 1;
else
addr <= 7'd 0;
else
addr <= addr;
end
endmodule
第三部分:rom模块(把音符转换成二进程存储即可)
第四部分:频率控制(根据频率设置各音符参数,为避免蜂鸣器通电就先发声,beep先默认为1,计数达标后再拉低)
module freq_ctrl(
input rst_n,
input clk,
input [4:0] rom_data, //高5位,代表低音、音符
output beep
);
reg [17:0] cnt; //低音频率262hz,f-clk = 50mhz
reg [17:0] freq_max;
always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
freq_max <= 18'd 0;
else
case(rom_data)
//低音1-7
5'b 00000 : freq_max <= 18'd 190840;
5'b 00001 : freq_max <= 18'd 170668;
5'b 00010 : freq_max <= 18'd 151515;
5'b 00011 : freq_max <= 18'd 143266;
5'b 00100 : freq_max <= 18'd 127511;
5'b 00101 : freq_max <= 18'd 113636;
5'b 00110 : freq_max <= 18'd 101215;
//中音1-7
5'b 01000 : freq_max <= 18'd 95602;
5'b 01001 : freq_max <= 18'd 85179;
5'b 01010 : freq_max <= 18'd 75873;
5'b 01011 : freq_max <= 18'd 71633;
5'b 01100 : freq_max <= 18'd 63776;
5'b 01101 : freq_max <= 18'd 56818;
5'b 01110 : freq_max <= 18'd 50607;
//高音1-7
5'b 10000 : freq_max <= 18'd 47801;
5'b 10001 : freq_max <= 18'd 42553;
5'b 10010 : freq_max <= 18'd 37936;
5'b 10011 : freq_max <= 18'd 35791;
5'b 10100 : freq_max <= 18'd 31888;
5'b 10101 : freq_max <= 18'd 28409;
5'b 10110 : freq_max <= 18'd 25304;
default: freq_max <= 18'd 0;
endcase
end
always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
cnt <= 18'd 0;
else
if(cnt <freq_max - 1)
cnt <= cnt + 18'd 1;
else
cnt <= 18'd 0;
end
assign beep = (cnt < 2*freq_max/5)?1'b 1:1'b 0; //分频
endmodule