用鸿蒙OS在蜂鸣器上播放一曲《两只老虎》

元胞露台
• 阅读 4586

本文介绍如何在HiSpark Wi-Fi IoT套件上,使用Harmony OS IoT硬件子系统的PWM接口 驱动蜂鸣器 播放音乐。

用PWM输出方波的API

鸿蒙系统IoT硬件子系统提供了PWM相关接口,接口头文件为wifiiot_pwm.h,其中开始输出方波的接口为:

/**
 * @brief Outputs PWM signals based on the input parameters.
 *
 * This function outputs PWM signals from a specified port based on
 * the configured frequency division multiple and duty cycle.
 *
 * @param port Indicates the PWM port number.
 * @param duty Indicates the PWM duty cycle.
 * @param freq Indicates the frequency-division multiple.
 * @return Returns {@link WIFI_IOT_SUCCESS} if the operation is successful;
 * returns an error code defined in {@link wifiiot_errno.h} otherwise.
 * @since 1.0
 * @version 1.0
 */
unsigned int PwmStart(WifiIotPwmPort port, unsigned short duty, unsigned short freq);

PWM输出的方波频率

通过PwmStart接口的注释,可以知道freq参数是分频倍数,PWM实际输出的方波频率等于 PWM时钟源频率 除以 分频倍数,即

f = Fcs / freq

其中,Fcs是PWM时钟源频率;

PWM输出方波的占空比

通过PwmStart接口的duty参数可以控制输出方波的占空比,占空比是指PWM输出的方波波形的高电平时间占整个方波周期的比例,具体占空比值是 duty 和 freq的比值,例如想要输出占空比 50%的方波信号,那么duty填的值就要是 freq/2;

音符-频率对应关系
用鸿蒙OS在蜂鸣器上播放一曲《两只老虎》

这个表中有一个规律——音高升高一个八度,频率升高一倍。

表格来自:https://liam.page/2018/04/09/...

开发板可以输出的最低频率
通过前面的公式,我们知道:

  1. PWM输出的方波频率和freq成反比,freq越大,输出的方波频率越小;
  2. freq是unsinged short类型,最大值为65535;

因此,输出频率的最小值取决于时钟源,而PWM的默认时钟源为160M:

unsigned int HalPwmInit(HalWifiIotPwmPort port)
{
    if (hi_pwm_set_clock(PWM_CLK_160M) != HI_ERR_SUCCESS) {
        return (unsigned int)HAL_WIFI_IOT_FAILURE;
    }
    return hi_pwm_init((hi_pwm_port)port);
}

160M时钟源条件下,输出方波的最低频率是:160M/65535=2441.44...,这个频率还是略高,在上面的表格中没有找到音名。但是我可以用上面表格值继续往后推算两个八度,就能够覆盖这个频率(不过通常只使用7个八度,所以还是有点高)。

如果时钟源频率可以更低,那么输出频率也可以更低!
幸运的是,通过调用hi_pwm_set_clock接口,可以修改时钟源:

/**
 * @ingroup iot_pwm
 *
 * Enumerates the PWM clock sources.CNcomment:PWM时钟源枚举。CNend
 */
typedef enum {
    PWM_CLK_160M, /**< 160M APB clock.CNcomment:160M 工作时钟 CNend */
    PWM_CLK_XTAL, /**< 24M/40M crystal clock.CNcomment:24M或40M 晶体时钟 CNend */
    PWM_CLK_MAX   /**< Maximum value, which cannot be used.CNcomment:最大值,不可使用CNend */
} hi_pwm_clk_source;

hi_u32 hi_pwm_set_clock(hi_pwm_clk_source clk_type);

通过注释我们知道hi_pwm_set_clock(PWM_CLK_XTAL);可以将时钟源设置为晶体时钟,晶体时钟可能为24M或40M;
那么问题来了——晶体时钟频率到底是多少?

晶体时钟频率是多少?
可以通过实验测算出晶体时钟频率,具体步骤如下:

  1. 使用 hi_pwm_set_clock(PWM_CLK_XTAL); 设置时钟源为晶体时钟;
  2. 使用PwmStart(WIFI_IOT_PWM_PORT_PWM0, 201000, 401000);输出方波信号;
  3. 使用示波器测量方波频率,根据测量的频率计算时钟源频率;

经实际测量,方波频率为1000Hz,

因此,时钟频率为 1000 40 1000,即 40 MHz;

可以输出的方波最低频率
因此,方波最低频率就是 40M / 65535 ,也就是:

40 1000 1000 / 65535
610.3608758678569
对照上面的频率表,可以知道,能够输出E5及以上的所有音符;

准备曲谱
为了代码实现起来简单,我选择了《两只老虎》的曲谱作为素材,在简谱网找到了简谱:
用鸿蒙OS在蜂鸣器上播放一曲《两只老虎》

简谱说明
简谱上的一些记号,有的同学可能不太清楚是什么意思,这里简单说明一下:

  1. 左上角的1=C是表示调式(可以不用关心),1是唱名,C是音名,1=C是正调(就是常规的对应关系: 1-C,2-D, 3-E, 4-F, 5-G, 6-A, 7-B);
  2. 左上角的 4/4 是四四拍,是指 四分音符为一拍, 每小节有四拍;
  3. 下面谱子上的竖线就是每个小节分隔符,和4/4对应;
  4. “跑得快”上面5后面的横线表示延时一拍;
  5. “一只没有眼睛”一句,5后面的点表示顺延半拍,一条下划线表示二分之一时间,两条下划线表示四分之一时间;

编写代码
有了以上知识,我们就可以编写代码了,关键代码如下:

static const uint16_t g_tuneFreqs[] = { // 音符对应的分频系数
    0, // 40M Hz 时钟源,C6 ~ B6:
    38223, // 1 1046.5
    34052, // 2 1174.7
    30338, // 3 1318.5
    28635, // 4 1396.9
    25511, // 5 1568
    22728, // 6 1760
    20249, // 7 1975.5
    51021 // 5_ 783.99 // 低一个八度的 5
};

// 曲谱音符
static const uint8_t g_scoreNotes[] = {
    // 《两只老虎》简谱:http://www.jianpu.cn/pu/33/33945.htm
    1, 2, 3, 1,        1, 2, 3, 1,        3, 4, 5,  3, 4, 5,
    5, 6, 5, 4, 3, 1,  5, 6, 5, 4, 3, 1,  1, 8, 1,  1, 8, 1, // 最后两个 5 应该是低八度的,链接图片中的曲谱不对,声音到最后听起来不太对劲
};

// 曲谱时值,根据简谱记谱方法转写
static const uint8_t g_scoreDurations[] = {
    4, 4, 4, 4,        4, 4, 4, 4,        4, 4, 8,  4, 4, 8,
    3, 1, 3, 1, 4, 4,  3, 1, 3, 1, 4, 4,  4, 4, 8,  4, 4, 8,
};

static void *BeeperMusicTask(const char *arg)
{
    (void)arg;

    printf("BeeperMusicTask start!\r\n");

    hi_pwm_set_clock(PWM_CLK_XTAL); // 设置时钟源为晶体时钟(40MHz,默认时钟源160MHz)

    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 %d\r\n", tune, (40*1000*1000) / freqDivisor, freqDivisor, tuneInterval);
        PwmStart(WIFI_IOT_PWM_PORT_PWM0, freqDivisor/2, freqDivisor);
        usleep(tuneInterval);
        PwmStop(WIFI_IOT_PWM_PORT_PWM0);
    }

    return NULL;
}

谱子中最后两个5是错误的,应该是低八度的5,也就是5下面应该打一个点;我修改了代码,让整个曲子听起来更自然;

完整代码:https://gitee.com/hihopeorg/HarmonyOS-IoT-Application-Development/blob/master/02_device_control/beeper_music_demo.c


原文链接:
https://developer.huawei.com/consumer/cn/forum/topic/0204398682948650101?fid=0101303901040230869
作者:思维

点赞
收藏
评论区
推荐文章
Easter79 Easter79
3年前
stm32F4 控制舵机
正点原子stm32f407开发板对于一般的舵机来说,所对应的PWM波的周期为20ms,即舵机接收的PWM信号频率为50HZ。其空占比与转动角度的关系。普通舵机的舵量是0~180°。脉冲宽度范围0.5ms~2.5ms0.5ms/20ms0度1.0ms/20ms45度
Wesley13 Wesley13
3年前
STM32 PWM输出
文将介绍通过STM32的定时器输出PWM,如果对定时器不太熟悉的同学可以看下之前的文章《STM32基础定时器详解(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzIxNTg1NzQwMQ%3D%3D%26mid%3D2247
山东彭于晏 山东彭于晏
4年前
认识树莓派及树莓派登录配置(SecureCRT)
什么是树莓派它是一款基于ARM的微型电脑主板,以SD卡为内存硬盘,卡片主板周围有两个USB接口和一个网口,可连接键盘、鼠标和网线,同时拥有视频模拟信号的电视输出接口和HDMI高清视频输出接口,以上部件全部整合在一张仅比信用卡稍大的主板上,具备所有PC的基本功能只需接通电视机和键盘,就能执行如电子表格、文字处理、玩游戏、播放高清视频等诸多功能。Raspb
CuterCorley CuterCorley
4年前
uni-app入门教程(6)接口的扩展应用
前言本文主要介绍了接口的扩展应用:设备相关的接口包括获取系统信息、网络状态、拨打电话、扫码等;导航栏的动态设置;下拉刷新、上拉加载更多的实现,通过动作链获取节点信息;用条件编译实现小程序、APP等多端兼容;提示框、Loading、模态弹窗等的交互反馈。一、设备相关1.系统信息uni.getSystemInfo(OBJECT)接口用来异步
Wesley13 Wesley13
3年前
Java适配器模式
默认的MediaPlayer接口只能播放mp3格式的音乐,然而我们现在想播放vlc和mp4格式的音乐,怎么做呢?这里我们引入AdvancedMediaPlayer接口,AdvancedMediaPlayer接口可以播放vlc和mp4格式的音乐。怎么将AdvancedMediaPlayer接口“适配”到MediaPlayer接口中呢?
Wesley13 Wesley13
3年前
Android的Framework分析
大家都知道android是基于linux的kernel上的。android可以运行在intel,高通,nvidia等硬件平台。但是涉及到一些GPU,显卡和一些设备的驱动问题,因为这些驱动都不是开源的,google位了兼容这些设备厂商的驱动源码,提出了硬件抽象层HAL的概念。HAL层对上为framework和native开发提供统一的API接口,为下层驱动的
Stella981 Stella981
3年前
FMOD Event System——事件树策略、加载、内存分配
FMOD最新API—EventSytem,提供了比FMODEx更高层的接口,使引擎开发人员无需关注诸如音频数据管理、播放控制、channels管理等底层细节,而把精力放在考虑如何为上层应用(如:场景/技能/UI编辑器中的音效、音乐的编辑,游戏中各种音效、音乐的播放)设计适合的框架。此外,它还提供了相应的设计工具—FMODDesigner,让音效制作人员
赵颜 赵颜
1年前
GC9008 12V 全桥驱动芯片,可替代TMI8118,应用于摄像机、消费类产品上
GC9008是一款12V全桥驱动芯片,为提供高性价比的方案。它能提供0.1A的持续输出电流。可以工作在4.515V的电源电压上。具有PWM(IN1/IN2)输入接口,与行业标准器件兼容.是SOP8封装,GC9008D是DIP封装芯片特点●H桥电机驱动器电源
赵颜 赵颜
1年前
有刷直流驱动芯片的GC9114 pin to pin替代TC118S,内置过温,低压欠压保护等功能更具性价比,应用于牙刷,电子锁,红外开关等上
GC9114是一款低压5V全桥驱动芯片,为摄像机、消费类产品、玩具和其他低压或者电池供电的运动控制类应用提供了集成的电机驱动解决方案。能提供高达1.3A的持续输出电流。可以工作在26V的电源电压上。它具有PWM(IN/IN)输入接口,与行业标准器件兼容,并