news 2026/8/26 16:24:35

FPGA入门实战:按键控制蜂鸣器播放音乐,LED还能跟着节奏闪烁

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FPGA入门实战:按键控制蜂鸣器播放音乐,LED还能跟着节奏闪烁

FPGA入门实战:按键控制蜂鸣器播放音乐,LED还能跟着节奏闪烁

  • 一、缘起:从“让蜂鸣器响”到“让蜂鸣器唱歌”
  • 二、基础知识:音符频率与PWM波
    • 2.1 音符与频率的对应关系
    • 2.2 用PLL产生精确时钟
  • 三、系统架构总览
  • 四、模块代码实现(逐模块讲解)
    • 4.1 模块一:PLL时钟管理
    • 4.2 模块二:按键消抖与长短按检测
    • 4.3 模块三:旋律存储器(ROM)
    • 4.4 模块四:音符发生器(PWM方波)
    • 4.5 模块五:呼吸灯PWM
    • 4.6 模块六:顶层模块
  • 五、总结与扩展

本文适合:已经学完LED闪烁、按键消抖、蜂鸣器驱动、呼吸灯和PLL时钟IP核的FPGA初学者,想做一个综合性的小项目来巩固知识。

一、缘起:从“让蜂鸣器响”到“让蜂鸣器唱歌”

学完蜂鸣器,我知道了蜂鸣器可以发出不同频率的声音,既然蜂鸣器能发出不同频率的声音,那能不能让它播放一段旋律呢?
说干就干!我决定做一个“FPGA电子门铃”,功能规划如下:

操作反应
上电呼吸灯柔和闪烁,系统待机
短按按键1蜂鸣器播放“叮咚~”门铃声,LED闪两下
短按按键2蜂鸣器播放“滴-滴-滴-”警报声,LED快速闪烁
长按按键1(3秒)蜂鸣器播放《小星星》旋律

二、基础知识:音符频率与PWM波

2.1 音符与频率的对应关系

蜂鸣器发出不同音调的原理很简单:不同频率的方波驱动蜂鸣器,就会发出不同音高的声音。

简谱中的 1(Do)、2(Re)、3(Mi)… 分别对应一个特定的频率:

简谱唱名频率(Hz)周期(μs)
1Do5231912
2Re5871704
3Mi6591517
4Fa6981433
5Sol7841276
6La8801136
7Si9881012

📌核心公式:周期 = 1 / 频率。比如 523Hz 的周期 ≈ 1912μs。要让蜂鸣器输出 523Hz 的声音,只需要让输出电平每 956μs 翻转一次(半个周期翻转一次)。

2.2 用PLL产生精确时钟

我的开发板输入时钟是 50MHz,直接用它来产生音符频率时,分频系数计算如下:

分频系数 = 50,000,000 / (频率 × 2) = 25,000,000 / 频率 例如 Do(523Hz):25,000,000 / 523 ≈ 47,801

为了让分频更精确,我使用 PLL(MMCM)将 50MHz 倍频到 100MHz,这样分频步长更小,音符频率更准:

分频系数 = 50,000,000 / 频率 (100MHz下)

三、系统架构总览

在动手写代码之前,先画出整个系统的架构图:

四、模块代码实现(逐模块讲解)

4.1 模块一:PLL时钟管理

使用Vivado的Clock IP核生成100MHz时钟。操作步骤:

  1. 点击 IP Catalog → 搜索 “Clocking Wizard”
  2. 输入时钟 50MHz,输出时钟 100MHz
  3. 点击 Generate

生成的PLL模块例化代码:

clk_wiz_0 u_pll ( .clk_out1(clk_100m), // 100MHz 输出 .clk_in1(clk_50m), // 50MHz 输入 .reset(~rst_n), .locked(pll_locked) );

4.2 模块二:按键消抖与长短按检测

功能说明:

  • 检测按键按下(下降沿)
  • 消除按键抖动
  • 区分短按(<1秒)和长按(>3秒)
module key_detect ( input clk, // 100MHz input rst_n, input key, output reg key_short, // 短按脉冲 output reg key_long // 长按脉冲 ); // 按键消抖:采样20ms reg [20:0] cnt_20ms; reg key_sync1, key_sync2; wire key_falling; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin key_sync1 <= 1; key_sync2 <= 1; end else begin key_sync1 <= key; key_sync2 <= key_sync1; end end assign key_falling = key_sync2 & ~key_sync1; // 下降沿检测 // 长短按判断 reg [31:0] press_cnt; reg key_down; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin press_cnt <= 0; key_down <= 0; key_short <= 0; key_long <= 0; end else begin key_short <= 0; key_long <= 0; if (key_falling) begin key_down <= 1; press_cnt <= 0; end else if (key_down && key_sync2) begin // 按键持续按下,开始计数 press_cnt <= press_cnt + 1; // 3秒 = 300,000,000 个周期(100MHz) if (press_cnt == 300_000_000) begin key_long <= 1; key_down <= 0; // 长按触发后不再响应短按 end end else if (key_down && ~key_sync2) begin // 按键释放 if (press_cnt < 100_000_000) begin // <1秒为短按 key_short <= 1; end key_down <= 0; end end end endmodule

4.3 模块三:旋律存储器(ROM)

把乐谱存储为ROM数组,用索引读取:

module melody_rom ( input clk, input [4:0] addr, // 地址 0~31 output reg [3:0] note,// 音符 1~7,0=休止符/结束 output reg [3:0] dur // 节拍时长 ); // "叮咚" 门铃声 always @(posedge clk) begin case (addr) 0: begin note = 3; dur = 4; end // Mi 1: begin note = 1; dur = 6; end // Do(长音) default: begin note = 0; dur = 0; end endcase end endmodule

4.4 模块四:音符发生器(PWM方波)

根据音符频率产生对应频率的方波信号:

module note_generator ( input clk, // 100MHz input rst_n, input [3:0] note, // 当前音符 1~7 input play_en, // 播放使能 output reg pwm_out, // 蜂鸣器驱动 output reg note_done // 当前音符播放完成 ); // 音符频率查找表(100MHz时钟下的分频系数) reg [31:0] div_max; always @(*) begin case (note) 4'd1: div_max = 32'd95500; // Do 523Hz 4'd2: div_max = 32'd85100; // Re 587Hz 4'd3: div_max = 32'd75800; // Mi 659Hz 4'd4: div_max = 32'd71600; // Fa 698Hz 4'd5: div_max = 32'd63800; // Sol 784Hz 4'd6: div_max = 32'd56800; // La 880Hz 4'd7: div_max = 32'd50600; // Si 988Hz default: div_max = 0; // 0=静音 endcase end reg [31:0] cnt; reg [31:0] duration_cnt; reg playing; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin cnt <= 0; duration_cnt <= 0; pwm_out <= 0; note_done <= 0; playing <= 0; end else begin note_done <= 0; if (play_en && !playing) begin playing <= 1; duration_cnt <= 0; end if (playing) begin if (div_max == 0) begin // 休止符:输出低电平 pwm_out <= 0; duration_cnt <= duration_cnt + 1; // 休止符持续0.2秒(100MHz计数) if (duration_cnt >= 20_000_000) begin note_done <= 1; playing <= 0; end end else begin // 产生PWM方波(50%占空比) cnt <= cnt + 1; if (cnt >= div_max) begin cnt <= 0; pwm_out <= ~pwm_out; end // 每个音符持续0.4秒(100MHz计数) duration_cnt <= duration_cnt + 1; if (duration_cnt >= 40_000_000) begin note_done <= 1; playing <= 0; duration_cnt <= 0; end end end else begin pwm_out <= 0; cnt <= 0; end end end endmodule

4.5 模块五:呼吸灯PWM

用占空比周期性变化的PWM驱动LED,实现柔和呼吸效果:

module breath_led ( input clk, input rst_n, input beat_signal, // 播放音乐时随节拍闪烁 input play_mode, // 1=播放模式,0=待机 output reg led ); reg [15:0] pwm_cnt; reg [15:0] duty_cycle; reg [24:0] slow_cnt; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin pwm_cnt <= 0; duty_cycle <= 0; slow_cnt <= 0; led <= 0; end else begin if (play_mode) begin // 播放模式:随节拍闪烁 led <= beat_signal; end else begin // 待机模式:呼吸灯(2秒一个周期) slow_cnt <= slow_cnt + 1; // 占空比从0→65535→0变化(PWM周期约0.4ms) if (slow_cnt < 25_000_000) begin duty_cycle <= slow_cnt[15:0]; end else if (slow_cnt < 50_000_000) begin duty_cycle <= 65535 - (slow_cnt - 25_000_000); end else begin slow_cnt <= 0; end pwm_cnt <= pwm_cnt + 1; led <= (pwm_cnt < duty_cycle) ? 1 : 0; end end end endmodule

4.6 模块六:顶层模块

将以上模块连接起来:

module top ( input clk_50m, // 50MHz输入 input rst_n, // 复位 input key1, // 按键1(门铃) input key2, // 按键2(警报) output buzzer, // 蜂鸣器 output led // 呼吸灯/状态指示 ); // 信号定义 wire clk_100m; wire pll_locked; wire key1_short, key1_long; wire key2_short; wire [3:0] note; wire [3:0] dur; wire note_done; wire pwm_out; wire play_en; wire beat_signal; // 1. PLL实例化 clk_wiz_0 u_pll ( .clk_out1(clk_100m), .clk_in1(clk_50m), .reset(~rst_n), .locked(pll_locked) ); // 2. 按键检测 key_detect u_key1 ( .clk(clk_100m), .rst_n(rst_n & pll_locked), .key(key1), .key_short(key1_short), .key_long(key1_long) ); key_detect u_key2 ( .clk(clk_100m), .rst_n(rst_n & pll_locked), .key(key2), .key_short(key2_short), .key_long() ); // 3. 旋律选择与地址控制 reg [4:0] melody_addr; reg playing_mode; wire [3:0] note_out; wire [3:0] dur_out; // 旋律ROM:根据按键选择不同旋律 melody_rom u_melody ( .clk(clk_100m), .addr(melody_addr), .note(note_out), .dur(dur_out) ); // 4. 播放控制状态机 reg [4:0] addr_cnt; reg play_enable; reg play_busy; always @(posedge clk_100m or negedge rst_n) begin if (!rst_n) begin addr_cnt <= 0; play_enable <= 0; play_busy <= 0; playing_mode <= 0; end else begin // 按键触发播放 if (key1_short && !play_busy) begin addr_cnt <= 0; play_enable <= 1; play_busy <= 1; playing_mode <= 1; end else if (key2_short && !play_busy) begin addr_cnt <= 8; // 第二段旋律起始地址 play_enable <= 1; play_busy <= 1; playing_mode <= 1; end // 播放进行中 if (play_busy) begin if (note_done) begin if (note_out == 0) begin // 遇到0表示旋律结束 play_busy <= 0; play_enable <= 0; playing_mode <= 0; end else begin addr_cnt <= addr_cnt + 1; end end end else begin play_enable <= 0; end end end // 5. 音符发生器 note_generator u_note ( .clk(clk_100m), .rst_n(rst_n & pll_locked), .note(note_out), .play_en(play_enable & pll_locked), .pwm_out(pwm_out), .note_done(note_done) ); // 6. 呼吸灯 assign beat_signal = pwm_out & play_busy; // 播放时LED随声音节奏闪烁 breath_led u_breath ( .clk(clk_100m), .rst_n(rst_n & pll_locked), .beat_signal(beat_signal), .play_mode(play_busy), .led(led) ); // 输出蜂鸣器 assign buzzer = pwm_out & pll_locked; endmodule

五、总结与扩展

通过这个项目,我们综合运用了:

  • PLL时钟管理:倍频获得更精确的分频
  • 按键消抖与长短按检测:状态机设计
  • ROM存储:乐谱数据存储
  • PWM方波生成:音符频率控制
  • 呼吸灯:占空比调制

扩展思路

  1. 增加更多旋律,用更大的ROM存储
  2. 加入数码管显示当前播放的音符
  3. 用SD卡存储MIDI文件,播放任意音乐
  4. 增加音量控制(PWM占空比调制)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/26 16:15:34

C++数字炸弹小游戏(英文版)

C数字炸弹小游戏&#xff01;(英文不好的不要玩&#xff01;&#xff01;&#xff01;) 这是本蒟蒻的第一篇博客&#xff01; 如果有其他想法&#xff0c;作者的私信和该文的评论区永远向您敞开&#xff01; 在此鸣谢&#xff1a;XY_Lmf、XY_Yzy 附上友链&#xff08;中文版…

作者头像 李华
网站建设 2026/8/26 16:14:44

梯载文化,步见初心 —— 企业楼梯文化氛围布置的深层价值

写字楼、产业园、厂区里&#xff0c;楼梯往往是最容易被忽略的空间。多数企业只把楼梯当作通行通道&#xff0c;墙面空白单调、过道平淡乏味&#xff0c;白白浪费了每日员工必经的流动场景。 殊不知&#xff0c;楼梯是高频、轻量化、沉浸式的天然文化阵地。员工上下班、午休走动…

作者头像 李华
网站建设 2026/8/26 16:14:10

Umi-OCR 离线OCR实操指南:5个技巧搞定扫描件

Umi-OCR 离线OCR实操指南&#xff1a;5个技巧搞定扫描件 【免费下载链接】Umi-OCR OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片&#xff0c;PDF文档识别&#xff0c;排除水印/页眉页脚&#xff0c;扫描/生成二维码。内置多国语言库。 项…

作者头像 李华
网站建设 2026/8/26 16:08:40

洛雪音乐助手 免费音乐播放器 5 分钟上手指南

洛雪音乐助手 免费音乐播放器 5 分钟上手指南 【免费下载链接】lx-music-desktop 一个基于 Electron 的音乐软件 项目地址: https://gitcode.com/GitHub_Trending/lx/lx-music-desktop 想听的歌在 A 家 App&#xff0c;无损版本在 B 家&#xff0c;歌词又藏在 C 家&…

作者头像 李华