今天将为大家展示如何巧用Arduino、RGB LED矩阵扩展板和音频频谱分析扩展板,亲手打造一个RGB LED矩阵音频可视化器,并配上透明的亚克力外壳,为你的音乐带来一场美妙的灯光秀,增添一抹绚烂的光影!
项目来
源:hackster
驱动代码:
https://www.eetree.cn/project/3045
一、项目介绍
对于音频频谱分析部分,本项目中使用的是SparkFun的频谱扩展板,它使用两个MSGEQ7图形均衡器显示滤波器,将立体声音频输入分割成7个频段(每个通道),并使用Arduino上的ADC读取每个频段的幅度。它附带了Arduino示例草图,以便开始使用。
对于RGB LED矩阵,本项目中使用的是Adafruit为Arduino设计的NeoPixel扩展板,它由40个RGB NeoPixels组成(Adafruit对他们的WS2812光源的术语)。红色、绿色和蓝色LED与驱动芯片集成在一起,通过单根导线进行控制。它们可以单独使用,串联成更长的灯串,或者组装成更有趣的形态。在扩展板上,它们是串联在一起的。扩展板还附带了Adafruit_NeoMatrix库,该库简化了对RGB LED矩阵的访问和LED的控制。
最后是外壳部分,本项目创建了一种新的可堆叠、模块化的外壳系统,叫做ProtoStax。它支持不同阶段的原型设计,提供保护和开放访问,以便在开始时使用,并有能力稍后添加侧壁和顶部,但也有能力水平堆叠或垂直堆叠多个单元,从而能够根据原型设计需求和其他板卡及组件的增加进行扩展。
本项目中使用ProtoStax for Arduino,一个为Arduino设计的透明亚克力外壳——它适合Uno/Leonardo尺寸以及更大的Mega/Due尺寸——这也是可堆叠和模块化的,并且有足够的空间容纳两个扩展板。采用了透明且坚固的亚克力材料,方便展示你的音频可视化器以及你的音乐系统!
步骤1 - 将Arduino安装到外壳基座板上
首先将Arduino(以示例中的Uno为例)安装到外壳的基座板上。这样做可以在配置和设置Arduino时提供保护,同时提供完全开放的访问权限,以便进行操作和调整。当你准备将其封闭起来时,可以轻松地添加侧壁和顶板,并用螺丝将所有部件固定。
将Arduino安装到基座板上,并添加脚垫和其他硬件,以准备外壳的平板配置。请参阅下图所有步骤的动态演示图:
步骤2 - 为Arduino准备SparkFun频谱扩展板
SparkFun频谱扩展板没有附带插座。幸运的是,Adafruit为Arduino设计的NeoPixel扩展板既附带堆叠插座也附带普通插座。由于想要NeoPixel扩展板位于顶部,本项目中使用普通插座,这样它就能平齐,这样就留下了堆叠插座供频谱扩展板使用。
但带有堆叠接头的 Spectrum Shield 并不紧密地适合,Arduino Uno 上的 USB 和电源端口会妨碍,如下图所示:
现在它很贴合!
步骤3 - 将Adafruit NeoPixel扩展板插入频谱扩展板的堆叠插座中
Adafruit NeoPixel Shield 位于 Spectrum Shield 之上。首先需要焊接常规接头,在本项目中还焊接了它附带的终端连接器,尽管在这个例子中,使用 Arduino 为其供电,因为所有 LED 不会同时亮起,所以功耗在Arduino能够提供的范围内。
来自Adafruit的NeoPixel扩展板页面的消息:
为了便于启动,LED 默认由 5V 板载 Arduino 电源供电。
只要你不是让所有像素都以全功率白色点亮,那应该没问题。
如果要使用外部电源为扩展板供电,请焊接随板附带的接线端子(专业提示:
将其放在电路板底部,以免粘住)以连接到外部 4-6VDC 电源 - 该电源也将为 Arduino 和扩展板供电。
如果您想使用接线端子为屏蔽层供电,但保持Arduino本身仅使用DC或USB电源,请将焊接跳线的中心切到接线端子的右侧。
外部输入端有一个极性保护 FET,以防您将电源向后接线。
步骤4 - 演示代码
-
-
将其转换为 8x5 NeoPixel Matrix 的显示/配色方案。
演示代码链接:
https://github.com/protostax/ProtoStax_Audio_Visualizer_Demo/blob/master/ProtoStax_Audio_Visualizer_Demo.ino
二、频谱分析
参考 Spectrum Shield 连接指南,了解有关 Spectrum Shield 的更多信息。
通过将数字序列写入 Spectrum Shield 的 STROBE 和 RESET 引脚,可以初始化 Shield 使用的 MSGEQ7 芯片。然后,继续读取频谱被分成的 7 个不同频段中每个频段的幅度。每个频段被读取后,通过脉冲STROBE引脚来启动下一个频段的读取。这些值被存储在Frequencies_One[7]和Frequencies_Two[7]中,分别对应立体声输入的两个通道。使用Arduino的10位ADC读取这些值,因此输出值可以是0 - 1023之间,它们提供了每个频段的振幅的表示。
//Declare Spectrum Shield pin connections
#define STROBE 4
#define RESET 5
#define DC_One A0
#define DC_Two A1
//Define spectrum variables
int freq_amp;
int Frequencies_One[7];
int Frequencies_Two[7];
int i;
void setup() {
...
//Initialize Spectrum Analyzers
digitalWrite(STROBE, LOW);
delay(1);
digitalWrite(RESET, HIGH);
delay(1);
digitalWrite(STROBE, HIGH);
delay(1);
digitalWrite(STROBE, LOW);
delay(1);
digitalWrite(RESET, LOW);
...
}
void loop() {
...
Read_Frequencies();
...
}
/*******************Pull frquencies from Spectrum Shield********************/
void Read_Frequencies(){
...
//Read frequencies for each band
for (freq_amp = 0; freq_amp<7; freq_amp++)
{
Frequencies_One[freq_amp] = (analogRead(DC_One) + analogRead(DC_One) ) >> 1 ;
Frequencies_Two[freq_amp] = (analogRead(DC_Two) + analogRead(DC_Two) ) >> 1;
...
digitalWrite(STROBE, HIGH);
digitalWrite(STROBE, LOW);
}
}
频谱的 7 个频段是:
-
63赫兹
-
160赫兹
-
400赫兹
-
1kHz
-
2.5k赫兹
-
6.25k赫兹
-
16k赫兹
将这些频段分为三个范围 - 低音(BASS)、中音(MID_RANGE)和高音(TREBLE)。典型的低音范围是 60 到 250 Hz,因此前两个频段在低音范围内。中频频率通常为 500 Hz 至 2 kHz,因此将接下来的 3 个频段归入中音范围,剩余的两个频段归入高音范围。
注意:每个波段的最大读数到一个单独的变量中。这可能被用来自动将读数缩放到RGB矩阵列所代表的级别 - 这在输入信号较弱的情况下很有用,否则在这种情况下只有极少数 RGB 矩阵会亮起。
三、RGB矩阵
可以参考 Adafruit NeoPixel Überguide 了解有关 NeoPixel Shield 和 NeoPixel 的更多信息。
首先要注意的是,在坐标系中,[0, 0] 总是指左上角,无论方向如何。
其次是记下您感兴趣的任何方向的宽度,然后是高度。
第三是注意物理 LED #0 的位置(以 Adafruit 标志为标志)。右上角、左上角、左下角和右下角,视情况而定。还要注意物理 LED 的进展方向。在本项目的电路板中,布局是渐进式的(一行结束后的下一个物理 LED 从下一行的开头开始,如黄线所示)。进展的方向在宽度更宽(水平方向)时是沿着行的(由短的绿色箭头指示),在宽度更窄(垂直方向)时是沿着列的(同样由短的绿色箭头指示)。
以下图片说明了这些:
在示例中,有 7 个频段和一个 8 x 5(或 5 x 8,取决于您查看它的方式!本项目中选择沿着8的维度展示这7个频率带(留下一个未使用)。然后沿着5的维度展示每个频率带的幅度表示。
原点从左下角开始(代表最低频段的最低电平),然后向上移动。但由于在坐标系统中首先要记住的是[0, 0]总是指左上角,因此您应该将头向左倾斜并查看下图,以了解初始化 NeoMatrix 的值选择!(宽度 = 5,高度 = 8,右上角,逐行列)
现在来深入研究一下与 NeoMatrix 相关的演示代码并绘制频率图。首先,确定 NeoPixel 的宽度=5,高度=8,我们选择的朝向是右上角(TOP-RIGHT),并且列是递增的(COLUMNS PROGRESSIVE)。按照setup()函数中所需的矩阵设置进行操作。
在 loop()中,读取任何串行输入以选择配色方案 ,本项目中定义了 3 种不同的配色方案。
enum SCHEME {
MAGNITUDE_HUE = 0,
MAGNITUDE_HUE_2 = 1,
HSV_COLOR_WHEEL = 2
};
接着,根据选定的颜色方案调用Graph_Frequencies函数。同时请注意,该函数的第一个参数可以选择要展示的频率范围(低音、中音或高音)。
enum RANGE {
BASS = 0,
MID_RANGE = 1,
TREBLE = 2,
ALL = 3
};
现在,选择所有要显示的范围,通过串行输入或包括一个瞬时按钮在 BASS、MID_RANGE、TREBLE 或 ALL 之间切换显示。RANGE的选择决定了要显示的行的起始和结束范围。
对于每一行(频段),选取立体声输入的左右两个声道中较大的频率幅度。该值介于 0 和 1023 之间,需要将其映射到显示器的 5 个不同的列中,因此将频率除以FREQ_DIV_FACTOR(1023/204 = 5,它将输出 1023 映射到 5)。为了安全起见,确保要显示的 numCol 不大于 5,这决定了要为每个频段显示的列数。
然后使用matrix.drawPixel()函数来以正确的颜色显示正确的像素。
在图形显示中使用 HSV 色轮,通常,通常情况下,使用方法是matrix.drawPixel(column, row, Color(r, g, b)),这里Color(r, g, b)代表由红色、绿色和蓝色值定义的颜色。然而,使用HSV可以带来一些平滑的颜色渐变效果。
NeoMatrix提供了matrix.ColorHSV(uint16_t hue)方法,它接受一个uint16_t的色调值并返回一个uint32_t的HSV颜色。
但是,matrix.Color(r, g, b)返回的是uint16_t颜色。matrix.drawPixel()也期望一个16位的颜色值。
解决此问题的方法是,使用 matrix.setPassThruColor(32 位颜色值)。这将在矩阵中设置一个标志,使 drawPixel 忽略其颜色参数,使用上述方法已设置的 32 位颜色。只需记住调用 matrix.setPassThruColor() 来重置该标志。
static uint16_t hue = 0; //21845 22250 to -250
uint16_t hueDelta = 200;
hue += hueDelta;
...
rgbcolor = matrix.ColorHSV(hue);
...
matrix.setPassThruColor(rgbcolor);
matrix.drawPixel(col, row, (uint16_t)0); // color does not matter here
matrix.setPassThruColor();
...
matrix.show();
使用 HSV,可以递增16位的色调值并生成HSV颜色码,这样就可以实现颜色的平滑过渡。
以下是供参考的不同代码片段:
#define NEO_MATRIX_WIDTH 5
#define NEO_MATRIX_HEIGHT 8
#define NEOPIXEL_PIN 6 // Shield maps it to pin 6
Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(NEO_MATRIX_WIDTH, NEO_MATRIX_HEIGHT, NEOPIXEL_PIN,
NEO_MATRIX_TOP + NEO_MATRIX_RIGHT +
NEO_MATRIX_COLUMNS + NEO_MATRIX_PROGRESSIVE,
NEO_GRB + NEO_KHZ800);
....
void setup() {
...
matrix.begin();
matrix.setTextWrap(false);
matrix.setBrightness(40);
matrix.fillScreen(0);
matrix.show();
...
}
void loop() {
static int scheme = 0;
while (Serial.available() > 0) {
scheme = Serial.parseInt();
}
...
Graph_Frequencies(ALL, scheme);
...
delay(50);
}
void Graph_Frequencies(CHANNEL c, SCHEME s){
...
for( row= from; row
{
int freq = (Frequencies_Two[row] > Frequencies_One[row])?Frequencies_Two[row]:Frequencies_One[row];
int numCol = (freq/FREQ_DIV_FACTOR);
if (numCol > 5) numCol = 5;
for (int col = 0 ; col < numCol ; col++) {
... // pick color scheme to display
matrix.setPassThruColor(rgbcolor);
matrix.drawPixel(col, row, (uint16_t)0); // color does not matter here
matrix.setPassThruColor();
//matrix.show();
}
matrix.show();
}
}
接下来是配色方案的选择。请注意,我已经预留了根据不同频率范围选择颜色的选项(低音色调、中音色调、高音色调)。我创建了 3 种不同的配色方案 - 一种方案是从最低幅度到最高幅度使用绿色至红色/粉色的渐变,另一种方案使用更多偏向粉色/蓝色的范围。第三种方案则是所有像素使用单一颜色,但在运行时会循环遍历整个色轮。
switch(s) {
case MAGNITUDE_HUE:
bassHue = 22250;
midHue = 22250; //54613
trebleHue = 22250; //43690