大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。
今天给大侠带来基于FPGA的电子琴设计,附源码,获取源码,请在“FPGA技术江湖”公众号内回复“ 电子琴设计源码”,可获取源码文件。话不多说,上货。
设计原理
在之前也出了几篇源码系列,基本上都是一些小设计,源码系列主要就会想通过实操训练让各位学习者,尤其是初学者去更好的理解学习FPGA,或者给要的学生提供一些源码,之前设计过各个芯片的配置等,之后笔者会通过简单的例子来让大家去系统的学习和认识FPGA。本次的电子琴设计也算是一次简单的各个模块的联系调用的一个过程,也可以帮助各位去加深理解,多动手,熟练掌握会有意想不到的效果。
本次的设计主要是通过控制ps2键盘来使蜂鸣器发出哆来咪法嗦拉西7种音,音符又有高低音之分等,本次只选择发出高音的多来咪发嗦啦西。本设计中还用到了VGA的设计,通过VGA来在显示屏上画出如下图的黑白的电子琴键:
当按下多来咪发嗦啦西时,对应的键值变颜色表示按下,不变色表示不按下,颜色自己可以调节,但是琴的按键必须为黑白色来显示出来。
当按下按键的时候,蜂鸣器来鸣响对应时间的音符,本设计蜂鸣器响的时间为0.25S一个音符持续的时间。
本次设计用到的PS2和VGA的设计原理笔者在这里就不过多的介绍了,不明白的可以翻看前面发的文档内容。
在本设计中介绍蜂鸣器的使用和各音符发声的频率大小。本设计用的是无源蜂鸣器,原理图如下:
由于FPGA的驱动能力不够,我们添加了一个三极管来驱动这个无源蜂鸣器,而无源蜂鸣器的主要特点是内部不带振荡源,所以如果使用直流信号是无法使无源蜂鸣器鸣叫的,必须使用方波去驱动它。
现在我们明白了,只要往蜂鸣器发送一定频率的方波,就可以使得蜂鸣器发出声音,然后现在的问题是,我们究竟要往蜂鸣器发送什么频率的方波信号呢?具体的频率可以查看下图:
现在我们知道如何让蜂鸣器响起,又知道发送什么频率可以让蜂鸣器响起什么的声音,所以我相信我们已经有能力让蜂鸣器响起我们需要的音乐了。
设计架构
设计架构图:
在这里没有去画设计框架图,就直接给大家展示RTL级视图,各位也可以通过RTL级视图看到设计的总框架。
设计代码
顶层模块music_ps2代码:
代码语言:javascript复制module music_ps2(clk, rst_n, hs, vs, r_g_b, ps2_clk, ps2_data, beep);
input clk;
input rst_n;
output hs;
output vs;
output [7:0]r_g_b;
output beep;
input ps2_clk;
input ps2_data;
wire flag;
wire [7:0] data, data_n;
wire clk_1M;
frenp frep_dut(
.clk(clk),
.rst_n(rst_n),
.clk_1M(clk_1M)
);
ps2_rec rec_dut(
.clk(clk_1M),
.rst_n(rst_n),
.ps2_clk(ps2_clk),
.ps2_data(ps2_data),
.flag(flag),
.data(data)
);
decode decode_dut(
.clk(clk_1M),
.rst_n(rst_n),
.flag(flag),
.data(data),
.data_n(data_n)
);
music music_dut(
.clk(clk_1M),
.rst_n(rst_n),
.data_n(data_n),
.beep(beep)
);
vga vga_dut(
.clk(clk),
.rst_n(rst_n),
.hs(hs),
.vs(vs),
.r_g_b(r_g_b),
.data_n(data_n)
);
endmodule
蜂鸣器music模块代码:
代码语言:javascript复制module music(clk, rst_n, data_n, beep); 端口列表
input clk;
input rst_n;
input [7:0] data_n; //输入的键值
output reg beep; //蜂鸣器
reg [10:0] music_data;
wire [10:0] data;
always @ (posedge clk)
if(!rst_n)
begin
music_data <= 0;
end
else
case (data_n)
1 : music_data <= 478; //蜂鸣器的高音1
2 : music_data <= 425; //蜂鸣器的高音2
3 : music_data <= 379; //蜂鸣器的高音3
4 : music_data <= 358; //蜂鸣器的高音4
5 : music_data <= 319; //蜂鸣器的高音5
6 : music_data <= 284; //蜂鸣器的高音6
7 : music_data <= 253; //蜂鸣器的高音7
default: music_data <= 0;
endcase
reg [20:0] count, cnt;
always @ (posedge clk)
if(!rst_n && !data_n)
begin
count <= 0;
end
else
if(count < 250_000 - 1)
begin
count <= count 1;
end
else
begin
count <= 0;
end
//计数0.25S的时间
assign data = (count == 250_000 - 1) ? music_data : data;
always @ (posedge clk)
if(!rst_n)
begin
cnt <= 1;
beep <= 0;
end
else
if(data == 0) //控制蜂鸣器不响
begin
cnt <= 1;
beep <= 0;
end
else if(cnt < data) //计数对应的频率
begin
cnt <= cnt 1;
end
else
begin
cnt <= 1; //蜂鸣器响
beep <= ~beep;
end
endmodule
frenp模块代码:
代码语言:javascript复制module frenp(clk,rst_n,clk_1M);
input clk;
input rst_n;
output reg clk_1M;
reg [4:0] count;
always @(posedge clk or negedge rst_n)
if(!rst_n)
begin
count <= 5'd0;
clk_1M <= 1'b1;
end
else
begin
if(count == 50_000_000 / 1000_000 / 2 - 1)
begin
count <= 5'd0;
clk_1M <= ~clk_1M;
end
else
begin
count <= count 1'b1;
end
end
endmodule
VGA模块代码:
代码语言:javascript复制module vga(
clk,
rst_n,
hs,
vs,
r_g_b,
data_n
);
input clk;
input rst_n;
input [7:0] data_n;
output reg hs;
output reg vs;
output reg [7:0]r_g_b;
reg [10:0] count_hs; //列计数
reg [10:0] count_vs; //行计数
parameter h_a=96,h_b=48,h_c=640,h_d=16,h_e=800,
v_a=2,v_b=33,v_c=480,v_d=10,v_e=525;
reg clk_25M;
always @ (posedge clk)
if(!rst_n)
clk_25M <= 1;
else
clk_25M <= ~ clk_25M;
/*================列扫描=================*/
always@(posedge clk_25M or negedge rst_n )
begin
if(!rst_n)
begin
count_hs<=0;
end
else
begin
if(count_hs==h_e)
count_hs<=0;
else
count_hs<=count_hs 11'd1;
end
end
/*================行扫描=================*/
always@(posedge clk_25M or negedge rst_n )
begin
if(!rst_n)
begin
count_vs<=0;
end
else
begin
if(count_vs==v_e)
count_vs<=0;
else
begin
if(count_hs==h_e)
count_vs<=count_vs 11'd1;
else
count_vs<=count_vs;
end
end
end
/*================列同步=================*/
always@(posedge clk_25M or negedge rst_n )
begin
if(!rst_n)
begin
hs<=1;
end
else
begin
if(count_hs>=h_a)
hs<=1;
else
hs<=0;
end
end
/*================行同步=================*/
always@(posedge clk_25M or negedge rst_n )
begin
if(!rst_n)
begin
vs<=1;
end
else
begin
if(count_vs>=v_a)
vs<=1;
else
vs<=0;
end
end
/*=============有效区域显示====================*/
reg yes;
always@(posedge clk_25M or negedge rst_n)
begin
if(!rst_n)
begin
yes <= 0;
end
else
begin
if((count_hs>h_a h_b)&&(count_hs<h_e-h_d)&&(count_vs>v_a v_b)&&(count_vs<v_e-v_d))
yes <= 1;
else
yes <= 0;
end
end
reg [4:0] flag;
always @ (*)
if(!rst_n)
begin
flag <= 0;
end
else if (yes)
begin
if ((count_hs - (144 30 * 0)) <= 30 && count_vs < 400 && count_vs > 100 )
flag <= 1;
else if ((count_hs - (144 30 * 1)) <= 30 && count_vs < 400 && count_vs > 100 )
flag <= 0; // 黑
else if ((count_hs - (144 30 * 2)) <= 30 && count_vs < 400 && count_vs > 100 )
flag <= 2;
else if ((count_hs - (144 30 * 3)) <= 30 && count_vs < 400 && count_vs > 100 )
flag <= 0;
else if ((count_hs - (144 30 * 4)) <= 30 && count_vs < 400 && count_vs > 100 )
flag <= 3;
else if ((count_hs - (145 30 * 5)) <= 4 && count_vs > 100 )
flag <= 0;
else if ((count_hs - (149 30 * 5)) <= 30 && count_vs < 400 && count_vs > 100 )
flag <= 4; // 白
else if ((count_hs - (149 30 * 6)) <= 30 && count_vs < 400 && count_vs > 100 )
flag <= 0; // 黑
else if ((count_hs - (149 30 * 7)) <= 30 && count_vs < 400 && count_vs > 100 )
flag <= 5;
else if ((count_hs - (149 30 * 8)) <= 30 && count_vs < 400 && count_vs > 100 )
flag <= 0;
else if ((count_hs - (149 30 * 9)) <= 30 && count_vs < 400 && count_vs > 100 )
flag <= 6;
else if ((count_hs - (149 30 * 10)) <= 30 && count_vs < 400 && count_vs > 100 )
flag <= 0;
else if ((count_hs - (149 30 * 11)) <= 30 && count_vs < 400 && count_vs > 100 )
flag <= 7;
else if ((count_hs - (150 30 * 12)) <= 4 && count_vs > 100 )
flag <= 0;
else if (count_hs < 186 && count_vs >= 400)
flag <= 1;
else if (count_hs < 190 && count_vs >= 400)
flag <= 0;
else if (count_hs < 234 12 && count_vs >= 400)
flag <= 2;
else if (count_hs < 250 && count_vs >= 400)
flag <= 0;
else if (count_hs < 295 && count_vs >= 400)
flag <= 3;
else if (count_hs < 329 12 && count_vs >= 400)
flag <= 4;
else if (count_hs < 329 16 && count_vs >= 400)
flag <= 0;
else if (count_hs < 389 12 && count_vs >= 400)
flag <= 5;
else if (count_hs < 389 16 && count_vs >= 400)
flag <= 0;
else if (count_hs < 449 12 && count_vs >= 400)
flag <= 6;
else if (count_hs < 449 16&& count_vs >= 400)
flag <= 0;
else if (count_hs < 510 && count_vs >= 400)
flag <= 7;
else
flag <= 8; ;
end
else
flag<=0;
reg [1:0] state;
always @ (posedge clk)
if(!rst_n)
begin
state <= 0;
r_g_b<=8'b000_000_00;
end
else
if(data_n)
begin
if(flag == data_n)
r_g_b<=8'b111_100_11;
else if(flag != data_n && flag !=8 && flag !=0)
r_g_b<=8'b111_111_111;
else if (flag == 0)
r_g_b<=8'b000_000_000;
else if(flag == 8)
r_g_b<=8'b000_111_00;
end
else
begin
if(flag !=8 && flag !=0)
r_g_b<=8'b111_111_111;
else if (flag == 0)
r_g_b<=8'b000_000_000;
else if(flag == 8)
r_g_b<=8'b000_111_00;
end
endmodule
ps_2rec模块代码:
代码语言:javascript复制module ps2_rec(clk,rst_n,ps2_clk,ps2_data,flag,data);
input clk;
input rst_n;
input ps2_clk;
input ps2_data;
output reg flag;
output [7:0] data;
wire nege_dge;
reg [1:0] signle_s;
always @ (posedge clk or negedge rst_n)
if(!rst_n)
begin
signle_s <= 2'b11;
end
else
begin
signle_s[0] <= ps2_clk;
signle_s[1] <= signle_s[0];
end
assign nege_dge = ~signle_s[0] && signle_s[1];
reg [3:0] count;
reg [10:0] temp ;
assign data = temp[8:1];
always @ (posedge clk or negedge rst_n)
if(!rst_n)
begin
count <= 4'd0;
flag <= 1'b0;
temp <= 11'd0;
end
else
begin
if(nege_dge && count < 11)
begin
count <= count 1'd1;
temp[count] <= ps2_data;
end
else
begin
if(count == 11)
begin
count <= 4'd0;
flag <= 1'b1;
end
else
begin
flag <= 1'b0;
end
end
end
endmodule
代码验证正确无误,笔者在这边就不过多的验证,源工程已提供给各位大侠,如有需要,可以自行获取,供大家参考学习。
END
后续会持续更新,带来Vivado、 ISE、Quartus II 、candence等安装相关设计教程,学习资源、项目资源、好文推荐等,希望大侠持续关注。
大侠们,江湖偌大,继续闯荡,愿一切安好,有缘再见!