18. 蜂鸣器
📝本节您将学习如何通过 PWM 驱动天巧星板载无源蜂鸣器,实现音调控制、音量调节和简易旋律播放。
🏆本章目标
1️⃣ 理解有源蜂鸣器与无源蜂鸣器的区别
2️⃣ 掌握通过 PWM 频率控制音调的方法
3️⃣ 实现按键提示音和警告音效
4️⃣ 实现简单的旋律(音符序列)播放
18.1 蜂鸣器基础
有源 vs 无源
| 类型 | 内部结构 | 驱动方式 | 特点 |
|---|---|---|---|
| 有源蜂鸣器 | 内置振荡电路 | 通电即响(固定频率) | 只能发出一种音调 |
| 无源蜂鸣器 | 无振荡电路 | 需外部方波驱动 | 频率可变,能发出不同音调 |
天巧星板载的是无源蜂鸣器,可通过改变 PWM 频率发出不同音调的声音。
驱动原理
- 频率决定音调高低:频率越高音调越尖,频率越低音调越沉
- 占空比影响音量:50% 占空比时声音最响
- 持续时间控制节拍:每个音符持续多长时间
18.2 硬件连接
| 参数 | 规格 |
|---|---|
| 引脚 | PB27 |
| 定时器 | TIMG6 CCP1 |
| PWM 时钟 | 1MHz(80MHz / 预分频 80) |
18.3 SysConfig 配置
在第 7 章 PWM 配置基础上,针对蜂鸣器专门配置:
| 参数 | 设置值 | 说明 |
|---|---|---|
| Timer Module | TIMG6 | |
| PWM Clock Source | BUSCLK | 80MHz |
| PWM Clock Prescaler | 80 | 分频后 1MHz |
| PWM Period | 2273 | 默认 440Hz (A4) |
| PWM Channel 1 Duty | 1136 | 50% 占空比 |
| PWM Channel 1 Pin | PB27 |
为什么预分频选 80
80MHz / 80 = 1MHz 计数频率。1MHz 下:
- Period = 频率对应的周期微秒数
- 例如 440Hz → 周期 2273μs → Period = 2273
1MHz 计数频率在人耳可听范围(20Hz~20kHz)内都有足够的分辨率。
18.4 蜂鸣器驱动实现
c
#include "ti_msp_dl_config.h"
#define BUZZER_INST PWM_BUZZER_INST // TIMG6
#define BUZZER_CC_IDX DL_TIMER_CC_1_INDEX
// 设置蜂鸣器频率(Hz),0 = 静音
void buzzer_set_freq(uint16_t freq)
{
if (freq == 0) {
DL_TimerG_stopCounter(BUZZER_INST);
return;
}
uint32_t period = 1000000 / freq; // 1MHz 计数频率
DL_TimerG_setLoadValue(BUZZER_INST, period);
DL_TimerG_setCaptureCompareValue(BUZZER_INST, period / 2, BUZZER_CC_IDX);
DL_TimerG_startCounter(BUZZER_INST);
}
// 开启蜂鸣器(使用当前频率)
void buzzer_on(void)
{
DL_TimerG_startCounter(BUZZER_INST);
}
// 关闭蜂鸣器
void buzzer_off(void)
{
DL_TimerG_stopCounter(BUZZER_INST);
}
// 设置音量(占空比,0~100)
void buzzer_set_volume(uint8_t volume)
{
if (volume > 100) volume = 100;
uint32_t period = DL_TimerG_getLoadValue(BUZZER_INST);
uint32_t duty = period * volume / 100;
DL_TimerG_setCaptureCompareValue(BUZZER_INST, duty, BUZZER_CC_IDX);
}
// 播放指定频率和持续时间的音符
void buzzer_beep(uint16_t freq, uint16_t duration_ms)
{
buzzer_set_freq(freq);
delay_cycles((uint32_t)(CPUCLK_FREQ / 1000) * duration_ms);
buzzer_off();
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
18.5 音符频率表
标准音乐音符的频率定义:
c
// 音符频率定义(Hz)
#define NOTE_REST 0 // 休止符
#define NOTE_C4 262 // Do
#define NOTE_CS4 277
#define NOTE_D4 294 // Re
#define NOTE_DS4 311
#define NOTE_E4 330 // Mi
#define NOTE_F4 349 // Fa
#define NOTE_FS4 370
#define NOTE_G4 392 // Sol
#define NOTE_GS4 415
#define NOTE_A4 440 // La(标准音)
#define NOTE_AS4 466
#define NOTE_B4 494 // Si
#define NOTE_C5 523 // 高 Do
#define NOTE_D5 587 // 高 Re
#define NOTE_E5 659 // 高 Mi
#define NOTE_F5 698 // 高 Fa
#define NOTE_G5 784 // 高 Sol
#define NOTE_A5 880 // 高 La
#define NOTE_B5 988 // 高 Si
#define NOTE_C6 1047 // 更高 Do1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
对应 PWM Period 值(1MHz 计数频率):
| 音名 | 频率 (Hz) | Period 值 |
|---|---|---|
| C4 (Do) | 262 | 3817 |
| D4 (Re) | 294 | 3401 |
| E4 (Mi) | 330 | 3030 |
| F4 (Fa) | 349 | 2865 |
| G4 (Sol) | 392 | 2551 |
| A4 (La) | 440 | 2273 |
| B4 (Si) | 494 | 2024 |
| C5 (高Do) | 523 | 1912 |
18.6 旋律播放引擎
18.6.1 音符结构
c
typedef struct {
uint16_t freq; // 频率(Hz),0 为休止
uint16_t duration; // 持续时间(ms)
} Note_t;1
2
3
4
2
3
4
18.6.2 播放函数
c
// 播放一段旋律(阻塞式)
void buzzer_play_melody(const Note_t *melody, uint16_t len)
{
for (uint16_t i = 0; i < len; i++) {
if (melody[i].freq == NOTE_REST) {
buzzer_off();
} else {
buzzer_set_freq(melody[i].freq);
}
delay_cycles((uint32_t)(CPUCLK_FREQ / 1000) * melody[i].duration);
buzzer_off();
// 音符间短暂间隔,使音符分离
delay_cycles((uint32_t)(CPUCLK_FREQ / 1000) * 30);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18.6.3 预定义音效
c
// 按键提示音(短促清脆)
const Note_t TONE_KEYPRESS[] = {
{NOTE_C6, 30},
};
#define TONE_KEYPRESS_LEN 1
// 三连音提示
const Note_t TONE_TRITONE[] = {
{NOTE_E5, 80},
{NOTE_G5, 80},
{NOTE_C6, 80},
};
#define TONE_TRITONE_LEN 3
// 警告音
const Note_t TONE_WARNING[] = {
{NOTE_A5, 200},
{NOTE_REST, 100},
{NOTE_A5, 200},
{NOTE_REST, 100},
{NOTE_A5, 200},
};
#define TONE_WARNING_LEN 5
// 开机音效
const Note_t TONE_STARTUP[] = {
{NOTE_C5, 100},
{NOTE_E5, 100},
{NOTE_G5, 100},
{NOTE_C6, 200},
};
#define TONE_STARTUP_LEN 4
// 小星星(示例旋律)
const Note_t MELODY_TWINKLE[] = {
{NOTE_C4, 400}, {NOTE_C4, 400}, {NOTE_G4, 400}, {NOTE_G4, 400},
{NOTE_A4, 400}, {NOTE_A4, 400}, {NOTE_G4, 800},
{NOTE_F4, 400}, {NOTE_F4, 400}, {NOTE_E4, 400}, {NOTE_E4, 400},
{NOTE_D4, 400}, {NOTE_D4, 400}, {NOTE_C4, 800},
};
#define MELODY_TWINKLE_LEN 141
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
18.7 非阻塞播放(定时器驱动)
阻塞式播放会卡住主循环。出厂固件使用系统定时器(5ms tick)驱动非阻塞播放:
c
typedef struct {
const Note_t *melody;
uint16_t length;
uint16_t position; // 当前音符索引
uint16_t time_remain; // 当前音符剩余时间(ms)
uint8_t playing; // 是否正在播放
} Beeper_t;
static Beeper_t beeper = {0};
// 启动播放(非阻塞)
void buzzer_play_async(const Note_t *melody, uint16_t len)
{
beeper.melody = melody;
beeper.length = len;
beeper.position = 0;
beeper.time_remain = melody[0].duration;
beeper.playing = 1;
if (melody[0].freq > 0)
buzzer_set_freq(melody[0].freq);
else
buzzer_off();
}
// 在定时器中断中调用(每 10ms 调用一次)
void buzzer_tick(uint16_t elapsed_ms)
{
if (!beeper.playing) return;
if (beeper.time_remain <= elapsed_ms) {
// 当前音符结束,切换到下一个
beeper.position++;
if (beeper.position >= beeper.length) {
// 旋律播放完毕
buzzer_off();
beeper.playing = 0;
return;
}
// 加载下一个音符
const Note_t *note = &beeper.melody[beeper.position];
beeper.time_remain = note->duration;
if (note->freq > 0)
buzzer_set_freq(note->freq);
else
buzzer_off();
} else {
beeper.time_remain -= elapsed_ms;
}
}
// 查询是否正在播放
uint8_t buzzer_is_playing(void)
{
return beeper.playing;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
在系统定时器中断(如 TIMA0 的 5ms 或 10ms 周期中断)中调用:
c
// 在定时器 ISR 中
void TIMA0_IRQHandler(void)
{
// ...
buzzer_tick(10); // 每 10ms 调用一次
// ...
}1
2
3
4
5
6
7
2
3
4
5
6
7
18.8 完整实验
c
#include "ti_msp_dl_config.h"
int main(void)
{
SYSCFG_DL_init();
// 播放开机音效
buzzer_play_melody(TONE_STARTUP, TONE_STARTUP_LEN);
delay_cycles(CPUCLK_FREQ); // 等待 1 秒
// 播放小星星
buzzer_play_melody(MELODY_TWINKLE, MELODY_TWINKLE_LEN);
while (1) {
__WFI();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
18.9 与按键联动
按键按下时发出提示音:
c
void GROUP1_IRQHandler(void)
{
switch (DL_Interrupt_getPendingGroup(DL_INTERRUPT_GROUP_1)) {
case GPIO_KEY_INT_IIDX:
buzzer_beep(NOTE_C6, 30); // 短促按键音
DL_GPIO_togglePins(GPIO_LED_PORT, GPIO_LED_PIN_PIN);
break;
default:
break;
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
18.10 知识总结
| 内容 | 说明 |
|---|---|
| 蜂鸣器类型 | 无源(需外部方波驱动) |
| 驱动方式 | PWM(TIMG6 CCP1) |
| 引脚 | PB27 |
| 音调控制 | 改变 PWM 频率 |
| 音量控制 | 改变 PWM 占空比 |
| 计数频率 | 1MHz(80MHz / 80) |
| 播放方式 | 阻塞式 / 非阻塞式(定时器驱动) |
下一章将学习板载 IMU 姿态传感器的驱动和数据读取。