基于stm32的音乐喷泉设计

目录

 

一、原理简述

二、系统硬件设计

1.stm32f103c8t6核心控制器

2.无线蓝牙模块

3.LM386音频放大模块

4.PWM水泵控制模块

三、系统软件设计

1.ADC初始化及使用

2.PWM初始化及使用

3.ADC检测与PWM反馈

四、实物展示

五、完整原理图

六、完整代码


 

一、原理简述

        所谓音乐喷泉就是喷泉水柱会随着音乐节奏的快慢或者声音的高低而起伏变化,要实现这个变化,从技术的角度上来说需要解决如下两个问题:

        ①如何感知音乐节奏的快慢或者声音的高低?

        ②获取到音乐的变化后,如何变化成水柱的变化?

        事实上解决了上述两个问题,本设计就完成了一大半。按照常识,可以使用水泵来控制水柱的高低,音频可以使用模数转换将音乐这一连续变化的模拟量变成可以量化、方便处理的数字量,再将这个变化反馈给水泵,让水泵随着音频的变化而变化,这样就完成了本设计。

        本设计,将以stm32为核心,设计一个音乐喷泉,使用蓝牙模块与手机相连后放大相应的音频,并将音乐音调高低的变化转换成水泵水柱喷射高低的变化。具体表现为:手机通过蓝牙,将音频信号传输至音频放大器,音频放大器将音频处理后,通过喇叭输出,同时将这个处理之后的信号送至stm32自带ADC模块采集,stm32将以此为依据,控制PWM输出,从而达到控制水泵水柱高度的目的。

 

二、系统硬件设计

        本系统中,将以stm32为核心,配合LM386音频放大模块、无线蓝牙模块、PWM水泵控制模块,共同实现本设计所有功能设计。

        系统的整体硬件设计框图如下:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATEhfU01E,size_20,color_FFFFFF,t_70,g_se,x_16

                                                                                               图一  整体硬件框图

        下面,将分别介绍这四个核心模块:

1.stm32f103c8t6核心控制器

        stm32f103c8t6的实物图如下:

                                                                                 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATEhfU01E,size_20,color_FFFFFF,t_70,g_se,x_16

                                                                                             图二  stm32最小系统板

        其原理图如下:    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATEhfU01E,size_20,color_FFFFFF,t_70,g_se,x_16

                                                                                             图三  stm32最小系统原理图

        stm32f103c8t6为意法半导体生产的一款高性能32位处理器,采用ARM cortex-M3为内核,在稳定运行的情况下,主频可高达72M,是传统51单片机的性能的几十倍,能完成许多复杂的功能。其最小系统主要包括:stm32芯片、复位电路、时钟电路、电源电路、代码烧录电路和boot选择电路。

        stm32f103c8t6用着丰富的外设,例如GPIO、USART、ADC、PWM、TIMER、硬件SPI、硬件IIC、USB等。在本设计中,将会使用到的它的外设有:GPIO、ADC、TIMER和PWM。其中,ADC负责采集音频音调的高低,然后反馈给PWM,控制PWM占空比的高低,从而达到控制水柱高低的效果。

 

2.无线蓝牙模块

        无线蓝牙模块实物图如下:

                                                                                        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATEhfU01E,size_8,color_FFFFFF,t_70,g_se,x_16

                                                                                                   图四  无线蓝牙模块

        该蓝牙模块输出音频可以达到双声道立体效果,且无线距离最远可达到20m,配对后可自动会连手机,且支持低功耗,且成本低廉。在本设计中,只需要用音频线,连接蓝牙模块的PHONE口和LM386模块的PHONE,打开手机音频就可以达到音频无线传输的效果,操作简单,易于控制,方便快捷。

 

3.LM386音频放大模块

        LM386是专为低损耗电源所设计的功率放大器集成电路。其内置增益为20,透过pin 1 和pin8脚位间电容的搭配,增益最高可达200。LM386可使用电池为供应电源,输入电压范围可由4V~12V,无作动时仅消耗4mA电流,且失真低。其应用原理图如下:                              watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATEhfU01E,size_20,color_FFFFFF,t_70,g_se,x_16

                                                                                                  图五  LM386应用电路

        上图应用电路中,LM386的增益为200。将音频线一端连接蓝牙模块,一端连接图四中的PHONE中,这样,手机传输过来的音频就可以通过蓝牙,传输至LM386,经过LM386的放大之后,经过JP1上插着的喇叭播放。图中,RP1为电位器,可以通过旋转电位器,改变输入电阻,从而调节JP1喇叭的音量大小输出;E2为一个电解电容,起着“通交流、隔直流”的作用,可以滤除掉电路总的直流成分,这样,输出到JP1的声音就比较完整,且杂音相对小很多。同时,在VOUT引脚,连接着stm32的PB0脚,该引脚为stm32自带adc的输入引脚,起着采集音量大小的作用。

 

4.PWM水泵控制模块

         当stm32通过自带adc采集到音频大小后,需要反馈给水泵,控制水柱输出。这里的水泵,就是通过PWM控制输出功率高低的,其原理图如下: 

                                                       029cba5f085b45cc8272a848083f443c.png  

 

                                                                                                   图六  PWM水泵控制原理图

        水泵通过两个三极管来驱动。其前级SS8050,为一个NPN三极管,它的基级连着stm32的PB5,后级是SS8550,为一个PNP管。当stm32的PB5输出一个高电平时,SS8050导通,此时,SS8550的基级电压被拉低,8550导通,JP2上面的水泵工作。同理,PB5输出低电平时,8050截止,8550的基级为高电平,8550也是截止的,水泵不工作。当PB5输出PWM时,水泵的输出功率就会随着PB5输出PWM的占空比的高低而高低变化,从而达到控制水柱高低的效果。

 

三、系统软件设计

        本设计中,软件设计流程明晰,具体系统的软件设计流程图七所示:

                                                                                             watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATEhfU01E,size_16,color_FFFFFF,t_70,g_se,x_16

                                                                                            图七  软件设计流程图

        上图中大致可以将软件代码分为三个部分:ADC初始化、PWM初始化、ADC检测及PWM的反馈。

1.ADC初始化及使用

        stm32的ADC是12位ADC,stm32f103c8t6一共有三个ADC,每个ADC最多有18个通道,其最大转换速率可达到1Mhz。本设计中使用的ADC1的通道八进行音频音调高低信号的采集。在初始化任何外设之前,都需要先初始化其对应的外设时钟,然后再设置对应的ADC通道、设置ADC采样速率转换方式等。其具体代码如下:

void  Adc_Init(void)
{ 	
	ADC_InitTypeDef ADC_InitStructure; 
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB |RCC_APB2Periph_ADC1	, ENABLE );	  //使能ADC1通道时钟


	RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M

	//PB0 作为模拟通道输入引脚                         
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模拟输入引脚
	GPIO_Init(GPIOB, &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));	 //等待校准结束
}	

        初始化完成之后,在后续的代码中就可以使用PB0开始测量工作了。为了避免信号的干扰,使测量的数据更加准确,这里采用多次测量取平均值的方式。具体代码如下:

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;
} 	 

2.PWM初始化及使用

        PWM,译为脉冲宽度调制,简单说就是对脉冲宽度的控制。stm32有八个定时器,分为基本定时器、通用定时器和高级定时器。除了基本定时器,其他定时器都可以用来产生PWM,而TIM1和TIM8这两个高级定时器,可以同时产生7路PWM输出。本设计中使用的TIM3的通道2来产生PWM,对应stm32的PB5引脚。

        首先是PWM的初始化,其具体代码如下:

//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM3_PWM_Init(u16 arr,u16 psc,u16 TIM_Pulse)
{  
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);	//使能定时器3时钟
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB  | RCC_APB2Periph_AFIO, ENABLE);  //使能GPIO外设和AFIO复用功能模块时钟
	
	GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射  TIM3_CH2->PB5    
 
   //设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形	GPIOB.5
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
 
   //初始化TIM3
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
	
	//初始化TIM3 Channel2 PWM模式	 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
 	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性高
	TIM_OCInitStructure.TIM_Pulse = TIM_Pulse;
	TIM_OC2Init(TIM3, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM3 OC2

	TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIM3在CCR2上的预装载寄存器
 
	TIM_Cmd(TIM3, ENABLE);  //使能TIM3
   
}

        PWM初始化完成之后,就可以控制占空比输出了,可以直接调用下面函数,修改PWM的占空比:

void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);

        完成上述操作之后,需要在主函数中调用初始化函数,设定PWM的频率,这里设置PWM的频率为1KHz,如下:

TIM3_PWM_Init(899,159, 800);    /* PWM:72000/((899+1)(158+1)) = 1KHZ */

        如果设置PWM的占空比为50%,可以这样:

TIM_SetCompare2(TIM3, 450);

TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);的第二个参数占TIM3_PWM_Init(899,159, 800);函数第一个参数的百分比,即为PWM所占的占空比。

 

3.ADC检测与PWM反馈

        初始化完成之后,接下来的就是根据要求完成具体功能逻辑了。在本设计中,需要根据ADC的值来调节PWM的输出。声音音频越大,对应的ADC值越大,那么输出的PWM占空比就越高。前面章节已经知道,stm32的ADC为12位,对应十进制最大值我4096,PWM的占空比为1-99%,这里用adc_value表示测量的ADC值,tim_pulse表示PWM的占空比,adc_value为输入,tim_pulse为输出,为了计算方便,这里不妨取ADC值的范围为0-4000,占空比取0-100(事实上,根据实际情况,他们并不能达到极限值,这么处理完全是可行的)。那么他们可以处理为一个简单的一元一次方程:                              

tim_pulse = 1/40 * adc_value

        事实上,根据实际情况,其变化并不是根据上述式子来的,而且相差较大,下面是实际测试后效果比较好的处理:

adc_value = Get_Adc_Average(ADC_Channel_8,  20);  
TIM_SetCompare2(TIM3,(adc_value*40 + 1));  

 

四、实物展示

                                               watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATEhfU01E,size_18,color_FFFFFF,t_70,g_se,x_16

                                                  watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATEhfU01E,size_16,color_FFFFFF,t_70,g_se,x_16

 

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATEhfU01E,size_20,color_FFFFFF,t_70,g_se,x_16

 

五、完整原理图

0e3d4e16278745478af3de720af8b4fa.png

 

 

六、完整代码

main.c

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "adc.h"
#include "timer.h"
#include <stdbool.h>
#include "pwm.h"


int main(void)
{ 
	u16 adc_value = 0;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2
	delay_init();	    	 //延时函数初始化
	delay_ms(1500);	

	Adc_Init();   /* ADC 初始化 */
	TIM3_PWM_Init(899,159,800);
	while(1)
	{		
		adc_value = Get_Adc_Average(ADC_Channel_8,  20);  
        TIM_SetCompare2(TIM3,(adc_value*40 + 1));
			
	}											    
}	

pwm.c

#include "pwm.h"
#include "led.h"



//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM3_PWM_Init(u16 arr,u16 psc,u16 TIM_Pulse)
{  
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);	//使能定时器3时钟
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB  | RCC_APB2Periph_AFIO, ENABLE);  //使能GPIO外设和AFIO复用功能模块时钟
	
	GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射  TIM3_CH2->PB5    
 
   //设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形	GPIOB.5
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
 
   //初始化TIM3
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
	
	//初始化TIM3 Channel2 PWM模式	 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
 	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性高
	TIM_OCInitStructure.TIM_Pulse = TIM_Pulse;
	TIM_OC2Init(TIM3, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM3 OC2

	TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIM3在CCR2上的预装载寄存器
 
	TIM_Cmd(TIM3, ENABLE);  //使能TIM3
   
}

pwm.h

#ifndef __PWM_H
#define __PWM_H
#include "sys.h"

void TIM3_PWM_Init(u16 arr,u16 psc,u16 TIM_Pulse);

#endif

adc.c

 #include "adc.h"
 #include "delay.h"
 #include "led.h"
		   																   
void  Adc_Init(void)
{ 	
	ADC_InitTypeDef ADC_InitStructure; 
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB |RCC_APB2Periph_ADC1	, ENABLE );	  //使能ADC1通道时钟


	RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M

	//PB0 作为模拟通道输入引脚                         
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模拟输入引脚
	GPIO_Init(GPIOB, &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));	 //等待校准结束
}				  

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;
} 	 

adc.h

#ifndef __ADC_H
#define __ADC_H	
#include "sys.h"

void Adc_Init(void);
u16  Get_Adc(u8 ch); 
u16 Get_Adc_Average(u8 ch,u8 times); 
 
#endif 

 

注:如果你没有什么基础,那么你自己做出来的概率几乎为0,做出来的效果肯定不好。提问请注意你的问题,不要问为什么我照着做系统跑不起来等这种类似的问题,这种问题我不知道回答你!

 

 

 

 

 

 

 

  • 27
    点赞
  • 188
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

all of the time

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值