目录
日常·唠嗑
21年6月的时候,那会刚开始创业,跟着合伙人园丁(笔名)在创业的道路上,瞎灯黑火乱摸索,基本上是能跟开发有关合法赚钱的方式(嘿嘿,我这人比较庸俗,就想着赚钱),我们都思考过。为了一个看不见的投资,也是跟着另外三个小伙伴,去外面搞应酬(不过这个经历,也是我们人生中,一个不过珍贵的回忆,即使没拿到第一个投资,但那是我们以一个创业人为自己事业打拼的一件事)。
这创业的过程,我们也像大多数出来创业的年轻技术人员那样去接单子做(俗称接外包赚外快),这个磁流体项目就是我们那会接的单子,21年磁流体音箱项目网上还没有这么多
呸呸呸。尽讲些没用的,直接上视频,看效果进入正文。
一、视频效果
21年6月做的,后面因为找不到投资人,所以没有再进行外包装优化,当时我们还给这个作品起了个中二的名字:毒液音箱(嘿嘿)
STM32:磁流体蓝牙音箱项目开源(源码+PCB)
二、硬件设计
主控器,我们是自己画的最小系统板,板子上有:控制电路+电磁铁电路+电源电路
控制电路:STM32F103C8T6最小系统
电源电路:AMS117电路
电磁铁电路:一个大功率的MOS管做开关电路
除了自己设计的最小系统板外,还需要到某宝买一个蓝牙音箱+一个36V可调电源,这两个东西都可以自己做,但是没必要,网上便宜,性能还稳定,毕竟别人做了这么多年这东西了,还是可以的。
在电磁铁电路,开始,我们相过用大功率三极管做,但是电流实在太大了,工作不到一会,就会严重发烫。后来我三师兄给我介绍了MOS电路,发现就好了。
这个PCB跟原理图,我等会会跟源码放在一起,打包放在文末,有兴趣的,自己下载。
三、程序设计
其实这东西做起来没那么难,有些基础的人都可以做,主要用到FFT算法,我讲一下算法思路:这里我不会讲FFT算法的内容,网上讲解很多,感兴趣的自己去搜。我讲一下,大致原理,在第二大点的硬件设计中,我们用到了一个大功率MOS管做开关电路,其实就是让电磁铁把磁流体吸起来(给电就有磁力就吸起来了,没电没磁力就又掉下去),那么如何做到磁流体随着音乐跳动呢?
其实很简单,就是我们把单片的一个端口,根据音乐的跳动(FFT频率)不断输出PWM信号去控制这个MOS管的开关。(注意:因为电磁铁的电流是很大的,必须很单片机直接做隔绝,所以你可以看到,我设计的板子是有一些大电容跟小三极管做隔绝的)。
主程序: 代码已经做了注释,相信有基础的人都能看懂,没基础的,建议把基础打好,再来搞这项目
/* Includes ------------------------------------------------------------------*/#include"main.h"#include"stm32f1xx_hal.h"#include"adc.h"#include"dma.h"#include"tim.h"#include"usart.h"#include"gpio.h"/* USER CODE BEGIN Includes */#include"stm32_dsp.h"#include"table_fft.h"#include"math.h"#include"oled.h"#include"config.h"#include"bg.h"/* USER CODE END Includes *//* Private variables ---------------------------------------------------------*//* USER CODE BEGIN PV *//* Private variables ---------------------------------------------------------*/#defineNPT1024//256#definePI26.28318530717959#defineK1HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_4)#defineK2HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_11)#defineK3HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_12)//采样率计算//分辨率:Fs/NPT //#define Fs 10000#defineFs25600//取9984能出来整数的分辨率 9984/256 = 39Hz#defineDCT_0HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9, GPIO_PIN_RESET);#defineDCT_1HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9, GPIO_PIN_SET);/* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/voidSystemClock_Config(void);voidError_Handler(void);/* USER CODE BEGIN PFP *//* Private function prototypes -----------------------------------------------*/voidCreat_Single(void);voidGetPowerMag(void);voidSingle_Get(void);voiddisplay1(void);voiddisplay2(void);voidKey_Scan(void);/* USER CODE END PFP *//* USER CODE BEGIN 0 */uint32_t adc_buf[NPT]={0};uint8_t DCT_QD_flag;uint16_t DCT_QD_flag1;uint32_t error2;//差值long lBufInArray[NPT];long error1[NPT/2]={0};//差值long lBufOutArray[NPT/2];long lBufMagArray[NPT/2];//当前幅值long lastSpectrum[NPT/2];//上一次幅值 差值处理使用uint8_t ucmagarry[37];#defineSPECTRUM_WND_SIZE10//窗口数#defineTHRESHOLD_WINDOW_SIZE10//均值窗口数#defineMULTIPLIER1.0f//增益系数uint16_t wndNum =0;uint16_t spectralFlux[SPECTRUM_WND_SIZE];//前后差值uint16_t threshold[SPECTRUM_WND_SIZE];//均值阈值uint16_t peakSpectrum[SPECTRUM_WND_SIZE];//节拍值long DCT_error_max;//电磁铁差值最大值uint8_t DCT_flag=0;//电磁铁工作标志位0开启 1关闭uint8_t prt =10;//量化显示的比例#defineSHOW_NUM4//显示函数的个数uint8_t display_num =1;//控制显示方式的uint8_t auto_display_flag =0;//自动切换显示标志 1:自动切换 0:手动uint8_t fall_pot[128];//记录下落点的坐标/* USER CODE END 0 */intmain(void){/* USER CODE BEGIN 1 */uint16_t i =0;/* USER CODE END 1 *//* MCU Configuration----------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* Configure the system clock */SystemClock_Config();/* Initialize all configured peripherals */MX_GPIO_Init();MX_DMA_Init();MX_USART1_UART_Init();MX_ADC1_Init();MX_TIM3_Init();/* USER CODE BEGIN 2 */printf("uart test! \r\n");/*初始化显示*/GUI_Initialize();/*设置前景色和背景色 这里用1和0代替*/GUI_SetColor(1,0);GUI_LoadPic(0,0,(uint8_t*)&gImage_bg,128,64);GUI_Exec();HAL_Delay(3000);//初始化下落点 把下落的点 初始化为最底部显示for(i=0;i<128;i++)
fall_pot[i]=63;/*启动ADC的DMA传输 配合下面定时器来触发ADC转换*/HAL_ADC_Start_DMA(&hadc1, adc_buf, NPT);/*开启定时器 用溢出事件来触发ADC转换*/HAL_TIM_Base_Start(&htim3);
DCT_QD_flag=0;
DCT_QD_flag1=0;while(1){// DCT_1;/**********************************/if(DCT_QD_flag==1)//有音乐{
DCT_0;if(DCT_flag ==0)//开启电磁铁{
DCT_1;HAL_Delay(120);
DCT_flag =1;
DCT_0;}else{
DCT_0;}HAL_Delay(10);}else{
DCT_1;}}}/** System Clock Configuration
*/voidSystemClock_Config(void){
RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_ClkInitTypeDef RCC_ClkInitStruct;
RCC_PeriphCLKInitTypeDef PeriphClkInit;/**Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;if(HAL_RCC_OscConfig(&RCC_OscInitStruct)!= HAL_OK){Error_Handler();}/**Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2)!= HAL_OK){Error_Handler();}
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6;if(HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit)!= HAL_OK){Error_Handler();}/**Configure the Systick interrupt time
*/HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);/**Configure the Systick
*/HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);/* SysTick_IRQn interrupt configuration */HAL_NVIC_SetPriority(SysTick_IRQn,0,0);}/* USER CODE BEGIN 4 *//************FFT相关*****************///测试用 生成一个信号voidCreat_Single(void){
u16 i =0;float fx=0.0;for(i=0; i<NPT; i++){
fx =2048+2048*sin(PI2 * i *200.0/ Fs)+3100*sin(PI2 * i *502.0/ Fs)+1300*sin(PI2 * i *990.0/ Fs);
lBufInArray[i]=((signedshort)fx)<<16;}}//获取FFT后的直流分量voidGetPowerMag(void){signedshort lX,lY;float X,Y,Mag;unsignedshort i;for(i=0; i<NPT/2; i++){
lX =(lBufOutArray[i]<<16)>>16;
lY =(lBufOutArray[i]>>16);//除以32768再乘65536是为了符合浮点数计算规律
X = NPT *((float)lX)/32768;
Y = NPT *((float)lY)/32768;
Mag =sqrt(X * X + Y * Y)*1.0/ NPT;if(i ==0)
lBufMagArray[i]=(unsignedlong)(Mag *32768);else
lBufMagArray[i]=(unsignedlong)(Mag *65536);}}/*柱状显示*/voiddisplay1(void){uint16_t i =0;uint8_t x =0;uint8_t y =0;/*******************显示*******************/GUI_ClearSCR();for(i =0; i <32; i++)//间隔的取32个频率出来显示{
x =(i<<2);//i*4
y =63-(lBufMagArray[x+1]/prt)-2;//加1是为了丢掉第一个直流分量if(y>63) y =63;GUI_LineWith(x,y,x,63,3,1);//画下落的点if(fall_pot[i]>y) fall_pot[i]= y;else{if(fall_pot[i]>63) fall_pot[i]=63;GUI_LineWith(x,fall_pot[i],x,fall_pot[i]+3,3,1);
fall_pot[i]+=2;}}GUI_Exec();}/*单柱状显示*/voiddisplay2(void){uint16_t i =0;uint8_t y =0;/*******************显示*******************/GUI_ClearSCR();for(i =1; i <128; i++){
y =63-(lBufMagArray[i]/prt)-2;if(y>63) y =63;GUI_RLine(i,y,63,1);//画下落的点if(fall_pot[i]>y) fall_pot[i]= y;else{if(fall_pot[i]>63) fall_pot[i]=63;GUI_RLine(i,fall_pot[i],fall_pot[i]+1,1);
fall_pot[i]+=2;}}GUI_Exec();}/*柱状显示 中间对称*/voiddisplay3(void){uint16_t i =0;uint8_t y =0;/*******************显示*******************/GUI_ClearSCR();for(i =0; i <127; i++){
y =31-(lBufMagArray[i+1]/prt)-2;//加1是为了丢掉第一个直流分量if(y>31) y =31;GUI_RLine(i,32,y,1);GUI_RLine(i,32,63-y,1);//画下落的点if(fall_pot[i]>y) fall_pot[i]= y;else{if(fall_pot[i]>30) fall_pot[i]=30;GUI_RLine(i,fall_pot[i],fall_pot[i]+1,1);GUI_RLine(i,63-fall_pot[i],63-(fall_pot[i]+1),1);
fall_pot[i]+=2;}}GUI_Exec();}/*单柱状显示 中间对称*/voiddisplay4(void){uint16_t i =0;uint8_t x =0;uint8_t y =0;/*******************显示*******************/GUI_ClearSCR();for(i =0; i <32; i++)//间隔的取32个频率出来显示{
x =(i<<2);//i*4
y =31-(lBufMagArray[x+1]/prt)-2;//加1是为了丢掉第一个直流分量if(y>31) y =31;GUI_LineWith(x,y,x,32,3,1);GUI_LineWith(x,63-y,x,32,3,1);//画下落的点if(fall_pot[i]>y) fall_pot[i]= y;else{if(fall_pot[i]>31) fall_pot[i]=31;GUI_LineWith(x,fall_pot[i],x,fall_pot[i]+3,3,1);GUI_LineWith(x,63- fall_pot[i],x,63- fall_pot[i]-3,3,1);
fall_pot[i]+=2;}}GUI_Exec();}//ADC DMA传输中断voidHAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){uint16_t i =0,m=0;uint32_t flux =0;int start,end,j;float mean;staticuint16_t num =0;// printf("adc dma interrupt \r\n");HAL_ADC_Stop_DMA(&hadc1);//完成一次测量 关闭DMA传输//填充数组for(i=0;i<NPT;i++)
lBufInArray[i]=((signedshort)(adc_buf[i]-2048))<<16;//这里因为单片机的ADC只能测正的电压 所以需要前级加直流偏执//加入直流偏执后 软件上减去2048即一半 达到负半周期测量的目的// for(i=0;i<NPT-1;i++){// printf("i:%3d,%10d\r\n", i,adc_buf[i]);// printf("i:%3d,%.1f\r\n", i,(float)adc_buf[i]/4096*3.3);if(adc_buf[i+1]>adc_buf[i]){
error2=adc_buf[i+1];}}if(error2>992)//说明音乐启动{
DCT_QD_flag=1;//有音乐}else{
DCT_QD_flag1++;if(DCT_QD_flag1>25){
DCT_QD_flag1=0;
DCT_QD_flag=0;}}// if(error2>500)//说明音乐启动// {// DCT_QD_flag=1;//有音乐// }// else// {// DCT_QD_flag=0;// }cr4_fft_1024_stm32(lBufOutArray, lBufInArray, NPT);//FFT变换// cr4_fft_256_stm32(lBufOutArray, lBufInArray, NPT);GetPowerMag();//取直流分量对应的AD值// DCT_flag=1; //关闭电磁铁/***********求差值******************/for(i=0;i<NPT/2;i++){if(lBufMagArray[i]<0.05){
lBufMagArray[i]=0;}}for(i=0;i<NPT/2;i++){
error1[i]=lBufMagArray[i]-lastSpectrum[i];if(error1[i]<0){
error1[i]=0;}// printf("i:%3d, f:%.2f, Power:%10d\r\n", i, (float)i*Fs/NPT, error1[i]);}for(i=0;i<NPT/2;i++){
lastSpectrum[i]= lBufMagArray[i];//上次幅值赋值 }for(i=400;i<NPT/2-1;i++){if(error1[i+1]>error1[i]){
DCT_error_max=error1[i+1];}else{
DCT_error_max=0;}}/************************判断是否开启电磁铁************************************/if(DCT_error_max>=5){
DCT_flag=0;//开启电磁铁}else{
DCT_flag=1;//关闭电磁铁}//自动显示if(auto_display_flag ==1){if(num>300){
num =0;
display_num ++;if(display_num>SHOW_NUM) display_num =1;}}
num++;//显示switch(display_num){case1:display1();break;case2:display2();break;case3:display3();break;case4:display4();break;default:display3();break;}HAL_ADC_Start_DMA(&hadc1, adc_buf, NPT);}voidKey_Scan(void){staticuint8_t mode_num =0;if(K1 == RESET){HAL_Delay(10);if(K1 == RESET){while(!K1);
mode_num=!mode_num;if(mode_num ==1)//自动显示模式{
auto_display_flag =1;GUI_PutString(0,0,"Auto");GUI_Exec();}else//正常显示模式 手动切换效果{
auto_display_flag =0;GUI_PutString(0,0,"Manual");GUI_Exec();}}}if(K2 == RESET){HAL_Delay(10);if(K2 == RESET){while(!K2);if(mode_num ==0)//手动模式 {
display_num ++;if(display_num > SHOW_NUM) display_num =1;}}}if(K3 == RESET){HAL_Delay(10);if(K3 == RESET){while(!K3);if(mode_num ==0)//手动模式 {if(display_num ==1) display_num = SHOW_NUM+1;
display_num --;}}}}/* USER CODE END 4 *//**
* @brief This function is executed in case of error occurrence.
* @param None
* @retval None
*/voidError_Handler(void){/* USER CODE BEGIN Error_Handler *//* User can add his own implementation to report the HAL error return state */while(1){}/* USER CODE END Error_Handler */}#ifdefUSE_FULL_ASSERT/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/voidassert_failed(uint8_t* file,uint32_t line){/* USER CODE BEGIN 6 *//* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) *//* USER CODE END 6 */}#endif/**
* @}
*//**
* @}
*//************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
ADC程序:
/* Includes ------------------------------------------------------------------*/#include"adc.h"#include"gpio.h"#include"dma.h"/* USER CODE BEGIN 0 *//* USER CODE END 0 */
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;/* ADC1 init function */voidMX_ADC1_Init(void){
ADC_ChannelConfTypeDef sConfig;/**Common config
*/
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion =1;if(HAL_ADC_Init(&hadc1)!= HAL_OK){Error_Handler();}/**Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank =1;
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;if(HAL_ADC_ConfigChannel(&hadc1,&sConfig)!= HAL_OK){Error_Handler();}}voidHAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle){
GPIO_InitTypeDef GPIO_InitStruct;if(adcHandle->Instance==ADC1){/* USER CODE BEGIN ADC1_MspInit 0 *//* USER CODE END ADC1_MspInit 0 *//* Peripheral clock enable */__HAL_RCC_ADC1_CLK_ENABLE();/**ADC1 GPIO Configuration
PA0-WKUP ------> ADC1_IN0
*/
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);/* Peripheral DMA init*/
hdma_adc1.Instance = DMA1_Channel1;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR;
hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;if(HAL_DMA_Init(&hdma_adc1)!= HAL_OK){Error_Handler();}__HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1);/* USER CODE BEGIN ADC1_MspInit 1 *//* USER CODE END ADC1_MspInit 1 */}}voidHAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle){if(adcHandle->Instance==ADC1){/* USER CODE BEGIN ADC1_MspDeInit 0 *//* USER CODE END ADC1_MspDeInit 0 *//* Peripheral clock disable */__HAL_RCC_ADC1_CLK_DISABLE();/**ADC1 GPIO Configuration
PA0-WKUP ------> ADC1_IN0
*/HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0);/* Peripheral DMA DeInit*/HAL_DMA_DeInit(adcHandle->DMA_Handle);}/* USER CODE BEGIN ADC1_MspDeInit 1 *//* USER CODE END ADC1_MspDeInit 1 */}/* USER CODE BEGIN 1 *//* USER CODE END 1 *//**
* @}
*//**
* @}
*//************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
四、工程获取
1、直接在平台下载: 基于STM32:磁流体蓝牙音箱(源码工程+PCB+原理图)
五、专利
关于磁流体蓝牙音箱,本团队已经申请了专利(已授权下证),本团队有成熟的技术,有兴趣的朋友私信我们可以合作。
版权归原作者 千歌叹尽执夏 所有, 如有侵权,请联系我们删除。