经过一段时间的分享,基于Hi3861开发板的鸿蒙开发已经完成了下面核心板和OLED板两个子板的分享了。
我们今天分享红黄绿灯板上的资源,对应原理图在下面文件中:
HiSpark_WiFi_IoT_SSL_VER.A.pdf
这个红黄绿灯板子上有三个LED,分别是红黄绿三种颜色;
此外还有一个按键和一个无源蜂鸣器;
对应资源如下表所示:
板载资源 | 系统资源 |
---|---|
红灯_RED | D10(GPIO10)/UART2_CTS/SPI0_CLK/PWM1_OUT/I2C0_SDA |
黄灯_YELLOW | A12(GPIO12)/UART2_RXD/SPI0_CS1/ADC0/PWM3_OUT |
绿灯_GREEN | A11(GPIO11)/UART2_TXD/SPI0_RXD/ADC5/PWM2_OUT |
蜂鸣器_BEEP(MLT-8530) | A9(GPIO09)/UART2_RTS/SPI0_TXD/ADC4/PWM0_OUT/I2C0_SC |
按键_SWITCH | MOSI(SPI0_TXD)/GPIO08/PWM1_OUT |
LED和按键的驱动我们之前已经分享过了,请参见下文:
HarmonyOS实现点亮LED
基于鸿蒙OS的按键驱动
一个ADC实现多个按键检测
今天我们介绍一下鸿蒙系统下PWM的使用方法。
当BEEP为高电平的时候,三极管MMBT3904导通,蜂鸣器B1有电流流过;
当BEEP为低电平的时候,三极管截止,蜂鸣器B1没有电流流过。
设置按键对应GPIO08为输入、上拉模式的GPIO,并设置上升沿的中断函数为:
OnButtonPressed_isr
IoSetFunc(WIFI_IOT_IO_NAME_GPIO_8, WIFI_IOT_IO_FUNC_GPIO_8_GPIO);
GpioSetDir(WIFI_IOT_IO_NAME_GPIO_8, WIFI_IOT_GPIO_DIR_IN);
IoSetPull(WIFI_IOT_IO_NAME_GPIO_8, WIFI_IOT_IO_PULL_UP);
GpioRegisterIsrFunc(WIFI_IOT_IO_NAME_GPIO_8, WIFI_IOT_INT_TYPE_EDGE, WIFI_IOT_GPIO_EDGE_RISE_LEVEL_HIGH, OnButtonPressed_isr, NULL);
中断函数中主要改变控制音乐播放的一个布尔变量,在真和假之间来回切换。
static void OnButtonPressed_isr(char* arg) {
(void)arg;
printf("[beep_demo.c] OnButtonPressed() \r\n");
bSongPlaying = !bSongPlaying;
}
hi_pwm_set_clock(PWM_CLK_XTAL); //设置时钟源为晶体时钟;
IoSetFunc(WIFI_IOT_IO_NAME_GPIO_9, WIFI_IOT_IO_FUNC_GPIO_9_PWM0_OUT);//IO复用为PWM功能
ret = GpioSetDir(WIFI_IOT_IO_NAME_GPIO_9, WIFI_IOT_GPIO_DIR_OUT);//设置为输出
if (ret != WIFI_IOT_SUCCESS) {
printf("===== ERROR ======gpio -> GpioSetDir ret:%d \r\n", ret);
return;
}
hi_pwm_init(HI_PWM_PORT_PWM0);//初始化PWM
改写工程中的两个BUILD.gn文件。
添加完上面代码,如果直接编译的话,会报如下错误:
出错的原因是未开启PWM功能,开启PWM功能:
在 \vendor\hisi\hi3861\hi3861\build\config\usr_config.mk 文件中下图位置,添加一行代码:
CONFIG_PWM_SUPPORT=y
再编译就没有问题了。
改变PWM输出使用如下两个函数:
hi_u32 hi_pwm_stop(hi_pwm_port port)
hi_u32 hi_pwm_start(hi_pwm_port port, hi_u16 duty, hi_u16 freq)
修改参数freq可以修改输出脉冲的频率,修改参数 duty可以改变占空比。
执行hi_pwm_start()函数开始PWM输出,执行hi_pwm_stop()函数PWM输出停止。
hi_pwm_stop(HI_PWM_PORT_PWM0);
hi_pwm_start(HI_PWM_PORT_PWM0, 1, 2); //25ns/25ns
hi_pwm_stop(HI_PWM_PORT_PWM0);
hi_pwm_start(HI_PWM_PORT_PWM0, 3, 10); //75ns/250ns
hi_pwm_stop(HI_PWM_PORT_PWM0);
hi_pwm_start(HI_PWM_PORT_PWM0, 30, 100); //0.75us/2.5us
由上面的测试,我们可以得出,参数 duty 和 freq 的值,1单位代表25 ns。
现在我们实现了PWM输出的控制,接下来就是实现本文的目标了—实现蜂鸣器演奏《爱若琉璃》,蜂鸣器演奏音乐的原理详见之前网文:
蜂鸣器演奏音乐“你笑起来真好看”
由上面的网文我们知道,要想实现一个乐谱,我们只要确定“音符(音调)”和“节拍”即可。
音调表示一个音符响的频率,节拍表示一个音符该唱多长的时间。
一般钢琴键盘有88个按键:
钢琴上88按键的频率如下表所示:
比如中央C音的标准频率就是261,在上面表格中就是:音名为C4的按键。
程序中定义一个音频的数组:
因为上面初始化PWM的时候,使用的是外部晶振作为时钟源hi_pwm_set_clock(PWM_CLK_XTAL);
根据上面示波器截图中实测的结果可以推算出,这个时钟源为40M。
而输出的脉冲频率由这个函数决定的:hi_u32 hi_pwm_start(hi_pwm_port port, hi_u16 duty, hi_u16 freq)
。
参数freq决定了脉冲的频率,这个参数是hi_u16类型,那么这个参数的有效范围为:2~65535。
所以输出的脉冲的最低频率为:40M / 65535 (下面有计算方法) ,即 40 * 1000 * 1000 / 65535 = 610.3608758678569。
所以这个Hi3861芯片只能产生频率610Hz以上的音,即D#5
及以上的按键音,所以我们选择C6
那组按键的音作为主音。所以我们预定义正常音的C6对应数组的起始索引为63。
#define N_B 63
我们要想让蜂鸣器发出某个音只需要让PWM输出对应频率的脉冲即可。
网上找个《爱若琉璃》的简谱,然后按照对应关系创建如下乐谱数组:
节拍就是一个音持续的时间,这里就是PWM脉冲持续的时间,那么持续的时间如何确定呢?
音符(音调)和节拍的计算方法我们上面介绍完了,我们如何利用蜂鸣器播放第一个音呢?
要想播放一个音,就要使用hi_pwm_start()
函数来产生一定频率的脉冲信号。
那频率是怎么计算出来的呢?也就是下面代码中delay_time这个变量是怎么计算出来的呢?
delay_time = 40 * 1000 * 1000/((hi_u32)pitch_names_frequency[ai_ruo_liu_li[0]]);
hi_pwm_start(HI_PWM_PORT_PWM0, delay_time / 2, delay_time);
由上面示波器截图我们得出,hi_pwm_start()函数的最后一个参数,一个单位长度为25ns(即:1/(40*1000*1000)),所以我们发送一定频率的脉冲,其实传进去的参数即为单位时间(25ns)的个数,具体推导过程如下:
一个音确定完之后,只要将这个音按照节拍播放即可,然后遍历完咱们自己转换的乐谱数组即可完成整首歌曲的播放。
因为在按键的中断函数中,每按一次,改变一次布尔变量bSongPlaying
的真假。
通过布尔变量bSongPlaying
的真假,我们可以实现控制音乐播放的开启或停止。
歌曲还挺好听吧?教程写的够详细吧?亲们赏个三连鼓励一下吧,有问题文末留言区讨论交流哈。
公众号留言区置顶留言获取本文对应工程文件及《爱若琉璃》的简谱。
ps: 文章首发于电子发烧友。
程序员小哈带你玩转嵌入式,微信搜索:嵌入式从0到1,更多干货等着你。