利用STM32播放音乐

使用STM32F411CC,接上一个无源蜂鸣器,播放音乐。

无源蜂鸣器需要外部输入PWM信号驱动蜂鸣器振动发声。

(1)蜂鸣器接在PB10上。

(2)使用定时器2控制输出PWM。

第一次构思的版本:

  1 // 音频参数结构体
  2 // 周期:每个音符都有自己的周期,参考https://wenku.baidu.com/view/4d69b296dd88d0d233d46a80.html
  3 // 节拍:大概就是每个音符持续多长的时间
  4 struct tone_play_data {
  5     uint16_t    period;
  6     uint16_t    duration;
  7 };
  8 
  9 struct tone_play_ctrl {
 10     uint32_t    arrCount;
 11     const struct tone_play_data *toneParam;
 12 };
 13 
 14 // 定义一组测试使用的音符
 15 // 本来想用另外一个硬件定时器来专门控制节拍时长,以便达到精确控制
 16 // 后来想了想就没有这么做了
 17 // 现在只采用一个硬件定时器来控制节拍的时长,当让不很精确,不过也差不太远吧
 18 #define COUNT_10MS(N) ((N)*10)
 19 const struct tone_play_data testTone[] = {
 20     {1912, COUNT_10MS(200)},
 21     {1703, COUNT_10MS(200)},
 22     {1517, COUNT_10MS(200)},
 23     {1432, COUNT_10MS(200)},
 24     {1275, COUNT_10MS(200)},
 25     {1136, COUNT_10MS(200)},
 26     {1012, COUNT_10MS(200)},
 27     {955, COUNT_10MS(200)},
 28     {851, COUNT_10MS(200)},
 29     {758, COUNT_10MS(200)},
 30     {751, COUNT_10MS(200)},
 31     {637, COUNT_10MS(200)},
 32     {568, COUNT_10MS(200)},
 33     {508, COUNT_10MS(200)},
 34     {3816, COUNT_10MS(200)},
 35     
 36     // 注意不要忘记最后的这组元素,用来控制结束播放的
 37     {0, 0},
 38 };
 39 
 40 // 播放控制变量,每次启动播放前必须初始化
 41 static struct tone_play_ctrl mController = {
 42     .arrCount = 0,
 43     .toneParam = &testTone[0],
 44 };
 45 
 46 // 定时器中断回调,要是硬件自己处理该多好啊,这样的效率有点低
 47 // 定时器1&8有个RCR(repetition counter register),也许应该换定时器1&8的
 48 extern void TIM2_IRQHandler(void)
 49 {
 50     if (TIM_GetFlagStatus(TIM2, TIM_IT_Update))
 51     {
 52         // 进来一次说明经历了ARR时长
 53         mController.arrCount ++;
 54         
 55         // duration单位是ms,ARR经过我们的配置后是1us,所以比较时要对duration乘以1000
 56         // 这里的音符的时长精度肯定有较大的误差,但是咱们暂不考虑处理这个
 57         if ((TIM2->ARR + 1) * mController.arrCount >= mController.toneParam->duration * 1000)
 58         {
 59             mController.toneParam++;
 60             if (0 < mController.toneParam->period)
 61             {
 62                 // 音符切换,必须重置arrCount,重设定时器的ARR寄存器和PWM的CCR寄存器
 63                 mController.arrCount = 0;
 64                 TIM_SetAutoreload(TIM2, mController.toneParam->period - 1);
 65                 TIM_SetCompare3(TIM2, (mController.toneParam->period + 1) / 2);
 66             }
 67             else
 68             {
 69                 // 播放完毕了就关闭定时器
 70                 TIM_Cmd(TIM2, DISABLE);
 71             }
 72         }
 73         
 74         TIM_ClearFlag(TIM2, TIM_IT_Update);
 75     }
 76 }
 77 
 78 /*
 79 STM32F411CC使用的外部晶振16M,所得到的sysclk为100MHZ
 80 */
 81 void environment_setup(void)
 82 {
 83     /* TIMER2, CHANNEL 3, PB10 */
 84     GPIO_InitTypeDef gpioInitData = {
 85         GPIO_Pin_10, 
 86         GPIO_Mode_AF,
 87         GPIO_Fast_Speed,
 88         GPIO_OType_PP,
 89         GPIO_PuPd_UP,
 90     };
 91     TIM_TimeBaseInitTypeDef timeData = {
 92         .TIM_CounterMode = TIM_CounterMode_Up,
 93         .TIM_ClockDivision = TIM_CKD_DIV1,
 94     };
 95     TIM_OCInitTypeDef ocdata = {
 96         .TIM_OCMode = TIM_OCMode_PWM2, // PWM模式2:CNT>CCR时输出有效
 97         .TIM_OutputState = TIM_OutputState_Enable,
 98         .TIM_OutputNState = TIM_OutputNState_Enable, // 仅Timer1&8, PWM模式用不上这个
 99         .TIM_OCPolarity = TIM_OCPolarity_High,
100         .TIM_OCNPolarity = TIM_OCNPolarity_Low, // PWM模式用不上这个
101         .TIM_OCIdleState = TIM_OCIdleState_Set, // PWM模式用不上这个
102         .TIM_OCNIdleState = TIM_OCNIdleState_Reset, // PWM模式用不上这个
103     };
104 
105     /* 定时器溢出周期,
106        系统主频100MHz不分频(TIM_ClockDivision=TIM_CKD_DIV1)直接给到定时器2作为计数时钟,
107        计数时钟信号100倍分频(TIM_Prescaler=100-1)就是1MHz,一个时钟周期就是1us。
108        所以对计数时钟信号计数n个(TIM_Prescaler=n-1)就达到了n微秒。
109        注意,我们后面主要控制 TIM_Prescaler 来达到音乐频率的控制。
110        这里TIM_Prescaler=99是固定配置,一个计数时钟周期为1us,
111        如果你使用的是72MHz(比如STM32F103),或者TIM_ClockDivision做了分频,要注意更改。
112     */
113     timeData.TIM_Prescaler = 100 - 1; 
114     timeData.TIM_Period = toneParam->period - 1;
115     // PWM占空比50%,就是 TIM_Period 的一半
116     ocdata.TIM_Pulse = (timeData.TIM_Period + 1) / 2;
117 
118     // 我们要使用中断
119     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
120     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
121     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
122 
123     // 初始化GPIO以及功能复用
124     GPIO_Init(GPIOB, &gpioInitData);
125     GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_TIM2);
126 
127     // 定时器中断分配
128     NVIC_InitTypeDef nvicInitData;
129     nvicInitData.NVIC_IRQChannel = TIM2_IRQn;
130     nvicInitData.NVIC_IRQChannelPreemptionPriority = 2;
131     nvicInitData.NVIC_IRQChannelSubPriority = 2;
132     nvicInitData.NVIC_IRQChannelCmd = ENABLE;
133     NVIC_Init(&nvicInitData);
134     
135     TIM_DeInit(TIM2);
136     TIM_TimeBaseInit(TIM2, &timeData);
137     TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 启用中断
138     TIM_OC3Init(TIM2, &ocdata); //TIM2的通道2PWM 模式设置
139     TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能预装载寄存器
140     TIM_Cmd(TIM2, ENABLE); // 启动定时器
141 }
142 
143 /*
144 接口函数,注意形参传值的正确性
145 */
146 void tone_play(const struct tone_play_data *param)
147 {
148     TIM_Cmd(TIM2, DISABLE);
149     if (NULL == param)
150         return;
151     mController.arrCount = 0;
152     mController.toneParam = param;
153     TIM_SetAutoreload(TIM2, mController.toneParam->period - 1);
154     TIM_SetCompare3(TIM2, (mController.toneParam->period + 1) / 2);
155     TIM_Cmd(TIM2, ENABLE);    
156 }
157 
158 void tone_stop(void)
159 {
160     TIM_Cmd(TIM2, DISABLE);
161 }
View Code

第二次构思的版本:

上面的代码还可以再继续优化一下:

(1)每个音符的频率都是固定的,我们可以弄一个固定的音符表格。

(2)我们设定一个变量,用来设定一个节拍的周期,这样对于1/4,1/8之类的节拍就只要对应的乘除就好。

(3)比上面那种处理方式要节约空间。

  1 // 最新编辑:2020/9/24
  2 typedef enum {
  3     TONE_END = 0,
  4     // 好像有个不发音的?保持1KHz的频率
  5     TONE_MUTE,
  6     TONE_LOW_1,
  7     TONE_LOW_2,
  8     TONE_LOW_3,
  9     TONE_LOW_4,
 10     TONE_LOW_5,
 11     TONE_LOW_6,
 12     TONE_LOW_7,
 13     TONE_MIDDLE_1,
 14     TONE_MIDDLE_2,
 15     TONE_MIDDLE_3,
 16     TONE_MIDDLE_4,
 17     TONE_MIDDLE_5,
 18     TONE_MIDDLE_6,
 19     TONE_MIDDLE_7,
 20     TONE_HIGH_1,
 21     TONE_HIGH_2,
 22     TONE_HIGH_3,
 23     TONE_HIGH_4,
 24     TONE_HIGH_5,
 25     TONE_HIGH_6,
 26     TONE_HIGH_7,
 27     // 将你的音符索引添加到这一行的上面
 28     TONE_TOTAL
 29 } ToneSymbIdx; 
 30 
 31 #define TONE_SYMBOL_END_PERIOD     (0)
 32 #define TONE_SYMBOL_MUTE_PERIOD    (1000)
 33 const uint16_t toneSymbols[TONE_TOTAL] = {
 34     TONE_SYMBOL_END_PERIOD,
 35     TONE_SYMBOL_MUTE_PERIOD,
 36     // 低音12345657
 37     3816, 3401, 3030, 2865, 2551, 2272, 2024,
 38     // 中音12345657
 39     1912, 1703, 1517, 1432, 1275, 1136, 1012,
 40     // 高音12345657
 41     955,  851,  758,  751,  637,  568,  508,
 42 };
 43 // 音乐节拍主要分为:
 44 // 1/4,2/4,3/4,4/4
 45 const uint32_t toneBeatBaseUS = 128000;
 46 #define TONE_BEAT_1_4_MULTIPLIER   (1)
 47 #define TONE_BEAT_2_4_MULTIPLIER   (2)
 48 #define TONE_BEAT_3_4_MULTIPLIER   (4)
 49 #define TONE_BEAT_4_4_MULTIPLIER   (8)
 50 
 51 struct tone_play_data {
 52     ToneSymbIdx symbolIndex;
 53     uint8_t beatMultiple;
 54 };
 55 
 56 struct tone_play_ctrl {
 57     uint32_t    arrCount;
 58     const struct tone_play_data *toneParam;
 59 };
 60 
 61 // 相比前面的设计,这样一首歌就可以节省很多空间出来了
 62 const struct tone_play_data testTone[] = {
 63     {TONE_LOW_1,    TONE_BEAT_1_4_MULTIPLIER},
 64     {TONE_LOW_2,    TONE_BEAT_2_4_MULTIPLIER},
 65     {TONE_LOW_3,    TONE_BEAT_3_4_MULTIPLIER},
 66     {TONE_LOW_4,    TONE_BEAT_4_4_MULTIPLIER},
 67     {TONE_LOW_5,    TONE_BEAT_1_4_MULTIPLIER},
 68     {TONE_LOW_6,    TONE_BEAT_2_4_MULTIPLIER},
 69     {TONE_LOW_7,    TONE_BEAT_3_4_MULTIPLIER},
 70     {TONE_MUTE,     TONE_BEAT_1_4_MULTIPLIER},
 71     {TONE_MIDDLE_1, TONE_BEAT_1_4_MULTIPLIER},
 72     {TONE_MIDDLE_2, TONE_BEAT_2_4_MULTIPLIER},
 73     {TONE_MIDDLE_3, TONE_BEAT_3_4_MULTIPLIER},
 74     {TONE_MIDDLE_4, TONE_BEAT_4_4_MULTIPLIER},
 75     {TONE_MIDDLE_5, TONE_BEAT_1_4_MULTIPLIER},
 76     {TONE_MIDDLE_6, TONE_BEAT_2_4_MULTIPLIER},
 77     {TONE_MIDDLE_7, TONE_BEAT_3_4_MULTIPLIER},
 78     {TONE_MUTE,     TONE_BEAT_1_4_MULTIPLIER},
 79     {TONE_HIGH_1,   TONE_BEAT_1_4_MULTIPLIER},
 80     {TONE_HIGH_2,   TONE_BEAT_2_4_MULTIPLIER},
 81     {TONE_HIGH_3,   TONE_BEAT_3_4_MULTIPLIER},
 82     {TONE_HIGH_4,   TONE_BEAT_4_4_MULTIPLIER},
 83     {TONE_HIGH_5,   TONE_BEAT_1_4_MULTIPLIER},
 84     {TONE_HIGH_6,   TONE_BEAT_3_4_MULTIPLIER},
 85     {TONE_HIGH_7,   TONE_BEAT_4_4_MULTIPLIER},
 86 
 87     // 最后一个元素必须是{0, 0}
 88     {TONE_END, 0},
 89 };
 90 
 91 // 播放控制变量,每次启动播放前必须初始化
 92 static struct tone_play_ctrl mController;
 93 
 94 // 定时器中断回调,要是硬件自己处理该多好啊,这样的效率有点低
 95 // 定时器1&8有个RCR(repetition counter register),也许应该换定时器1&8的
 96 extern void TIM2_IRQHandler(void)
 97 {
 98     if (TIM_GetFlagStatus(TIM2, TIM_IT_Update))
 99     {
100         // 进来一次说明经历了ARR时长
101         mController.arrCount ++;
102         
103         // duration单位是ms,ARR经过我们的配置后是1us,所以比较时要对duration乘以1000
104         // 这里的音符的时长精度肯定有较大的误差,但是咱们暂不考虑处理这个
105         if ((TIM2->ARR + 1) * mController.arrCount >= mController.toneParam->beatMultiple * toneBeatBaseUS)
106         {
107             mController.toneParam++;
108             if (0 < mController.toneParam->beatMultiple && TONE_END < mController.toneParam->symbolIndex)
109             {
110                 // 音符切换,必须重置arrCount,重设定时器的ARR寄存器和PWM的CCR寄存器
111                 mController.arrCount = 0;
112                 TIM_SetAutoreload(TIM2, toneSymbols[mController.toneParam->symbolIndex] - 1);
113                 
114                 // 如果是静音音符,直接控制PWM周期与节拍周期一致就能达到静音了
115                 if (TONE_MUTE == mController.toneParam->symbolIndex)
116                     TIM_SetCompare3(TIM2, toneSymbols[mController.toneParam->symbolIndex]);
117                 else
118                     TIM_SetCompare3(TIM2, (toneSymbols[mController.toneParam->symbolIndex] + 1) / 2);
119             }
120             else
121             {
122                 // 播放完毕了就关闭定时器
123                 TIM_Cmd(TIM2, DISABLE);
124                 TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Disable);
125             }
126         }
127         
128         TIM_ClearFlag(TIM2, TIM_IT_Update);
129     }
130 }
131 
132 /*
133 STM32F411CC使用的外部晶振16M,所得到的sysclk为100MHZ
134 */
135 void environment_setup(void)
136 {
137     /* TIMER2, CHANNEL 3, PB10 */
138     GPIO_InitTypeDef gpioInitData = {
139         GPIO_Pin_10, 
140         GPIO_Mode_AF,
141         GPIO_Fast_Speed,
142         GPIO_OType_PP,
143         GPIO_PuPd_UP,
144     };
145     TIM_TimeBaseInitTypeDef timeData = {
146         .TIM_CounterMode = TIM_CounterMode_Up,
147         .TIM_ClockDivision = TIM_CKD_DIV1,
148     };
149     TIM_OCInitTypeDef ocdata = {
150         .TIM_OCMode = TIM_OCMode_PWM2, // PWM模式2:CNT>CCR时输出有效
151         .TIM_OutputState = TIM_OutputState_Enable,
152         .TIM_OutputNState = TIM_OutputNState_Enable, // 仅Timer1&8, PWM模式用不上这个
153         .TIM_OCPolarity = TIM_OCPolarity_High,
154         .TIM_OCNPolarity = TIM_OCNPolarity_Low, // PWM模式用不上这个
155         .TIM_OCIdleState = TIM_OCIdleState_Set, // PWM模式用不上这个
156         .TIM_OCNIdleState = TIM_OCNIdleState_Reset, // PWM模式用不上这个
157     };
158 
159     /* 定时器溢出周期,
160        系统主频100MHz不分频(TIM_ClockDivision=TIM_CKD_DIV1)直接给到定时器2作为计数时钟,
161        计数时钟信号100倍分频(TIM_Prescaler=100-1)就是1MHz,一个时钟周期就是1us。
162        所以对计数时钟信号计数n个(TIM_Prescaler=n-1)就达到了n微秒。
163        注意,我们后面主要控制 TIM_Prescaler 来达到音乐频率的控制。
164        这里TIM_Prescaler=99是固定配置,一个计数时钟周期为1us,
165        如果你使用的是72MHz(比如STM32F103),或者TIM_ClockDivision做了分频,要注意更改。
166     */
167     timeData.TIM_Prescaler = 100 - 1; // 这个值为99,固定不动,如果是72M,则改成71
168     timeData.TIM_Period = 1000 - 1; // 这个999是1ms,后面会动态更改,这里随便写
169     // PWM占空比50%,就是 TIM_Period 的一半
170     ocdata.TIM_Pulse = 500;
171 
172     // 我们要使用中断
173     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
174     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
175     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
176 
177     // 初始化GPIO以及功能复用
178     GPIO_Init(GPIOB, &gpioInitData);
179     GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_TIM2);
180 
181     // 定时器中断分配
182     NVIC_InitTypeDef nvicInitData;
183     nvicInitData.NVIC_IRQChannel = TIM2_IRQn;
184     nvicInitData.NVIC_IRQChannelPreemptionPriority = 2;
185     nvicInitData.NVIC_IRQChannelSubPriority = 2;
186     nvicInitData.NVIC_IRQChannelCmd = ENABLE;
187     NVIC_Init(&nvicInitData);
188     
189     TIM_DeInit(TIM2);
190     TIM_TimeBaseInit(TIM2, &timeData);
191     TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 启用中断
192     TIM_OC3Init(TIM2, &ocdata); //TIM2的通道2PWM 模式设置
193     TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Disable); //使能预装载寄存器
194     TIM_Cmd(TIM2, DISABLE); // 启动定时器
195 }
196 
197 /*
198 接口函数,注意形参传值的正确性
199 */
200 void tone_play(const struct tone_play_data *param)
201 {
202     if (NULL == param)
203         return;
204     // 如果你有RTOS,这里要注意使用互斥量,保证线程安全
205     TIM_Cmd(TIM2, DISABLE);
206     mController.arrCount = 0;
207     mController.toneParam = param;
208     TIM_SetAutoreload(TIM2, toneSymbols[mController.toneParam->symbolIndex] - 1);
209     TIM_SetCompare3(TIM2, (toneSymbols[mController.toneParam->symbolIndex] + 1) / 2);
210     TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable);
211     TIM_Cmd(TIM2, ENABLE);
212 }
213 
214 void tone_stop(void)
215 {
216     // 如果你有RTOS,这里要注意使用互斥量,保证线程安全
217     TIM_Cmd(TIM2, DISABLE);
218     TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Disable);
219 }
View Code

最终版本(源码H和C文件都贴上来了,复制黏贴后就能使用):

  1 // H文件内容
  2 #if defined(__BEEP_MUSIC_SUPPORT__)
  3 typedef enum tonesymbol {
  4     TONE_SYMB_END = 0,
  5     // 好像有个不发音的?保持1KHz的频率
  6     TONE_SYMB_MUTE,
  7     TONE_SYMB_LOW_1,
  8     TONE_SYMB_LOW_1H,
  9     TONE_SYMB_LOW_2,
 10     TONE_SYMB_LOW_2H,
 11     TONE_SYMB_LOW_3,
 12     TONE_SYMB_LOW_3H,
 13     TONE_SYMB_LOW_4,
 14     TONE_SYMB_LOW_4H,
 15     TONE_SYMB_LOW_5,
 16     TONE_SYMB_LOW_5H,
 17     TONE_SYMB_LOW_6,
 18     TONE_SYMB_LOW_6H,
 19     TONE_SYMB_LOW_7,
 20     TONE_SYMB_LOW_7H,
 21     TONE_SYMB_MIDDLE_1,
 22     TONE_SYMB_MIDDLE_1H,
 23     TONE_SYMB_MIDDLE_2,
 24     TONE_SYMB_MIDDLE_2H,
 25     TONE_SYMB_MIDDLE_3,
 26     TONE_SYMB_MIDDLE_3H,
 27     TONE_SYMB_MIDDLE_4,
 28     TONE_SYMB_MIDDLE_4H,
 29     TONE_SYMB_MIDDLE_5,
 30     TONE_SYMB_MIDDLE_5H,
 31     TONE_SYMB_MIDDLE_6,
 32     TONE_SYMB_MIDDLE_6H,
 33     TONE_SYMB_MIDDLE_7,
 34     TONE_SYMB_MIDDLE_7H,
 35     TONE_SYMB_HIGH_1,
 36     TONE_SYMB_HIGH_1H,
 37     TONE_SYMB_HIGH_2,
 38     TONE_SYMB_HIGH_2H,
 39     TONE_SYMB_HIGH_3,
 40     TONE_SYMB_HIGH_3H,
 41     TONE_SYMB_HIGH_4,
 42     TONE_SYMB_HIGH_4H,
 43     TONE_SYMB_HIGH_5,
 44     TONE_SYMB_HIGH_5H,
 45     TONE_SYMB_HIGH_6,
 46     TONE_SYMB_HIGH_6H,
 47     TONE_SYMB_HIGH_7,
 48     TONE_SYMB_HIGH_7H,
 49     // 将你的音符索引添加到这一行的上面
 50     TONE_TOTAL
 51 } ToneSymbIdx; 
 52 
 53 typedef enum tonebeat {
 54     TONE_BEAT_END = 0,
 55     TONE_BEAT_4_1, // 4/1拍
 56     TONE_BEAT_3_1, // 3/1拍
 57     TONE_BEAT_2_1, // 2/1拍
 58     TONE_BEAT_3_2, // 3/2拍
 59     TONE_BEAT_1_1, // 1/1拍
 60     TONE_BEAT_3_4, // 3/4拍
 61     TONE_BEAT_1_2, // 1/2拍
 62     TONE_BEAT_1_3, // 1/3拍
 63     TONE_BEAT_1_4, // 1/4拍
 64 
 65     TONE_BEAT_TOTAL,
 66 } ToneBeatIdx;
 67 
 68 struct tone_play_data {
 69     ToneSymbIdx symbolIndex;
 70     ToneBeatIdx beatIndex;
 71 };
 72 
 73 extern const struct tone_play_data beep_Du[];
 74 /*
 75 // 定义自己的音乐曲谱
 76 // const struct tone_play_data beep_Du[] = {
 77 //     {TONE_SYMB_HIGH_1, TONE_BEAT_1_2},
 78 //     // 最后一个元素必须是{0, 0}
 79 //     {TONE_SYMB_END, TONE_BEAT_END},
 80 // };
 81 */
 82 
 83 /*
 84 接口函数,注意形参传值的正确性
 85 */
 86 extern void tone_play(const struct tone_play_data *param);
 87 extern void tone_stop(void);
 88 #endif
 89 
 90 // C文件内容
 91 #if defined(__BEEP_MUSIC_SUPPORT__) // TONE PLAY
 92 // 每个音符的周期
 93 #define TONE_SYMBOL_END_PERIOD     (0)
 94 #define TONE_SYMBOL_MUTE_PERIOD    (10000)
 95 static const uint16_t toneSymbolPeriod[TONE_TOTAL] = {
 96     #ifdef ADD_SYMB_VALUE
 97     #undef ADD_SYMB_VALUE
 98     #endif
 99     #define ADD_SYMB_VALUE(E,V) (V)
100     ADD_SYMB_VALUE(TONE_SYMB_END,    TONE_SYMBOL_END_PERIOD),
101     ADD_SYMB_VALUE(TONE_SYMB_MUTE,   TONE_SYMBOL_MUTE_PERIOD),
102     ADD_SYMB_VALUE(TONE_SYMB_LOW_1,  2865),
103     ADD_SYMB_VALUE(TONE_SYMB_LOW_1H, 2703),
104     ADD_SYMB_VALUE(TONE_SYMB_LOW_2,  2551),
105     ADD_SYMB_VALUE(TONE_SYMB_LOW_2H, 2410),
106     ADD_SYMB_VALUE(TONE_SYMB_LOW_3,  2273),
107     ADD_SYMB_VALUE(TONE_SYMB_LOW_3H, 2155),
108     ADD_SYMB_VALUE(TONE_SYMB_LOW_4,  2024),
109     ADD_SYMB_VALUE(TONE_SYMB_LOW_4H, 1912),
110     ADD_SYMB_VALUE(TONE_SYMB_LOW_5,  1805),
111     ADD_SYMB_VALUE(TONE_SYMB_LOW_5H, 1704),
112     ADD_SYMB_VALUE(TONE_SYMB_LOW_6,  1608),
113     ADD_SYMB_VALUE(TONE_SYMB_LOW_6H, 1517),
114     ADD_SYMB_VALUE(TONE_SYMB_LOW_7,  1000),
115     ADD_SYMB_VALUE(TONE_SYMB_LOW_7H, 1000),
116     ADD_SYMB_VALUE(TONE_SYMB_MIDDLE_1,  1433),
117     ADD_SYMB_VALUE(TONE_SYMB_MIDDLE_1H, 1351),
118     ADD_SYMB_VALUE(TONE_SYMB_MIDDLE_2,  1276),
119     ADD_SYMB_VALUE(TONE_SYMB_MIDDLE_2H, 1205),
120     ADD_SYMB_VALUE(TONE_SYMB_MIDDLE_3,  1137),
121     ADD_SYMB_VALUE(TONE_SYMB_MIDDLE_3H, 1078),
122     ADD_SYMB_VALUE(TONE_SYMB_MIDDLE_4,  1012),
123     ADD_SYMB_VALUE(TONE_SYMB_MIDDLE_4H,  956),
124     ADD_SYMB_VALUE(TONE_SYMB_MIDDLE_5,   903),
125     ADD_SYMB_VALUE(TONE_SYMB_MIDDLE_5H,  852),
126     ADD_SYMB_VALUE(TONE_SYMB_MIDDLE_6,   804),
127     ADD_SYMB_VALUE(TONE_SYMB_MIDDLE_6H,  759),
128     ADD_SYMB_VALUE(TONE_SYMB_MIDDLE_7,  1000),
129     ADD_SYMB_VALUE(TONE_SYMB_MIDDLE_7H, 1000),
130     ADD_SYMB_VALUE(TONE_SYMB_HIGH_1,  716),
131     ADD_SYMB_VALUE(TONE_SYMB_HIGH_1H, 676),
132     ADD_SYMB_VALUE(TONE_SYMB_HIGH_2,  638),
133     ADD_SYMB_VALUE(TONE_SYMB_HIGH_2H, 603),
134     ADD_SYMB_VALUE(TONE_SYMB_HIGH_3,  569),
135     ADD_SYMB_VALUE(TONE_SYMB_HIGH_3H, 539),
136     ADD_SYMB_VALUE(TONE_SYMB_HIGH_4,  506),
137     ADD_SYMB_VALUE(TONE_SYMB_HIGH_4H, 478),
138     ADD_SYMB_VALUE(TONE_SYMB_HIGH_5,  452),
139     ADD_SYMB_VALUE(TONE_SYMB_HIGH_5H, 426),
140     ADD_SYMB_VALUE(TONE_SYMB_HIGH_6,  402),
141     ADD_SYMB_VALUE(TONE_SYMB_HIGH_6H, 379),
142     ADD_SYMB_VALUE(TONE_SYMB_HIGH_7,  1000),
143     ADD_SYMB_VALUE(TONE_SYMB_HIGH_7H, 1000),
144     #undef ADD_SYMB_VALUE
145 };
146 
147 // 每个节拍的周期
148 static const uint16_t toneBeatDuration[TONE_BEAT_TOTAL] = {
149     #ifdef ADD_BEAT_VAL_MS
150     #undef ADD_BEAT_VAL_MS
151     #endif
152     #define ADD_BEAT_VAL_MS(E,V) (V)
153     ADD_BEAT_VAL_MS(TONE_BEAT_END, 0),
154     ADD_BEAT_VAL_MS(TONE_BEAT_4_1, 2000), // 4/1拍
155     ADD_BEAT_VAL_MS(TONE_BEAT_3_1, 3000), // 3/1拍
156     ADD_BEAT_VAL_MS(TONE_BEAT_2_1, 1000), // 2/1拍
157     ADD_BEAT_VAL_MS(TONE_BEAT_3_2,  750), // 3/2拍
158     ADD_BEAT_VAL_MS(TONE_BEAT_1_1,  500), // 1/1拍
159     ADD_BEAT_VAL_MS(TONE_BEAT_3_4,  313), // 3/4拍
160     ADD_BEAT_VAL_MS(TONE_BEAT_1_2,  250), // 1/2拍
161     ADD_BEAT_VAL_MS(TONE_BEAT_1_3,  125), // 1/3拍
162     ADD_BEAT_VAL_MS(TONE_BEAT_1_4,   63), // 1/4拍
163     #undef ADD_BEAT_VAL_MS
164 };
165 
166 struct tone_play_ctrl {
167     uint32_t    arrCount;
168     const struct tone_play_data *toneParam;
169 };
170 
171 const struct tone_play_data beep_Du[] = {
172     // 一闪一闪亮晶晶,1=C2/4
173     {TONE_SYMB_HIGH_1, TONE_BEAT_1_3},
174     // 最后一个元素必须是{0, 0}
175     {TONE_SYMB_END, TONE_BEAT_END},
176 };
177 
178 /*
179 const struct tone_play_data testTone[] = {
180     // 一闪一闪亮晶晶,1=C2/4
181     {TONE_SYMB_MIDDLE_1, TONE_BEAT_1_2},
182     {TONE_SYMB_MIDDLE_1, TONE_BEAT_1_2},
183     {TONE_SYMB_MIDDLE_5, TONE_BEAT_1_2},
184     {TONE_SYMB_MIDDLE_5, TONE_BEAT_1_2},
185     {TONE_SYMB_MIDDLE_6, TONE_BEAT_1_2},
186     {TONE_SYMB_MIDDLE_6, TONE_BEAT_1_2},
187     {TONE_SYMB_MIDDLE_5, TONE_BEAT_1_1},
188 
189     {TONE_SYMB_MIDDLE_4, TONE_BEAT_1_2},
190     {TONE_SYMB_MIDDLE_4, TONE_BEAT_1_2},
191     {TONE_SYMB_MIDDLE_3, TONE_BEAT_1_2},
192     {TONE_SYMB_MIDDLE_3, TONE_BEAT_1_2},
193     {TONE_SYMB_MIDDLE_2, TONE_BEAT_1_2},
194     {TONE_SYMB_MIDDLE_2, TONE_BEAT_1_2},
195     {TONE_SYMB_MIDDLE_1, TONE_BEAT_1_1},
196 
197     {TONE_SYMB_MIDDLE_5, TONE_BEAT_1_2},
198     {TONE_SYMB_MIDDLE_5, TONE_BEAT_1_2},
199     {TONE_SYMB_MIDDLE_4, TONE_BEAT_1_2},
200     {TONE_SYMB_MIDDLE_4, TONE_BEAT_1_2},
201     {TONE_SYMB_MIDDLE_3, TONE_BEAT_1_2},
202     {TONE_SYMB_MIDDLE_3, TONE_BEAT_1_2},
203     {TONE_SYMB_MIDDLE_2, TONE_BEAT_1_1},
204 
205     {TONE_SYMB_MIDDLE_5, TONE_BEAT_1_2},
206     {TONE_SYMB_MIDDLE_5, TONE_BEAT_1_2},
207     {TONE_SYMB_MIDDLE_4, TONE_BEAT_1_2},
208     {TONE_SYMB_MIDDLE_4, TONE_BEAT_1_2},
209     {TONE_SYMB_MIDDLE_3, TONE_BEAT_1_2},
210     {TONE_SYMB_MIDDLE_3, TONE_BEAT_1_2},
211     {TONE_SYMB_MIDDLE_2, TONE_BEAT_1_1},
212 
213     {TONE_SYMB_MIDDLE_1, TONE_BEAT_1_2},
214     {TONE_SYMB_MIDDLE_1, TONE_BEAT_1_2},
215     {TONE_SYMB_MIDDLE_5, TONE_BEAT_1_2},
216     {TONE_SYMB_MIDDLE_5, TONE_BEAT_1_2},
217     {TONE_SYMB_MIDDLE_6, TONE_BEAT_1_2},
218     {TONE_SYMB_MIDDLE_6, TONE_BEAT_1_2},
219     {TONE_SYMB_MIDDLE_5, TONE_BEAT_1_1},
220 
221     {TONE_SYMB_MIDDLE_4, TONE_BEAT_1_2},
222     {TONE_SYMB_MIDDLE_4, TONE_BEAT_1_2},
223     {TONE_SYMB_MIDDLE_3, TONE_BEAT_1_2},
224     {TONE_SYMB_MIDDLE_3, TONE_BEAT_1_2},
225     {TONE_SYMB_MIDDLE_2, TONE_BEAT_1_2},
226     {TONE_SYMB_MIDDLE_2, TONE_BEAT_1_2},
227     {TONE_SYMB_MIDDLE_1, TONE_BEAT_1_1},
228 
229     // 最后一个元素必须是{0, 0}
230     {TONE_SYMB_END, TONE_BEAT_END},
231 };
232 */
233 
234 // 播放控制变量,每次启动播放前必须初始化
235 static struct tone_play_ctrl mTonePlayController;
236 
237 // 定时器中断回调,要是硬件自己处理该多好啊,这样的效率有点低
238 // 定时器1&8有个RCR(repetition counter register),也许应该换定时器1&8的
239 extern void TIM2_IRQHandler(void)
240 {
241     if (TIM_GetFlagStatus(TIM2, TIM_IT_Update))
242     {
243         // 进来一次说明经历了ARR时长
244         mTonePlayController.arrCount ++;
245         
246         // duration单位是ms,ARR经过我们的配置后是1us,所以比较时要对duration乘以1000
247         // 这里的音符的时长精度肯定有较大的误差,但是咱们暂不考虑处理这个
248         if ((TIM2->ARR + 1) * mTonePlayController.arrCount >= toneBeatDuration[mTonePlayController.toneParam->beatIndex] * 1000)
249         {
250             mTonePlayController.toneParam++;
251             if (TONE_BEAT_END < mTonePlayController.toneParam->beatIndex && TONE_SYMB_END < mTonePlayController.toneParam->symbolIndex)
252             {
253                 // 音符切换,必须重置arrCount,重设定时器的ARR寄存器和PWM的CCR寄存器
254                 mTonePlayController.arrCount = 0;
255                 TIM_SetAutoreload(TIM2, toneSymbolPeriod[mTonePlayController.toneParam->symbolIndex] - 1);
256                 if (TONE_SYMB_MUTE == mTonePlayController.toneParam->symbolIndex)
257                     TIM_SetCompare3(TIM2, toneSymbolPeriod[mTonePlayController.toneParam->symbolIndex]);
258                 else
259                     TIM_SetCompare3(TIM2, (toneSymbolPeriod[mTonePlayController.toneParam->symbolIndex] + 1) / 2);
260             }
261             else
262             {
263                 // 播放完毕了就关闭定时器
264                 TIM_Cmd(TIM2, DISABLE);
265                 TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Disable);
266             }
267         }
268         
269         TIM_ClearFlag(TIM2, TIM_IT_Update);
270     }
271 }
272 
273 /*
274 STM32F411CC使用的外部晶振16M,所得到的sysclk为100MHZ
275 */
276 static void _tone_play_env_setup(void)
277 {
278     /* TIMER2, CHANNEL 3, PB10 */
279     GPIO_InitTypeDef gpioInitData = {
280         GPIO_Pin_10, 
281         GPIO_Mode_AF,
282         GPIO_Fast_Speed,
283         GPIO_OType_PP,
284         GPIO_PuPd_UP,
285     };
286     TIM_TimeBaseInitTypeDef timeData = {
287         .TIM_CounterMode = TIM_CounterMode_Up,
288         .TIM_ClockDivision = TIM_CKD_DIV1,
289     };
290     TIM_OCInitTypeDef ocdata = {
291         .TIM_OCMode = TIM_OCMode_PWM2, // PWM模式2:CNT>CCR时输出有效
292         .TIM_OutputState = TIM_OutputState_Enable,
293         .TIM_OutputNState = TIM_OutputNState_Enable, // 仅Timer1&8, PWM模式用不上这个
294         .TIM_OCPolarity = TIM_OCPolarity_High,
295         .TIM_OCNPolarity = TIM_OCNPolarity_Low, // PWM模式用不上这个
296         .TIM_OCIdleState = TIM_OCIdleState_Set, // PWM模式用不上这个
297         .TIM_OCNIdleState = TIM_OCNIdleState_Reset, // PWM模式用不上这个
298     };
299 
300     /* 定时器溢出周期,
301        系统主频100MHz不分频(TIM_ClockDivision=TIM_CKD_DIV1)直接给到定时器2作为计数时钟,
302        计数时钟信号100倍分频(TIM_Prescaler=100-1)就是1MHz,一个时钟周期就是1us。
303        所以对计数时钟信号计数n个(TIM_Prescaler=n-1)就达到了n微秒。
304        注意,我们后面主要控制 TIM_Prescaler 来达到音乐频率的控制。
305        这里TIM_Prescaler=99是固定配置,一个计数时钟周期为1us,
306        如果你使用的是72MHz(比如STM32F103),或者TIM_ClockDivision做了分频,要注意更改。
307     */
308     timeData.TIM_Prescaler = 100 - 1; 
309     timeData.TIM_Period = 1000 - 1;
310     // PWM占空比50%,就是 TIM_Period 的一半
311     ocdata.TIM_Pulse = 500;
312 
313     // 我们要使用中断
314     // NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
315     // RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
316     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
317 
318     // 初始化GPIO以及功能复用
319     GPIO_Init(GPIOB, &gpioInitData);
320     GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_TIM2);
321 
322     // 定时器中断分配
323     NVIC_InitTypeDef nvicInitData;
324     nvicInitData.NVIC_IRQChannel = TIM2_IRQn;
325     nvicInitData.NVIC_IRQChannelPreemptionPriority = 2;
326     nvicInitData.NVIC_IRQChannelSubPriority = 2;
327     nvicInitData.NVIC_IRQChannelCmd = ENABLE;
328     NVIC_Init(&nvicInitData);
329     
330     TIM_DeInit(TIM2);
331     TIM_TimeBaseInit(TIM2, &timeData);
332     TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 启用中断
333     TIM_OC3Init(TIM2, &ocdata); //TIM2的通道2PWM 模式设置
334     TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Disable); //使能预装载寄存器
335     TIM_Cmd(TIM2, DISABLE); // 启动定时器
336 }
337 
338 /*
339 接口函数,注意形参传值的正确性
340 */
341 void tone_play(const struct tone_play_data *param)
342 {
343     if (NULL == param)
344         return;
345 
346     xSemaphoreTake(semTonePlay, portMAX_DELAY);
347     TIM_Cmd(TIM2, DISABLE);
348     mTonePlayController.arrCount = 0;
349     mTonePlayController.toneParam = param;
350     TIM_SetAutoreload(TIM2, toneSymbolPeriod[mTonePlayController.toneParam->symbolIndex] - 1);
351     TIM_SetCompare3(TIM2, (toneSymbolPeriod[mTonePlayController.toneParam->symbolIndex] + 1) / 2);
352     TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable);
353     TIM_Cmd(TIM2, ENABLE);
354     xSemaphoreGive(semTonePlay);
355 }
356 
357 void tone_stop(void)
358 {
359     xSemaphoreTake(semTonePlay, portMAX_DELAY);
360     TIM_Cmd(TIM2, DISABLE);
361     TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Disable);
362     xSemaphoreGive(semTonePlay);
363 }
364 #endif
View Code

亲测可用。

如果转载,请注明出处。https://www.cnblogs.com/ssdq/
原文地址:https://www.cnblogs.com/ssdq/p/13712853.html