一、无源蜂鸣器和有源蜂鸣器
有源蜂鸣器内含振荡源,只要一通电就发声,但发生频率固定,音色单一;无源蜂鸣器内部不含振荡源,内部结构相当于电磁场扬声器,可以通过给他输出一定频率的信号才能发声。 人耳能听到的频率范围在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,然后根据music的值在tone数组中得到该音的发声频率(tone[music]),调用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;"。
6、在"stm32f10x.h"中添加函数参数检查宏
- #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端口位带操作的宏。
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.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,494,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函数播放歌曲。
|