一、项目介绍
本项目旨在设计一款基于心理声学的 STM32 多功能音乐频谱分析仪。该分析仪利用 STM32 微控制器的高性能处理能力,结合心理声学原理,对音乐信号进行实时采集、处理和分析,展示音乐频谱的分布情况。在听音乐的同时,可以直观地了解音乐信号的频率成分,增加视听感受,从而更好地进行音乐创作、教学和欣赏。
该项目采用了 STM32U083CCU6 芯片,在声音信号进入麦克风之后,使用同相放大器电路来放大和滤波麦克风输出信号。再将经过放大器的音频信号输入单片机,STM32 的内置 12 位 ADC 确保了声音信号能被精准采集。通过快速傅里叶变换(FFT)技术,将复杂的时域信号转换为了易于分析的频域信号。并且融入了心理声学原理,充分考虑了人耳听觉系统的感知特性。通过这种结合,频谱显示更加贴近人耳的实际听觉体验,提供更为真实和直观的音乐分析。采用了由 SK9822 灯珠组成的自制屏幕,以动态且直观的方式展示音乐频谱。
为了进一步提升体验感,后期加入了蓝牙功放板并且外接了喇叭,能够直接由 STM32 将蓝牙音频进行实时转换和分析。手机、电脑可以通过蓝牙连接作为无线音箱,或者音频线接入作为手机电脑的外置有线音箱,加入按键切换状态,按键可以切换选择使用蓝牙输入还是直接麦克风采集。最后给整体进行了外观结构设计,将板子的各部分结合在一起有一个好看外型,在欣赏音乐的同时,也能感受到科技带来的魅力。
1.1 硬件介绍
主板集控制电路和显示电路于一体,减少电路板的数量,降低制作成本。正面是由灯珠组成的自制屏幕电路,背面是控制电路,两面可同时焊接作为驱动+灯,或者只焊接 LED 作为扩展灯板。板子整体大小为 10 x 10 cm,一个电路板上的由 8 x 8 个灯珠组成屏幕。SK9822 LED 灯珠支持级联功能,允许多个灯珠串联使用。为了让显示的效果更好,屏幕是将另一不安装MCU的板子与完整的电路拼接在一起,通过级联,扩展显示面积为 16 x 8 像素,实现更大规模的显示效果。背面的控制电路包括主控、麦克风放大电路、供电电路、音频输入电路、调试电路、按键切换、灯板控制于一体,各部分功能明确。
加入蓝牙功放板并连接喇叭。通过按键切换蓝牙输入音频信号还是直接麦克风采集这两种状态,选择蓝牙输入时,外接的喇叭与电路相连,这是电路相当于变成了一个外接音箱设备。硬件部分功能模块化,易于复现。
主控:STM32U083CCU6
基于高性能 Arm® Cortex-M0®+ 32 位 RISC 内核的超低功耗微控制器,工作频率高达 56 MHz,提供高性能的计算能力。集成 12位 ADC,支持高精度模拟信号处理,适用于需要模拟信号处理的应用场景。提供 256 KB 的 Flash 内存和 40 KB 的 SRAM,支持高速数据处理和存储。
自制 SK9822 LED 灯珠屏幕板
SK9822 LED 灯珠屏幕板作为显示频谱的设备,SK9822 的驱动相对较为方便,支持 256 级灰度 PWM 调整和 32 级亮度调整,可实现丰富的色彩和亮度控制。
2X25W蓝牙功放板
通过蓝牙功放板连接手机蓝牙播放音乐,按键可以切换工作状态,决定是由直接通过蓝牙输入还是外部输入。
1.2 应用场景
- 音频设备测试:音频工程师可以利用它来测试和比较不同音响设备的频率响应。
- 教育领域:音乐频谱分析仪可以作为教学工具,帮助学生直观地理解声波的频率成分和音乐理论。
- 现场表演:在音乐会现场,音乐频谱分析仪可以作为视觉特效的一部分,增强现场氛围。
1.3 设计思路
硬件选择:
为了更有效地执行FFT算法并处理ADC采集到的数据,选择了处理能力强大的 STM32U083CCU6 作为主控芯片。该芯片具有较大的 RAM 空间,能够方便地处理大量数据。加入蓝牙功放板以方便地从外部设备接收音频信号。选择 SK9822 LED 灯珠组成自制屏幕,作为显示频谱的设备,SK9822 灯珠支持 256 级灰度 PWM 调整和 32级亮度调整,可实现丰富的色彩和亮度控制。并且支持级联功能,允许多个灯珠串联使用,实现更复杂的显示效果。根据频率闪烁相应的灯光提供丰富的视觉效果。
信号采集与数据处理:
设计音频采集电路,包括麦克风的信号放大和滤波,以确保信号质量。使用 STM32 的 ADC 模块采集模拟音频信号,并将其转换为数字信号。对采集的数字信号进行FFT 变换,提取音乐信号的频率成分。根据心理声学模型,将 FFT 得到的频谱数据进行处理,以转换为人耳听觉特性的频谱表示。最后将处理后的频谱数据映射到显示设备上。
外壳设计:
设计项目的外壳,既要美观,也要实用,确保所有组件都能妥善安装并得到保护。
二、功能实现
2.1 硬件设计
硬件设计分为供电电路、麦克风前置放大电路、麦克风输入电路、MCU最小系统、SK9822 LED 自制屏幕电路等部分组成,为了节约成本,主板集控制电路和显示电路于一体。蓝牙功放板和喇叭单独外接,再由按键控制切换状态。
(1)、供电电路
供电电路采用两套供电方案,由USB取电将 5V 电压转换成 3.3V ,转换的 3.3 V 电压一部分给模拟电路作为供电,一部分给芯片供电。
(2)、同相麦克风前置放大器电路
使用同相放大器电路配置来放大麦克风输出信号,此电路的幅度稳定性很好,在整个音频范围内仅具有微小的频率响应偏差。使用低电阻值电阻器和低噪声运算放大器实现低噪声的设计。
(3)、音频输入电路
音频输入电路的加入方便按键切换状态,按键切换选择是使用蓝牙输入音频信号还是直接麦克风采集输入音频信号。
(4)、MCU最小系统
按照数据手册的要求,绘制MCU最小系统。
(5)、SK9822自制屏幕电路
SK9822 LED 灯珠支持级联功能,实现更复杂的显示效果。并且支持数据线和时钟线同步的双线传输,实现同步控制。使用一组控制信号就可以方便地控制所有灯珠,简化控制系统设计。相比于使用多个独立的 LED 控制器,级联可以降低系统成本。
2.2 软件功能实现
初始化和配置:初始化 STM32 微控制器的硬件接口,包括ADC、定时器、DMA、蓝牙模块等。配置 ADC 采样率和分辨率,以适应声音信号的采集需求。初始化蓝牙模块,以便与外部设备建立连接。
音频信号采集及转换:STM32 的 ADC 模块采集来自麦克风的模拟音频信号。对采集的音频样本进行FFT变换,以获取频域表示。在STM32 CubeIDE 中移植 DSP 库非常方便,可以直接使用 DSP 库中集成好的库函数进行 FFT 运算。
频谱数据映射:将频谱数据映射到显示设备 SK9822 灯组上,设计映射算法,将频率成分转换以实现动态视觉效果。
显示与心理声学结合:模拟人耳对不同频率的敏感度,来调整频谱数据,实现更符合人耳听觉的动态视觉效果,增强更加炫酷的视听效果。
2.3 关键代码及说明
(1)、ADC 采集及 FFT 变换
ADC 的采样频率设置为 44.1 kHz,奈奎斯特采样定理告诉我们,要完整地恢复信号(更严格地)采样频率还必须大于信号中最高频率的两倍。人的听觉范围是20Hz~20000Hz,也即我们无法听到频率高于 2 万赫兹的声音。所以这个上限乘上两倍(再加上一些冗余)就得到了采样频率的 44.1 kHz,使用定时器触发 ADC,以实现等时间间隔的数据采集,再由 DMA 搬运到缓存数组中等待处理。
首先通过外部的麦克风放大电路将音频信号送至单片机,由 ADC 将采集到的模拟信号转换为数字信号,ADC 采集到的信号是时间序列数据,即在时域中表示的信号。FFT 变换可以将这些时域信号转换到频域,从而分析声音的频率成分,为进一步的可视化处理提供基础数据。FFT 变换后得到的频域数据可以用来驱动显示 SK9822 灯组,以实现音乐的可视化。通过将声音信号转换为视觉图像,用户可以直观地看到音乐的频率分布。
void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV1;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.LowPowerAutoPowerOff = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T15_TRGO;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc1.Init.SamplingTimeCommon1 = ADC_SAMPLETIME_1CYCLE_5;
hadc1.Init.SamplingTimeCommon2 = ADC_SAMPLETIME_1CYCLE_5;
hadc1.Init.OversamplingMode = DISABLE;
hadc1.Init.TriggerFrequencyMode = ADC_TRIGGER_FREQ_HIGH;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_6;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLINGTIME_COMMON_1;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
MX_TIM15_Init();
HAL_TIM_Base_Start(&htim15);
HAL_ADC_Start_DMA(&hadc1, ADC_DMA, 1024);
ADC_DMA_CPLT = 0;
int32_t ADC_Buffer[2048];
float32_t ADC_Buffer_f[1024];
int32_t ADC_Buffer_16[16];
int32_t ADC_Buffer_16_f[16];
uint16_t ADC_DMA[1024] = {0};
ADC_Sum=0;
for(int i = 0;i < 1024;i++)
{
ADC_Sum += ADC_DMA[i];
}
ADC_Sum = ADC_Sum/1024.0;
//2.
for(int i = 0;i < 1024;i++)
{
ADC_Buffer[2*i] = 8*(ADC_DMA[i]-ADC_Sum);//ADC范围0~4095,去均值后,范围正负2048,相比于int16,数值范围不够大,计算丢失精度
ADC_Buffer[2*i+1] =0;
}
//3.
arm_cfft_q31(&arm_cfft_sR_q31_len1024, ADC_Buffer, 0, 1);
//4.
for(int i = 0;i < 1024;i++)
{
ADC_Buffer_f[i] = ADC_Buffer[i];
}
arm_cmplx_mag_f32(ADC_Buffer_f,ADC_Buffer_f,512);
for(int i = 0;i < 512;i++)
{//只取前一半信号做进一步处理
ADC_Buffer[i]= ADC_Buffer_f[i];
}
(2)、SK9822灯珠的驱动
为什么选择 SK9822 作为显示板的灯珠,因为它支持级联功能,便于实现更复杂的显示效果。并且支持数据线和时钟线同步的双线传输,实现同步控制。为了实现对SK9822 的控制,就需要读懂数据手册,下图是一个字节的传输时序,可以看出数据位在时钟的下降沿时发生变化,在时钟线上升沿到来时传入芯片,这样的通信方式可以直接用 SPI 模拟,在STM32 CubeIDE里可以直接调用 SPI 外设,不用再手搓时序,大大减少了程序量,同时 SPI 通信为高速同步通信,使用硬件通信接口可以加快屏幕的刷新率。
上图中是一个字节的传输过程,接下来是如何控制级联的灯串,下图中可以看出每一组的刷新,首先是发送由 4 个 0x00 组成的起始帧,紧接着是每个 LED 的亮度和颜色代码(需要注意的是传输并不是 RGB 的顺序,而是 BGR),最后是发送 4 个连续的 0xFF 作为结尾帧。
此外每种基色有 8 位色深,所以每个 LED 可以发出 16,777,216 种不同的颜色!!
uint8_t R, G, B;
int16_t theta;
for (int x = 0; x < 16; x++)
{
for (int y = 0; y < 8; y++)
{
Led[x][y]=0;
}
}
for (int x = 0; x < 16; x++)
{
for (int y = 0; y < 8; y++)
{
if(y<=ADC_Buffer_16_f[x])
{
HSL_to_RGB(theta+x*12, 1, 0.5, &R, &G, &B);
Led[x][7-y]=(R<<16)+(G<<8)+B;
}
}
}
theta+=5;
if(theta > 360)
{
theta = 0;
}
HAL_ADC_Stop_DMA(&hadc1);
HAL_ADC_Start_DMA(&hadc1, ADC_DMA, 1024);////
ADC_DMA_CPLT = 0;
HAL_SPI_Transmit(&hspi1, start, 4, 100);
for (int x = 0; x < 16; x++)
{
for (int y = 0; y < 8; y++)
{
LED1[0]=0xE2;
LED1[1]=Led[x][y]%256;//b
LED1[2]=(Led[x][y]>>8)%256;//g
LED1[3]=Led[x][y]>>16;//R;//r
HAL_SPI_Transmit(&hspi1, LED1, 4, 100);
}
}
HAL_SPI_Transmit(&hspi1, stop, 4, 100);
(3)、HSL 与 RGB 颜色空间的相互转换
因为想要控制灯珠变换颜色来实现炫酷的效果,增加听音乐时候的视觉体验。而 LED 的颜色是由 RGB 三个基色的不同组合形成的,但是用 RGB 描述的颜色并不是很直观,比如从紫色变化到黄色需要经过哪些颜色,所以我们需要新的颜色模型以实现更加丝滑的颜色变化效果,即 HSL 色彩模型:
HSL色彩空间以色调(Hue)、饱和度(Saturation)、亮度(Lightness)来定义颜色,色调对应颜色的种类,饱和度表示颜色的纯度,亮度表示颜色的明暗程度,这些参数的调整更符合人们描述和感知颜色的方式。例如,要调整颜色的亮度,只需改变亮度值即可,而在 RGB 颜色空间中,可能需要同时调整三个颜色通道的值。在实现了 HSL 转为 RGB 的算法后,就可以直接操作 HSL 参数,由芯片算出一个对应的 RGB 颜色组合,再发送到 LED 灯上。从技术实现的角度来看,HSL 到 RGB 的转换算法使得颜色的表示和处理更加灵活,尤其是在需要进行颜色理论分析或者颜色心理学研究时,HSL 模型提供了一个有力的工具。
void HSL_to_RGB(float h, float s, float l, uint8_t *r, uint8_t *g, uint8_t *b)
{
float R, G, B;
h = (((int)(10*h))%3600)/10.0f;
int i = (int)(h / 60);
switch (i)
{
case 0:
R = 1;
G = (((int)(10*h))%600)/600.0f;
B = 0;
break;
case 1:
R = 1-(((int)(10*h))%600)/600.0f;
G = 1;
B = 0;
break;
case 2:
R = 0;
G = 1;
B = (((int)(10*h))%600)/600.0f;
break;
case 3:
R = 0;
G = 1-(((int)(10*h))%600)/600.0f;
B = 1;
break;
case 4:
R = (((int)(10*h))%600)/600.0f;
G = 0;
B = 1;
break;
case 5:
R = 1;
G = 0;
B = 1-(((int)(10*h))%600)/600.0f;
break;
}//H
if(l < 0.5)
{
R = R*l*2;
G = G*l*2;
B = B*l*2;
}
else
{
R = 1-2*(1-l)*(1-R);
G = 1-2*(1-l)*(1-G);
B = 1-2*(1-l)*(1-B);
}//L
R = l*(1-s)+s*R;
G = l*(1-s)+s*G;
B = l*(1-s)+s*B;
//S
*r = 255*R;
*g = 255*G;
*b = 255*B;
}
(4)、通过心理声学进行频谱数据到视觉元素的映射
根据心理声学可知,人耳对声音频率的感知是对数级别的,这一点在心理声学中非常重要。我们的听觉系统不是线性地感知声音频率,而是按照对数刻度来感知的。这种对数感知方式意味着,人耳对频率的分辨能力在低频端比高频端更为敏感。(而FFT 变换之后的数据是线性刻度的, 每个频点间隔是采样频率/数据点数)
这种对数感知的特性也体现在音乐的音阶设计中。在十二平均律中,两个相邻音符之间的频率比是 2 的十二次方根(大约是1.059463),这是一个对数关系,它确保了音乐的和谐性和旋律的流畅性。
上图是钢琴部分琴键音符和频率的对应关系, 下图是对数据进行对数拟合, 可以看出乐器确实是对数组成的频率关系!
结合人耳的特征,因此需要将 FFT 后的数据进行对数轴缩放后,再将处理后的频谱数据映射到屏幕上的视觉元素,更符合听觉效果
FFT 输出的是 1024 个复数,代表从 0 到 Nyquist 频率的频谱。对于 16 x 8 的屏幕,需要将这 1024 个频谱点映射到 16 个区间里。
1. 频率分组
由于 FFT 结果是对称的,我们只需要使用前一半的数据(512 个点)。
FFT 处理后的数据是复数,偶数位为 n 次谐波的实部,奇数为虚部,由于当前应用环境下,不关心谐波之间的相位关系,因此想要获得所有数据的模长。
2. 映射到屏幕尺寸
需要将 512 个点的频谱数据映射到 16 x 8 的屏幕上,可以选择将频率划分为 16 个非等长的频段,即根据人耳的听觉特征,将FFT后的数据进行对数轴缩放。
根据缩放后的数值大小绘制不同的灯亮起高度。
由于 FFT 的点数是 1024 个,所以每个结果代表的频率范围是 44.1k/1024 ≈43Hz,按照上图将 20 Hz 到 4300 Hz 分成 16 个区间,然后每个区间求平均值(或者最大值),所以可以得到如下代码。
ADC_Buffer_16[0]=(ADC_Buffer[1]+ADC_Buffer[2])/2;
ADC_Buffer_16[1]=ADC_Buffer[3];
ADC_Buffer_16[2]=ADC_Buffer[4];
ADC_Buffer_16[3]=ADC_Buffer[5];
ADC_Buffer_16[4]=ADC_Buffer[6];
ADC_Buffer_16[5]=(ADC_Buffer[7]+ADC_Buffer[8]+ADC_Buffer[9])/3;
ADC_Buffer_16[6]=(ADC_Buffer[10]+ADC_Buffer[11]+ADC_Buffer[12])/3;
for(int i=13;i<17;i++)
{
ADC_Buffer_16[7]+=ADC_Buffer[i];
}
ADC_Buffer_16[7]/=4;
for(int i=17;i<22;i++)
{
ADC_Buffer_16[8]+=ADC_Buffer[i];
}
ADC_Buffer_16[8]/=5;
for(int i=22;i<29;i++)
{
ADC_Buffer_16[9]+=ADC_Buffer[i];
}
ADC_Buffer_16[9]/=7;
for(int i=29;i<38;i++)
{
ADC_Buffer_16[10]+=ADC_Buffer[i];
}
ADC_Buffer_16[10]/=9;
for(int i=38;i<50;i++)
{
ADC_Buffer_16[11]+=ADC_Buffer[i];
}
ADC_Buffer_16[11]/=12;
for(int i=51;i<66;i++)
{
ADC_Buffer_16[12]+=ADC_Buffer[i];
}
ADC_Buffer_16[12]/=16;
for(int i=66;i<87;i++)
{
ADC_Buffer_16[13]+=ADC_Buffer[i];
}
ADC_Buffer_16[13]/=21;
for(int i=87;i<114;i++)
{
ADC_Buffer_16[14]+=ADC_Buffer[i];
}
ADC_Buffer_16[14]/=27;
for(int i=114;i<228;i++)
{
ADC_Buffer_16[15]+=ADC_Buffer[i];
}
ADC_Buffer_16[15]/=114;
for(int k=0;k<16;k++)
{
if(ADC_Buffer_16[k]/15 < 7)
{
ADC_Buffer_16_f[k] = ADC_Buffer_16[k]/15;
}
else
{
ADC_Buffer_16_f[k] = 7;
}
三、功能展示
3.1 硬件3D预览
正面:
背面:
3.2 实物展示
3.2.1 PCB实物
3.2.2 3D外壳加实物整体展示
3.3 运行结果展示
更多动态展示见视频。
四、总结
遇到的问题
1. 调试问题
开始调试程序的过程中,一直出现识别不到 ST-Link 的问题,后来发现是有一部分未使用的管脚悬空导致芯片进入了错误的状态(俗称跑飞),解决方案是将未使用的管脚下拉确保不会进入错误状态。
2. 电路板硬件问题
由于灯板和主板在一个板上,没有分开设计,为了能够显示更多的点数达到更好的视觉效果,灯板是由两块主板拼接在一起。设计时为了外观好看,两个板子之间的连接方式采用了两个半孔使用一个排针焊在一起。但是在实际使用过程中,这样的连接方式非常不结实,很容易接触不良。后来采用飞线的方式进行加固,能够正常使用。
3. 结构问题
总体结构比较简单,将板子整体、蓝牙模块、喇叭、按键各大部分结合在一起,并设计了3D外壳。在打印外壳的过程中,打印机一直出现问题,中途好几次打崩溃了。虽然打印过程困难重重,好在最后还是顺利将打印出来的各部分拼接在一起,也算是大获成功了。
心得体会
感谢此次的 FastBond 活动提供一个非常优秀的平台,我有幸将一闪而过的灵感转化为一项完整的创作,这个过程带来了巨大的成就感和自信心的提升。虽然在项目开发过程中遇到的各种技术挑战,为了使显示效果更好,学习将FFT转换之后的信号进行对数处理以更符合心理声学、HSL转RGB模型等,锻炼了解决实际问题的能力。
昔日,FFT 仅是课堂上的理论计算,未曾想过它能成为解决生活问题的利器。此刻,通过这次活动的磨砺,我将理论知识成功转化为工程实践,丰富了自我的技术底蕴。纸上得来终觉浅,绝知此事要躬行。当自己真正使用过之后,对FFT里的公式和应用才有了更强烈的感知。在学习HSL转RGB模型的过程中,最开始想要直接用别人的算法,结果发现公式深奥难懂,最后干脆自己静下心从头把每一个变量的功能推演一遍,在学习的过程中需要有耐心将更多的知识内化。
再次感谢电子森林和 DigiKey ,持续给我们这样优秀的平台和机会,让我们可以将想法变成现实!同时也期待能够见到更多的优秀活动!