音乐频谱显示小玩具——FFT在STM32中的实现与应用

6 篇文章 0 订阅
5 篇文章 0 订阅

0、前言

音乐频谱显示说白了就是“儿童版”频谱仪。笔者平时比较喜欢听音乐,闲暇之余听音乐的时候如果有个频谱显示的小玩具在旁边跳来跳去的也挺有意思的,所以笔者去万能的某宝上搜索了一下,发现便宜的都很小,大一点的都很贵,而且都需要音频接头输入,很麻烦,所以笔者就自制了这个小玩具。效果图如下图1所示。

效果视频:自制音乐频率响应小玩具_哔哩哔哩_bilibili

图1:实物展示

其功能有一下3个:

1、实时显示音乐频谱

2、将小玩具倒立,显示当前时间和温度。

3、在显示时间和温度时,一段时间后屏幕自动熄灭。触动小玩具即可再次点亮

1、FFT(Fast Fourier transform)

快速傅里叶变换是一种利用计算机进行高速快捷的离散傅里叶变换的一种方法,而什么是傅里叶变换?傅里叶变换的物理意义是什么呢?

当年,著名的数学家傅里叶表示:任何连续测量的时序或信号,都可以表示为不同频率的正弦波信号的无限叠加。而通过傅里叶变换就可以将叠加的波形以正弦波为基本单位将其拆分开。

图2:傅里叶变换

而快速傅里叶变换为什么可以快速呢?首先先看一下离散傅里叶变换的公式:

                                                                                 Y(Z)=\frac{1}{N}\sum_{N-1}^{n=0}X(n)e^{-j\pi nt/N}

通过公式不难看出如果我们让计算机去计算时,我们需要计算N^2次运算。但是通过观察可以发现,在计算中有想当一般的数据都是冗余的,并且不会显示在频谱中。所以我们可以将这些数据保存下来计算的时候直接调用或在此数据基础上做简单的运算。通过这样的方法我们就可以将DFT的N^2次运算减少到Nlog10(N)次运算了。

例如如果做1000采样点的傅里叶变换使用DFT的运算次数就是1000000次,而使用FFT就可以减少到3000次运算。

2、STM32实现FFT

因为笔者做的这个玩具使用的芯片是STM32单片机F10系列。所以在做FFT的时候没有官方库的支持(以前是有,但是F4系列上线后就取消了,真实万恶的资本主义\dogs)虽然网上有很多教程是直接移植或者找之前的DSP库,但是笔者觉得着实有些麻烦,F10系列没有DSP运算核心,这个DSP库就犹如花瓶。

这里主要借鉴了这位大神的文章:《C》C语言实现FFT算法_杨贵安的博客-CSDN博客_c语言fft

在单片机上做FFT时需要注意堆栈的大小,以免发生硬件错误。代码如下:

double * FFT(double *pr,int n)
{
	static double amp_data[FFT_max*2];//定义输出幅值
	static double fr_data[FFT_max*2];//定义输出实部
	static double fi_data[FFT_max*2];//定义输出虚部
	static double pr_data[FFT_max];//缓存输入实部
	static double pi_data[FFT_max];//缓存输入虚部
	unsigned int k;//n=2^k;
	int i,j;
	int it,m,is,nv;
	double p,q,s,vr,vi,poddr,poddi;
//===============初始化================================//
	k = log10(n)/log10(2);//n=2^k;
	for(i=0;i<n;i++)
	{
		pr_data[i] = *(pr+i);//读入缓存
		pi_data[i] = 0;//读入缓存
	}
//========将pr[0]和pi[0]循环赋值给fr_data[]和fi_data[]==========//
  for (it=0; it<=n-1; it++)  
  { 
		m=it; 
		is=0;
		for(i=0; i<=k-1; i++)
    { 
			j=m/2; 
			is=2*is+(m-2*j); 
			m=j;
		}
    fr_data[it]=pr_data[is]; 
    fi_data[it]=pi_data[is];
  }
//==================================================//
	pr_data[0]=1.0; 
	pi_data[0]=0.0;
	p=6.283185306/(1.0*n);
	pr_data[1]=cos(p); //将w=e^-j2pi_data/n用欧拉公式表示
	pi_data[1]=-sin(p);
//================计算pr_data和pi_data========================//
  for (i=2; i<=n-1; i++)  
  { 
		p=pr_data[i-1]*pr_data[1]; 
		q=pi_data[i-1]*pi_data[1];
		s=(pr_data[i-1]+pi_data[i-1])*(pr_data[1]+pi_data[1]);
		pr_data[i]=p-q; pi_data[i]=s-p-q;
  }
//===================计算fr_data和fi_data=====================//
  for (it=0; it<=n-2; it=it+2)  
  { 
		vr=fr_data[it]; 
		vi=fi_data[it];
		fr_data[it]=vr+fr_data[it+1]; 
		fi_data[it]=vi+fi_data[it+1];
		fr_data[it+1]=vr-fr_data[it+1]; 
		fi_data[it+1]=vi-fi_data[it+1];
  }
//=================蝴蝶操作=========================//
	m=n/2; 
	nv=2;
  for (i=k-2; i>=0; i--)
  { 
		m=m/2; 
		nv=2*nv;
    for (it=0; it<=(m-1)*nv; it=it+nv)
		{
			for (j=0; j<=(nv/2)-1; j++)
			{ 
				p=pr_data[m*j]*fr_data[it+j+nv/2];
				q=pi_data[m*j]*fi_data[it+j+nv/2];
				s=pr_data[m*j]+pi_data[m*j];
				s=s*(fr_data[it+j+nv/2]+fi_data[it+j+nv/2]);
				poddr=p-q; 
				poddi=s-p-q;
				fr_data[it+j+nv/2]=fr_data[it+j]-poddr;
				fi_data[it+j+nv/2]=fi_data[it+j]-poddi;
				fr_data[it+j]=fr_data[it+j]+poddr;
				fi_data[it+j]=fi_data[it+j]+poddi;
			}
		}
  }
//=================计算幅值输出==========================//
  for (i=0; i<=n-1; i++)
  {
		amp_data[i]=sqrt(fr_data[i]*fr_data[i]+fi_data[i]*fi_data[i]);  //幅值计算
  }
//====================返回数据=========================//
	return amp_data;//返回幅值
}

直接通过KEIL仿真可以观察出FFT的输出波形与MATLAB对比图如下图3,图4

图3:Keil仿真波形
图4:MATLAB波形

方波的FFT测试如下图5所示:

图5:方波测试

3、显示屏选用与点阵驱动

笔者使用了6个8*8点阵组成的24*16点阵作为显示屏。每个8*8点阵使用MAX7219驱动。MAX7219之间级联保证占用的I/O口最少。其电路原理图如下图6所示

图6:MAX7219驱动电路

因为MAX7219这款IC输入电压范围在4-5.5V故一般需要5V供电,信号端也需要5-3.3V的转换。虽然大部分STM32的I/O口都是可以容忍5V输入与输出的。但笔者为了保险起见加了3路的3.3V-5V电路。其电路原理图如下。

4、STM32与音频输入电路

笔者将整个小玩具分成了两个块电路板,其中一块全是点阵和MAX7219一块是STM32、锂电池充放电电路、MPU6050外围电路和一路的音频采集电路,引出剩下部分引脚。音频采集电路主要使用咪头,经MAX4466做前置放大,放大后信号直接接入STM32内置ADC引脚。电路图如下图7所示

图7:MAX4466驱动电路

5、点阵屏时间显示

时间的计算采用STM32内置的RTC时钟即可,首先需要显示数字的字模,根据电路和字体大小(5*3),计算出数字0-9字模如下:

unsigned char disp1[11][3]={
{0x1F,0x11,0x1F},//0
{0x00,0x1F,0x08},//1
{0x1D,0x15,0x17},//2
{0x1F,0x15,0x15},//3
{0x1F,0x04,0x1C},//4
{0x17,0x15,0x1D},//5
{0x17,0x15,0x1F},//6
{0x18,0x17,0x10},//7
{0x1F,0x15,0x1F},//8
{0x1F,0x15,0x1D},//9
{0x00,0x0A,0x00} //:
};

根据电路结构和扫描顺序,写出底层显示驱动程序,程序如下:

//参数从左到右依次为:字体数据,字体长度,字体宽度,起始位置的x坐标(自右向左),起始位置y坐标(自下而上)
void MAX7219_N_display(unsigned char *cData,unsigned char Length,unsigned char Width,unsigned char x,unsigned char y)
{
	static u16 iSbufWidth[24];//纵向显示缓存
	u16 iSbuf;//用户数据缓存
	u16 iSbuf_Middle;//缓存中间变量
	unsigned int i;
	for(i=0;i<Length;i++)
	{
		iSbuf = 0;//缓存清零
		iSbuf |= *(cData+i)<<y;//只有有效位存在"1"的可能,非有效位全为0
		iSbufWidth[i+x] |= iSbuf;//向显示缓存写入有效位的“1”
		iSbuf = ~*(cData+i);//按位取反
		iSbuf <<= y;//左移到显示位置
		iSbuf = ~iSbuf;//相当于左移补1
		iSbuf_Middle = 0xffff;//置位
		iSbuf_Middle <<=(y+Width);
		iSbuf |= iSbuf_Middle;//补全高位使得只有有效位存在"0"的可能,非有效位全为1
		iSbufWidth[i+x] &= iSbuf;//向缓存写入有效位的“0”
	}
	for(i=0;i<8;i++)
	{
		Write_Max7219_N(i+1,iSbufWidth[i],iSbufWidth[i+8],iSbufWidth[i+16],iSbufWidth[i+16]>>8,iSbufWidth[i+8]>>8,iSbufWidth[i]>>8);//显示
	}
}

6、总结

这个装置整体来说比较简单,因为有姿态传感器和一个点阵屏,所以开发的自由度也比较高。本文附资源,资源包含两个电路板的PCB源文件和开发的源代码。如有好玩的想法或者改进欢迎留言讨论。

资源下载:音乐频谱显示资料.zip-硬件开发其他资源-CSDN下载本资源内含STM32源程序和PCB文件。可直接DIY制作。声明:本资料仅供娱乐参考。如转发或在其更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/qq_24025329/12331286?spm=1001.2014.3001.5503

  • 19
    点赞
  • 141
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
FFT(快速傅里叶变换)是一种常用的信号处理方法,可以将时域信号转换为频域信号。在STM32实现FFT需要以下步骤: 1. 配置STM32的DMA(直接存储器访问)控制器,以便将采样数据从ADC(模数转换器)读入内存。 2. 编写FFT算法的代码,或者使用已经实现好的FFT库。 3. 定义用于存储FFT结果的数组,并将其与DMA通道相关联,以便在DMA传输完成后自动执行FFT计算。 4. 在主循环轮询DMA传输标志位,以便在DMA传输完成后触发FFT计算。 5. 在FFT计算完成后,将结果以适当的方式输出或使用。 以下是一个基本的实现示例,假设使用STM32F4 Discovery板和基于CMSIS库的FFT库: ```c #include "stm32f4xx.h" #include "arm_math.h" // 包含CMSIS库FFT函数 #define FFT_SIZE 1024 // FFT的大小 uint16_t ADC_Buffer[FFT_SIZE]; // 用于存储ADC采样数据的数组 float32_t FFT_Buffer[FFT_SIZE]; // 用于存储FFT结果的数组 int main(void) { // 配置ADC和DMA // 配置GPIO等等 // 初始化FFT库 arm_rfft_fast_init_f32(&S, FFT_SIZE); while (1) { // 等待DMA传输完成 while (DMA_GetFlagStatus(DMA2_Stream0, DMA_FLAG_TCIF0) == RESET); // 执行FFT计算 arm_rfft_fast_f32(&S, (float32_t *)ADC_Buffer, FFT_Buffer, 0); // 处理FFT结果 // ... // 重置DMA传输标志位 DMA_ClearFlag(DMA2_Stream0, DMA_FLAG_TCIF0); } } ``` 此示例,使用了CMSIS库的arm_math.h头文件的函数来执行FFT计算。在初始化FFT库后,可以使用arm_rfft_fast_f32函数将存储在ADC_Buffer数组的采样数据转换为频域数据,并将结果存储在FFT_Buffer数组。在处理FFT结果之后,应当重置DMA传输标志位,以便在下一个周期开始时重新等待DMA传输完成。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值