首页 > 其他 > 详细

利用STM32播放音乐

时间:2020-09-22 17:46:30      阅读:123      评论:0      收藏:0      [点我收藏+]

使用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);
}

  

利用STM32播放音乐

原文:https://www.cnblogs.com/ssdq/p/13712853.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!