如何用STM32驱动小喇叭或者蜂鸣器来演奏菊次郎的夏天

前言

上一篇文章写到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占空比的方波了。

代码为什么这样写,相信读者理解了上面的内容就懂了,然后我们跑一下代码就能听到小喇叭放出音乐啦!

  • 32
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
驱动4KHZ无源蜂鸣器,我们可以使用STM32的定时器功能来产生蜂鸣器所需的频率。以下是一个简单的步骤: 1. 配置定时器:使用STM32的定时器功能,需要先配置定时器的时钟源、分频系数、定时器的计数值等参数。这里假设使用TIM2定时器来产生蜂鸣器的频率。 2. 配置GPIO:将蜂鸣器连接到STM32的GPIO引脚上,并将该引脚配置为输出模式。 3. 编写中断处理函数:在定时器每次溢出时,会触发定时器的中断处理函数。我们可以在该函数中控制GPIO输出高低电平,从而产生蜂鸣器的声音。 下面是一个简单的代码示例: ```c #include "stm32f10x.h" #define BEEP_GPIO_PIN GPIO_Pin_0 #define BEEP_GPIO_PORT GPIOA void TIM2_IRQHandler(void) { static uint8_t beep_state = 0; if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); if (beep_state) { GPIO_SetBits(BEEP_GPIO_PORT, BEEP_GPIO_PIN); beep_state = 0; } else { GPIO_ResetBits(BEEP_GPIO_PORT, BEEP_GPIO_PIN); beep_state = 1; } } } void beep_init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; NVIC_InitTypeDef NVIC_InitStruct; GPIO_InitTypeDef GPIO_InitStruct; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStruct.GPIO_Pin = BEEP_GPIO_PIN; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(BEEP_GPIO_PORT, &GPIO_InitStruct); TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStruct.TIM_Period = 1000 / 4; // 4KHZ TIM_TimeBaseInitStruct.TIM_Prescaler = 72 - 1; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct); NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; NVIC_Init(&NVIC_InitStruct); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); TIM_Cmd(TIM2, ENABLE); } int main(void) { beep_init(); while (1) { // do something } } ``` 在上面的代码中,我们使用了TIM2定时器,并将其配置为计数频率为72MHz/72=1MHz,计数器自动重装载值为1000/4=250,即每个定时器溢出周期为250us,产生的声音频率为4KHZ。在定时器中断处理函数中,我们使用了一个静态变量beep_state来记录当前蜂鸣器的状态,并通过GPIO_SetBits和GPIO_ResetBits函数来控制蜂鸣器输出高低电平,从而产生声音。 注意,在使用定时器功能时,需要注意定时器的计数频率和计数值的选择,以及中断处理函数的编写等问题,否则可能会导致产生错误的频率或无法正常工作。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值