Arduino是一个开放源码的电子原型平台,它可以让你用简单的硬件和软件来创建各种互动的项目。Arduino的核心是一个微控制器板,它可以通过一系列的引脚来连接各种传感器、执行器、显示器等外部设备。Arduino的编程是基于C/C++语言的,你可以使用Arduino IDE(集成开发环境)来编写、编译和上传代码到Arduino板上。Arduino还有一个丰富的库和社区,你可以利用它们来扩展Arduino的功能和学习Arduino的知识。
Arduino的特点是:
1、开放源码:Arduino的硬件和软件都是开放源码的,你可以自由地修改、复制和分享它们。
2、易用:Arduino的硬件和软件都是为初学者和非专业人士设计的,你可以轻松地上手和使用它们。
3、便宜:Arduino的硬件和软件都是非常经济的,你可以用很低的成本来实现你的想法。
4、多样:Arduino有多种型号和版本,你可以根据你的需要和喜好来选择合适的Arduino板。
5、创新:Arduino可以让你用电子的方式来表达你的创意和想象,你可以用Arduino来制作各种有趣和有用的项目,如机器人、智能家居、艺术装置等。


我的圣诞项目完成了!大家都对LED立方体着迷。我本来想自己做一个,但又想让它变得更有挑战性,而且由于电路板的使用不受限制,所以我决定做一个LED球。
我花了24小时焊接,又熬了一个晚上才完成。但一切都值得。亮着的时候很美,不亮的时候更美。好好享受吧!



步骤 1:观看视频!
观看 LED 球形灯的完整制作视频。它会清晰地概述整个制作过程。当然,视频中不会包含任何细节,但那样太枯燥了。详细信息就在下方页面上。观看视频并按步骤操作即可(https://www.youtube.com/watch?v=Q5d8gTppuYo)。

第 2 步:收集所有工具和材料
收集所有工具和材料
工具:
窄钳
圆嘴钳
侧切钳
焊台
镊子
弯管机哈哈,不,开玩笑的。那对每个人来说都是一场表演的终结者。你只需要一些直径达到正确尺寸的圆形物体——啤酒罐、喷雾罐……
3D打印机
材料:
0.8 毫米黄铜线(您也可以使用铜或钢,但它们有一些缺点)
锡焊料
助焊剂
电子元件:
带电池支架的 ESP32 开发板 - 必须与球体匹配!例如TTGO T8或FireBeetle ESP32
WS2812b RGB LED(200个)
1000mAh锂聚合物电池

步骤3:模板是关键
如果没有合适的模板/夹具/模具,就不可能制作出如此复杂的形状。我设计了我的3D打印模型。模板由四个部分组成,它们由两个环连接在一起——就像一个桶。这四个部分在这里至关重要。如果用一个整体完成,最终的半球就会卡在里面。这个模板是为球体的一半设计的。不用担心,你也可以用它来制作另一半。
由于球体并非完全对称,因此这 4 个部件分为 2 种。请打印附件中每个 STL 文件各 2 个,以获得完整的模板。
附件
下载 {{ file.name }}戒指.stl下载3D视图
下载 {{ file.name }}模板A.stl下载3D视图
下载 {{ file.name }}模板B.stl下载3D视图


步骤 4:从金属环开始
从金属丝环开始
这个球体由11个LED环组成,每个环由两根圆形的电线和一定数量的LED组成。完整的球体总共需要22个电线环。然而,制作起来并非那么简单。每个环的尺寸都不一样。为了方便起见,我准备了一张纸,上面画出了半球环的完整形状。虽然它并非完全准确。
记住,球体的最终形状主要取决于你在此阶段弯曲的精度。如果金属丝圈不够圆整,最终的形状可能会变形。为了得到完美的形状,我使用了金属丝弯曲机。
附件
下载 {{ file.name }}戒指.svg下载
下载 {{ file.name }}戒指.pdf下载

步骤5:放置LED
将LED放入模板最底下的环中。第一个环由8个LED组成。我用黑色永久性笔从底部标记了所有LED的GND引线,以便能够识别GND(VSS)引脚的位置。将所有LED的GND引脚朝向内环,VCC引线朝向外环。这一点很重要!每个环都应完全一样!内环接地,外环供电。参见上图。插入LED之前,我已将其浸入助焊剂中,以便它们更好地固定在插槽中。
插入内环并将所有 GND 引脚焊接到内环上。插入外环并将所有 VCC 引脚焊接到外环上。VCC 引脚位于 GND 引脚的对角线处。确保外环没有连接 LED 的任何数据(DIN 和 DOUT)引脚。
重复此操作,直到获得 6 个环:
8个LED
14个LED
18个LED
20个LED
24个LED
26个LED
注意,整个球体只需要一个第六环——也就是中间的环。所以,只需将它放在两个半球中的一个即可——例如,第一个半球有5个环,第二个半球有6个环。
WS2812b LED
对于不熟悉这款 LED 的人来说,这是一款可单独寻址的RGB LED。简单来说,你只需要一根线就能点亮它,发出彩虹般的颜色。传统 LED 通过调节电流来调节亮度,而 WS2812b 则不同,它采用持续供电,并通过数字信号控制灯光。你可以告诉它要显示什么颜色。你可能会问:所以它只需要 3 根引线,对吧?第 4 根引线用于为灯链中的下一个 LED 提供控制信号。没错!这绝对是它最棒的功能。你可以拥有一长串这样的 LED,并且仍然只用一根线就能控制它们。你只需将其他 LED 的指令提供给灯链中的第一个 LED,它就会继续转发这些指令。(数据手册)





步骤6:连接数据线
电源线就是线环,你已经有了。现在你还需要连接LED的另外两根引线——DIN和DOUT。DIN位于GND引脚旁边,DOUT位于VCC引脚旁边。我使用了5毫米长的电线。正确连接线环也非常重要。确保数据线没有接触到地线或电源环。确保不要将数据引脚焊接到电源环上。
从最内圈开始,一圈一圈地重复此操作。当你到达可以将最开始的LED与最后一个LED互连的位置时,就该跳到上面的一圈了。我使用了C形跳线连接两个相邻环的数据引脚。之所以需要C形,是因为你需要绕过地线和电源线。继续连接接下来的环,直到所有LED都连接成一条长蛇形。最后应该只剩下两根导线没有连接——一根在最上面的一圈。这应该是整个半球的主DIN引脚。最下面的一个是DOUT引脚。



步骤7:连接电源线并重新开始
制作半球的最后一步是将环之间的电源线连接起来。这不仅是为了连接它们,也是为了创建一个将环固定在一起的结构。我再次使用了 C 形跳线来连接相邻环的电源线和地线。前三个较小的环至少使用 4 根跳线,较大的环至少使用 8 根跳线。这个数量将确保球体足够坚固。
放松时间
现在该把半球从模具中取出了!经过这么长时间的焊接,这是最放松的时刻了。首先,取下固定模具四个部分的塑料环。然后开始用力取出每个部分。动作一定要轻柔,以免弄坏任何LED!
对第一个半球满意吗?很好!现在重新开始创建第二个半球。这个半球的环数再次不同。如果你第一个半球有 6 个环,那么第二个就只有 5 个环,反之亦然。




步骤8:测试半球
两个半球都准备好了吗?现在是时候测试一下它们,然后再把它们组装成球体了。我知道你很想马上完成,但相信我,你最好先测试一下。
首先,用万用表做一个简单的测试,检查电源线和地线之间是否有短路。然后将两根短线分别焊接到半球的地线和电源线上。将电源线连接到 ESP32 开发板的 3.3V 引脚,并将地线连接到地。
最后,所有 LED 都连接到一条长链上。这意味着半球也连接在一起了。取较小的一个(5 个环),将其 DIN 连接到 ESP32 开发板的 IO21 引脚,将其 DOUT 连接到另一个半球的 DIN。现在只剩下一个未连接的 LED 引脚——大球上的 DOUT。这是链中的最后一个 LED。完成了吗?启动 ESP32 开发板,并上传本教程“代码”部分的代码。
所有LED都亮了吗?太棒了!你完成了。如果没有亮,而且链条在某个地方终止了,请检查该LED是否有冷焊点或短路。我花了一个小时才修复所有错误。所以别灰心!这玩意儿做起来相当费劲!而且你还坚持到了现在!




步骤9:插入微控制器和电池
取出 ESP32 开发板。先绕过开关。由于开关位于球体内部,所以无法操作。将其从开发板上拆焊下来,并焊接两根细铜线代替。让它们悬空,等微控制器放入球体后再焊接。
电路板的角落里有3到4个螺丝孔。我们不会用螺丝,而是用焊锡填充这些孔,以便以后更容易将它们连接到球形结构上——这些孔会被打磨!
取较小的半球,将微控制器放入底部较小圆环的中心,USB 接口和电池接口朝外,穿过最小的圆环。将角上的孔焊接到最小的圆环上——记住,它是接地的。将两根开关线也穿过孔,并将之前拆焊好的开关焊接到它们上面。开关有一个金属外壳,用它把它也焊接到最小的圆环上。注意不要让开关线与球体结构短路!不要让电源开关压在球体结构上——否则它会滚动。
取一根直导线,将其焊接到 ESP32 开发板上的 3.3V 引脚上。然后将另一端焊接到球体的最后一个环上——记住,这是电源。我还在接地环和开发板的 GND 引脚之间焊接了一些直导线,以加固内部结构。确保在开发板背面留出放置电池的空间!取一根铜线,将主 DIN 引脚连接到 ESP32 的 IO21 引脚。现在,半球体就搭建完成了。连接电池并进行测试。
它还能用吗?太好了!现在你可以用粘合剂把电池粘到 ESP32 开发板的背面,然后穿过孔连接。




步骤10:最后焊接!
测试 ESP32 开发板上的螺丝孔是否真的接地。我需要在螺丝孔和开发板最近的 GND 引脚之间加一根短线——这样可以为第二个半球提供接地。
用短线焊接第一个半球的 DOUT 和第二个半球的 DIN(与测试阶段的操作相同)。剪下 2 毫米长的导线,并将其焊接到第二个半球最顶端的环上。我用了 8 根这样的导线。记得吗,两个最顶端的环都是电源环。所以可以安全地将每个半球最顶端的两个环焊接在一起。将半球叠放在一起,然后焊接在一起。确保两个半球之间的间隙均匀。最后,用焊锡将 ESP32 开发板的螺丝孔与最小的接地环导线连接起来。
你现在得到了一个球体!摸摸它,握住它。感觉真棒,不是吗?打开它,它能用吗?




步骤11:代码
下载 Arduino IDE源代码。我使用Neopixel Bus库来控制 LED“灯带”。它有一个非常漂亮的界面,并且支持动画。
#include <NeoPixelBus.h>
#include <NeoPixelAnimator.h>
#define PIN_LEDS 21
#define NUMPIXELS 194
#define LIGHTNESS 0.05f
#define BRIGHTNESS 64
RgbColor BLACK(0, 0, 0);
RgbColor RED(BRIGHTNESS, 0, 0);
RgbColor GREEN(0, BRIGHTNESS, 0);
RgbColor BLUE(0, 0, BRIGHTNESS);
RgbColor YELLOW(BRIGHTNESS, BRIGHTNESS, 0);
RgbColor WHITE(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS);
NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod> leds(NUMPIXELS, PIN_LEDS);
NeoPixelAnimator animations(10); // NeoPixel animation management object
#define NUM_RINGS 11
#define RING_MAX_PIXELS 26
#define RING_MIN_PIXELS 8
// all rings starts at 0 ray
byte RINGS[NUM_RINGS][RING_MAX_PIXELS] = {
{78, 79, 80, 81, 82, 83, 84, 77}, // 8
{65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 63, 64}, // 14
{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 45, 46, 47}, // 18
{28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 25, 26, 27}, // 20
{5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 1, 2, 3, 4}, // 24
{102, 101, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 110, 109, 108, 107, 106, 105, 104, 103}, // 26
{127, 126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112, 111, 134, 133, 132, 131, 130, 129, 128}, // 24
{148, 147, 146, 145, 144, 143, 142, 141, 140, 139, 138, 137, 136, 135, 154, 153, 152, 151, 150, 149}, // 20
{167, 166, 165, 164, 163, 162, 161, 160, 159, 158, 157, 156, 155, 172, 171, 170, 169, 168}, // 18
{182, 181, 180, 179, 178, 177, 176, 175, 174, 173, 186, 185, 184, 183}, // 14
{192, 191, 190, 189, 188, 187, 194, 193} // 8
};
byte RING_SIZES[NUM_RINGS] = {8, 14, 18, 20, 24, 26, 24, 20, 18, 14, 8};
unsigned long startMillis = 0;
short animation = 0;
void setup() {
Serial.begin(115200);
initRandom();
leds.Begin();
leds.ClearTo(BLACK);
leds.Show();
//animations.StartAnimation(0, 6000, rainbowAnimation);
animations.StartAnimation(0, 2000, raysRainbow);
//animations.StartAnimation(0, 2000, ringsRainbow);
//animations.StartAnimation(0, 500, xmasOrbAnimation);
//animations.StartAnimation(0, 1000, ringAnimation);
//animations.StartAnimation(0, 500, rayAnimation);
//animations.StartAnimation(0, 100, randomAnimation);
}
void loop() {
animations.UpdateAnimations();
leds.Show();
delay(10);
if (false && (startMillis == 0 || startMillis + 20000 < millis())) { // 30s
startMillis = millis();
switch (animation) {
case 0:
animations.StartAnimation(0, 500, rayAnimation);
break;
case 1:
animations.StartAnimation(0, 1000, ringAnimation);
break;
case 2:
animations.StartAnimation(0, 2000, ringsRainbow);
break;
case 3:
animations.StartAnimation(0, 2000, raysRainbow);
break;
case 4:
animations.StartAnimation(0, 6000, rainbowAnimation);
break;
case 5:
animations.StartAnimation(0, 100, randomAnimation);
animation = -1;
break;
}
animation ++;
}
}
void randomAnimation(const AnimationParam& param) {
float hue;
HslColor color;
if (param.state == AnimationState_Completed) {
for (byte i = 0; i < 194; i ++) {
hue = random(0, 1000) / 1000.0f;
color = HslColor(hue, 1.0f, LIGHTNESS);
leds.SetPixelColor(i, color);
}
animations.RestartAnimation(0);
}
}
void rainbowAnimation(const AnimationParam& param) {
HslColor color = HslColor(param.progress, 1.0f, LIGHTNESS);
leds.ClearTo(color);
if (param.state == AnimationState_Completed) {
animations.RestartAnimation(0);
}
}
void raysRainbow(const AnimationParam& param) {
HslColor color;
float hue;
for (int i = 0; i < RING_MAX_PIXELS; i++) {
hue = param.progress + (float) i / (float) RING_MAX_PIXELS;
if (hue > 1.0f) {
hue -= 1.0f;
}
color = HslColor(hue, 1.0f, LIGHTNESS);
rayColor(i, RgbColor(color));
}
if (param.state == AnimationState_Completed) {
animations.RestartAnimation(0);
}
}
void ringsRainbow(const AnimationParam& param) {
HslColor color;
float hue;
for (int i = 0; i < NUM_RINGS; i++) {
hue = param.progress + (float) i / (float) NUM_RINGS;
if (hue > 1.0f) {
hue -= 1.0f;
}
color = HslColor(hue, 1.0f, LIGHTNESS);
ringColor(i, RgbColor(color));
}
if (param.state == AnimationState_Completed) {
animations.RestartAnimation(0);
}
}
void xmasOrbAnimation(const AnimationParam& param) {
ringColor(0, WHITE);
ringColor(1, RED);
ringColor(2, RED);
ringColor(3, RED);
ringColor(4, RED);
ringColor(5, WHITE);
ringColor(6, RED);
ringColor(7, RED);
ringColor(8, RED);
ringColor(9, RED);
ringColor(10, WHITE);
byte offset = round(param.progress);
for (byte i = offset; i < RING_SIZES[3]; i+=2) {
leds.SetPixelColor(RINGS[3][i] - 1, WHITE);
}
for (byte i = offset; i < RING_SIZES[7]; i+=2) {
leds.SetPixelColor(RINGS[7][i] - 1, WHITE);
}
if (param.state == AnimationState_Completed) {
animations.RestartAnimation(0);
}
}
void ringAnimation(const AnimationParam& param) {
int index = param.progress * (NUM_RINGS * 2 - 2);
leds.ClearTo(BLACK);
if (index < NUM_RINGS) {
ringColor(index, BLUE);
}
else {
ringColor(NUM_RINGS - (index - NUM_RINGS) - 2, BLUE);
}
if (param.state == AnimationState_Completed) {
animations.RestartAnimation(0);
}
}
void rayAnimation(const AnimationParam& param) {
int index = param.progress * (RING_MAX_PIXELS / 2);
if (index > 12) {
index = 12;
}
leds.ClearTo(BLACK);
rayColor(index, BLUE);
rayColor(index + (RING_MAX_PIXELS / 2), BLUE);
if (param.state == AnimationState_Completed) {
animations.RestartAnimation(0);
}
}
void rayColor(byte rayIndex, RgbColor color) {
int pixelIndex;
byte pixel;
if (rayIndex >= RING_MAX_PIXELS) {
return; // prevents out of bounds
}
for (byte i = 0; i < NUM_RINGS; i ++) {
pixelIndex = round((float) RING_SIZES[i] / (float) RING_MAX_PIXELS * rayIndex);
pixel = RINGS[i][pixelIndex];
if (pixel == 0) {
continue; // skip condition
}
leds.SetPixelColor(pixel - 1, color); // index starts from 1 (0 is stop condition)
}
}
void ringColor(byte ringIndex, RgbColor color) {
byte pixel;
if (ringIndex >= NUM_RINGS) {
return; // prevents out of bounds
}
for (byte i = 0; i < RING_MAX_PIXELS; i ++) {
pixel = RINGS[ringIndex][i];
if (pixel == 0) {
return; // end condition
}
leds.SetPixelColor(pixel - 1, color); // index starts from 1 (0 is stop condition)
}
}
void initRandom() {
// random works best with a seed that can use 31 bits
// analogRead on a unconnected pin tends toward less than four bits
uint32_t seed = analogRead(0);
delay(1);
for (int shifts = 3; shifts < 31; shifts += 3) {
seed ^= analogRead(0) << shifts;
delay(1);
}
// Serial.println(seed);
randomSeed(seed);
}
RgbColor colorWheel(byte wheelPos) {
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
wheelPos = 255 - wheelPos;
if (wheelPos < 85) {
return RgbColor(255 - wheelPos * 3, 0, wheelPos * 3);
}
if (wheelPos < 170) {
wheelPos -= 85;
return RgbColor(0, wheelPos * 3, 255 - wheelPos * 3);
}
wheelPos -= 170;
return RgbColor(wheelPos * 3, 255 - wheelPos * 3, 0);
}
为了能够控制动画,我将LED映射到11行26列的表格(数组的数组)。这样,我就能准确地知道LED在球体上的位置,并能点亮我想要的那个LED。
到目前为止我已经准备了几个动画:
垂直圆
水平圆
垂直彩虹
水平彩虹
彩虹
随机图案
注意:我必须将LED的亮度限制在满功率的20%左右。因为LED共有194个,满功率时会消耗10安培的电流。小电路板无法承受。不用担心,如果功率过大,电路板会重新启动,因此不会损坏。




第 12 步:就这样!
就这样!
希望你喜欢!制作过程很有挑战性,但最终收获颇丰。这个LED球即使不亮也很漂亮。它会成为我们圣诞树上的点睛之笔!
圣诞快乐!


附录
项目链接:https://www.instructables.com/Christmas-LED-Sphere/
项目作者:捷克共和国 吉里普劳斯
(白天是软件开发人员,晚上是硬件制造商。)
视频教程(15分钟):https://www.youtube.com/watch?v=Q5d8gTppuYo
项目代码:https://gist.github.com/jpraus/b5084df54580ebec81fac7cec86048d3
NeoPixelBus库:https://github.com/Makuna/NeoPixelBus


附件
评论