本帖最后由 正点原子运营 于 2023-11-6 16:35 编辑
第十章 按键控制蜂鸣器实验 1)实验平台:正点原子 ATK-DFPGL22G开发板
2) 章节摘自【正点原子】ATK-DFPGL22G之FPGA开发指南_V1.0
3)购买链接:https://detail.tmall.com/item_o.htm?id=692712955836
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-PGL22G.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)FPGA技术交流QQ群:435699340
蜂鸣器(Buzzer)是现代常用的一种电子发声器,主要用于产生声音信号。蜂鸣器在生活中已经得到广泛使用,其典型应用包括医疗,消防等领域的各种报警装置以及日常生活中的各种警报器等。本章我们主要学习如何使用按键来控制蜂鸣器发声。 本章包括以下几个部分: 1.1 蜂鸣器简介 1.2 实验任务 1.3 硬件设计 1.4 程序设计 1.5 下载验证
1.1 蜂鸣器简介蜂鸣器按照驱动方式主要分为有源蜂鸣器和无源蜂鸣器,其主要区别为蜂鸣器内部是否含有震荡源。一般的有源蜂鸣器内部自带了震荡源,只要通电就会发声。而无源蜂鸣器由于不含内部震荡源,需要外接震荡信号才能发声。 图 10.1.1 有源蜂鸣器(左)和无源蜂鸣器(右) 如上图所示,从外观上看,两种蜂鸣器很相似,如将两种蜂鸣器的引脚都朝上放置,能看到绿色电路板的是无源蜂鸣器,没有电路板而用黑胶封闭的一种是有源蜂鸣器。
相较于有源蜂鸣器,无源蜂鸣器成本更低,且发声频率可控。而有源蜂鸣器控制相对简单,由于内部自带震荡源,只要加上合适的直流电压即可发声。本次实验使用的蜂鸣器为有源蜂鸣器。
1.2 实验任务本节实验任务是使用ATK-DFPGL22G开发板上的KEY0按键来控制蜂鸣器发声。初始状态为蜂鸣器鸣叫,按下按键后蜂鸣器停止鸣叫,再次按下开关,蜂鸣器重新鸣叫。
1.3 硬件设计 上图为蜂鸣器控制电路的原理图。由于ATK-DFPGL22G开发板的IO驱动电压只有1.35V,所以我们在蜂鸣器的驱动电路中加入三级管Q25,以将开发板的IO驱动电压放大,然后再驱动蜂鸣器。
本实验的管脚分配如下表所示: 对应的FDC约束语句如下所示: - create_clock-name {clk} [get_ports {sys_clk}] -period {20} -waveform {0.000 10.000}
- define_attribute{p:beep} {PAP_IO_DIRECTION} {OUTPUT}
- define_attribute{p:beep} {PAP_IO_LOC} {P3}
- define_attribute{p:beep} {PAP_IO_VCCIO} {1.5}
- define_attribute{p:beep} {PAP_IO_STANDARD} {LVCMOS15}
- define_attribute{p:beep} {PAP_IO_DRIVE} {4}
- define_attribute{p:beep} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:beep} {PAP_IO_SLEW} {SLOW}
- define_attribute{p:key} {PAP_IO_DIRECTION} {INPUT}
- define_attribute{p:key} {PAP_IO_LOC} {F2}
- define_attribute{p:key} {PAP_IO_VCCIO} {1.5}
- define_attribute{p:key} {PAP_IO_STANDARD} {LVCMOS15}
- define_attribute{p:key} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:sys_clk} {PAP_IO_DIRECTION} {INPUT}
- define_attribute{p:sys_clk} {PAP_IO_LOC} {B5}
- define_attribute{p:sys_clk} {PAP_IO_VCCIO} {3.3}
- define_attribute{p:sys_clk} {PAP_IO_STANDARD} {LVCMOS33}
- define_attribute{p:sys_clk} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:sys_rst_n} {PAP_IO_DIRECTION} {INPUT}
- define_attribute{p:sys_rst_n} {PAP_IO_LOC} {G5}
- define_attribute{p:sys_rst_n} {PAP_IO_VCCIO} {1.5}
- define_attribute{p:sys_rst_n} {PAP_IO_STANDARD} {LVCMOS15}
- define_attribute{p:sys_rst_n} {PAP_IO_PULLUP} {TRUE}
复制代码
1.4 程序设计由实验任务可知,我们只需要在按键按下时改变蜂鸣器的鸣叫状态即可。但实际上在按键按下的过程中存在按键抖动的干扰,体现在数字电路中就是不断变化的高低电平。为避免在抖动过程中采集到错误的按键状态,我们需要对按键数据进行消除抖动处理。
在这里我们先介绍一下按键消抖的原理。通常我们所使用的开关为机械弹性开关,当我们按下或松开按键时,由于弹片的物理特性,不能立即闭合或断开,往往会在断开或闭合的短时间内产生机械抖动,。消除这种抖动的过程即称为按键消抖。
按键消抖可分为硬件消抖和软件消抖。硬件消抖主要使用RS触发器或电容等方法在硬件电路上实现消抖,一般在按键较少时使用。软件消抖的原理主要为按键按下或松开后,由处理器延时5ms至20ms,然后再对按键状态进行采样并判断。如下图所示: 由上面的分析可知,本次实验中的系统至少包含按键消抖和蜂鸣器控制两个模块。系统框图如下图所示: 图 10.4.2中,按键消抖模块用于消除按键的抖动,消抖之后的信号用于控制蜂鸣器的鸣叫状态。
程序中各模块端口及信号连接如下图所示: 由上图系统框图可知,代码部分包括三个模块。顶层模块(top_key_beep),作用为完成对另外两个模块的例化。按键消抖模块(key_debounce),主要用于对按键进行抖动滤除。按键控制蜂鸣器模块(beep_control),识别按键按下的那一刻,并对蜂鸣器的鸣叫状态进行翻转。
顶层模块代码如下: - 1 module top_key_beep(
- 2 input sys_clk, //时钟信号50Mhz
- 3 input sys_rst_n, //复位信号
- 4
- 5 input key, //按键信号
- 6 output beep //蜂鸣器控制信号
- 7 );
- 8
- 9 //wire define
- 10 wire key_value;
- 11 wire key_flag;
- 12
- 13 //*****************************************************
- 14 //** main code
- 15 //*****************************************************
- 16
- 17 //例化按键消抖模块
- 18 key_debounce u_key_debounce(
- 19 .sys_clk (sys_clk),
- 20 .sys_rst_n (sys_rst_n),
- 21
- 22 .key (key),
- 23 .key_flag (key_flag),
- 24 .key_value (key_value)
- 25 );
- 26
- 27 //例化蜂鸣器控制模块
- 28 beep_control u_beep_control(
- 29 .sys_clk (sys_clk),
- 30 .sys_rst_n (sys_rst_n),
- 31
- 32 .key_flag (key_flag),
- 33 .key_value (key_value),
- 34 .beep (beep)
- 35 );
- 36
- 37 endmodule
复制代码在顶层模块中例化了按键消抖模块和按键控制蜂鸣器模块。
按键消抖模块代码如下: - 1 module key_debounce(
- 2 input sys_clk, //外部50M时钟
- 3 input sys_rst_n, //外部复位信号,低有效
- 4
- 5 input key, //外部按键输入
- 6 output reg key_flag, //按键数据有效信号
- 7 output reg key_value //按键消抖后的数据
- 8 );
- 9
- 10 //reg define
- 11 reg [31:0 delay_cnt;
- 12 reg key_reg0;
- 13 reg key_reg1;
- 14
- 15 //*****************************************************
- 16 //** main code
- 17 //*****************************************************
- 18 always @(posedge sys_clk or negedge sys_rst_n) begin
- 19 if (!sys_rst_n) begin
- 20 key_reg0 <=1'b1;
- 21 key_reg1 <=1'b1;
- 22 delay_cnt <= 32'd0;
- 23 end
- 24 else begin
- 25 key_reg0 <= key; //将按键值延迟一拍
- 26 key_reg1 <= key_reg0; //将按键值延迟两拍
- 27 if(key_reg1 != key_reg0) //一旦检测到按键状态发生变化
- 28 //(有按键被按下或释放)
- 29 delay_cnt <= 32'd1000000; //给延时计数器重新装载初始值
- 30 //(计数时间为20ms)
- 31 else if(key_reg1 ==key_reg0) begin //在按键状态稳定时,计数器递
- 32 //减,开始20ms倒计时
- 33 if(delay_cnt > 32'd0)
- 34 delay_cnt <= delay_cnt - 1'b1;
- 35 else
- 36 delay_cnt <= delay_cnt;
- 37 end
- 38 end
- 39 end
- 40
- 41 always @(posedge sys_clk or negedge sys_rst_n) begin
- 42 if (!sys_rst_n) begin
- 43 key_flag <= 1'b0;
- 44 key_value <= 1'b1;
- 45 end
- 46 else begin
- 47 if(delay_cnt == 32'd1) begin //当计数器递减到1时,说明按键稳定
- 48 //状态维持了20ms
- 49 key_flag <= 1'b1; //此时消抖过程结束,给出一个时钟
- 50 //周期的标志信号
- 51 key_value <= key_reg1; //并寄存此时按键的值
- 52 end
- 53 else begin
- 54 key_flag <= 1'b0;
- 55 key_value <= key_value;
- 56 end
- 57 end
- 58 end
- 59
- 60 endmodule
复制代码代码中的第27行,每检测到按键被按下或松开,就让计数器从100_0000开始递减,时长20ms。在这20ms期间,每当有抖动产生,计数器就被重置回100_0000,即重新开始计时20ms。代码中的第47行,只有在计数器递减到1时,即此时计数器计时完了20ms,才会寄存按键的值。这样,每当按键被按下或松开,20ms内的抖动就被消除了。
蜂鸣器控制模块的代码如下: - 1 module beep_control(
- 2 //input
- 3 input sys_clk, //系统时钟
- 4 input sys_rst_n, //复位信号,低电平有效
- 5
- 6 input key_flag, //按键有效信号
- 7 input key_value, //消抖后的按键信号
- 8 output reg beep //蜂鸣器控制信号
- 9 );
- 10
- 11 //*****************************************************
- 12 //** main code
- 13 //*****************************************************
- 14 always @ (posedge sys_clk or negedge sys_rst_n) begin
- 15 if(!sys_rst_n)
- 16 beep <= 1'b1;
- 17 else if(key_flag && (~key_value)) //判断按键是否有效按下
- 18 beep <= ~beep;
- 19 end
- 20
- 21 endmodule
复制代码beep初始状态为高电平,蜂鸣器鸣叫。当key_flag拉高表明消抖之后的按键数据有效,此时若检测到按键值为0(即按键被按下),就将beep状态取反,以改变蜂鸣器的鸣叫状态。
为了验证我们的程序,我们在Modelsim内对代码进行仿真。为了减少仿真过程所需要的时间,这里我们将消抖时间间隔修改为80ns。如下图所示: Testbench模块代码如下: - 1 `timescale 1ns/1ns // 定义仿真时间单位1ns和仿真时间精度为1ns
- 2
- 3 module key_beep_tb(); // 测试模块
- 4
- 5 //输入
- 6 reg sys_clk; //时钟信号
- 7 reg sys_rst_n; // 复位信号
- 8 reg key;
- 9
- 10
- 11 //输出
- 12 wire beep;
- 13 //*****************************************************
- 14 //** maincode
- 15 //*****************************************************
- 16
- 17 //给输入信号初始值
- 18 initial begin
- 19 key =1'b1;
- 20 sys_clk =1'b0;
- 21 sys_rst_n =1'b0; //复位
- 22 #20 sys_rst_n = 1'b1; //在第21ns的时候复位信号信号拉高
- 23 #30 key =1'b0; //在第50ns的时候按下按键
- 24 #20 key =1'b1; //模拟抖动
- 25 #20 key =1'b0; //模拟抖动
- 26 #20 key =1'b1; //模拟抖动
- 27 #20 key =1'b0; //模拟抖动
- 28 #170 key = 1'b1; //在第300ns的时候松开按键
- 29 #20 key =1'b0; //模拟抖动
- 30 #20 key =1'b1; //模拟抖动
- 31 #20 key =1'b0; //模拟抖动
- 32 #20 key =1'b1; //模拟抖动
- 33 #170 key = 1'b0; //在第550ns的时候再次按下按键
- 34 #20 key =1'b1; //模拟抖动
- 35 #20 key =1'b0; //模拟抖动
- 36 #20 key =1'b1; //模拟抖动
- 37 #20 key =1'b0; //模拟抖动
- 38 #170 key = 1'b1; //在第800ns的时候松开按键
- 39 #20 key =1'b0; //模拟抖动
- 40 #20 key =1'b1; //模拟抖动
- 41 #20 key =1'b0; //模拟抖动
- 42 #20 key =1'b1; //模拟抖动
- 43 end
- 44
- 45 //50Mhz的时钟,周期则为1/50Mhz=20ns,所以每10ns,电平取反一次
- 46 always #10sys_clk = ~sys_clk;
- 47
- 48 //例化led模块
- 49 top_key_beep u0_top_key_beep (
- 50 .sys_clk (sys_clk ),
- 51 .sys_rst_n (sys_rst_n),
- 52 .key (key ),
- 53 .beep (beep )
- 54 );
- 55
- 56 endmodule
复制代码仿真得到的波形图如下图所示: 从图 10.4.5中可以看到,第一次将key拉低,并模拟按键抖动,可见在按键抖动停止后的第四个时钟周期时,key_flag出现一个时钟周期的高电平,同时beep被拉低(蜂鸣器停止鸣叫);在松开按键时,也模拟按键抖动,同理可知在抖动结束后的第四个时钟周期,key_flag信号被拉高。读者可以仔细观察仿真波形结合代码深入理解,仔细体会key_flag信号和key信号之间的关系。
1.5 下载验证 连接开发板的电源和下载器,并打开电源开关。在工程编译之后,将生成的sbit文件下载到开发板中。下载完成后,蜂鸣器处于鸣叫状态。然后按下按键KEY0,蜂鸣器停止鸣叫。再次按下按键,蜂鸣器再次开始鸣叫。如下图所示: 图 10.5.1 ATK-DFPGL22G开发板上的蜂鸣器 |