STM32L475蜂鸣器实验

通过原理图确定蜂鸣器引脚


电路理解

这里Q4 S8050是一个NPN型三极管, 原理图如下

它在这里的作用就是开关并且放大电流. 从芯片的BEEP GPIO口拉高时出来的是20mA左右的小电流, 然而蜂鸣器需要的电流是30~50mA, 所以这里需要三极管将BEEP引脚输出的电流放大.

大电流需要外部独立的电源供电, 所以蜂鸣器的上方接3.3V电源, 用来给蜂鸣器供电.

三极管的特性

  1. 基极(2)小电流流过时, 三极管导通, 大电流从集电极(3)--流向-->发射极(1), 这时蜂鸣器响
  2. 基极(2)关闭时, 三极管截止, 不导通, 这时蜂鸣器不响.
    所以, BEEP引脚输出高电平时, 蜂鸣器响, 低电平时, 蜂鸣器关闭

在这里我要通过软件频繁反转BEEP引脚产生高低电频变化, 实现模拟的PWM方波, 然后利用这个方波, 形成音调, 最终能够通过蜂鸣器播放一段简单的音乐

通过CubeMX配置BEEP引脚为推挽输出

生成的代码如下:

c 复制代码
	/** Configure pins as
        * Analog
        * Input
        * Output
        * EVENT_OUT
        * EXTI
*/
void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOE_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOE, LED_R_Pin|LED_G_Pin|LED_B_Pin, GPIO_PIN_SET);

  /*Configure GPIO pin : BEEP_Pin */
  GPIO_InitStruct.Pin = BEEP_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(BEEP_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pins : LED_R_Pin LED_G_Pin LED_B_Pin */
  GPIO_InitStruct.Pin = LED_R_Pin|LED_G_Pin|LED_B_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

}

其中增加了BEEP_Pin相关的硬件初始化

集成更加精准的delay函数

具体的函数实现参考文章 <<STM32L475实现精度更好的delay函数>>

编写PWM方波模拟函数, 及乐谱, 实现生日快乐歌
c 复制代码
#include "beep_music.h"
#include "main.h"
#include "delay.h"

/************************** 8位机音乐 标准音调频率表(含功夫专用升调,完美适配) **************************/
#define NOTE_REST 0        // 休止符,静音
#define NOTE_C4   262      // 中音1 (基准音)
#define NOTE_D4   294      // 中音2
#define NOTE_E4   330      // 中音3
#define NOTE_F4   349      // 中音4
#define NOTE_G4   392      // 中音5
#define NOTE_A4   440      // 中音6
#define NOTE_B4   494      // 中音7
#define NOTE_C5   523      // 高音1
#define NOTE_D5   587      // 高音2
#define NOTE_E5   659      // 高音3
#define NOTE_F5   698      // 高音4
#define NOTE_G5   784      // 高音5

/************************** 节拍定义 **************************/
#define BPM           120               // 每分钟120拍
#define QUARTER_BEAT  (60000 / BPM)     // 四分音符 = 500ms

#define BEAT_1_4      QUARTER_BEAT      // 四分音符
#define BEAT_1_8      (QUARTER_BEAT / 2)// 八分音符
#define BEAT_3_8      (BEAT_1_8 * 3)    // 附点八分音符 (生日快乐歌的核心节奏)
#define BEAT_1_16     (QUARTER_BEAT / 4)// 十六分音符
#define BEAT_1_2      (QUARTER_BEAT * 2)// 二分音符
#define BEAT_3_4      (QUARTER_BEAT * 3)// 附点二分音符 (小节末尾的长音)

#define BEAT_PAUSE    40                // 增加一点间隙感

// 音乐单元:音符 + 节拍(节拍对应持续时间)
typedef struct
{
    uint16_t note;    // 音符(如NOTE_E5、NOTE_REST)
    uint16_t beat;    // 节拍(如BEAT_1_8、BEAT_1_4)
} Music_Note_t;

// 生日快乐歌完整音乐序列
// 此处感谢妈妈耐心的乐理知识指导, 拥抱
Music_Note_t Birthday_Music[] = {
    // 第一句
    {NOTE_REST, BEAT_1_4}, {NOTE_REST, BEAT_1_4}, {NOTE_G4, BEAT_1_8},{NOTE_G4, BEAT_1_8}, {NOTE_A4, BEAT_1_4}, {NOTE_G4, BEAT_1_4}, {NOTE_C5, BEAT_1_4},  {NOTE_B4, BEAT_1_4},
    // 第二句
    {NOTE_G4, BEAT_1_8},{NOTE_G4, BEAT_1_8}, {NOTE_A4, BEAT_1_4}, {NOTE_G4, BEAT_1_4}, {NOTE_D4, BEAT_1_4}, {NOTE_C4, BEAT_1_4},
    // 第三句
    {NOTE_G4, BEAT_1_8},{NOTE_G4, BEAT_1_8},  {NOTE_G5, BEAT_1_4}, {NOTE_E5, BEAT_1_4}, {NOTE_C5, BEAT_1_4}, {NOTE_B4, BEAT_1_4}, {NOTE_A4, BEAT_1_4},
    // 第四句
    {NOTE_F4, BEAT_1_8}, {NOTE_F4, BEAT_1_8}, {NOTE_E5, BEAT_1_4}, {NOTE_C5, BEAT_1_4}, {NOTE_D4, BEAT_1_4}, {NOTE_C4, BEAT_3_4},

    {NOTE_REST, BEAT_1_2}
};

// 生日快乐歌音乐总长度(自动计算数组元素个数)
#define BIRTHDAY_MUSIC_LEN (sizeof(Birthday_Music)/sizeof(Music_Note_t))

// 软件模拟PWM蜂鸣器 - 可调音量、可调音调
// 参数说明:
// high_time  : 高电平持续时间(单位:微秒us)  高电平=蜂鸣器响
// low_time   : 低电平持续时间(单位:微秒us)  低电平=蜂鸣器停
// cycle_num  : 发声次数(0=无限循环发声,直到手动停止)
void BEEP_Soft_PWM(uint16_t high_time, uint16_t low_time, uint16_t cycle_num)
{
    uint16_t i = 0;
    if(cycle_num == 0) // 无限循环发声
    {
        while(1)
        {
            HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_SET);  // PB2高电平,蜂鸣器通电
            delay_us(high_time);                                   // 保持高电平时间
            HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_RESET);// PB2低电平,蜂鸣器断电
            delay_us(low_time);                                    // 保持低电平时间
        }
    }
    else // 指定次数发声
    {
        for(i=0; i<cycle_num; i++)
        {
            HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_SET);
            delay_us(high_time);
            HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_RESET);
            delay_us(low_time);
        }
    }
    // 发声结束,PB2置低,蜂鸣器彻底停止
    HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_RESET);
}

// 输入:音符频率(如NOTE_E5),输出:对应的high_time/low_time
void Note_To_PWM(uint16_t note, uint16_t *high_time, uint16_t *low_time)
{
    if(note == NOTE_REST) // 休止符:高低电平均为0(静音)
    {
        *high_time = 0;
        *low_time = 0;
        return;
    }
    uint32_t period = 1000000 / note; // 计算单个周期的总时长(us)
    *high_time = period / 2;          // 50%占空比
    *low_time = period - *high_time;  // 补全周期(避免整数截断误差)
}

void BEEP_Play_Birthday_Music(void)
{
    uint16_t high_time, low_time;
    uint32_t single_cycle_us;
    uint32_t cycle_num;

    for(uint16_t i=0; i<BIRTHDAY_MUSIC_LEN; i++)
    {
        Note_To_PWM(Birthday_Music[i].note, &high_time, &low_time);

        if(Birthday_Music[i].note == NOTE_REST)
        {
            delay_ms(Birthday_Music[i].beat);
            continue;
        }

        single_cycle_us = high_time + low_time;

        // 关键修改:实际播放时长 = 理论节拍时长 - 间隙时长
        // 确保 (Birthday_Music[i].beat) 必须大于 BEAT_PAUSE
        uint32_t actual_play_ms = Birthday_Music[i].beat - BEAT_PAUSE;
        cycle_num = (actual_play_ms * 1000) / single_cycle_us;

        BEEP_Soft_PWM(high_time, low_time, cycle_num);

        // 补回间隙,使得总时长等于节拍定义
        delay_ms(BEAT_PAUSE);
    }
}

运行效果如下:

虚拟PWM通过蜂鸣器实现音乐播放

相关推荐
悠哉悠哉愿意3 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
Lester_11013 天前
STM32霍尔传感器输入口设置为复用功能输入口时,还能用GPIO函数直接读取IO的状态吗
stm32·单片机·嵌入式硬件·电机控制
LCG元3 天前
低功耗显示方案:STM32L0驱动OLED,动态波形绘制与优化
stm32·嵌入式硬件·信息可视化
三佛科技-187366133973 天前
120W小体积碳化硅电源方案(LP8841SC极简方案12V10A/24V5A输出)
单片机·嵌入式硬件
z20348315203 天前
STM32F103系列单片机定时器介绍(二)
stm32·单片机·嵌入式硬件
古译汉书3 天前
【IoT死磕系列】Day 7:只传8字节怎么控机械臂?学习工业控制 CANopen 的“对象字典”(附企业级源码)
数据结构·stm32·物联网·http
Alaso_shuang3 天前
STM32 核心输入、输出模式
stm32·单片机·嵌入式硬件
脚后跟3 天前
AI助力嵌入式物联网项目全栈开发
嵌入式硬件·物联网·ai编程
2501_918126913 天前
stm32死锁是怎么实现的
stm32·单片机·嵌入式硬件·学习·个人开发
z20348315203 天前
STM32F103系列单片机定时器介绍(一)
stm32·单片机