STM32 零基础可移植教程 03:蜂鸣器响一声,LED 跟着翻转一次

STM32 零基础可移植教程 03:蜂鸣器响一声,LED 跟着翻转一次

上一篇我们已经用 GPIO 点亮了 LED。LED 能闪,说明你已经把 CubeMX 引脚配置、Keil 编译下载、main.c 调用位置这条链路跑通了。

这一篇继续用 GPIO,不过换一个更容易踩坑的小外设:蜂鸣器。

蜂鸣器最容易让新手迷糊的地方,不是 HAL_GPIO_WritePin() 这个函数,而是硬件类型和有效电平:

  • 有源蜂鸣器:给一个固定电平就能响;

  • 无源蜂鸣器:需要 PWM 方波才能稳定发声;

  • 有些蜂鸣器高电平响;

  • 有些蜂鸣器低电平响;

  • 有些板子中间还加了三极管或 MOS 管,GPIO 控制的其实是"开关管"。

所以这一篇不急着做歌曲,也不急着做不同音调。我们先做一个非常明确的目标:

bash 复制代码
蜂鸣器短响一次,同时 LED 翻转一次,然后等待 2 秒,循环重复

这样做有两个好处:

  1. 蜂鸣器响一下,说明蜂鸣器 GPIO 控制通了;

  2. LED 翻转一下,说明主循环确实在跑,也顺手复习上一章的 app_led 封装。

本篇目标

最终现象:

bash 复制代码
蜂鸣器短响一声
LED 状态翻转一次
等待 2 秒
然后重复

本篇用到的外设:

bash 复制代码
GPIO Output

本篇跑通标准:

  • Keil 编译通过;

  • 程序能下载到开发板;

  • 蜂鸣器能按周期短响;

  • LED 能随着每次蜂鸣器动作翻转亮灭;

  • 能说清楚自己的蜂鸣器是高电平有效还是低电平有效;

  • 能说清楚自己的蜂鸣器是有源还是无源。

如果你的蜂鸣器是无源蜂鸣器,这一篇用 GPIO 固定电平可能只能听到很短的"咔哒"声,甚至可能不响。那不是代码一定错了,而是无源蜂鸣器本来就需要 PWM。PWM 发声我们后面单独讲。

准备工作

你需要准备:

|

项目

|

说明

|

| --- | --- |

|

STM32 开发板

|

任意 STM32 开发板都可以

|

|

下载器

|

ST-LINK/V2 或板载 ST-LINK

|

|

LED

|

沿用上一篇 LED 工程

|

|

蜂鸣器

|

推荐先用有源蜂鸣器模块

|

|

原理图

|

如果是板载蜂鸣器,一定要看原理图

|

|

杜邦线

|

外接蜂鸣器模块时使用

|

这里先把概念说清楚:

bash 复制代码
有源蜂鸣器:内部带振荡电路,给固定电平就能响
无源蜂鸣器:内部没有振荡电路,需要 MCU 输出 PWM 方波

如果你手里的模块有 VCCGNDSIG/I/O 三个引脚,而且卖家说明"高电平触发"或"低电平触发",大概率是有源蜂鸣器模块。

如果你手里只是一个两脚蜂鸣器本体,或者原理图里明确写的是无源蜂鸣器,那固定电平不一定能让它稳定发声。

硬件连接

如果你的开发板自带蜂鸣器,先看原理图,确认蜂鸣器控制脚接到哪个 MCU 引脚。

如果你外接一个常见有源蜂鸣器模块,一般这样接:

|

蜂鸣器模块

|

STM32 开发板

|

| --- | --- |

|

VCC

|

3.3V 或 5V,按模块说明来

|

|

GND

|

GND

|

|

SIG / I/O

|

一个普通 GPIO 引脚

|

注意三件事:

  1. GND 必须和 STM32 共地。

  2. 模块电压要看说明,有些 5V 蜂鸣器用 3.3V 也能响,有些声音会很小。

  3. 如果是直接驱动蜂鸣器本体,最好通过三极管或 MOS 管,不建议让 GPIO 直接带大电流负载。

常见驱动电路有两类:

bash 复制代码
GPIO 输出高电平 -> 蜂鸣器响

或者:

bash 复制代码
GPIO 输出低电平 -> 蜂鸣器响

所以这一篇和 LED 一样,也要先确认有效电平。

这张图展示了蜂鸣器驱动电路,核心功能是通过PC0引脚输出的PWM方波信号控制蜂鸣器发声。

电路中,PC0连接至NPN三极管Q3的基极,R17(1kΩ)用于基极限流,R16(10kΩ)作为下拉电阻,防止IO口悬空时三极管误触发。

BEEP1为蜂鸣器,其一端接3.3V电源,另一端接Q3的集电极,Q3的发射极接地,因此Q3起到电子开关的作用。

当PC0输出高电平时,Q3导通,蜂鸣器得电发声;当PC0输出低电平时,Q3截止,蜂鸣器断电停止。

由于蜂鸣器为感性负载,断电瞬间会产生反向感应电动势,二极管D6(1N4148)作为续流二极管,为该反向电压提供泄放回路,从而保护Q3不被击穿。

需要注意的是,该电路适配无源蜂鸣器,需要单片机提供方波信号才能发声,若想发出不同音调,可通过改变PWM频率实现。

如果你的原理图像下面这种结构:

bash 复制代码
MCU GPIO -> 基极电阻 -> NPN 三极管 -> 蜂鸣器 -> 3.3V

那 MCU 的 GPIO 通常不是直接驱动蜂鸣器,而是在控制三极管这个"电子开关"。

以常见 NPN 三极管低端开关为例:

  • GPIO 输出高电平:三极管导通,蜂鸣器得到电流;

  • GPIO 输出低电平:三极管截止,蜂鸣器断电;

  • 基极串联电阻用于限流;

  • 下拉电阻用于避免 MCU 引脚悬空时误响;

  • 如果蜂鸣器或负载有感性特征,旁边可能会加续流二极管保护三极管。

如果图纸或资料明确写的是"无源蜂鸣器",那就记住:这一篇只能先验证控制脚和开关电路,真正稳定发声、改变音调,需要等 PWM 篇。

CubeMX 配置步骤

1. 复制上一篇 LED 工程

建议从上一篇 02_led_gpio 复制一份,改名为:

bash 复制代码
03_buzzer_gpio

这样你可以保留上一篇 LED 工程,后面遇到问题时方便对比。

如果你重新新建工程,也可以按第一篇的流程走:

  1. 选择芯片型号;

  2. SYS -> Debug 设置为 Serial Wire

  3. 时钟先保持默认 HSI;

  4. Project Manager 选择 MDK-ARM

  5. 生成 Keil 工程。

2. 保留 LED 输出配置

这一篇会继续使用 LED,所以第 02 篇里的 LED 配置要保留:

|

配置项

|

推荐值

|

| --- | --- |

|

GPIO mode

|

GPIO_Output

|

|

User Label

|

LED

|

|

初始电平

|

按 LED 有效电平设置为默认灭

|

如果你的 CubeMX 里 LED 标签叫 LED_Red,那代码里就要适配 LED_Red_PinLED_Red_GPIO_Port;如果想直接复用教程里的 app_led.c,建议把 User Label 统一改成 LED

你现在如果已经用 LED_Red 跑通了,也可以保留这个名字,只要你的 app_led.c 里已经做了对应适配即可。

3. 找到蜂鸣器对应的 GPIO 引脚

假设你的蜂鸣器控制脚接在 PB8

在 CubeMX 的 Pinout 页面找到 PB8,点击后选择:

bash 复制代码
GPIO_Output

如果你的蜂鸣器接在 PA8PB5PC0,就配置对应引脚。

原则还是那句话:原理图接到哪个 MCU 引脚,CubeMX 就配置哪个引脚。

4. 给蜂鸣器引脚起名字

进入:

bash 复制代码
System Core -> GPIO

找到刚才配置的蜂鸣器引脚,把 User Label 改成:

bash 复制代码
BUZZER

这样 CubeMX 生成代码后,会在 main.h 里生成类似:

bash 复制代码
#define BUZZER_Pin GPIO_PIN_8
#define BUZZER_GPIO_Port GPIOB

后面我们的 app_buzzer.c 就直接用这两个宏。

为什么要这样做?

因为换板子时,只要 CubeMX 里仍然把蜂鸣器标签叫 BUZZER,应用层代码就不用到处改。

5. 设置蜂鸣器 GPIO 参数

蜂鸣器控制脚作为普通输出,可以这样配置:

|

配置项

|

推荐值

|

说明

|

| --- | --- | --- |

|

GPIO output level

|

按有效电平选择默认"关闭"状态

|

上电时不要让蜂鸣器乱响

|

|

GPIO mode

|

Output Push Pull

|

普通推挽输出

|

|

GPIO Pull-up/Pull-down

|

No pull-up and no pull-down

|

输出模式一般不需要内部上下拉

|

|

Maximum output speed

|

Low

|

本篇只是开关控制,不需要高速

|

|

User Label

|

BUZZER

|

生成 BUZZER_PinBUZZER_GPIO_Port

|

如果你的蜂鸣器是高电平响,初始电平建议设为 Low。

如果你的蜂鸣器是低电平响,初始电平建议设为 High。

这样上电后蜂鸣器默认不响,只有代码调用 App_Buzzer_On() 时才响。

6. 生成 Keil 工程

配置完成后点击:

bash 复制代码
GENERATE CODE

然后打开 Keil 工程,先编译一次,确认 CubeMX 生成的工程没有问题。

Keil 工程生成和编译

打开 Keil 后,先编译:

bash 复制代码
Build / F7

确认输出里没有错误:

bash 复制代码
0 Error(s)

如果这一步报错,先别怀疑蜂鸣器代码,因为我们还没写。优先检查工程路径、芯片 Pack、CubeMX 是否生成成功。

完整代码

这一篇会用到两个应用模块:

bash 复制代码
Core/Inc/app_led.h
Core/Src/app_led.c
Core/Inc/app_buzzer.h
Core/Src/app_buzzer.c

其中 app_led.h/.c 沿用上一篇,app_buzzer.h/.c 是本篇新增。

main.c 只负责初始化和调用,不把蜂鸣器控制逻辑直接写散。

1. 新建 Core/Inc/app_buzzer.h

Core/Inc 目录下新建:

bash 复制代码
app_buzzer.h

写入下面代码:

bash 复制代码
#ifndef APP_BUZZER_H
#define APP_BUZZER_H

#include "main.h"
#include <stdint.h>

void App_Buzzer_Init(void);
void App_Buzzer_On(void);
void App_Buzzer_Off(void);
void App_Buzzer_Beep(uint32_t ms);

#endif

2. 新建 Core/Src/app_buzzer.c

Core/Src 目录下新建:

bash 复制代码
app_buzzer.c

写入下面代码:

bash 复制代码
#include "app_buzzer.h"

/*
 * BUZZER_GPIO_Port and BUZZER_Pin are generated by CubeMX in main.h.
 * Set the buzzer pin User Label to BUZZER in CubeMX.
 */
#ifndef BUZZER_GPIO_Port
#error "BUZZER_GPIO_Port is not defined. Set the buzzer pin User Label to BUZZER in CubeMX."
#endif

#ifndef BUZZER_Pin
#error "BUZZER_Pin is not defined. Set the buzzer pin User Label to BUZZER in CubeMX."
#endif

/*
 * Many external active buzzer modules are active-high.
 * If your board uses active-low buzzer drive, swap these two macros.
 */
#ifndef APP_BUZZER_ON_LEVEL
#define APP_BUZZER_ON_LEVEL   GPIO_PIN_SET
#endif

#ifndef APP_BUZZER_OFF_LEVEL
#define APP_BUZZER_OFF_LEVEL  GPIO_PIN_RESET
#endif

void App_Buzzer_Init(void)
{
    App_Buzzer_Off();
}

void App_Buzzer_On(void)
{
    HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, APP_BUZZER_ON_LEVEL);
}

void App_Buzzer_Off(void)
{
    HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, APP_BUZZER_OFF_LEVEL);
}

void App_Buzzer_Beep(uint32_t ms)
{
    App_Buzzer_On();
    HAL_Delay(ms);
    App_Buzzer_Off();
}

这段代码里最关键的是这两个宏:

bash 复制代码
#define APP_BUZZER_ON_LEVEL   GPIO_PIN_SET
#define APP_BUZZER_OFF_LEVEL  GPIO_PIN_RESET

它默认认为蜂鸣器是高电平响。

如果你的蜂鸣器是低电平响,改成:

bash 复制代码
#define APP_BUZZER_ON_LEVEL   GPIO_PIN_RESET
#define APP_BUZZER_OFF_LEVEL  GPIO_PIN_SET

不要觉得这一步多余。不同开发板的蜂鸣器驱动电路不一样,有些是高电平打开三极管,有些是低电平打开。把有效电平集中放在宏里,后面移植会省很多事。

3. 把 app_buzzer.c 加入 Keil 工程

如果你是手动新建 .c 文件,Keil 不一定会自动编译它。

在 Keil 工程树里右键:

bash 复制代码
Application/User/Core

选择:

bash 复制代码
Add Existing Files to Group 'Application/User/Core'

然后添加:

bash 复制代码
Core/Src/app_buzzer.c

如果你复用了上一篇 LED 代码,也要确认:

bash 复制代码
Core/Src/app_led.c

已经在 Keil 工程里。

如果忘了这一步,可能会报:

bash 复制代码
undefined symbol App_Buzzer_Init
undefined symbol App_Buzzer_Beep

这不是函数写错了,而是 app_buzzer.c 没有参与编译。

main.c 调用方式

接下来修改 main.c

这里有一个非常重要的习惯:自己加的代码尽量写在 USER CODE 区域里。你现在如果把 [#include](javascript:;) "app_buzzer.h" 写在 CubeMX 自动生成区域,或者把 while 里的逻辑写在 USER CODE BEGIN 3 外面,代码能编译,但下次 CubeMX 重新生成时可能会被覆盖。

1. 在 Includes 区域添加头文件

找到:

bash 复制代码
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */

改成:

bash 复制代码
/* USER CODE BEGIN Includes */
#include "app_led.h"
#include "app_buzzer.h"
/* USER CODE END Includes */

如果你现在的代码是这样:

bash 复制代码
#include "main.h"
#include "app_buzzer.h"
#include "app_led.h"

也能编译,但不建议这样放。更稳的做法是把自己加的头文件放进 USER CODE BEGIN Includes 里面。

2. 在初始化区域调用初始化函数

找到 main() 里的初始化部分,确保 MX_GPIO_Init() 已经在前面执行:

bash 复制代码
MX_GPIO_Init();

然后在 USER CODE BEGIN 2 里添加:

bash 复制代码
/* USER CODE BEGIN 2 */
App_LED_Init();
App_Buzzer_Init();
/* USER CODE END 2 */

为什么要放在 MX_GPIO_Init() 后面?

因为 LED 和蜂鸣器引脚必须先被 CubeMX 初始化成输出模式,我们自己的初始化函数才能正确控制它们。

3. 在 while 循环里蜂鸣器响一下,LED 翻转一次

找到:

bash 复制代码
while (1)
{
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
  /* USER CODE END 3 */
}

改成:

bash 复制代码
while (1)
{
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
  App_Buzzer_Beep(100);
  App_LED_Toggle();
  HAL_Delay(2000);
  /* USER CODE END 3 */
}

这段逻辑很直白:

bash 复制代码
蜂鸣器响 100 ms
LED 翻转一次
等待 2 秒
再重复

如果你想让蜂鸣器声音更短,可以改成:

bash 复制代码
App_Buzzer_Beep(10);

不过 10 ms 对很多蜂鸣器来说太短,可能只是很轻的一声。教程里建议先用 100 ms 或 200 ms,确认能听清楚以后再改短。

注意:不要把这几行放在 /* USER CODE END WHILE *//* USER CODE BEGIN 3 */ 中间。那个位置不适合手写逻辑,CubeMX 重新生成代码时更容易出问题。手写循环逻辑应该放在 USER CODE BEGIN 3USER CODE END 3 之间。

编译、下载和验证

代码加完后,先编译:

bash 复制代码
Build / F7

没有错误后再下载:

bash 复制代码
Download

下载后观察现象:

bash 复制代码
蜂鸣器短响一声
LED 从亮变灭,或者从灭变亮
等待 2 秒
然后重复

如果你用 Keil 下载后程序没有自动跑,但按复位键或点 Run 能跑,这不是蜂鸣器代码问题。前面调试时已经遇到过类似情况,本质是 Keil/ST-LINK 下载后自动复位运行没有触发。先以"按复位后能正常运行"为准继续验证外设。

移植到其他板子的修改点

这篇的移植点主要有 7 个。

|

要改的地方

|

为什么要改

|

在哪里改

|

| --- | --- | --- |

|

蜂鸣器引脚

|

不同板子的蜂鸣器接到不同 GPIO

|

CubeMX Pinout 页面

|

|

蜂鸣器 User Label

|

代码依赖 BUZZER_PinBUZZER_GPIO_Port

|

CubeMX GPIO 页面,标签设为 BUZZER

|

|

蜂鸣器有效电平

|

有些蜂鸣器高电平响,有些低电平响

| app_buzzer.c

里的 APP_BUZZER_ON_LEVELAPP_BUZZER_OFF_LEVEL

|

|

蜂鸣器初始电平

|

上电后不要让蜂鸣器乱响

|

CubeMX 的 GPIO output level

|

|

LED 引脚和标签

|

本篇用 LED 显示主循环节奏

|

沿用第 02 篇,建议标签为 LED

|

|

驱动电路

|

GPIO 不能直接带大电流负载

|

看原理图是否有三极管/MOS 管

|

|

蜂鸣器类型

|

无源蜂鸣器不能只靠固定电平稳定发声

|

确认是有源还是无源

|

换板子的推荐顺序:

  1. 看原理图,确认蜂鸣器接到哪个 MCU 引脚;

  2. 确认蜂鸣器是有源还是无源;

  3. 确认蜂鸣器是高电平响还是低电平响;

  4. 在 CubeMX 里把对应引脚设置成 GPIO_Output

  5. User Label 填 BUZZER

  6. 保留或配置 LED 引脚,确保 App_LED_Toggle() 能正常工作;

  7. 根据有效电平修改 APP_BUZZER_ON_LEVELAPP_BUZZER_OFF_LEVEL

  8. 重新生成代码、编译、下载。

如果你手上是无源蜂鸣器,本篇代码可能只会让它"咔嗒"一下,或者完全不响。这不是代码错,而是驱动方式不对。无源蜂鸣器要等 PWM 篇再处理。

常见问题排查

1. 蜂鸣器完全不响,但 LED 会翻转

这种情况说明主循环在跑,LED 也正常,问题基本集中在蜂鸣器侧。

优先检查:

|

优先检查

|

具体方法

|

| --- | --- |

|

是不是有源蜂鸣器

|

看模块说明;无源蜂鸣器需要 PWM

|

|

蜂鸣器引脚是否选错

|

回原理图确认蜂鸣器控制脚接到哪个 GPIO

|

|

有效电平是否反了

|

交换 APP_BUZZER_ON_LEVELAPP_BUZZER_OFF_LEVEL

|

|

模块是否供电

|

量 VCC 和 GND,确认电压正常

|

|

GND 是否共地

|

外接模块必须和 STM32 共地

|

|

响声时间是否太短

|

先把 App_Buzzer_Beep(10) 改成 App_Buzzer_Beep(200)

|

2. 蜂鸣器会响,但 LED 不翻转

优先检查:

  • app_led.c 是否加入 Keil 工程;

  • LED 的 User Label 是否和 app_led.c 使用的宏一致;

  • LED 是高电平亮还是低电平亮;

  • App_LED_Toggle() 是否写在 while (1) 里;

  • App_LED_Init() 是否放在 MX_GPIO_Init() 后面。

如果你用的是 LED_Red_PinLED_Red_GPIO_Port,而教程代码使用 LED_PinLED_GPIO_Port,就要么把 CubeMX 里的 User Label 改成 LED,要么在 app_led.c 里做一次适配。

3. 蜂鸣器一直响

常见原因有三个:

  • CubeMX 里 GPIO 初始电平设成了"打开蜂鸣器"的电平;

  • APP_BUZZER_ON_LEVELAPP_BUZZER_OFF_LEVEL 写反;

  • 驱动电路是低电平有效,但你按高电平有效写了。

先做一个简单测试:

bash 复制代码
App_Buzzer_Off();
while (1)
{
}

如果这样蜂鸣器还一直响,优先看有效电平和原理图。

4. 编译报 BUZZER_GPIO_Port is not defined

说明 CubeMX 没有生成:

bash 复制代码
BUZZER_GPIO_Port
BUZZER_Pin

Core/Inc/main.h 里看一下,如果只有类似:

bash 复制代码
#define BEEP_Pin ...
#define BEEP_GPIO_Port ...

那说明你的 User Label 不是 BUZZER

解决方法:

  1. 回 CubeMX;

  2. 找到蜂鸣器 GPIO;

  3. 把 User Label 改成 BUZZER

  4. 重新 Generate Code。

5. 编译报 undefined symbol App_Buzzer_Beep

通常是 app_buzzer.c 没有加入 Keil 工程。

解决方法:

  1. 右键 Application/User/Core

  2. 选择 Add Existing Files to Group

  3. 添加 Core/Src/app_buzzer.c

  4. 重新编译。

6. 声音很小或者断断续续

优先检查:

  • 蜂鸣器模块是否需要 5V;

  • STM32 GPIO 是否只是接了信号脚,模块 VCC 是否供电正常;

  • App_Buzzer_Beep() 的时间是否太短;

  • 如果是直接驱动蜂鸣器本体,GPIO 驱动能力可能不够,需要三极管/MOS 管;

  • 如果是无源蜂鸣器,用固定电平不会得到稳定声音,需要 PWM。

7. 下载后不响,按复位键才响

这说明程序本身没问题,只是 Keil 下载后没有自动复位运行。

可以先这样验证:

bash 复制代码
下载完成 -> 按开发板复位键 -> 观察蜂鸣器和 LED

后续再慢慢处理 Keil 的 Reset and Run、ST-LINK 复位线、CubeProgrammer 下载等问题。不要把这个现象误判成蜂鸣器驱动失败。

本篇小结

这一篇我们完成了一个更完整的 GPIO 输出小实验:蜂鸣器短响一次,LED 翻转一次。

你现在至少应该知道:

  • 蜂鸣器分有源和无源,不能混着写驱动;

  • 有源蜂鸣器可以用 GPIO 固定电平控制;

  • 无源蜂鸣器需要 PWM,这篇先不处理;

  • CubeMX 里蜂鸣器引脚要配置成 GPIO_Output

  • User Label 填 BUZZER 后,代码里可以使用 BUZZER_PinBUZZER_GPIO_Port

  • 有效电平要根据原理图或模块说明来改;

  • app_buzzer.capp_led.c 都要加入 Keil 工程;

  • main.c 里自己的 include 和 while 逻辑要放在 USER CODE 区域里。

下一篇我们开始处理输入:

STM32 按键输入:为什么按下去读到的是 0 或 1。

按键这一篇会把 GPIO 输入、上拉/下拉、按键有效电平讲清楚。等按键跑通后,我们再继续讲按键消抖和外部中断。

相关推荐
星夜夏空992 小时前
STM32单片机学习(15) —— PC串口通信实验
stm32·单片机·学习
星夜夏空992 小时前
STM32单片机学习(14) —— STM32的串口外设
stm32·单片机·学习
都在酒里2 小时前
STM32标准库驱动L298N双H桥电机驱动模块(调速/正反转/多模式实战,附完整工程代码)
stm32·单片机·嵌入式硬件
Hello_Embed3 小时前
USB 学习指南+软硬件框架
网络·笔记·stm32·嵌入式·ai编程
key_3_feng3 小时前
鸿蒙车规级MCU开发方案
单片机·华为·harmonyos
踏着七彩祥云的小丑3 小时前
嵌入式测试学习第 13 天:串口助手软件安装、界面认识、参数配置
单片机·嵌入式硬件
黑猫学长呀3 小时前
存储宝典第4篇:存储芯片中常说的E2E是啥?
linux·单片机·嵌入式硬件·e2e·ssd·ufs·存储芯片
xiangw@GZ4 小时前
DDR的硬件拓扑与ODT匹配技术
嵌入式硬件
熙芯XiChip4 小时前
压电片工作原理
嵌入式硬件