Arduino与Proteus仿真实例-WS2812实现音乐氛围灯仿真

WS2812实现音乐氛围灯仿真

本文将使用WS2812实现一个音乐氛围灯。Arduino通过检测音频信号强度,然后转换成W2812灯带驱动信号,从而实现音乐氛围灯。

WS2812的驱动和使用在前面的文章中作了详细的介绍,请参考:

1、仿真电路原理图

在这里插入图片描述

在仿真电路原理图中,IO3、IO4连接到WS2812,分别模拟音箱的左右氛围灯。LM386放大采集的音频信号并连接到AD0。

2、仿真代码实现

本实例代码将使用到如下开源库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#include <Adafruit\_NeoPixel.h>
#include <math.h>

#define N\_PIXELS 24 // WS2812像素个数
#define MIC\_PIN A0 // 麦克风采样引脚
#define LEFT\_STRIP\_PIN 3 // WS2812引脚
#define RIGHT\_STRIP\_PIN 4 // WS2812引脚
#define SAMPLE\_WINDOW 10 // 平均水平的样本窗口
#define PEAK\_HANG 15 // 峰点下降前暂停时间
#define PEAK\_FALL 6 // 峰点下降率
#define INPUT\_FLOOR 40 // 模拟读取输入的较低范围 建议 40
#define INPUT\_CEILING 400 // analogRead 输入最大量程,值越小越灵敏(1023 = max) 建议 400

byte peak = 20; // 列的峰值水平; 用于下降的点
unsigned int sample;

byte dotCount = 0; // 峰值点帧计数器
byte dotHangCount = 0; // 用于保持峰值点的帧计数器

Adafruit_NeoPixel leftStrip = Adafruit\_NeoPixel(N_PIXELS, LEFT_STRIP_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel rightStrip = Adafruit\_NeoPixel(N_PIXELS, RIGHT_STRIP_PIN, NEO_GRB + NEO_KHZ800);




volatile int state = LOW;

void setup() {
// 初始化灯带
leftStrip.begin();
leftStrip.show();

rightStrip.begin();
rightStrip.show();

}

void loop() {

VUmeter();
}

void VUmeter(){
unsigned long startMillis= millis(); // 样本窗口开始
float peakToPeak = 0;

unsigned int signalMax = 0;
unsigned int signalMin = 1023;
unsigned int c, y;
// 收集样本窗口长度的数据(以 mS 为单位)
while (millis() - startMillis < SAMPLE_WINDOW)
{
sample = analogRead(MIC_PIN);
if (sample < 1024) // 剔除虚假读数
{
if (sample > signalMax)
{
signalMax = sample; // 保存最大值
}
else if (sample < signalMin)
{
signalMin = sample; // 保存最小值
}
}
}
peakToPeak = signalMax - signalMin; // max - min = peak-peak 振幅


//用彩虹渐变填充
for (int i=0;i<=rightStrip.numPixels()-1;i++){
leftStrip.setPixelColor(i,Wheel(map(i,0,leftStrip.numPixels()-1,10,200)));
rightStrip.setPixelColor(i,Wheel(map(i,0,rightStrip.numPixels()-1,10,200)));
}

//以对数方式而不是线性方式缩放输入
c = fscale(INPUT_FLOOR, INPUT_CEILING, leftStrip.numPixels(), 0, peakToPeak, 2);

if(c < peak) {
peak = c; // 保持点在顶部
dotHangCount = 0; // 让点在落下之前挂起
}
if (c <= leftStrip.numPixels()) { // 用关闭像素填充部分列
drawLine(leftStrip.numPixels(), leftStrip.numPixels()-c, leftStrip.Color(0, 0, 0));
}

//设置峰值点以匹配彩虹渐变
y = leftStrip.numPixels() - peak;

leftStrip.setPixelColor(y-1,Wheel(map(y,0,leftStrip.numPixels()-1,10,200)));
rightStrip.setPixelColor(y-1,Wheel(map(y,0,rightStrip.numPixels()-1,10,200)));

leftStrip.show();
rightStrip.show();

// 基于帧的峰点动画
if(dotHangCount > PEAK_HANG) { //峰值停顿长度
if(++dotCount >= PEAK_FALL) { //跌落率
peak++;
dotCount = 0;
}
}
else {
dotHangCount++;
}
}

//用于在给定颜色的两点之间画一条线
void drawLine(uint8\_t from, uint8\_t to, uint32\_t c) {
uint8\_t fromTemp;
if (from > to) {
fromTemp = from;
from = to;
to = fromTemp;
}
for(int i=from; i<=to; i++){
leftStrip.setPixelColor(i, c);
rightStrip.setPixelColor(i, c);
}
}

// 缩放输入值
float fscale( float originalMin, float originalMax, float newBegin, float
newEnd, float inputValue, float curve){

float OriginalRange = 0;
float NewRange = 0;
float zeroRefCurVal = 0;
float normalizedCurVal = 0;
float rangedValue = 0;
boolean invFlag = 0;

// 条件曲线参数限制范围

if (curve > 10) curve = 10;
if (curve < -10) curve = -10;

// 反转和缩放 - 正数对输出的高端给予更多权重
curve = (curve \* -.1) ;
curve = pow(10, curve); // 将线性刻度转换为其他 pow 函数的对数指数

// 检查范围
if (inputValue < originalMin) {
inputValue = originalMin;
}
if (inputValue > originalMax) {
inputValue = originalMax;
}


OriginalRange = originalMax - originalMin;

if (newEnd > newBegin){
NewRange = newEnd - newBegin;
}
else
{
NewRange = newBegin - newEnd;
invFlag = 1;
}

zeroRefCurVal = inputValue - originalMin;
normalizedCurVal = zeroRefCurVal / OriginalRange; // 归一化

if (originalMin > originalMax ) {
return 0;
}

if (invFlag == 0){
rangedValue = (pow(normalizedCurVal, curve) \* NewRange) + newBegin;

}
else // 反转范围
{
rangedValue = newBegin - (pow(normalizedCurVal, curve) \* NewRange);
}

return rangedValue;
}
// 转换颜色
uint32\_t Wheel(byte WheelPos) {
if(WheelPos < 85) {
return leftStrip.Color(WheelPos \* 3, 255 - WheelPos \* 3, 0);
}
else if(WheelPos < 170) {
WheelPos -= 85;
return leftStrip.Color(255 - WheelPos \* 3, 0, WheelPos \* 3);
}
else {
WheelPos -= 170;
return leftStrip.Color(0, WheelPos \* 3, 255 - WheelPos \* 3);
}
}

在示例代码中,首先将WS2812初始化

1
2
3
4
5
6
7
// 初始化灯带
leftStrip.begin();
leftStrip.show();

rightStrip.begin();
rightStrip.show();

在示例代码中,UVMeter将采样音频输入信号,根据音频信号强度对WS2812灯带进行渐变式填充

1
2
3
4
5
6
//用彩虹渐变填充
for (int i=0;i<=rightStrip.numPixels()-1;i++){
leftStrip.setPixelColor(i,Wheel(map(i,0,leftStrip.numPixels()-1,10,200)));
rightStrip.setPixelColor(i,Wheel(map(i,0,rightStrip.numPixels()-1,10,200)));
}

然后将输入信号强度值通过调用fscale函数以对数方式而不是线性方式缩放输入信号强度值:

1
2
c = fscale(INPUT_FLOOR, INPUT_CEILING, leftStrip.numPixels(), 0, peakToPeak, 2);

接着计算峰值,及动画效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
if(c < peak) {
peak = c; // 保持点在顶部
dotHangCount = 0; // 让点在落下之前挂起
}
if (c <= leftStrip.numPixels()) { // 用关闭像素填充部分列
drawLine(leftStrip.numPixels(), leftStrip.numPixels()-c, leftStrip.Color(0, 0, 0));
}

//设置峰值点以匹配彩虹渐变
y = leftStrip.numPixels() - peak;

leftStrip.setPixelColor(y-1,Wheel(map(y,0,leftStrip.numPixels()-1,10,200)));
rightStrip.setPixelColor(y-1,Wheel(map(y,0,rightStrip.numPixels()-1,10,200)));

leftStrip.show();
rightStrip.show();

// 基于帧的峰点动画
if(dotHangCount > PEAK_HANG) { //峰值停顿长度
if(++dotCount >= PEAK_FALL) { //跌落率
peak++;
dotCount = 0;
}
}
else {
dotHangCount++;
}

3、仿真结果

在这里插入图片描述

文章来源: https://iotsmart.blog.csdn.net/article/details/128506475