驱动层开发——蜂鸣器驱动

开发流程:

搞清原理,知道用哪个引脚

到cubemx配置引脚并更新keil

在对应层(common interface等文件夹)中创建.c .h文件

在keil中添加层文件夹路径并将文件纳入编译范围内

在vscode中开发

先在原理图中搜索数据手册弄清驱动原理

从高层项底层写

I 搞清原理,知道用哪个引脚

一眼盯真,用PB4。

原理就是通过PB4发出不同频率方波控制蜂鸣器发声。

对于有源无源的蜂鸣器的驱动方式是不同的,因此我们需要查看数据手册:

1. 关键参数:Resonant Frequency = 2700Hz
  • 这是 无源蜂鸣器的核心特征
  • "谐振频率"(Resonant Frequency)意味着它需要外部提供 2700Hz 的方波信号 才能有效发声。
  • 如果是有源蜂鸣器,这个参数通常不会出现,或者会写成"内置振荡电路,输出频率 X Hz"。

💡 类比:就像一个喇叭,本身不发声,必须输入音频信号才能响。


2. Rated Current 条件中明确提到"Square Wave"
复制代码
1Max.95mA, at 2700Hz 50% duty Square Wave 5Vo-p
  • 它的额定电流是在 2700Hz 方波驱动 下测得的。
  • 这说明它不是简单地接直流电就响,而是需要 特定频率的交流信号 驱动。

⚠️ 如果是有源蜂鸣器,这里应该写成"DC 5V"或类似描述。

因此必须使用无源蜂鸣器的驱动方式,使用定时器(GPIO 无法直接生成精确频率)生成PWM方波进行驱动。

我们使用内部时钟,这意味,定时器共享芯片内部的时钟,也就是主频72MHz。

在通道一选择PWM波

由于do大概是261Hz,72MHz太高了,我们需要做分频处理。

这里第一个是分频器,范围是0-65535,代表除数,除以0是不可能的,因此代表除以1,因此除以x则需要填除以x-1,我们没法填72000-1,只能填7200-1

现在的分频后频率是10kHz,还是很大,因此我们考虑用重装载继续分频。

重装载就是指走n个步长触发一次事件,比如步长20,则走20个分频周期触发一次,这就相当于给分频后的频率再做了一次分频。

我们选择20-1为重装载值(其范围也从0开始,因此要减一),此时的频率是10k/20=500hHz。

已经是人耳的听觉范围了。

一、PWM 工作原理回顾

在 STM32 定时器中,PWM 输出由两个关键寄存器决定:

寄存器 含义
ARR(Auto-Reload Register) 决定 PWM 周期(最大计数值)
CCR(Capture/Compare Register) 决定 PWM 高电平持续时间(即"脉冲宽度")

在 CubeMX 中,Pulse 就是 CCR 的值(即 Compare Value)

为了使得输出更稳定,最好的实践是将CCR占到50%,也就是高电平占用一半的时间,因此Pulse填重装载系数20的一半:

然后generatecode。

II 在对应层(common interface等文件夹)中创建.c .h文件

这里改为Int_buzzer.h .c即可

这一步之前直接把整个interface层加入了,因此不需要修改

III 在keil中添加层文件夹路径并将文件纳入编译范围内

然后是添加到编译文件中:

IV 在vscode中开发

总体思路:

先在原理图中搜索数据手册弄清驱动原理 从高层项底层写

开发流程:

在.c文件include自己的头文件,然后在.h文件先#ifndef def endif

然后在.h定义暴露给外部的函数,并/** */写函数声明和参数声明

复制下来到.c文件中写实现函数,如果需要嵌套函数,则需要static声明

在main.c中的USERINCLUDE加入头文件,并在USERCODE2加入测试代码

这里找到Int_buzzer说明成功添加了

一般来说,生成code的时候会自动加入定时器的初始化函数,因此可以直接写代码。

那么,根据流程,Int_buzzer.h

cpp 复制代码
#ifndef __INT_BUZZER_H__
#define __INT_BUZZER_H__
#include "tim.h"

/**
 * @brief 打开蜂鸣器
 * 
 */
void Int_buzzer_on(void);

/**
 * @brief 关闭蜂鸣器
 * 
 */
void Int_buzzer_off(void);

#endif /* __INT_BUZZER_H__ */

Int_buzzer.c

由于我们已经在cubemx写好了TIM的配置,因此之间开启就有声音,也就是说,控制TIM即可。

cpp 复制代码
#include "Int_buzzer.h"


/**
 * @brief 打开蜂鸣器
 * 
 */
 void Int_buzzer_on(void){
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
 }

 /**
  * @brief 关闭蜂鸣器
  * 
  */
 void Int_buzzer_off(void){
    HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_1);
 }

在main.c

正常编译

蜂鸣器编曲

这是1-6的发声频率,可以根据这个让蜂鸣器唱歌。

那么,依然是先在buzzer.h添加暴露函数,并宏定义各个音高的频率

cpp 复制代码
#define BUZZER_FREQ_1 261
#define BUZZER_FREQ_2 294
#define BUZZER_FREQ_3 329
#define BUZZER_FREQ_4 349
#define BUZZER_FREQ_5 392
#define BUZZER_FREQ_6 440

/**
 * @brief 设置当前蜂鸣器的频率和占空比
 * 
 * @param freq 频率
 */
void Int_buzzer_set(uint16_t freq);

/**
 * @brief 播放音乐
 * 
 */
void Int_buzzer_music(void);

那么如何设置音高呢?

我们考虑重装载,第二次分频的值,来调整。

经过第一次分频,频率是10kHz,假设频率是freq,那么分频数就是10k/freq。

那么重装载值修改了,对应的pulse占空比也要修改(对应重装载值一半),否则就会发不出声音。

最后还需要重置重装载计数值。

这个函数就是:

cpp 复制代码
/**
 * @brief 设置当前蜂鸣器的频率和占空比
 * 
 * @param freq 频率
 */
void Int_buzzer_set(uint16_t freq){
    __HAL_TIM_SetAutoreload(&htim3, 10000/freq);
    __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, 5000/freq);
    __HAL_TIM_SetCounter(&htim3, 0);
}

剩余的就是编写音乐了:

cpp 复制代码
#include "Int_buzzer.h"


/**
 * @brief 打开蜂鸣器
 * 
 */
 void Int_buzzer_on(void){
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
 }

 /**
  * @brief 关闭蜂鸣器
  * 
  */
 void Int_buzzer_off(void){
    HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_1);
 }

 /**
 * @brief 设置当前蜂鸣器的频率和占空比
 * 
 * @param freq 频率
 */
void Int_buzzer_set(uint16_t freq){
    __HAL_TIM_SetAutoreload(&htim3, 10000/freq);
    __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, 5000/freq);
    __HAL_TIM_SetCounter(&htim3, 0);
}

static void Int_buzzer_byte(uint16_t freq){
    Int_buzzer_set(freq);
    Int_buzzer_on();
    HAL_Delay(800);
    Int_buzzer_off();
    HAL_Delay(200);
}

static void Int_buzzer_byte_long(uint16_t freq){
    Int_buzzer_set(freq);
    Int_buzzer_on();
    HAL_Delay(1600);
    Int_buzzer_off();
    HAL_Delay(400);
}

/**
 * @brief 播放音乐
 * 
 */
void Int_buzzer_music(void){
    //每个音节响0.8 间隔0.2 - 响1.6 乐句间间隔0.4
    //1155665 4433221
    Int_buzzer_byte(BUZZER_FREQ_1);
    Int_buzzer_byte(BUZZER_FREQ_1);
    Int_buzzer_byte(BUZZER_FREQ_5);
    Int_buzzer_byte(BUZZER_FREQ_5);
    Int_buzzer_byte(BUZZER_FREQ_6);
    Int_buzzer_byte(BUZZER_FREQ_6);
    Int_buzzer_byte_long(BUZZER_FREQ_5);
    Int_buzzer_byte(BUZZER_FREQ_4);
    Int_buzzer_byte(BUZZER_FREQ_4);
    Int_buzzer_byte(BUZZER_FREQ_3);
    Int_buzzer_byte(BUZZER_FREQ_3);
    Int_buzzer_byte(BUZZER_FREQ_2);
    Int_buzzer_byte(BUZZER_FREQ_2);
    Int_buzzer_byte_long(BUZZER_FREQ_1);
}

成功编译:

补充资料

🔔 一、两种蜂鸣器的本质区别

类型 工作原理 控制方式 是否需要定时器
有源蜂鸣器(Active Buzzer) 内部自带振荡电路 只需给 直流电压(高/低电平) ❌ 不需要 TIM,用 GPIO 即可
无源蜂鸣器(Passive Buzzer) 本质是电磁式扬声器,类似喇叭 需要外部提供 一定频率的方波信号 才能发声 ✅ 必须用 TIM(或 PWM)生成频率

✅ 所以:"蜂鸣器往往使用 TIM 定时器"这个说法,其实特指「无源蜂鸣器」


🎯 二、为什么无源蜂鸣器必须用 TIM 定时器?

1. 发声原理决定

  • 无源蜂鸣器没有内部振荡源,必须靠外部交变信号驱动膜片振动
  • 要让它发出 特定音调(如 1kHz 的"嘀"声) ,就必须输入 1kHz 的方波
  • 方波的 频率 = 音调高低占空比 ≈ 响度

2. GPIO 无法直接生成精确频率

  • 如果只用 GPIO + 软件延时(如 for 循环或 HAL_Delay)翻转电平:

    复制代码
    1while(1) {
    2    HAL_GPIO_TogglePin(BEEP_GPIO, BEEP_PIN);
    3    HAL_Delay(0.5); // 想生成 1kHz,但 HAL_Delay 最小单位是 1ms!
    4}
    • 精度差HAL_Delay 最小是 1ms,无法生成 1kHz(周期 1ms,半周期 0.5ms);
    • 占用 CPU:主程序被阻塞,无法做其他事;
    • 频率不稳定:受中断、编译优化等影响,音调不准甚至失真。

3. TIM 定时器的优势

  • 硬件自动翻转 :配置好后,TIM 自动在精确时间点翻转 GPIO(通过输出比较或 PWM 模式),无需 CPU 干预
  • 高精度:基于系统时钟(如 72MHz),可生成微秒级甚至纳秒级精度的方波;
  • 支持任意频率:通过调整 PSC(预分频)和 ARR(自动重载值),轻松生成 100Hz ~ 20kHz 的音频信号;
  • 可播放音乐:动态修改 TIM 参数,就能切换不同音符(如《小星星》旋律)。

💡 示例:用 TIM3 输出 1kHz 方波驱动无源蜂鸣器

复制代码
1// PSC=71, ARR=999 → 计数频率 = 72MHz/(71+1)=1MHz → 周期=1000 → 频率=1kHz
2htim3.Instance = TIM3;
3htim3.Init.Prescaler = 71;
4htim3.Init.Period = 999;
5HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); // 硬件自动输出 PWM

⚙️ 三、有源蜂鸣器:为什么不用 TIM?

  • 有源蜂鸣器内部已集成振荡电路(通常固定频率,如 2.7kHz 或 4kHz);

  • 只需通电就响,断电就停

  • 控制逻辑极简单:

    复制代码
    1HAL_GPIO_WritePin(BEEP_GPIO, BEEP_PIN, GPIO_PIN_RESET); // 响(低电平有效)
    2HAL_Delay(100);
    3HAL_GPIO_WritePin(BEEP_GPIO, BEEP_PIN, GPIO_PIN_SET);   // 停
  • 用 GPIO 足够,用 TIM 反而浪费资源

相关推荐
小刘爱玩单片机1 小时前
【stm32简单外设篇】- 测速传感器模块(光电)
c语言·stm32·单片机·嵌入式硬件
hateregiste2 小时前
嵌入式软件开发中常见知识点问答集锦!
c语言·单片机·嵌入式软件
电化学仪器白超2 小时前
EC20CEHDLG-128-SNNS调试记录
python·单片机·嵌入式硬件·自动化
极客小张2 小时前
基于STM32的智能水质监测与远程预警系统设计与实现
c语言·python·stm32·单片机·嵌入式硬件·物联网
2501_918126912 小时前
stm32最级别的烧录解锁是什么?
stm32·单片机·嵌入式硬件·学习·个人开发
qq_241585612 小时前
jump_to_app
单片机·嵌入式硬件
你好,奋斗者!2 小时前
74HC595芯片原理及代码示例
嵌入式硬件·软件·电路设计
小刘爱玩单片机2 小时前
【stm32简单外设篇】- KY-025 干簧管(磁控)模块
c语言·stm32·单片机·嵌入式硬件
forAllforMe2 小时前
STM32的分散加载问题--使用场合
stm32·单片机·嵌入式硬件