使用STM32F411CC,接上一个无源蜂鸣器,播放音乐。
无源蜂鸣器需要外部输入PWM信号驱动蜂鸣器振动发声。
(1)蜂鸣器接在PB10上。
(2)使用定时器2控制输出PWM。
// 音频参数结构体 // 周期:每个音符都有自己的周期,参考https://wenku.baidu.com/view/4d69b296dd88d0d233d46a80.html // 节拍:大概就是每个音符持续多长的时间 struct tone_play_data { uint16_t period; uint16_t duration; }; struct tone_play_ctrl { uint32_t arrCount; const struct tone_play_data *toneParam; }; // 定义一组测试使用的音符 // 本来想用另外一个硬件定时器来专门控制节拍时长,以便达到精确控制 // 后来想了想就没有这么做了 // 现在只采用一个硬件定时器来控制节拍的时长,当让不很精确,不过也差不太远吧 #define COUNT_10MS(N) ((N)*10) const struct tone_play_data testTone[] = { {1912, COUNT_10MS(200)}, {1703, COUNT_10MS(200)}, {1517, COUNT_10MS(200)}, {1432, COUNT_10MS(200)}, {1275, COUNT_10MS(200)}, {1136, COUNT_10MS(200)}, {1012, COUNT_10MS(200)}, {955, COUNT_10MS(200)}, {851, COUNT_10MS(200)}, {758, COUNT_10MS(200)}, {751, COUNT_10MS(200)}, {637, COUNT_10MS(200)}, {568, COUNT_10MS(200)}, {508, COUNT_10MS(200)}, {3816, COUNT_10MS(200)}, // 注意不要忘记最后的这组元素,用来控制结束播放的 {0, 0}, }; // 播放控制变量,每次启动播放前必须初始化 static struct tone_play_ctrl mController = { .arrCount = 0, .toneParam = &testTone[0], }; // 定时器中断回调,要是硬件自己处理该多好啊,这样的效率有点低 // 定时器1&8有个RCR(repetition counter register),也许应该换定时器1&8的 extern void TIM2_IRQHandler(void) { if (TIM_GetFlagStatus(TIM2, TIM_IT_Update)) { // 进来一次说明经历了ARR时长 mController.arrCount ++; // duration单位是ms,ARR经过我们的配置后是1us,所以比较时要对duration乘以1000 // 这里的音符的时长精度肯定有较大的误差,但是咱们暂不考虑处理这个 if ((TIM2->ARR + 1) * mController.arrCount >= mController.toneParam->duration * 1000) { mController.toneParam++; if (0 < mController.toneParam->period) { // 音符切换,必须重置arrCount,重设定时器的ARR寄存器和PWM的CCR寄存器 mController.arrCount = 0; TIM_SetAutoreload(TIM2, mController.toneParam->period - 1); TIM_SetCompare3(TIM2, (mController.toneParam->period + 1) / 2); } else { // 播放完毕了就关闭定时器 TIM_Cmd(TIM2, DISABLE); } } TIM_ClearFlag(TIM2, TIM_IT_Update); } } /* STM32F411CC使用的外部晶振16M,所得到的sysclk为100MHZ */ void environment_setup(void) { /* TIMER2, CHANNEL 3, PB10 */ GPIO_InitTypeDef gpioInitData = { GPIO_Pin_10, GPIO_Mode_AF, GPIO_Fast_Speed, GPIO_OType_PP, GPIO_PuPd_UP, }; TIM_TimeBaseInitTypeDef timeData = { .TIM_CounterMode = TIM_CounterMode_Up, .TIM_ClockDivision = TIM_CKD_DIV1, }; TIM_OCInitTypeDef ocdata = { .TIM_OCMode = TIM_OCMode_PWM2, // PWM模式2:CNT>CCR时输出有效 .TIM_OutputState = TIM_OutputState_Enable, .TIM_OutputNState = TIM_OutputNState_Enable, // 仅Timer1&8, PWM模式用不上这个 .TIM_OCPolarity = TIM_OCPolarity_High, .TIM_OCNPolarity = TIM_OCNPolarity_Low, // PWM模式用不上这个 .TIM_OCIdleState = TIM_OCIdleState_Set, // PWM模式用不上这个 .TIM_OCNIdleState = TIM_OCNIdleState_Reset, // PWM模式用不上这个 }; /* 定时器溢出周期, 系统主频100MHz不分频(TIM_ClockDivision=TIM_CKD_DIV1)直接给到定时器2作为计数时钟, 计数时钟信号100倍分频(TIM_Prescaler=100-1)就是1MHz,一个时钟周期就是1us。 所以对计数时钟信号计数n个(TIM_Prescaler=n-1)就达到了n微秒。 注意,我们后面主要控制 TIM_Prescaler 来达到音乐频率的控制。 这里TIM_Prescaler=99是固定配置,一个计数时钟周期为1us, 如果你使用的是72MHz(比如STM32F103),或者TIM_ClockDivision做了分频,要注意更改。 */ timeData.TIM_Prescaler = 100 - 1; timeData.TIM_Period = toneParam->period - 1; // PWM占空比50%,就是 TIM_Period 的一半 ocdata.TIM_Pulse = (timeData.TIM_Period + 1) / 2; // 我们要使用中断 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 初始化GPIO以及功能复用 GPIO_Init(GPIOB, &gpioInitData); GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_TIM2); // 定时器中断分配 NVIC_InitTypeDef nvicInitData; nvicInitData.NVIC_IRQChannel = TIM2_IRQn; nvicInitData.NVIC_IRQChannelPreemptionPriority = 2; nvicInitData.NVIC_IRQChannelSubPriority = 2; nvicInitData.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvicInitData); TIM_DeInit(TIM2); TIM_TimeBaseInit(TIM2, &timeData); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 启用中断 TIM_OC3Init(TIM2, &ocdata); //TIM2的通道2PWM 模式设置 TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能预装载寄存器 TIM_Cmd(TIM2, ENABLE); // 启动定时器 } /* 接口函数,注意形参传值的正确性 */ void tone_play(const struct tone_play_data *param) { TIM_Cmd(TIM2, DISABLE); if (NULL == param) return; mController.arrCount = 0; mController.toneParam = param; TIM_SetAutoreload(TIM2, mController.toneParam->period - 1); TIM_SetCompare3(TIM2, (mController.toneParam->period + 1) / 2); TIM_Cmd(TIM2, ENABLE); } void tone_stop(void) { TIM_Cmd(TIM2, DISABLE); }
原文:https://www.cnblogs.com/ssdq/p/13712853.html