一、無源蜂鳴器和有源蜂鳴器
有源蜂鳴器內含振盪源,只要一通電就發聲,但發生頻率固定,音色單一;無源蜂鳴器內部不含振盪源,內部結構相當於電磁場揚聲器,可以通過給他輸出一定頻率的信號才能發聲。
人耳能聽到的頻率範圍在20Hz--20kHz之間,通過STM32的GPIO引腳快速切換高低電平輸出就能實現無源蜂鳴器的發聲,切換的頻率不同,發出的音調就不一樣。
二、音樂播放的實現
一段音樂就是不同頻率的聲音按一定的時間節拍轉換髮出。所以音樂包含音調和節拍信息。
C調各音符頻率如下:
音符 | 頻率 Hz | 音符 | 頻率 Hz |
低1 Do | 262 | 中1 Do | 523 |
低2 Re | 294 | 中2 Re | 587 |
低3 Mi | 330 | 中3 Mi | 659 |
低4 Fa | 349 | 中4 Fa | 698 |
低5 So | 392 | 中5 So | 784 |
低6 La | 440 | 中6 La | 880 |
低7 Si | 523 | 中7 Si | 988 |
如果要實現歌曲“紅塵情歌”,要準備相應的數據。
歌譜如下:
程序中首先準備音頻數據表:
// 低Si Do Re Mi Fa So La Si ¸高Do¸高Re¸高Mi¸高Fa¸高So 無
uc16 tone[] ={247,262,294,330,349,392,440,294,523, 587, 659, 698, 784, 1000};
u8 music[]={ 5,5,6,8,7,6,5,6,13,13,……};//音調
u8 time[] = { 2,4,2,2,2,2,2,8,4, 4, ……}; //節拍時間
依次從音調數組中取music[i],然後根據music[i]的值在tone數組中得到該音的發聲頻率(tone[music[i]]),調用sound函數控制蜂鳴器發聲,聲音的發聲時間有time數組控制。
三、項目創建與配置
1、創建項目文件夾(設爲pMusic)
2、通過Keil5創建新項目,保存在所創建的文件夾中(設項目名爲pMusic),選擇MCU芯片爲"STM32F103ZE"(本程序使用的硬件爲:STM32-PZ6806L開發板)
3、在pMusic項目文件夾中新建"CMSIS"、"Device"、"Public"、"Startup"、"User"和"Lib"文件夾。
① 在"CMSIS"文件夾中複製"core_cm3.h"和"core_cm3.c"文件;
② 在" Device "文件夾中複製"stm32f10x.h"、"system_stm32f10x.h"和"system_stm32f10x.c"文件;
③ 在" Startup "文件夾中複製"startup_stm32f10x_hd.s"文件;
④在"Lib"文件夾中新建"inc"和"src"兩個子文件夾,在"inc"文件夾中複製"misc.h"、"stm32f10x_gpio.h"和"stm32f10x_rcc.h"文件;在"src"文件夾中複製"misc.c"、"stm32f10x_gpio.c"和"stm32f10x_rcc.c"文件;
4、爲項目添加"CMSIS"、"Device"、"Public"、"Startup"、"User"和"Lib"組,並將上述C程序文件和"startup_stm32f10x_hd.s"啓動文件加入到相應組中。展開項目樹如下:
5、打開“項目配置”對話框,在"Output"選項卡中選擇"Create HEX File",在"C/C++"選項卡中的"Include Paths"中添加如下包含路徑:".\CMSIS;", ".\Device;", ".\Lib\inc;",".\Public;"。
(以上步驟可以參看:使用STM32固件庫操作控制LED燈(CMSIS) 使用STM32固件庫函數操作控制LED燈
6、在"stm32f10x.h"中添加函數參數檢查宏
(參看:使用STM32固件庫函數操作控制LED燈)
#ifdef USE_FULL_ASSERT /** * @brief 這個assert_param宏用於函數參數檢查 * @param expr:如果expr是 false,就調用 assert_failed函數報告源文件名和 * 失敗的行號,如果expr是 true ,就返回一個空值 * @retval None */ #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__)) /* Exported functions ------------------------------------------------------- */ void assert_failed(uint8_t* file, uint32_t line); #else #define assert_param(expr) ((void)0) #endif /* USE_FULL_ASSERT */ |
7、新建一個文件(system.h),保存到"Public"文件夾中,內容爲:
#ifndef __SYSTEM__H #define __SYSTEM__H #include "stm32f10x.h" //定義位帶地址宏 #define BITBAND(addr,bitnum) ((addr&0xF0000000) + 0x02000000 + ((addr&0x000FFFFF)<<5) + (bitnum<<2)) #define MEM_ADDR(addr) *((volatile unsigned long *)(addr)) #define BIT_ADDR(addr,bitnum) MEM_ADDR(BITBAND(addr,bitnum)) //IO口地址映射 //數據輸出寄存器地址 #define GPIOA_ODR_Addr (GPIOA_BASE + 12) #define GPIOB_ODR_Addr (GPIOB_BASE + 12) #define GPIOC_ODR_Addr (GPIOC_BASE + 12) #define GPIOD_ODR_Addr (GPIOD_BASE + 12) #define GPIOE_ODR_Addr (GPIOE_BASE + 12) #define GPIOF_ODR_Addr (GPIOF_BASE + 12) #define GPIOG_ODR_Addr (GPIOG_BASE + 12) //數據輸入寄存器地址 #define GPIOA_IDR_Addr (GPIOA_BASE + 12) #define GPIOB_IDR_Addr (GPIOB_BASE + 12) #define GPIOC_IDR_Addr (GPIOC_BASE + 12) #define GPIOD_IDR_Addr (GPIOD_BASE + 12) #define GPIOE_IDR_Addr (GPIOE_BASE + 12) #define GPIOF_IDR_Addr (GPIOF_BASE + 12) #define GPIOG_IDR_Addr (GPIOG_BASE + 12) #endif |
該文件定義了GPIO端口位帶操作的宏。
(位帶操作請參看:通過位帶地址操作GPIO在數碼管顯示數字)
8、新建文件"SysTick.h",保存到"Public"文件夾中,內容爲:
#ifndef __SysTick__H #define __SysTick__H #include "stm32f10x.h" void SysTick_Init(u8 SYSCLK); void delay_us(u32 nus); void delay_ms(u16 nms); #endif |
新建文件"SysTick.c",保存到"Public"文件夾中,內容爲:
#include "SysTick.h" #include "misc.h" u8 fac_us = 0; u16 fac_ms = 0; void SysTick_Init(u8 SYSCLK) { SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); fac_us = SYSCLK / 8; fac_ms = (u16)fac_us*1000; }
void delay_us(u32 nus) { u32 temp; SysTick->LOAD = nus * fac_us; SysTick->VAL = 0; SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; do{ temp = SysTick->CTRL; }while((temp&0x01)&&(!(temp&(1<<16)))); SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; SysTick->VAL = 0; } void delay_ms(u16 nms) { u32 temp; SysTick->LOAD = nms * fac_ms; SysTick->VAL = 0; SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; do{ temp = SysTick->CTRL; }while((temp&0x01)&&(!(temp&(1<<16)))); SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; SysTick->VAL = 0; } |
這兩個文件實現了通過SysTick精準延時的函數,提供給後續的音頻頻率產生程序使用。
(關於SysTick,請參看:在STM32項目中使用SysTick實現延時)
將"SysTick.c"文件添加到項目的"Public"組中。
9、實現發聲
①開發板無源蜂鳴器的電路連接如下:
從電路連接可以看出通過MCU的PB5(GPIOB_5)控制蜂鳴器的發聲。
②在項目文件夾的"User"文件夾下新建"Beep"文件夾,在項目中新建"beep.h"文件,保存在"User/Beep"文件夾中,文件內容爲:
#ifndef __BEEP__H #define __BEEP__H #include "system.h" #include "stm32f10x_gpio.h" #include "stm32f10x_rcc.h" //定義GPIOB的位地址變量宏 #define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) #define PBeep PBout(5) #define BEEP_PORT GPIOB #define BEEP_PIN GPIO_Pin_5 #define BEEP_PORT_RCC RCC_APB2Periph_GPIOB void BEEP_Init(void); void Sound(u16 frq); void play(void); #endif |
③在項目中新建"beep.c"文件,保存在"User/Beep"文件夾中,文件內容爲:
#include "beep.h" #include "systick.h" void BEEP_Init(void) { GPIO_InitTypeDef GPIO_mode; RCC_APB2PeriphClockCmd( BEEP_PORT_RCC, ENABLE ); //使能GPIOB時鐘 GPIO_mode.GPIO_Pin = BEEP_PIN; GPIO_mode.GPIO_Speed = GPIO_Speed_50MHz; GPIO_mode.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(BEEP_PORT, &GPIO_mode); //設置GPIOB_5爲推輓輸出,50MHz速度 }
void Sound(u16 frq) { u32 n; if(frq != 1000) //如果頻率不爲1000則按頻率輸出,否則只延時 { n = 500000/((u32)frq); PBeep = 0; delay_us(n); PBeep = 1; delay_us(n); }else delay_us(1000); }
void play(void) { // 低7 1 2 3 4 5 6 7 高1 高2 高3 高4 高5 不發音 uc16 tone[] = {247,262,294,330,349,392,440,294,523,587,659,698,784,1000};//音頻數據表 //紅塵情歌 u8 music[]={ 5,5,6,8,7,6,5,6,13,13,//音調 5,5,6,8,7,6,5,3,13,13, 2,2,3,5,3,5,6,3,2,1, 6,6,5,6,5,3,6,5,13,13,
5,5,6,8,7,6,5,6,13,13, 5,5,6,8,7,6,5,3,13,13, 2,2,3,5,3,5,6,3,2,1, 6,6,5,6,5,3,6,1,
13,8,9,10,10,9,8,10,9,8,6, 13,6,8,9,9,8,6,9,8,6,5, 13,2,3,5,5,3,5,5,6,8,7,6, 6,10,9,9,8,6,5,6,8 }; u8 time[] = { 2,4,2,2,2,2,2,8,4, 4, //時間 2,4,2,2,2,2,2,8,4, 4, 2,4,2,4,2,2,4,2,2,8, 2,4,2,2,2,2,2,8,4 ,4,
2,4,2,2,2,2,2,8,4, 4, 2,4,2,2,2,2,2,8,4, 4, 2,4,2,4,2,2,4,2,2,8, 2,4,2,2,2,2,2,8,
4, 2,2,2, 4, 2,2,2, 2,2,8, 4, 2,2,2,4,2,2,2,2,2,8, 4, 2,2,2,4,2,2,5,2,6,2,4, 2,2 ,2,4,2,4,2,2,12 }; u32 yanshi; u16 i,e; yanshi = 10; for(i=0;i<sizeof(music)/sizeof(music[0]);i++){ for(e=0;e<((u16)time[i])*tone[music[i]]/yanshi;e++){ Sound((u32)tone[music[i]]); } } } |
程序中定義了端口使能和方式配置函數BEEP_Init,輸出音頻函數Sound和播放音樂函數play。
④ 將"beep.c"文件加入到項目的"User"組中;在"C/C++"選項卡中的"Include Paths"中添加包含路徑:"\User\Beep;"。
10、在項目中新建"main.c"文件,保存在"User "文件夾中,文件內容爲:
#include "beep.h" #include "SysTick.h" int main() { SysTick_Init(72); BEEP_Init(); while(1) { play(); } } |
"main.c"文件中包含main函數反覆調用play函數播放歌曲。
11、編譯、連接、下載程序。
代碼下載地址:下載源碼