本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的“傻瓜式”讲解,让电子、信息、通信类专业学生、初入职场小白及打算进阶提升的职业开发者都可以有系统性学习的机会。
系统性的掌握技术开发以及相关要求,对个人就业以及职业发展都有着潜在的帮助,希望对大家有所帮助。后续会陆续更新 Xilinx 的 Vivado、ISE 及相关操作软件的开发的相关内容,学习FPGA设计方法及设计思想的同时,实操结合各类操作软件,会让你在技术学习道路上无比的顺畅,告别技术学习小BUG卡破脑壳,告别目前忽悠性的培训诱导,真正的去学习去实战应用。话不多说,上货。
音乐蜂鸣器设计-ISE操作工具
作者:李西锐 校对:陆辉
Xilinx ISE 系列实操所使用的开发设备为叁芯智能科技研发的SANXIN B02 FPGA开发板,如果有想入手的大侠,可登陆叁芯智能科技官方淘宝店咨询以及购买。
叁芯智能科技官方淘宝店链接:
https://shop588964188.taobao.com
同时也有对应的系统性学习课程,欢迎报名参加。
FPGA工程师就业班,9月份开课!
利用开发板上的蜂鸣器播放一首音乐。
- 硬件介绍
蜂鸣器“鸣叫”需要的电流较大,故而采用三极管进行驱动,FPGA控制三极管是否导通。
蜂鸣器是一种一体化结构的电子讯响器,采用直流电压供电,广泛应用于计算机、打印机、复印机、报警器、电子玩具、汽车电子设备、电话机、定时器等电子产品中作发声器件。
蜂鸣器主要分为压电式蜂鸣器和电磁式蜂鸣器两种类型。
压电式蜂鸣器 压电式蜂鸣器主要由多谐振荡器、压电蜂鸣片、阻抗匹配器及共鸣箱、外壳等组成。有的压电式蜂鸣器外壳上还装有发光二极管。多谐振荡器由晶体管或集成电路构成。当接通电源后(1.5~15V直流工作电压),多谐振荡器起振,输出1.5~2.5kHZ的音频信号,阻抗匹配器推动压电蜂鸣片发声。
电磁式蜂鸣器电磁式蜂鸣器由振荡器、电磁线圈、磁铁、振动膜片及外壳等组成。接通电源后,振荡器产生的音频信号电流通过电磁线圈,使电磁线圈产生磁场。振动膜片在电磁线圈和磁铁的相互作用下,周期性地振动发声。
按照内部有无震荡源可以分为有源蜂鸣器和无源蜂鸣器。有源蜂鸣器内部带震荡源,所以只要一通电就会发出声音;而无源内部不带震荡源,所以如果用直流信号无法令其鸣叫。必须用一定频率的方波去驱动它。
- 设计原理
本例程研究如何利用蜂鸣器演唱一首曲子《世上只有妈妈好》。
下图为《世上只有妈妈好》的简谱。
简谱是一种比较简单易学的音乐记谱法。据说简谱是由法国思想家卢梭于1742年发明的。而最早把简谱引进我国的是我国近代音乐教育家沈心工。简谱应该说是一种比较简单易学的音乐记谱法。它的最大好处是仅用7个阿拉伯数字----1234567,就能将万千变化的音乐曲子记录并表示出来。
在简谱中,用以表示音的高低及相互关系的基本符号为七个阿拉伯数字,即1、2、3、4、5、6、7,唱作do、re、mi、fa、sol、la、si,称为唱名。
音符:1234567
唱名:do re mi fa sol la si
汉字:哆来米发梭拉西
显然,单用以上七个音是无法表现众多的音乐形象的。在实际作品中,还有一些更高或更低的音,如在基本音符上方加记一个"·",表示该音升高一个八度,称为高音;加记两个" :",则表示该音升高两个八度,称为倍高音。在基本音符下方加记一个"·",表示该音降低一个八度,称为低音;加记两个" :",则表示该音降低两个八度,称为倍低音。
在一般歌曲中,无论是在基本音符上方或下方加记两个以上的"·"的音符都是很少见的。
在简谱中,1、2、3、4、5、6、7这七个基本音符,不仅表示音的高低,而且还是表示时值长短的基本单位,称为四分音符,其他音符均是在四分音符的基础上,用加记短横线"-"和附点"·"表示。
在基本音符右侧加记一条短横线,表示增长一个四分音符的时值。这类加记在音符右侧、使音符时值增长的短横线,称为增时线。增时线越多,音符的时值越长。
在基本音符下方加记一条短横线,表示缩短原音符时值的一半。这类加记在音符下方、使音符时值缩短的短横线,称为减时线。减时线越多,音符的时值越短。
在简谱中,加记在单纯音符的右侧的、使音符时值增长的小圆点"·",称为附点。加记附点的音符称为附点音符。附点本身并无一定的长短,其长短由前面的单纯音符来决定。附点的意义在于增长原音符时值的一半,常0用于四分音符和小于四分音符的各种音符之后。
在《世上只有妈妈好》的简谱中,每两个竖线之间为2秒钟的时长。每两个竖线之间有4个音符时长,但是其中有较多半个音符的长,本设计采用1/4秒为基本单位。
蜂鸣器给予不同的频率是可以发出近似1、2、3、4、5、6、7这七个基本音符。
在设计时,首先将简谱中的音符存起来;利用计数器产生1/4秒为周期的脉冲,在此脉冲驱动下,将事先存好的音符一个个输出;根据音符的值,计算出分频比;根据分频比,产生对应频率的波形。将此波形输出即可。
《世上只有妈妈好》的简谱中共有8个四拍,根据上述原理,简谱中存在多个半拍的情况,所以每个四拍我们用8个音符来表示,合计共64个音符。
简谱中有高低中三种音符。用一个位宽为9的存储器,来储存音符。高三位表示高音,中间三位表示中音,低三位表示低音。例:中音5,用9’b000_101_000,低音3,用9’b000_000_011,高音7,用9’b111_000_000。
- 设计架构和信号说明
此模块命名为music_beep。
在设计中共分为4个模块:
addr_ctrl模块(地址控制模块):每1/4秒让地址进行加1,共有64个音符,故而输出地址采用6位即可。
music_mem模块(音符存储模块):将64个音符存储起来,然后根据地址,将存储的音符进行输出。
music_freq模块(音符转换频率模块):根据输入的音符以及不同音符所对应的频率,输出对应的频率值。
wave_gen模块(产生对应频率的方波):根据输入的频率值,产生对应频率的方波。
- addr_ctrl设计实现
本模块中首先设计1/4秒的计时器。当到1/4秒时,让输出的addr进行变化:小于63时,进行加一操作;等于63时,进行清零操作。此时蜂鸣器将不断的重复播放这个音乐。
代码设计为:
代码语言:javascript复制module addr_ctrl (
input wire clk,
input wire rst_n,
output reg [5:0] addr
);
parameter T_1s = 50_000_000;
localparam SEC_1_4 = T_1s/4;
reg [25:0] cnt;
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
cnt <= 26'd0;
else
if (cnt < SEC_1_4 - 1'b1)
cnt <= cnt 1'b1;
else
cnt <= 26'd0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
addr <= 6'd0;
else
if (cnt == SEC_1_4 - 1'b1)
if (addr < 6'd63)
addr <= addr 1'b1;
else
addr <= 6'd0;
else
addr <= addr;
end
endmodule
在设计中,没有直接给出1/4秒的参数,而是首先给出了1秒的参数,然后通过计算得出1/4秒的参数。等设计完成后,我们可以修改参数来达到控制方波速度的功能。
- music_mem设计实现
本模块中,根据简谱将64个音符存储起来。通过外部的地址来确定应该输出那个音符。
设计代码为:
代码语言:javascript复制module music_mem (
input wire clk,
input wire rst_n,
input wire [5:0] addr,
output reg [8:0] music
);
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
music <= 9'd0;
else
case (addr)
6'd0 : music <= 9'b000_110_000;
6'd1 : music <= 9'b000_110_000;
6'd2 : music <= 9'b000_110_000;
6'd3 : music <= 9'b000_101_000;
6'd4 : music <= 9'b000_011_000;
6'd5 : music <= 9'b000_011_000;
6'd6 : music <= 9'b000_101_000;
6'd7 : music <= 9'b000_101_000;
6'd8 : music <= 9'b001_000_000;
6'd9 : music <= 9'b001_000_000;
6'd10 : music <= 9'b000_110_000;
6'd11 : music <= 9'b000_101_000;
6'd12 : music <= 9'b000_110_000;
6'd13 : music <= 9'b000_110_000;
6'd14 : music <= 9'b000_110_000;
6'd15 : music <= 9'b000_110_000;
6'd16 : music <= 9'b000_011_000;
6'd17 : music <= 9'b000_011_000;
6'd18 : music <= 9'b000_101_000;
6'd19 : music <= 9'b000_110_000;
6'd20 : music <= 9'b000_101_000;
6'd21 : music <= 9'b000_101_000;
6'd22 : music <= 9'b000_011_000;
6'd23 : music <= 9'b000_011_000;
6'd24 : music <= 9'b000_001_000;
6'd25 : music <= 9'b000_000_110;
6'd26 : music <= 9'b000_101_000;
6'd27 : music <= 9'b000_011_000;
6'd28 : music <= 9'b000_010_000;
6'd29 : music <= 9'b000_010_000;
6'd30 : music <= 9'b000_010_000;
6'd31 : music <= 9'b000_010_000;
6'd32 : music <= 9'b000_010_000;
6'd33 : music <= 9'b000_010_000;
6'd34 : music <= 9'b000_010_000;
6'd35 : music <= 9'b000_011_000;
6'd36 : music <= 9'b000_101_000;
6'd37 : music <= 9'b000_101_000;
6'd38 : music <= 9'b000_110_000;
6'd39 : music <= 9'b000_110_000;
6'd40 : music <= 9'b000_011_000;
6'd41 : music <= 9'b000_011_000;
6'd42 : music <= 9'b000_011_000;
6'd43 : music <= 9'b000_010_000;
6'd44 : music <= 9'b000_001_000;
6'd45 : music <= 9'b000_001_000;
6'd46 : music <= 9'b000_001_000;
6'd47 : music <= 9'b000_001_000;
6'd48 : music <= 9'b000_101_000;
6'd49 : music <= 9'b000_101_000;
6'd50 : music <= 9'b000_101_000;
6'd51 : music <= 9'b000_011_000;
6'd52 : music <= 9'b000_010_000;
6'd53 : music <= 9'b000_001_000;
6'd54 : music <= 9'b000_000_110;
6'd55 : music <= 9'b000_001_000;
6'd56 : music <= 9'b000_000_101;
6'd57 : music <= 9'b000_000_101;
6'd58 : music <= 9'b000_000_101;
6'd59 : music <= 9'b000_000_101;
6'd60 : music <= 9'b000_000_101;
6'd61 : music <= 9'b000_000_101;
6'd62 : music <= 9'b000_000_101;
6'd63 : music <= 9'b000_000_101;
default : music <= 9'b000_000_000;
endcase
end
endmodule
在case语句中,首先会判断变量和那个分支相同,并且执行对应的表达式。当和所有的分支都不相同时,执行default后的表达式。
代码语言:javascript复制case(变量)
分支0 :表达式0;
分支1 :表达式1;
分支n :表达式n;
default :表达式d;
endcase
- music_freq设计实现
本模块把所有的音符所对应的频率存储起来,根据外部输入的音符查找出对应的频率进行输出。
设计代码为:
代码语言:javascript复制module music_freq (
input wire clk,
input wire rst_n,
input wire [8:0] music,
output reg [10:0] freq
);
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
freq <= 1'b1;
else
case (music)
9'b000_000_001 : freq <= 11'd262;
9'b000_000_010 : freq <= 11'd294;
9'b000_000_011 : freq <= 11'd330;
9'b000_000_100 : freq <= 11'd349;
9'b000_000_101 : freq <= 11'd392;
9'b000_000_110 : freq <= 11'd440;
9'b000_000_111 : freq <= 11'd494;
9'b000_001_000 : freq <= 11'd523;
9'b000_010_000 : freq <= 11'd587;
9'b000_011_000 : freq <= 11'd659;
9'b000_100_000 : freq <= 11'd699;
9'b000_101_000 : freq <= 11'd784;
9'b000_110_000 : freq <= 11'd880;
9'b000_111_000 : freq <= 11'd988;
9'b001_000_000 : freq <= 11'd1050;
9'b010_000_000 : freq <= 11'd1175;
9'b011_000_000 : freq <= 11'd1319;
9'b100_000_000 : freq <= 11'd1397;
9'b101_000_000 : freq <= 11'd1568;
9'b110_000_000 : freq <= 11'd1760;
9'b111_000_000 : freq <= 11'd1976;
default : freq <= 11'd1;
endcase
end
endmodule
在本模块设计时,将所有的频率进行了取整,输出的声音不会有较大的改变。硬件表示小数和输出带有小数精度的频率会加大设计难度,在此不考虑。
- wave_gen设计实现
产生波形的方法采用计时器计时半个周期,然后进行取反。利用时钟的频率(50MHz)除以想要的波形的频率,得出分频比(想要波形周期是50MHz波形周期的多少倍),将分频比除以2,得到半个周期的计数值。
设计代码为:
代码语言:javascript复制module wave_gen (
input wire clk,
input wire rst_n,
input wire [10:0] freq,
output reg beep
);
parameter F_clk = 50_000_000;
wire [25:0] half;
assign half = F_clk/(2*freq);
reg [24:0] cnt;
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
cnt <= 25'd0;
else
if (cnt < half - 1'b1)
cnt <= cnt 1'b1;
else
cnt <= 25'd0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
beep <= 1'b0;
else
if (cnt == half - 1'b1)
beep <= ~beep;
else
beep <= beep;
end
endmodule
half为半个周期的计数值。
- music_beep设计实现
本模块负责将各个模块按照架构图的设计方式进行连接,形成最终的设计。
设计代码为:
代码语言:javascript复制module music_beep (
input wire clk,
input wire rst_n,
output wire beep
);
wire [5:0] addr;
wire [8:0] music;
wire [10:0] freq;
addr_ctrl addr_ctrl_inst(
.clk (clk),
.rst_n (rst_n),
.addr (addr)
);
music_mem music_mem_inst(
.clk (clk),
.rst_n (rst_n),
.addr (addr),
.music (music)
);
music_freq music_freq_inst(
.clk (clk),
.rst_n (rst_n),
.music (music),
.freq (freq)
);
wave_gen wave_gen_inst(
.clk (clk),
.rst_n (rst_n),
.freq (freq),
.beep (beep)
);
endmodule
通过综合器形成的RTL视图如下:
- RTL仿真
在设计中,音符是每1/4秒输出一个,所产生的波形也是1/4秒更换一个频率,仿真时间比较长。此设计中还有对应的频率输出,不太适合进行更改参数仿真。
仿真代码为:
代码语言:javascript复制`timescale 1ns/1ps
module music_beep_tb;
reg clk;
reg rst_n;
wire beep;
music_beep music_beep_inst(
.clk (clk),
.rst_n (rst_n),
.beep (beep)
);
initial begin
forever begin
clk = 1'b0;
# 10;
clk = 1'b1;
# 10;
end
end
initial begin
rst_n = 1'b0;
# 201
rst_n = 1'b1;
#1_000_000_000;
#1_000_000_000;
#1_000_000_000;
#1_000_000_000;
#1_000_000_000;
#1_000_000_000;
$stop;
end
endmodule
仿真时,仿真时间设置为6秒钟。不要直接写6_000_000_000,在verilog中此延时时间不能够大于32位二进制所表示的时间范围。
仿真时间较长,需要等待较长时间。
可以对输出的波形进行测量,观测输出波形是否为我们想要的频率。
下板后,就可以听到《世上只有妈妈好》的歌曲,通过去修改addr增加1的时间,可以达到控制播放速度的效果。
代码语言:javascript复制 localparam SEC_1_4 = T_1s/8;
- End -