很早之前便看到有大佬做了蜂鸣器播放音乐的项目,当时也想尝试一番,奈何对鸿蒙系统不太熟悉,经过一段时间摸索,对鸿蒙系统有了一些了解,终于完成了蜂鸣器播放音乐的项目。
下面先介绍蜂鸣器播放音乐的原理,然后介绍Hi3861的PWM外设配置,最后介绍如何实现PWM播放音乐。
一、蜂鸣器播放音乐的原理
单片机通过I/O口输出不同频率的PWM可以使无源蜂鸣器产生不同频率的声音,为了增强驱动能力,通常单片机通过控制三极管来控制蜂鸣器的通断。控制蜂鸣器发声的频率、时长、间隔,便可以演奏出一首音乐。
在最常见的定律法——十二平均律中,中央C(钢琴最中间的C音)右边的第一个A音被定义为440 Hz,然后其它音的频率用等比数列推出,相隔半音的两个音的频率之比为。由此可以推断出中央C的频率为262Hz,它右边的#C的频率为277 Hz,D的频率为293Hz等等。还有低音、高音,在此频率基础上减半或加倍。
音名(中音) | 频率(Hz) |
C | 262 |
#C | 277 |
D | 293 |
#D | 311 |
E | 329 |
F | 349 |
#F | 369 |
G | 392 |
#G | 415 |
A | 440 |
#A | 466 |
B | 494 |
二、配置PWM外设
找到vendor/hisi/hi3861/hi3861/app/wifiiot_app/init/app_io_init.c文件夹,修改PWM引脚,如下所示。板卡上连接蜂鸣器的引脚为GPIO09,复用后的PWM为PWM0。
找到vendor/hisi/hi3861/hi3861/build/config/usr_config.mk文件,添加"
CONFIG_PWM_SUPPORT=y",使能PWM功能。
在applications/sample/wifi-iot/app下建立buzzer文件夹,然后在applications/sample/wifi-iot/app/oled_demo下建立buzzer.c、buzzer.h(未用,可以不建)、BUILD.gn(编译脚本)三个文件。
为了蜂鸣器声音更悦耳,PWM占空比一般设为50%,buzzer.c代码如下:
- #include <unistd.h>
- #include "stdio.h"
- #include "ohos_init.h"
- #include "cmsis_os2.h"
- #include "wifiiot_gpio.h"
- #include "wifiiot_gpio_ex.h"
- #include <hi_pwm.h>
- #include "buzzer.h"
- #define BUZZER_TASK_STACK_SIZE 512
- #define BUZZER_TASK_PRIO 25
- static void *BuzzerTask(const char *arg)
- {
- (void)arg;
- hi_pwm_start(HI_PWM_PORT_PWM0,32767,65535); //端口号;占空比(需计算);分频系数
- return NULL;
- }
- static void BuzzerExampleEntry(void)
- {
- osThreadAttr_t attr;
- IoSetFunc(WIFI_IOT_IO_NAME_GPIO_9,
- hi_pwm_init(HI_PWM_PORT_PWM0); //初始化PWM端口
- hi_pwm_set_clock(PWM_CLK_XTAL); //设置时钟源 40MHz
- attr.name = "BuzzerTask";
- attr.attr_bits = 0U;
- attr.cb_mem = NULL;
- attr.cb_size = 0U;
- attr.stack_mem = NULL;
- attr.stack_size = BUZZER_TASK_STACK_SIZE;
- attr.priority = BUZZER_TASK_PRIO;
- if (osThreadNew((osThreadFunc_t)BuzzerTask, NULL, &attr) == NULL) {
- printf("[BuzzerExample] Falied to create BuzzerTask!n");
- }
- }
- SYS_RUN(BuzzerExampleEntry);
复制代码 BUILD.gn脚本文件如下:
- static_library("buzzer") {
- sources = [
- "buzzer.c"
- ]
- include_dirs = [
- "//utils/native/lite/include",
- "//kernel/liteos_m/components/cmsis/2.0",
- "//base/iot_hardware/interfaces/kits/wifiiot_lite",
- ]
- }
复制代码 最后修改applications/sample/wifi-iot/app/BUILD.gn 文件,如下所示:
- import("//build/lite/config/component/lite_component.gni")
- lite_component("app") {
- features = [
- "buzzer:buzzer",
- ]
- }
复制代码 完成上述步骤后,编译下载即可驱动蜂鸣器,但是Hi3861的PWM外设的最低频率为610Hz(40MHz/65535),为了能正常播放音乐,只能将音乐频率升高两个8度,即最低频率为262Hz*2=524Hz,能满足一些简单音乐的频率要求。
三、播放音乐
前面说过,蜂鸣器播放音乐的三要素为频率、时长、停顿间隔,但为了简化流程,一般取消频率间隔,只保留频率和时长。
(1)选好一首乐谱,统计好乐谱所需的所有频率,为了后续方便调用,可写成数组,并按照调式顺序排列,以《两只老虎为例》。
- static const uint16_t g_tuneFreqs[] = {
- 0, // 40M Hz 对应的分频系数:
- 38223, // 1046.5
- 34052, // 1174.7
- 30338, // 1318.5
- 28635, // 1396.9
- 25511, // 1568
- 22728, // 1760
- 20249, // 1975.5
- 51021 // 5_ 783.99 // 第一个八度的 5
- };
复制代码 (2)根据简谱找出每拍的频率,按照其在频率表中的顺序,分别列出,并写成数组形式。
- tatic const uint8_t g_scoreNotes[] = {
- 1, 2, 3, 1, 1, 2, 3, 1, 3, 4, 5, 3, 4, 5, 5, 6,
复制代码 (3)将每个节拍的时长列出,为了方便调用,同样写成数组形式
- static const uint8_t g_scoreDurations[] = {
- 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 4, 4, 8, 3, 1,
复制代码 (4) 最后按照以上描述,顺序调用PWM即可播放音乐。这一步主要是修改'二'中的 BuzzerTask 函数。
- static void *BuzzerTask(const char *arg)
- {
- (void)arg;
- for (size_t i = 0; i < sizeof(g_scoreNotes)/sizeof(g_scoreNotes[0]); i++) {
- uint32_t tune = g_scoreNotes[i]; // 音符
- uint16_t freqDivisor = g_tuneFreqs[tune];
- uint32_t tuneInterval = g_scoreDurations[i] * (125*1000); // 音符时间
- printf("%d %d %d %drn", tune, (40*1000*1000) / freqDivisor, freqDivisor, tuneInterval);
- hi_pwm_start(HI_PWM_PORT_PWM0, freqDivisor/2, freqDivisor);
- usleep(tuneInterval);
- hi_pwm_stop(HI_PWM_PORT_PWM0);
- }
- return NULL;
- }
复制代码