本文介紹如何在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;
音符-頻率對應關系
這個表中有一個規律——音高升高一個八度,頻率升高一倍。
表格來自:https://liam.page/2018/04/09/pitch-interval-and-harmonic/
開發板可以輸出的最低頻率
通過前面的公式,我們知道:
-
PWM輸出的方波頻率和freq成反比,freq越大,輸出的方波頻率越小;
-
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;
那么問題來了——晶體時鍾頻率到底是多少?
晶體時鍾頻率是多少?
可以通過實驗測算出晶體時鍾頻率,具體步驟如下:
-
使用 hi_pwm_set_clock(PWM_CLK_XTAL); 設置時鍾源為晶體時鍾;
-
使用PwmStart(WIFI_IOT_PWM_PORT_PWM0, 201000, 401000);輸出方波信號;
-
使用示波器測量方波頻率,根據測量的頻率計算時鍾源頻率;
經實際測量,方波頻率為1000Hz,
因此,時鍾頻率為 1000 * 40 * 1000,即 40 MHz;
可以輸出的方波最低頻率
因此,方波最低頻率就是 40M / 65535 ,也就是:
40 * 1000 * 1000 / 65535
610.3608758678569
對照上面的頻率表,可以知道,能夠輸出E5及以上的所有音符;
准備曲譜
為了代碼實現起來簡單,我選擇了《兩只老虎》的曲譜作為素材,在簡譜網找到了簡譜:
簡譜說明
簡譜上的一些記號,有的同學可能不太清楚是什么意思,這里簡單說明一下:
-
左上角的1=C是表示調式(可以不用關心),1是唱名,C是音名,1=C是正調(就是常規的對應關系: 1-C,2-D, 3-E, 4-F, 5-G, 6-A, 7-B);
-
左上角的 4/4 是四四拍,是指 四分音符為一拍, 每小節有四拍;
-
下面譜子上的豎線就是每個小節分隔符,和4/4對應;
-
“跑得快”上面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://developer.huawei.com/consumer/cn/forum/topic/0204398682948650101?fid=0101303901040230869
作者:思維