前言
上一篇文章写到STlink的小插曲,问题已经解决,于是就继续完成了目标,就是驱动一个小喇叭工作,它的原理与蜂鸣器相同,之前提到过原本我是想整个驱动电路的,毕竟功率与声音的大小有关系,后来觉得麻烦就没做,试了一下IO可以驱动起来,就这样直接做了。
准备
首先准备一个小喇叭或者蜂鸣器,这两个都是通过PWM来驱动的,理论上来说PWM的频率决定了声调,占空比决定了振幅,我还没试过输出其他声音,理论上来说都是震动,或许PWM能力有限,只能靠震动频率分辨出音调,音色或许与波形有关,但是想输出一首钢琴曲还是足够的。
使用的板子是STM32F103C8最小板,简单好用,通过STlink烧录代码,使用定时器1来做,用C1口输出PWM,也就是PA8,用CubeMX省点力气,如下图配置很简单:
这里可能要注意一下时钟的问题,我给的定时器时钟是72MHz,可以从时钟树上读出来,72分频也就是1MHz,然后Counter的值和比较值后面会重新赋值,初始化的时候分别给1000和500就可以响了。
此时我们在主函数中加两个函数:
HAL_TIM_Base_Start(&htim1);
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
这两句就能够驱动起来定时器和PWM了,这样连接好喇叭的GND和PA8之后烧录代码运行就会响了。
但是响不是目的,我们要输出《菊次郎的夏天》
音乐数组准备
第一步我们要先弄清楚每个音调的频率,直接搜博客上已经有前辈讲过,再次不赘述,也可以直接抄我的数组:
uint16_t cn_music_3[28] = {
956,851,758,715,637,568,506,
477,425,379,358,318,284,253,
239,212,189,179,159,142,126,
119,106,94,89,79,71,63};
说明一下这里一共28个值,分别是在刚才配置情况下的音调对应的频率。也就是说当我们的定时器频率是1MHz的时候,Counter的值是956时震动的音调接近do,这里并不是C调,主要是因为频率太低声音很小就没有了,所以我就往高频方向移了几个八度。向后Counter的数值越来越小,频率越来越高,我也没有写升调,好像用不到,读者拿计算器一按基本上就能发现规律。
第二步,找一个谱子,我虽然也钢琴入了门,但是太不熟练了就找了一个按数字写的,然后到网上找一个曲谱转蜂鸣器的软件(这里可以自己慢慢算,但是实在太慢,还是使用软件效率高一些,或者直接抄我的数组也可以)。
软件界面长这样:
在右边按对应的音调,然后转换就能出数组,我按照谱子转换了一部分,同时进行了一定的修正,如下:
uint8_t music_3[] = {
6,3, 13,3, 16,3, 13,3, 4,3,
11,3, 14,3, 11,3, 5,3, 12,3,
15,3, 12,3, 11,3, 15,3, 21,3,
15,3, 6,3, 13,3, 16,3, 13,3,
4,3,11,3, 14,3, 11,3, 5,3,
12,3,15,3, 12,3, 11,3, 15,3,
25,4, 31,4, 32,4, 33,4,32,3,
31,4, 31,4, 16,3, 13,3,4,3,
11,3, 25,4, 31,4, 32,4,33,4,
32,3, 31,4, 32,5, 33,3,
33,2, 25,4, 31,4, 32,4, 33,4,
32,3, 31,4, 31,4, 16,3, 13,3,
4,3, 11,3, 25,4, 31,4, 32,4,
33,4, 32,3, 31,4, 32,3, 35,3,
33,2, 33,3, 34,3, 35,3, 35,4,
35,5, 35,3, 35,3, 33,4,
31,4, 14,3, 33,4, 34,4, 35,3,
35,4, 35,5, 35,3, 35,3,
33,4, 31,4,16,3, 31,4, 32,4,
33,3, 33,4,33,5, 33,3,
33,3, 36,3,33,3, 31,3, 32,2,
5,3, 12,3,25,4, 31,4, 32,4,
33,4, 32,3,31,4, 31,4, 16,3,
13,3, 4,3,11,3, 25,4, 31,4,
32,4, 33,4,32,3, 31,4, 32,5,
33,3,33,2, 25,4, 31,4,
32,4, 33,4,32,3, 31,4, 31,4,
16,3, 13,3,4,3, 11,3, 25,4,
31,4, 32,4,33,4, 32,3, 31,4,
32,5, 35,3, 33,2, 33,3,
34,3, 35,3,35,4, 35,5,
35,3, 35,3,33,4, 31,4, 14,3,
33,4, 34,4,35,3, 35,4, 35,5,
35,3, 35,3, 33,4, 31,4,
16,3, 31,4,32,4, 33,3, 33,4,
33,5, 33,3, 33,3, 11,3,
33,4, 32,4,31,4, 26,4, 31,2,
31,2, 23,3,16,3, 13,3,
};
转换完成后格式基本上跟上面一样,但是我作了略微修改,第一个数是音调,相当于十位是所在的八度(没有学过乐理大家将就一下我的措辞,理解就好,这里的意思是说21比11高一个八度),个位是音调取值1-7,然后第二个数是与拍相关的,数字越大拍越短,这里不会出现5,但是限于软件做不出来连音,我就自己改了一些5出来,配合下面的数组使用:
uint16_t delay_1[4] = {420,180,60,300},delay_2[4] = {60,60,60,60};
这个数组的含义就是延时时间,也就是每个音持续的时间和它响完之后静音的时间,其实静音时间都是60ms,然后将上面数组的数字减去2对应的delay_1数组中的值就是延时时间,单位ms,比如3-2=1,那就是这个音持续180ms。
然后由于我只有一个小喇叭,就只能输出双手中的高音,将低音覆盖掉。
主函数
有了上面的数组之后,加上主函数就能跑了:
int main(void)
{
/* USER CODE BEGIN 1 */
uint16_t i = 0;
uint16_t len = sizeof(music_3) / sizeof(music_3[0]); //放数组长度
uint8_t x;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM1_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start(&htim1);
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
x = (music_3[i] / 10)*7 + music_3[i] % 10 - 1;
__HAL_TIM_SET_PRESCALER(&htim1,cn_music_3[x]);
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,cn_music_3[x]-1);
i++;
i %= len;
HAL_Delay(delay_1[music_3[i]-2]);
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,0);
HAL_Delay(delay_2[music_3[i]-2]);
i++;
i %= len;
}
/* USER CODE END 3 */
}
这里补充一下占空比,我为了图省事直接用Counter中的数值减去1作为比较值,小喇叭是可以正常工作(而且理论上已经做到了最大的响度了,或者还可以继续调定时器高频率就能等效调高占空比,但是其他数据也要跟着改变),就是不知道蜂鸣器行不行,不行的话只需要将它除2就行,就能做出来接近50占空比的方波了。
代码为什么这样写,相信读者理解了上面的内容就懂了,然后我们跑一下代码就能听到小喇叭放出音乐啦!