炫彩 LED 球

用 LED 串联起来的闪光球

Cherry

喜欢

7880
浏览
10
喜欢

> 更多图片

项目状态:已完成
开放度:公开
所属分类:电子
发布时间:2019-12-21
最近更新:2020-01-04

详细说明


本期带大家制作一个 LED 的炫彩发光球,使用3D 打印外壳搭建球体并将 LED 灯嵌入在球体表面,最后再上传代码,一个颜色绚丽的 LED 球就完成了。用它来做装饰品,效果异常炫酷!
焊接球体会比较耗时,大家一定要有耐心。

制作视频:
https://v.qq.com/x/page/d3044bep6oq.html

链接表


文件库

rings.svg
[10053 Bytes at 2019-12-30, 382 次下载]

ring.stl
[67484 Bytes at 2019-12-30, 349 次下载]

templateA.stl
[122084 Bytes at 2019-12-30, 350 次下载]

templateB.stl
[121584 Bytes at 2019-12-30, 310 次下载]

neo-sphere.ino
[7394 Bytes at 2019-12-30, 324 次下载]



教程

组件清单
  • ESP32 开发板(带电池) × 1
  • WS2812b RGB LED × 200
  • 1000mAh 锂电池 × 1
  • 0.8mm 黄铜线 × 若干
  • 锡焊 × 若干
  • 助焊剂 × 若干
  • 3D 打印机 × 1
  • 窄钳 × 1
  • 圆嘴钳 × 1
  • 侧切刀 × 1
  • 焊台 × 1
  • 镊子 × 1
  • 自制弯曲机 × 1

材料清单


ESP32 开发板(带电池) × 1
WS2812b RGB LED × 200
1000mAh 锂电池 × 1
0.8mm 黄铜线 × 若干
锡焊 × 若干
助焊剂 × 若干
3D 打印机 × 1
窄钳 × 1
圆嘴钳 × 1
侧切刀 × 1
焊台 × 1
镊子 × 1
自制弯曲机×1

3D 打印外壳

1、设计出 3D 打印的球体模板,球体的模板是由四个部分组成的半圆。

2、再由两个环固定到位。这四个组成部分很重要,注意这四个部分分为 templateA 和 templateB 两种,因为球体不是完全对称的。请分别打印两次这两个部分就可以打印出一个半圆球体。

3D 打印文件请在项目文件库中下载。
https://make.quwj.com/project/186

制作黄铜圈


整个球体由十一个 LED 环组成,每个环由两条圆形的线和一定数量的 LED 组成。整个球体共需要二十二个黄铜圈。如图所示,纸上画出半个球体所需的黄铜圈的大小。具体的大小还是根据你的实际情况来。
最后,球体的最终形状主要取决于黄铜弯曲的准确性。如果黄铜圈的形状不是很圆,那么可能在末端弯曲。你可以参考我的另一个项目:金属材料弯曲机https://www.instructables.com/id/Spring-Making-Wire-Bending-Machine/, 打造出完美的形状。
关于环形的文件请在项目文件库中下载。
https://make.quwj.com/project/186

放置 LED 灯

1、从模板的最底部处开始安放 LED。第一环由八个 LED 灯组成。用黑色记号笔在底部标记出 LED 灯的所有 GND 引线,以便能够识别 GND(VSS)引脚的位置。放置所有 LED 灯时,应将 GND 引脚朝向环内,VCC 引脚朝向环外。如图所示,之后的每一环都是如此,内线接地,外线通电。


2、每个 LED 灯都会涂抹一些助焊剂中,以便插入它们时,能够更好的固定在插槽内。

3、插入内环并所有的 GND 引脚焊接到环上。插入外环并将所有的 VCC 引脚焊接到环上。其中 VCC 引脚位于 GND 引脚的对角线上。确保环上没有连接 LED 灯的任何 DATA(DIN 和 DOUT)引线。

4、模板上一共六个环,LED 灯分布情况如下(从底部到顶部依次排列):
第一个环为八个 LED 灯,第二个环为十四个 LED 灯,第三个环为十八个 LED 灯,第四个环为二十个 LED 灯,第五个环为二十四个 LED 灯,第六个环为二十六个 LED 灯。

请注意整个球体只需要一个第六个环,因为它是中间的一圈。因此,球体一半带有五个环的 LED 灯,另一半带有六个环的 LED 灯。

WS2812b LED 说明
这款 LED 灯是可独立寻址的 RGB LED 灯。你只需一根电线就可以点亮它,颜色可选。与经典的通过电流量来调节亮度的 LED 灯不同,WS2812b 会始终处于供电状态并且通过数字信号来控制光。你可以任意选择灯光的颜色。你可能会问,它需要三根线吗?再用第四根线为控制链中的下一个 LED 供电。它最大的特点是将所有灯都放在一起,只用一根线来控制它们。你只需按照说明中的将第一个 LED 灯连接到其他 LED 灯上,如此继续即可。
https://www.seeedstudio.com/document/pdf/WS2812B%20Datasheet.pdf

架接数据线

电源线是环形的,这个部分已经完成。现在,需要连接 LED 灯的其他两条引线——DIN 和 DOUT。DIN 位于 GND 引脚旁,DOUT 位于 VCC 引脚旁。使用 5mm 长的线来连接。期间,务必确保数据线不接地或电源环,务必确保不要将数据引脚焊接到电源环。请正确操作,这很重要。

从最里一环开始一圈一圈地连接。当你连接完成并达到与上一环的交叉点时,就可以跳到上一环了。我使用 C 形跳线来连接两个后续环的数据引脚。因为 C 形跳线可以绕过接地线和电源线。然后再继续连接下去直到所有 LED 灯都串连起来。

最后,只剩下两条引线为连接,一根位于最顶端的环上,那是整个半球的主导 DIN 引线。另一个在底部,那是一个 DOUT 引线。

连接电源线并制作另一半

将半球上每个环之间的电源线互连。连接它们时可以创建某种结构让它们串在一起。我再次使用 C 形跳线的方式来连接电源和相邻圈的 GND 线。前面三个较小的环上至少使用四根跳线,在较大的三个环上使用至少八根跳线。这样结构就会比较稳固。

取下模具上固定四个部分的塑料环。并开始加固每个零件。请注意在过程中,不要损坏任何一个 LED 灯。

现在半球就完成了,可以开始制作另一个半。再制作的半球的环数会不一样。就如之前提到的那样,如果你的第一个半球是六环,那么第二个半球就是五环,反之亦然。

测试两个半球

在将两个半球组装前,你最好将两个半球测试一下。
1、使用万用表进行简单测试,并检查电源线和地线之间是否短路。

2、将两条短线焊接到每个半球接地线和电源线上。将电源线连接至 ESP32 板的 3.3V 引脚并接地。将所有 LED 灯都连接到一条线上。

3、取较小的半球(五个环),将它的 DIN 连接到 ESP32 板的 IO21 引脚上,将 DOUT 连接到第二个半球的 DIN 引脚上。现在,只剩下一个未连接的 LED 引脚——大半球的 DOUT 引脚。那是链中的最后一个 LED 灯。然后接通 ESP32 电路板的电源,再上传代码。

4、如果所有的灯都点亮就成功了。如果没有,并且终止在某处,请仔细检查 LED 灯上是否有冷接点或短路。

插入微控制器和电池

1、处理 ESP32 开发板。将开关从板子上移除(你无法接触到它,因为它将嵌入在球体内)并用两条铜线代替它。将其悬空,将微控制器放入球体中时,将其焊接。
2、将电路板的角落的三或四个螺孔填充封闭,因为不会使用到,而且可以使它们更易于连接到球体上。

3、取较小的半球,将微控制器放置其底部的中心处,并将 USB 和电池连接器朝向球部的外侧。将角孔焊接到最小的环上,它是接地的,将两根开关线穿过孔,然后将之前移除的开关焊接到它们上。开关是金属主体,也可以焊接到最小的环上。请确保不要让开关线与结构短路,也不要让电源开关超出球体。

4、取一根线将其焊接到 ESP32 板上的 3.3V 引脚上。然后将另一端焊接到球体的最后一个环上,它是接电源的。我通过接地和电路板 GND 引脚之间焊接的直线来增强内部结构。

5、确保在板子背面留出一块可容纳电池的空间。用铜线将主导的 DIN 引脚连接到 ESP32 的 IO21 引脚上。现在半球已经设置好,连接电池即可进行测试。
6、将电池连接到 ESP32 板的背面,然后通过孔将其连接。

焊接球体

1、焊接前,测试 ESP32 板上的螺孔是否接地。同时在它和电路板的最近的 GND 引脚之间加一条短线,它将为第二个半球提供接地。

2、焊接短线,将 DOUT 从第一个半球连接到第二个半球的 DIN 引脚上(与测试阶段相同)。取一根 2mm 长的电线,并将其焊接到第二个半球的最上面的环上。我一共使用了八根这样的线。最上面的两个环都通电。因此将每个半球的两个最上面的环焊接在一起是安全的。将两个半球放置在一起并焊接。确保它们之间的空间均匀且平滑。最后,将 ESP32 板的螺丝孔与最小的接地环线连接起来。

编程

Arduino IDE 源代码:

#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);
}

请在项目文件库中下载。
https://make.quwj.com/project/186
同时配合使用 Neopixel BUS 库来控制 LED 条。它具有非常好的界面并动画支持的效果。
https://github.com/Makuna/NeoPixelBus
为了控制动画,我将 LED 映射设置为 11 行 26 列的表(数组数列)。通过这种方式,我能够确切地了解 LED 灯如何放置在球体上的位置,并且可以点亮我想点亮的 LED 灯。

可以变化的 6 个动画为:1、垂直圆模式;2、水平圆模式;3、垂直彩虹模式;4、水平彩虹模式;5、彩虹模式;6、随机模式。

注意:LED 的亮度限制为全功率的大约 20%。因为其中有 194 个灯是全功率,它们将消耗 10 安的电流。板子将无法处理。如果电量过大,也不用担心,该板子会重新启动,不会损坏板子。

完成


现在整个项目就完成了,这将是一个挑战自己耐心的项目,非常具有实践的意义。漂亮的 LED 炫彩球可以装饰圣诞树,也可以用来点亮户外广告。