用鴻蒙OS在蜂鳴器上播放一曲《兩只老虎》
想了解更多內(nèi)容,請(qǐng)?jiān)L問(wèn):
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
本文介紹如何在HiSpark Wi-Fi IoT套件上,使用Harmony OS IoT硬件子系統(tǒng)的PWM接口 驅(qū)動(dòng)蜂鳴器 播放音樂(lè)。
用PWM輸出方波的API
鴻蒙系統(tǒng)IoT硬件子系統(tǒng)提供了PWM相關(guān)接口,接口頭文件為wifiiot_pwm.h,其中開(kāi)始輸出方波的接口為:
- /**
- * @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輸出的方波頻率
通過(guò)PwmStart接口的注釋,可以知道freq參數(shù)是分頻倍數(shù),PWM實(shí)際輸出的方波頻率等于 PWM時(shí)鐘源頻率 除以 分頻倍數(shù),即
f = Fcs / freq
其中,F(xiàn)cs是PWM時(shí)鐘源頻率;
PWM輸出方波的占空比
通過(guò)PwmStart接口的duty參數(shù)可以控制輸出方波的占空比,占空比是指PWM輸出的方波波形的高電平時(shí)間占整個(gè)方波周期的比例,具體占空比值是 duty 和 freq的比值,例如想要輸出占空比 50%的方波信號(hào),那么duty填的值就要是 freq/2;
音符-頻率對(duì)應(yīng)關(guān)系
這個(gè)表中有一個(gè)規(guī)律——音高升高一個(gè)八度,頻率升高一倍。
表格來(lái)自:https://liam.page/2018/04/09/pitch-interval-and-harmonic/
開(kāi)發(fā)板可以輸出的最低頻率
通過(guò)前面的公式,我們知道:
- PWM輸出的方波頻率和freq成反比,freq越大,輸出的方波頻率越??;
- freq是unsinged short類型,最大值為65535;
因此,輸出頻率的最小值取決于時(shí)鐘源,而PWM的默認(rèn)時(shí)鐘源為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時(shí)鐘源條件下,輸出方波的最低頻率是:160M/65535=2441.44...,這個(gè)頻率還是略高,在上面的表格中沒(méi)有找到音名。但是我可以用上面表格值繼續(xù)往后推算兩個(gè)八度,就能夠覆蓋這個(gè)頻率(不過(guò)通常只使用7個(gè)八度,所以還是有點(diǎn)高)。
如果時(shí)鐘源頻率可以更低,那么輸出頻率也可以更低!
幸運(yùn)的是,通過(guò)調(diào)用hi_pwm_set_clock接口,可以修改時(shí)鐘源:
- /**
- * @ingroup iot_pwm
- *
- * Enumerates the PWM clock sources.CNcomment:PWM時(shí)鐘源枚舉。CNend
- */
- typedef enum {
- PWM_CLK_160M, /**< 160M APB clock.CNcomment:160M 工作時(shí)鐘 CNend */
- PWM_CLK_XTAL, /**< 24M/40M crystal clock.CNcomment:24M或40M 晶體時(shí)鐘 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);
通過(guò)注釋我們知道hi_pwm_set_clock(PWM_CLK_XTAL);可以將時(shí)鐘源設(shè)置為晶體時(shí)鐘,晶體時(shí)鐘可能為24M或40M;
那么問(wèn)題來(lái)了——晶體時(shí)鐘頻率到底是多少?
晶體時(shí)鐘頻率是多少?
可以通過(guò)實(shí)驗(yàn)測(cè)算出晶體時(shí)鐘頻率,具體步驟如下:
- 使用 hi_pwm_set_clock(PWM_CLK_XTAL); 設(shè)置時(shí)鐘源為晶體時(shí)鐘;
- 使用PwmStart(WIFI_IOT_PWM_PORT_PWM0, 20*1000, 40*1000);輸出方波信號(hào);
- 使用示波器測(cè)量方波頻率,根據(jù)測(cè)量的頻率計(jì)算時(shí)鐘源頻率;
經(jīng)實(shí)際測(cè)量,方波頻率為1000Hz,
因此,時(shí)鐘頻率為 1000 * 40 * 1000,即 40 MHz;
可以輸出的方波最低頻率
因此,方波最低頻率就是 40M / 65535 ,也就是:
>>> 40 * 1000 * 1000 / 65535
610.3608758678569
對(duì)照上面的頻率表,可以知道,能夠輸出E5及以上的所有音符;
準(zhǔn)備曲譜
為了代碼實(shí)現(xiàn)起來(lái)簡(jiǎn)單,我選擇了《兩只老虎》的曲譜作為素材,在簡(jiǎn)譜網(wǎng)找到了簡(jiǎn)譜:
簡(jiǎn)譜說(shuō)明
簡(jiǎn)譜上的一些記號(hào),有的同學(xué)可能不太清楚是什么意思,這里簡(jiǎn)單說(shuō)明一下:
- 左上角的1=C是表示調(diào)式(可以不用關(guān)心),1是唱名,C是音名,1=C是正調(diào)(就是常規(guī)的對(duì)應(yīng)關(guān)系: 1-C,2-D, 3-E, 4-F, 5-G, 6-A, 7-B);
- 左上角的 4/4 是四四拍,是指 四分音符為一拍, 每小節(jié)有四拍;
- 下面譜子上的豎線就是每個(gè)小節(jié)分隔符,和4/4對(duì)應(yīng);
- “跑得快”上面5后面的橫線表示延時(shí)一拍;
- “一只沒(méi)有眼睛”一句,5后面的點(diǎn)表示順延半拍,一條下劃線表示二分之一時(shí)間,兩條下劃線表示四分之一時(shí)間;
編寫(xiě)代碼
有了以上知識(shí),我們就可以編寫(xiě)代碼了,關(guān)鍵代碼如下:
- static const uint16_t g_tuneFreqs[] = { // 音符對(duì)應(yīng)的分頻系數(shù)
- 0, // 40M Hz 時(shí)鐘源,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 // 低一個(gè)八度的 5
- };
- // 曲譜音符
- static const uint8_t g_scoreNotes[] = {
- // 《兩只老虎》簡(jiǎn)譜: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, // 最后兩個(gè) 5 應(yīng)該是低八度的,鏈接圖片中的曲譜不對(duì),聲音到最后聽(tīng)起來(lái)不太對(duì)勁
- };
- // 曲譜時(shí)值,根據(jù)簡(jiǎn)譜記譜方法轉(zhuǎn)寫(xiě)
- 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); // 設(shè)置時(shí)鐘源為晶體時(shí)鐘(40MHz,默認(rèn)時(shí)鐘源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); // 音符時(shí)間
- 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;
- }
譜子中最后兩個(gè)5是錯(cuò)誤的,應(yīng)該是低八度的5,也就是5下面應(yīng)該打一個(gè)點(diǎn);我修改了代碼,讓整個(gè)曲子聽(tīng)起來(lái)更自然;
想了解更多內(nèi)容,請(qǐng)?jiān)L問(wèn):
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)