stm32f103c6t6制作音乐频谱分析仪

前言

这个学期学习数字信号处理,需要制作一个音乐频谱分析仪,但是本人比较菜,所以只能复刻别人做好的。

参考来自: stm32f103+FFT+OLED的音乐频谱制作(只需三步即可)

原文使用的是stm32f103c8t6,我用的是stm32f103c6t6,两个大部分引脚是通用的。我将OLED换成了四针的IIC接口。
在这里插入图片描述
stm32f103c6t6是自己做的最小系统板,接了一个扩展版,把oled接口和ADC采集接口引到了右边。

所有硬件软件开源地址(点这里!!!

硬件

电路使用LM358进行放大,采用电源5V直流供电。由于单片机的ADC不能采集到负值,所以我们把信号加上了1/2Vcc的直流偏置,50倍增益可调。
在这里插入图片描述
本来打算做成贴片,可测试信号放大很差,信号干扰特别强,学识有限,搞不明白为什么,最后就做成插件了。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

软件

stm32f103c6t6最小系统板是自己画的,包含了RTC的外部低速时钟,如果下载中No target connected或者Internal command error的问题,需要按住复位键再下载,下载的时候再松开复位键。

详细操作参考:自制stm32F103c6t6出现No target connected或者Internal command error的原因猜测和解决方法

oled引脚接口可以通过OLED.h修改
在这里插入图片描述
程序框架采用HAL库的形式建立,如果没学过可能有点蒙圈。使用的是PA0接口进行ADC采集。

参考链接:ADC采样的HAL配置以及程序参考

代码中主要涉及到了GUI的移植:(利用类似如下函数画柱状图)

/****************************************************************************
* 名称:GUI_LineWith()
* 功能:画任意两点之间的直线,并且可设置线的宽度。
* 入口参数: x0		直线起点的x坐标值
*           y0		直线起点的y坐标值
*           x1      直线终点的x坐标值
*           y1      直线终点的y坐标值
*           with    线宽(0-50)
*           color	显示颜色
* 出口参数:无
* 说明:操作失败原因是指定地址超出有效范围。
****************************************************************************/
void  GUI_LineWith(uint32 x0, uint32 y0, uint32 x1, uint32 y1, uint8 with, TCOLOR color)
{  int32   dx;						// 直线x轴差值变量
   int32   dy;          			// 直线y轴差值变量
   int8    dx_sym;					// x轴增长方向,为-1时减值方向,为1时增值方向
   int8    dy_sym;					// y轴增长方向,为-1时减值方向,为1时增值方向
   int32   dx_x2;					// dx*2值变量,用于加快运算速度
   int32   dy_x2;					// dy*2值变量,用于加快运算速度
   int32   di;						// 决策变量
   
   int32   wx, wy;					// 线宽变量
   int32   draw_a, draw_b;
   
   /* 参数过滤 */
   if(with==0) return;
   if(with>50) with = 50;
   
   dx = x1-x0;						// 求取两点之间的差值
   dy = y1-y0;
   
   wx = with/2;
   wy = with-wx-1;
   
   /* 判断增长方向,或是否为水平线、垂直线、点 */
   if(dx>0)							// 判断x轴方向
   {  dx_sym = 1;					// dx>0,设置dx_sym=1
   }
   else
   {  if(dx<0)
      {  dx_sym = -1;				// dx<0,设置dx_sym=-1
      }
      else
      {  /* dx==0,画垂直线,或一点 */
         wx = x0-wx;
         if(wx<0) wx = 0;
         wy = x0+wy;
         
         while(1)
         {  x0 = wx;
            GUI_RLine(x0, y0, y1, color);
            if(wx>=wy) break;
            wx++;
         }
         
      	 return;
      }
   }
   
   if(dy>0)							// 判断y轴方向
   {  dy_sym = 1;					// dy>0,设置dy_sym=1
   }
   else
   {  if(dy<0)
      {  dy_sym = -1;				// dy<0,设置dy_sym=-1
      }
      else
      {  /* dy==0,画水平线,或一点 */
         wx = y0-wx;
         if(wx<0) wx = 0;
         wy = y0+wy;
         
         while(1)
         {  y0 = wx;
            GUI_HLine(x0, y0, x1, color);
            if(wx>=wy) break;
            wx++;
         }
      	 return;
      }
   }
    
   /* 将dx、dy取绝对值 */
   dx = dx_sym * dx;
   dy = dy_sym * dy;
 
   /* 计算2倍的dx及dy值 */
   dx_x2 = dx*2;
   dy_x2 = dy*2;
   
   /* 使用Bresenham法进行画直线 */
   if(dx>=dy)						// 对于dx>=dy,则使用x轴为基准
   {  di = dy_x2 - dx;
      while(x0!=x1)
      {  /* x轴向增长,则宽度在y方向,即画垂直线 */
         draw_a = y0-wx;
         if(draw_a<0) draw_a = 0;
         draw_b = y0+wy;
         GUI_RLine(x0, draw_a, draw_b, color);
         
         x0 += dx_sym;				
         if(di<0)
         {  di += dy_x2;			// 计算出下一步的决策值
         }
         else
         {  di += dy_x2 - dx_x2;
            y0 += dy_sym;
         }
      }
      draw_a = y0-wx;
      if(draw_a<0) draw_a = 0;
      draw_b = y0+wy;
      GUI_RLine(x0, draw_a, draw_b, color);
   }
   else								// 对于dx<dy,则使用y轴为基准
   {  di = dx_x2 - dy;
      while(y0!=y1)
      {  /* y轴向增长,则宽度在x方向,即画水平线 */
         draw_a = x0-wx;
         if(draw_a<0) draw_a = 0;
         draw_b = x0+wy;
         GUI_HLine(draw_a, y0, draw_b, color);
         
         y0 += dy_sym;
         if(di<0)
         {  di += dx_x2;
         }
         else
         {  di += dx_x2 - dy_x2;
            x0 += dx_sym;
         }
      }
      draw_a = x0-wx;
      if(draw_a<0) draw_a = 0;
      draw_b = x0+wy;
      GUI_HLine(draw_a, y0, draw_b, color);
   } 
  
}
#endif

在ADC的采集中断里进行FFT计算

//ADC DMA传输中断
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
	uint16_t i = 0;
	static uint16_t num = 0;	
//	printf("adc dma interrupt \r\n");
	HAL_ADC_Stop_DMA(&hadc1);		//完成一次测量 关闭DMA传输
	
	//填充数组
	for(i=0;i<NPT;i++)
		lBufInArray[i] = ((signed short)(adc_buf[i]-2048)) << 16;		//这里因为单片机的ADC只能测正的电压 所以需要前级加直流偏执
		/加入直流偏执后 软件上减去2048即一半 达到负半周期测量的目的
			
	cr4_fft_256_stm32(lBufOutArray, lBufInArray, NPT);//FFT变换
	GetPowerMag();			//取直流分量对应的AD值
	··········

软件也都开源在前言那里。

总结

感谢大佬的开源,让我能复刻出来,难点主要在软件,这是我头一次接触GUI移植,同时也发现GUI的强大。同时,我也发现FFT软件复现对我挺难的,只是利用现成的库,还需要加强理论学习。最后就是贴片的信号放大不明白为什么。不知道是芯片选型的问题还是电路的问题。

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Main: #include "led.h" #include "delay.h" #include "key.h" #include "sys.h" #include "lcd.h" #include "usart.h" #include "adc.h" #include "stm32_dsp.h" #include /* Private typedef -----------------------------------------------------------*/ /* Private define ------------------------------------------------------------*/ #define DOUBLE_COLOR 1 //ÊÇ·ñΪ˫ɫÆÁ£¬ÈôΪ˫ɫÆÁÔò¸ÄΪ1 #define NPT 64 //FFT²ÉÑùµãÊý #define GREEN_STOP_TIME 15 //ÂÌÉ«µã¶¥¶ËÍ£¶Ùʱ¼ä£¬ÖµÔ½´óʱ¼äÔ½³¤ #define GREEN_SUB_SPEED 100 //ÂÌÉ«µãÏÂÒÆËٶȣ¬ÖµÔ½´óËÙ¶ÈÔ½Âý #define RED_SUB_SPEED 50 //ºìɫƵÖùÏòÏÂËõ¶ÌËٶȣ¬ÖµÔ½´óËÙ¶ÈÔ½Âý uint32_t ADC_DataNum=0; //ADC²ÉÑùµãÊý uint32_t RedTime=0; //ºìÉ«µãÏÂÒÆʱ¼ä±äÁ¿ #if DOUBLE_COLOR uint32_t GreenTime=0; //ÂÌÉ«µãÏÂÒÆʱ¼ä±äÁ¿ uint32_t GreenStopTime[32]={0}; //ÂÌÉ«µã¶¥¶ËÍ£¶Ùʱ¼äÊý¾Ý #endif volatile uint8_t ADC_TimeOutFlag=1; //ADC¶¨Ê±²ÉÑùʱ¼äµ½±êÖ¾ extern __IO uint16_t ADCConvertedValue; //ADC²ÉÑùÖµ extern int LCD_COLOR; long lBUFMAG[NPT+NPT/2]; //´æ´¢ÇóÄ£ºóµÄÊý¾Ý long lBUFOUT[NPT];//FFTÊä³öÐòÁÐ long lBUFIN[NPT];//FFTÊäÈëϵÁÐ uint8_t fftHightRedBuf[NPT/2]={0}; //ºìɫƵÖù¸ß¶ÈÊý×é uint8_t DisplayRedDataBuf[32*8]={0}; //ºìÉ«ÏÔʾ»º³åÇø #if DOUBLE_COLOR uint8_t fftHightGreenBuf[NPT/2]={0}; //ÂÌɫƵµã¸ß¶ÈÊý×é uint8_t DisplayGreenDataBuf[32*8]={0}; //ÂÌÉ«ÏÔʾ»º³åÇø #endif u16 color_tab[16]={DARKBLUE,BLUE,LIGHTBLUE,GREEN,LIGHTGREEN,RED,BRED,BRRED,BLACK,YELLOW,CYAN,MAGENTA,GRAYBLUE,LGRAYBLUE,BROWN,LGRAY}; void music_fft_main(uint8_t *RedNewHeight,uint8_t *GreenNewHeight) { int BarWidth = 8; int i=0; int j=0; static uint8_t RedOldHeight[32] = {0}; static uint8_t GreenOldHeight[32] = {0}; for(i=0;iRedOldHeight[i]){//Èç¹ûµ±Ç°µÄÂÌÉ«Öù×Ӹ߶ȱÈ֮ǰµÄ´óÔò²¹ÆëÂÌÉ«Öù×Ó LCD_Fill(RedOldHeight[i],(BarWidth+2)*i,RedNewHeight[i],(BarWidth+2)*i+BarWidth,color_tab[j]); }else{//Èç¹ûµ±Ç°ÏÔʾµÄÂÌÉ«Öù×Ӹ߶ÈСÓÚ֮ǰµÄÖù×ÓÔòÐèÒª½«¶àÓàµÄÂÌÉ«Öù×ÓÓñ³¾°É«Ìî³ä LCD_Fill(RedNewHeight[i],(BarWidth+2)*i,RedOldHeight[i],(BarWidth+2)*i+BarWidth,WHITE); } //½«ÐÂÊý¾Ý±£´æ RedOldHeight[i] = RedNewHeight[i]; GreenOldHeight[i] = GreenNewHeight[i]; if(j>=15) j=0; j++; } } void powerMag(long nfill) { int32_t lX,lY; uint32_t i; for (i=0; i < nfill; i++) { lX= (lBUFOUT[i]<>16; /* sine_cosine --> cos */ lY= (lBUFOUT[i] >> 16); /* sine_cosine --> sin */ { float X= 64*((float)lX)/32768; float Y = 64*((float)lY)/32768; float Mag = sqrt(X*X+ Y*Y)/nfill; // ÏÈƽ·½ºÍ,ÔÙ¿ª·½ lBUFMAG[i] = (long)(Mag*65536); } } } int main(void) {uint32_t i=0; delay_init(); //ÑÓʱº¯Êý³õʼ»¯ NVIC_Configuration(); //ÉèÖÃNVICÖжϷÖ×é2:2λÇÀÕ¼ÓÅÏȼ¶£¬2λÏìÓ¦ÓÅÏȼ¶ uart_init(9600); //´®¿Ú³õʼ»¯Îª9600 LED_Init(); //LED¶Ë¿Ú³õʼ»¯ TIM2_Configuration(); TIM2_NVIC_Configuration(); FFT_RCC_Configuration(); FFT_GPIO_Configuration(); FFT_DMA_Init(); FFT_ADC_Init(); LCD_Init(); // BACK_COLOR=BLACK; TIM_Cmd(TIM2, ENABLE); ADC_SoftwareStartConvCmd(ADC1, DISABLE); while(1) { if(ADC_TimeOutFlag){ #if DOUBLE_COLOR GreenTime++; #endif RedTime++; ADC_TimeOutFlag=0; if(ADC_DataNumCR2 |= 0x00500000;// ADC_SoftwareStartConvCmd(ADC1, ENABLE); while(!DMA_GetFlagStatus(DMA1_FLAG_TC1)); /* Clear channel1 transfer complete flag */ DMA_ClearFlag(DMA1_FLAG_TC1); // ADC1->CR2 &= 0xFFAFFFFF;// ADC_SoftwareStartConvCmd(ADC1, DISABLE); lBUFIN[ADC_DataNum]=ADCConvertedValue<<16; ADC_DataNum++; }else{ TIM_Cmd(TIM2, DISABLE); ADC_DataNum=0; cr4_fft_64_stm32(lBUFOUT,lBUFIN,NPT);//µ÷ÓÃSTM32µÄDSP¿â×÷FFT±ä»» powerMag(NPT);//¼ÆËãƵµã·ùÖµ //¸üкìÉ«µãµÄ¸ß¶È for(i=0;ifftHightRedBuf[i]){ fftHightRedBuf[i]=(lBUFMAG[i]); } #if DOUBLE_COLOR //Ë¢ÐÂÂÌÉ«µã¸ß¶È if(fftHightRedBuf[i]>=fftHightGreenBuf[i]){ fftHightGreenBuf[i]=fftHightRedBuf[i]; GreenStopTime[i]=GREEN_STOP_TIME;//Â̵ãÍ£¶Ùʱ¼ä if(fftHightRedBuf[i]>=235){ fftHightGreenBuf[i]=235; fftHightRedBuf[i]=235; } } #else if(fftHightRedBuf[i]>=239){ fftHightRedBuf[i]=239; } #endif } //ÏÔʾºìÉ«Öù×Ó music_fft_main(fftHightRedBuf,fftHightGreenBuf); //ÏÔʾÂÌÉ«µã #if DOUBLE_COLOR //ÂÌÉ«µãÏÂÒÆ if((GreenTime>GREEN_SUB_SPEED)){ //ÂÌÉ«µãϽµ¼ä¸ôʱ¼ä GreenTime=0; for(i=0;iRED_SUB_SPEED){ RedTime=0; for(i=0;i<NPT/2;i++){ if((fftHightRedBuf[i]!=0)){ fftHightRedBuf[i]--; } } } //ÂÌÉ«µãÍ£¶Ùʱ¼ä¼õÒ» #if DOUBLE_COLOR for(i=0;iSR = (uint16_t)~TIM_FLAG_Update; TIM_ClearITPendingBit(TIM2,TIM_FLAG_Update); //ÇåÖÐ¶Ï ADC_TimeOutFlag=1; } }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值