大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。
今天给大侠带来基于FPGA的电子琴设计,附源码,获取源码,请在“FPGA技术江湖”公众号内回复“ 电子琴设计源码”,可获取源码文件。话不多说,上货。
设计原理
在之前也出了几篇源码系列,基本上都是一些小设计,源码系列主要就会想通过实操训练让各位学习者,尤其是初学者去更好的理解学习FPGA,或者给要的学生提供一些源码,之前设计过各个芯片的配置等,之后笔者会通过简单的例子来让大家去系统的学习和认识FPGA。本次的电子琴设计也算是一次简单的各个模块的联系调用的一个过程,也可以帮助各位去加深理解,多动手,熟练掌握会有意想不到的效果。
本次的设计主要是通过控制ps2键盘来使蜂鸣器发出哆来咪法嗦拉西7种音,音符又有高低音之分等,本次只选择发出高音的多来咪发嗦啦西。本设计中还用到了VGA的设计,通过VGA来在显示屏上画出如下图的黑白的电子琴键:
当按下多来咪发嗦啦西时,对应的键值变颜色表示按下,不变色表示不按下,颜色自己可以调节,但是琴的按键必须为黑白色来显示出来。
当按下按键的时候,蜂鸣器来鸣响对应时间的音符,本设计蜂鸣器响的时间为0.25S一个音符持续的时间。
本次设计用到的PS2和VGA的设计原理笔者在这里就不过多的介绍了,不明白的可以翻看前面发的文档内容。
在本设计中介绍蜂鸣器的使用和各音符发声的频率大小。本设计用的是无源蜂鸣器,原理图如下:
由于FPGA的驱动能力不够,我们添加了一个三极管来驱动这个无源蜂鸣器,而无源蜂鸣器的主要特点是内部不带振荡源,所以如果使用直流信号是无法使无源蜂鸣器鸣叫的,必须使用方波去驱动它。
现在我们明白了,只要往蜂鸣器发送一定频率的方波,就可以使得蜂鸣器发出声音,然后现在的问题是,我们究竟要往蜂鸣器发送什么频率的方波信号呢?具体的频率可以查看下图:
现在我们知道如何让蜂鸣器响起,又知道发送什么频率可以让蜂鸣器响起什么的声音,所以我相信我们已经有能力让蜂鸣器响起我们需要的音乐了。
设计架构
设计架构图:
在这里没有去画设计框架图,就直接给大家展示RTL级视图,各位也可以通过RTL级视图看到设计的总框架。
设计代码
顶层模块music_ps2代码:
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模块代码:
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)beginmusic_data <= 0;endelsecase (data_n)1 : music_data <= 478; //蜂鸣器的高音12 : music_data <= 425; //蜂鸣器的高音23 : music_data <= 379; //蜂鸣器的高音34 : music_data <= 358; //蜂鸣器的高音45 : music_data <= 319; //蜂鸣器的高音56 : music_data <= 284; //蜂鸣器的高音67 : music_data <= 253; //蜂鸣器的高音7default: music_data <= 0;endcasereg [20:0] count, cnt;always @ (posedge clk)if(!rst_n && !data_n)begincount <= 0;endelseif(count < 250_000 - 1)begincount <= count + 1;endelsebegincount <= 0;end//计数0.25S的时间assign data = (count == 250_000 - 1) ? music_data : data;always @ (posedge clk)if(!rst_n)begincnt <= 1;beep <= 0;endelseif(data == 0) //控制蜂鸣器不响begincnt <= 1;beep <= 0;endelse if(cnt < data) //计数对应的频率begincnt <= cnt + 1;endelsebegincnt <= 1; //蜂鸣器响beep <= ~beep;endendmodule
frenp模块代码:
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)begincount <= 5'd0;clk_1M <= 1'b1;endelsebeginif(count == 50_000_000 / 1000_000 / 2 - 1)begincount <= 5'd0;clk_1M <= ~clk_1M;endelsebegincount <= count + 1'b1;endendendmodule
VGA模块代码:
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;elseclk_25M <= ~ clk_25M;/*================列扫描=================*/always@(posedge clk_25M or negedge rst_n )beginif(!rst_n)begincount_hs<=0;endelsebeginif(count_hs==h_e)count_hs<=0;elsecount_hs<=count_hs+11'd1;endend/*================行扫描=================*/always@(posedge clk_25M or negedge rst_n )beginif(!rst_n)begincount_vs<=0;endelsebeginif(count_vs==v_e)count_vs<=0;elsebeginif(count_hs==h_e)count_vs<=count_vs+11'd1;elsecount_vs<=count_vs;endendend/*================列同步=================*/always@(posedge clk_25M or negedge rst_n )beginif(!rst_n)beginhs<=1;endelsebeginif(count_hs>=h_a)hs<=1;elsehs<=0;endend/*================行同步=================*/always@(posedge clk_25M or negedge rst_n )beginif(!rst_n)beginvs<=1;endelsebeginif(count_vs>=v_a)vs<=1;elsevs<=0;endend/*=============有效区域显示====================*/reg yes;always@(posedge clk_25M or negedge rst_n)beginif(!rst_n)beginyes <= 0;endelsebeginif((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;elseyes <= 0;endendreg [4:0] flag;always @ (*)if(!rst_n)beginflag <= 0;endelse if (yes)beginif ((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;elseflag <= 8; ;endelseflag<=0;reg [1:0] state;always @ (posedge clk)if(!rst_n)beginstate <= 0;r_g_b<=8'b000_000_00;endelseif(data_n)beginif(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;endelsebeginif(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;endendmodule
ps_2rec模块代码:
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)beginsignle_s <= 2'b11;endelsebeginsignle_s[0] <= ps2_clk;signle_s[1] <= signle_s[0];endassign 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)begincount <= 4'd0;flag <= 1'b0;temp <= 11'd0;endelsebeginif(nege_dge && count < 11)begincount <= count + 1'd1;temp[count] <= ps2_data;endelsebeginif(count == 11)begincount <= 4'd0;flag <= 1'b1;endelsebeginflag <= 1'b0;endendendendmodule
代码验证正确无误,笔者在这边就不过多的验证,源工程已提供给各位大侠,如有需要,可以自行获取,供大家参考学习。
1410