使用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