STM32 f103搭配LM386声音传感器实现简单音乐识别

STM32 f103搭配LM386声音传感器实现简单音乐识别

1.前言

        2019年12月初,有一个中国机器人技能大赛中的双足机器人比赛项目,意思是机器人识别音乐跳对应节奏的舞蹈,五首音乐随机抽三首歌曲,音乐停,机器人停。
        新比赛,新项目,难度自然有,坑也不少。希望这篇文章能给大家带来一点帮助。废话不多说,进入正题。

2.效果

(健康歌)每100ms采样一次,歌曲前5秒内共测50次数据,重复12组
(卡路里) 重复7组在这里插入图片描述

        可以看出一首歌经过多次测值,其采样值数组呈现出有规律的特征;不同的歌曲的特征也有较好的区分度。达到了区分歌曲的效果。下面讲讲具体实现步骤。

3.思路

  1. 做什么:识别不同音乐,识别声音有无。

  2. 怎么做:a.利用传感器判断出音乐或声音(网上资料极少);
           b.利用手机app听歌识曲,返回对应值(app感觉太难) ;
           c.检测到声音就随机跳(下策,保命方案);
           d.遥控(作弊);
           e.人在旁边说出歌曲有关的词语,语音模块识别(干扰大)

  3. 我的选择:a+c

  4. 技术路线

4.硬件

        找到一块具有模拟量输出功能的声音传感器模块,我用的是下面这块,感觉不错,其他的没尝试过。将f103芯片的A1脚与模块的AOUT引脚相连(奇怪的是我与DOUT相连也会得到和AOUT差不多的模拟量值,很迷,有大佬懂的话麻烦指出一下问题所在)。

5.软件

        利用正点原子的adc.c文件来处理模拟量值,并最终返回给Get_Adc_Average()函数

//初始化ADC
//这里我们仅以规则通道为例
//我们默认将开启通道0~3
void  Adc_Init(void)
{ 	
    ADC_InitTypeDef ADC_InitStructure; 
	GPIO_InitTypeDef GPIO_InitStructure;
    //使能ADC1通道时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1	, ENABLE );	 
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
    //PA1 作为模拟通道输入引脚                         
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模拟输入引脚
	GPIO_Init(GPIOA, &GPIO_InitStructure);	

	ADC_DeInit(ADC1);  //复位ADC1 

	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;	
    //ADC工作模式:ADC1和ADC2工作在独立模式
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;	//模数转换工作在单通道模式
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;	//模数转换工作在单次转换模式
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	
    //转换由软件而不是外部触发启动
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//ADC数据右对齐
	ADC_InitStructure.ADC_NbrOfChannel = 1;	//顺序进行规则转换的ADC通道的数目
	ADC_Init(ADC1, &ADC_InitStructure);	
    //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   

  
	ADC_Cmd(ADC1, ENABLE);	//使能指定的ADC1
	
	ADC_ResetCalibration(ADC1);	//使能复位校准  
	 
	while(ADC_GetResetCalibrationStatus(ADC1));	//等待复位校准结束
	
	ADC_StartCalibration(ADC1);	 //开启AD校准
 
	while(ADC_GetCalibrationStatus(ADC1));	 //等待校准结束
 
    //ADC_SoftwareStartConvCmd(ADC1, ENABLE);		//使能指定的ADC1的软件转换启动功能
}

//获得ADC值
//ch:通道值 0~3
u16 Get_Adc(u8 ch)   
{
  	//设置指定ADC的规则组通道,一个序列,采样时间
	ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );	
    //ADC1,ADC通道,采样时间为239.5周期	  			    
  
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);		//使能指定的ADC1的软件转换启动功能	
	 
	while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束

	return ADC_GetConversionValue(ADC1);	//返回最近一次ADC1规则组的转换结果
}

u16 Get_Adc_Average(u8 ch,u8 times)
{
	u32 temp_val=0;
	u8 t;
	for(t=0;t<times;t++)
	{
		temp_val+=Get_Adc(ch);
		delay_ms(5);
	}
	return temp_val/times;
} 	 

        将Get_Adc_Average()拿到的值通过串口输出在电脑屏幕上,适当调整其数值范围,刷新时间间隔。我在代码中就进行了(4093-adcx),(delay_ms(100))相关处理。

while(1)
	{
	 printf("\r\n");
	 for(i=0;;i++) 
            {
		for(j=0;j<50;j++)
		   {
		      adcx=Get_Adc_Average(ADC_Channel_1,10);
		      printf("%-4d \r",4093-adcx);														                
		      delay_ms(100);
		    }
		 printf("\r\n");
		 delay_ms(1500);
	         delay_ms(1500);
	     }	
	}

        判断歌曲就更简单了,把采样值存入数组,写一个条件语句判断数组的特征就好了,如下:

u8 check_song(void)
{
	u16 adcx,adc[35]={0};
	u8 i,flag=0;
	printf("\r\n");
	for(i=0;i<35;i++)//存储数据
	   {
		adcx=CurrentAdc();
		adc[i]=adcx;
		if (adc[i]>300)
		   flag++;
		printf("%-4d \r", adc[i]);
		delay_ms(100);

	   }
		 

	 if(flag>32) 
           {
	        printf("flag%d\r",flag);
	        return 0;//太极拳
	   }
	 else
	   {
	        printf("flag%d\r",flag);
	        
		if((adc[23]<5&&adc[24]<5)
		 ||(adc[24]<5&&adc[25]<5)
		 ||(adc[25]<5&&adc[26]<5)
		 ||(adc[26]<5&&adc[27]<5)
		 ||(adc[27]<5&&adc[28]<5))
		   return 1;//健康歌
		   else return 2;//翻跟头
	   }
}

6.总结

a.未知是最大的恐惧,行动是最好的解药

        一看到比赛项目的时候,卧槽,感觉很难,果然网上一查,什么资料也没有。。绝望,想放弃。。比赛前半个月,老师开始问进度了,很慌,啥也没有。但不好意思空手去,于是和队友总结出上面几套方案,发现有个保命方案,心里稍微有点底,开始去探索更好的方法。最后搞出来声音的adc采集,其实现在看看代码,实在是简单,很惭愧。其实比赛现场能真正识别出音乐的不超过10个(共30个队伍),我难人亦难,但我只要去做,山重水复疑无路,柳暗花明又一村。这已超过了不少的人,最重要的是战胜了自己。一定要去做,用心做!

b.做项目就跟取西经样,人生何尝不是

        准备过程中是不可能一帆风顺的,一天一个小自闭,三天一个大自闭,烧板子查不出问题,断结构重新打印,破代码运行不出效果,软件崩溃文件没保存…简直怀疑人生。。一般遇到这时候,我就放下手中的工作,跑个步,洗个澡,吃个饭,归来还是少年。做人嘛,最重要的就是心态好啦。

c.持续学习,模仿优秀的人

        现阶段自身的能力还不足以输出很多很好的内容。模仿大佬,总结经验是比较好的成长路线。慢慢地就会有自己的风格了。加油!

需要源代码的同学请到Github自取
https://github.com/Iamliuguo

### 回答1: 首先,在STM32的开发环境中编写代码,先定义引脚和所需的相关库文件。 然后,准备好DTMF解调器芯片和LM386音频放大器芯片,并将它们连接到STM32引脚上。 接下来,使用STM32控制DTMF解调器芯片,将解调器输出连接到LM386音频放大器芯片的输入引脚。接着,编写代码驱动输出引脚,将LM386音频放大器芯片的输出连接到扬声器或耳机。 在程序的主函数中,使用定时器控制DTMF声音的播放时间和频率。也可以添加其他功能,如音量控制等。 最后,将程序下载到STM32单片机中,将DTMF解调器芯片和LM386音频放大器芯片连接到单片机引脚上,连接扬声器或耳机,即可通过STM32控制LM386播放DTMF声音。 ### 回答2: 实现这个功能的大致思路如下: 1.将DTMF按键数字转换成对应的频率,然后将频率转换成脉宽调制(PWM)波形信号。 2.将产生的PWM波形信号通过一个放大电路(比如LM386)进行放大。 3.将放大后的音频信号通过扬声器播放出来。 具体实现过程如下: 1.首先,我们需要对DTMF按键数字进行频率转换,可以通过直接查找DTMF信号表来实现: ```c++ uint16_t dtmfFreq[] = {697, 770, 852, 941, 1209, 1336, 1477, 1633}; ``` 其中前4个数字对应4个行频(697、770、852和941Hz),后4个数字则对应4个列频(1209、1336、1477和1633Hz)。 2.将产生的频率转换成PWM波形信号,可以通过STM32的定时器功能实现。首先初始化相关的定时器,然后在TIMER中断服务函数里更新占空比(通过改变ARR和CCR),具体代码如下: ```c++ // Init timer to generate PWM signals HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); // Generate PWM signals by update Pulse Width based on the frequency while(1) { for(int i=0; i<8; i++) { int halfPeriod = SystemCoreClock / (2*dtmfFreq[i]); int period = halfPeriod * 2; __HAL_TIM_SET_AUTORELOAD(&htim2, period - 1); __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, halfPeriod - 1); HAL_Delay(200); } } ``` 3.将PWM信号放大,可以使用LM386音频放大器。先将STM32产生的PWM信号通过一个低通滤波器进行滤波,再将输出信号输入到LM386的IN+端口,连接好LM386的其他引脚即可。具体代码如下: ```c++ // Interpolate the PWM signal to generate analog signal uint16_t audioBuffer[1000]; for(int i=0; i<1000; i++) { int duty = __HAL_TIM_GET_COMPARE(&htim2, TIM_CHANNEL_1); audioBuffer[i] = (duty * 0xff) / (__HAL_TIM_GET_AUTORELOAD(&htim2) - 1); } // Filter the analog signal to remove high frequency noise float alpha = 0.80f; float yc = 0.0f; for(int i=0; i<1000; i++) { yc = alpha*audioBuffer[i] + (1-alpha)*yc; audioBuffer[i] = (uint16_t)yc; } // Amplify the filtered analog signal using LM386 amplifier HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); for(int i=0; i<1000; i++) { uint16_t sample = audioBuffer[i]; HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_8B_R, sample & 0xff); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_8B_R, (sample >> 8) & 0xff); HAL_Delay(1); } HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); ``` 4.最后,需要连接一个扬声器,将音频信号通过扬声器播放出来即可。 总结:以上是用STM32驱动LM386播放DTMF声音的详细步骤及代码实现,具体实现操作需要根据电路及底层库函数的使用情况进行调整。
评论 70
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值