DIY Arduino Sneeuwvlok: 30-LED displayproject
Onderdelen en benodigdheden
| Onderdeel | Aantal |
|---|---|
| Arduino Nano R3 | 1 |
Over dit project
De sneeuwvlok bestaat uit 30 LEDs gegroepeerd in 17 onafhankelijke segmenten die apart bestuurd kunnen worden door de Arduino Nano. Elke LED-groep kan met PWM gedimd worden voor mooie animaties.
Tools
Je hebt alleen een solderbout, soldeer en tang nodig.
Constructie
Kies eerst een patroon. Ik koos een mooie, simpele sneeuwvlokkristal en printte het in de grootte om de Arduino Nano in de hexagoon – de kern van de kristal – te laten passen.

De ondersteuningsstructuur, die ook als bedrading fungeert, is gemaakt van 0,8mm messingstaven die met tin gesoldeerd zijn. Ik heb in totaal 2m staf gebruikt. Waarom freeform? Omdat ik dit altijd al wilde proberen en het een test is van je geduld en vaardigheid.

Eerst maakte ik een kernhexagoon door een enkele staaf te buigen en de uiteinden te solderen. Door nog 6 staven aan de hoekpunten van de hexagoon toe te voegen is de massa-aardbedrading compleet; alle kathoden van de LEDs moeten nu aan deze structuur gesoldeerd worden om het sneeuwvlokpatroon te creëren. Het lastige deel was de SMD-LEDs toevoegen, maar ik hielp mezelf met een hulpmiddel van karton en dubbelzijdig tape.

Vervolgens was het tijd om de Arduino Nano onder de kernstructuur te plaatsen, met voldoende ruimte ertussen voor 3 lagen messingstaf-bedadrading die de microcontrollerpinnen verbinden met alle anoden van de LEDs. Dit vereiste enorm veel geduld. Je moet niet alleen kortsluitingen tussen de draden vermijden, maar ook een stroombegrenzende weerstand toevoegen en het er mooi uit laten zien.

De blad-LEDs zijn elk apart aangesloten op de dichtstbijzijnde Arduino-outputpin. De tak-LEDs zijn per twee gegroepeerd en aangesloten op PWM-pinnen. De kern-LEDs zijn ook per twee gegroepeerd en aangesloten op de overige pinnen. De Arduino Nano heeft slechts 18 outputpinnen (A6 en A7 zijn alleen input) en ik heb er één nodig voor de touchsensor, wat 17 pinnen overlaat. Daardoor zijn twee paren kern-LEDs samen aangesloten tot een groep van 4. Ik gebruik 220Ω weerstanden om de stroom per pin te beperken tot ~8mA. Dat is 240mA in totaal, wat iets hoog is voor de ATmega328-chip (veilige maximum is 200mA), maar het werkt.
Touchsensor
Om interactie met de sneeuwvlok mogelijk te maken, voegde ik een extra messingstaaf toe als capacitieve touchsensor. Ik vond een geweldige bibliotheek en tutorial van Paul Stoffregen. De touchsensor wordt gebruikt om met de 'arduinoflake' te interacteren: animatie wijzigen, aan/uit zetten, knipperen bij aanraking, etc.

Eindnoot
Vergeet niet alle diodes te controleren vóór het solderen; het is heel lastig om ze later te vervangen als ze defect of omgekeerd gepoleerd zijn.

Code
Oorspronkelijk dacht ik alleen de tak-LEDs (aangesloten op hardware-PWM-pinnen) te kunnen dimmen. Maar gelukkig bestaat er een geweldige software-PWM-bibliotheek waarmee ik alle pinnen kan gebruiken alsof ze hardware-PWM waren. Dit creëert oneindige animatiemogelijkheden! Bekijk de onderstaande code met de eerste animaties.
Code
snowflake.ino (Arduino)
#include "SoftPWM.h"
#include <ADCTouchSensor.h>
byte edgeLedPins[] = {13, A4, A5, 2, 8, 12};
byte middleLedPins[] = {10, 6, 3, 5, 9, 11};
byte innerLedPins[] = {A2, A3, A1, 4, 7, A1};
ADCTouchSensor touchSensor = ADCTouchSensor(A0, 1);
void setup() {
Serial.begin(115200);
SoftPWMBegin();
}
byte animation = 0;
long touchAt = 0;
void loop() {
switch (animation) {
case 0: _fill(100); break;
case 1: shinyAnimation(); break;
case 2: circleAnimation(); break;
case 3: loopAnimation(); break;
case 4: fireworkAnimation(); fireworkAnimation(); fireworkAnimation(); fireworkAnimation(); fireworkAnimation(); fireworkAnimation(); animation++; break;
case 5: smileAnimation(); break;
default: animation = 0; break;
}
int touchValue = touchSensor.read();
if (touchAt + 2000 < millis() && touchValue > 1000) {
touchAt = millis();
animation++;
_fill(0);
}
}
void fireworkAnimation() {
for (int i = 0; i < 4; i++) {
SoftPWMSet(innerLedPins[i], 100);
delay(100);
}
SoftPWMSet(innerLedPins[4], 100);
for (int i = 0; i < 6; i++) {
SoftPWMSet(middleLedPins[i], 255);
}
delay(50);
for (int i = 0; i < 6; i++) {
SoftPWMSet(innerLedPins[i], 0);
SoftPWMSet(edgeLedPins[i], 255);
}
delay(50);
for (int i = 0; i < 6; i++) {
SoftPWMSet(middleLedPins[i], 0);
}
delay(50);
_fill(0);
}
void smileAnimation() {
SoftPWMSet(innerLedPins[1], 100);
SoftPWMSet(innerLedPins[3], 100);
SoftPWMSet(middleLedPins[0], 255);
SoftPWMSet(middleLedPins[5], 255);
SoftPWMSet(edgeLedPins[2], 255);
SoftPWMSet(edgeLedPins[3], 255);
delay(2000);
SoftPWMSet(innerLedPins[1], 0);
SoftPWMSet(innerLedPins[3], 0);
delay(100);
SoftPWMSet(innerLedPins[1], 100);
SoftPWMSet(innerLedPins[3], 100);
delay(100);
SoftPWMSet(innerLedPins[1], 0);
SoftPWMSet(innerLedPins[3], 0);
delay(100);
}
byte circleState[] = {100, 55, 10};
byte circleStateAnimation[] = {1, 1, 1};
void circleAnimation() {
for (int i = 0; i < 3; i++) {
if (circleState[i] >= 100) circleStateAnimation[i] = -1;
else if (circleState[i] <= 10) circleStateAnimation[i] = 1;
circleState[i] += circleStateAnimation[i];
}
for (int i = 0; i < 6; i++) {
SoftPWMSet(innerLedPins[i], circleState[0]);
SoftPWMSet(middleLedPins[i], circleState[1]);
SoftPWMSet(edgeLedPins[i], circleState[2]);
}
delay(5);
}
byte waveState[] = {100, 55, 10, 10, 55, 100};
byte waveStateAnimation[] = {1, 1, 1, -1, -1, -1};
void waveAnimation() {
for (int i = 0; i < 6; i++) {
if (waveState[i] >= 100) waveStateAnimation[i] = -1;
else if (waveState[i] <= 10) waveStateAnimation[i] = 1;
waveState[i] += waveStateAnimation[i];
}
for (int i = 0; i < 6; i+=2) {
SoftPWMSet(innerLedPins[i], waveState[0]);
SoftPWMSet(middleLedPins[i], waveState[1]);
SoftPWMSet(edgeLedPins[i], waveState[2]);
SoftPWMSet(innerLedPins[i + 1], waveState[3]);
SoftPWMSet(middleLedPins[i + 1], waveState[4]);
SoftPWMSet(edgeLedPins[i + 1], waveState[5]);
}
delay(10);
}
byte loopCounter = 0;
byte loopState = 150;
void loopAnimation() {
SoftPWMSet(innerLedPins[loopCounter], loopState);
SoftPWMSet(middleLedPins[loopCounter], loopState);
SoftPWMSet(edgeLedPins[loopCounter], loopState);
loopCounter = _nextIndex(loopCounter, 1);
if (loopCounter == 0) {
loopState = (loopState == 150 ? 0 : 150);
}
delay(100);
}
byte slowOnCounter = 0;
byte slowOnState = 150;
void slowOnAnimation() {
byte randomLed = random(0, 18);
if (randomLed < 6) SoftPWMSet(innerLedPins[randomLed], slowOnState);
else if (randomLed < 12) SoftPWMSet(middleLedPins[randomLed - 6], slowOnState);
else SoftPWMSet(edgeLedPins[randomLed - 12], slowOnState);
slowOnCounter++;
if (slowOnCounter >= 50) {
slowOnCounter = 0;
slowOnState = (slowOnState == 150 ? 0 : 150);
}
delay(50);
}
byte shinyState[] = {0, 100, 0, 100, 0, 100};
byte shinyStateAnimation[] = {1, 1, 1, 1, 1, 1};
byte shinyCounter = 0;
void shinyAnimation() {
for (int i = 0; i < 6; i++) {
if (shinyState[i] >= 100) shinyStateAnimation[i] = -1;
else if (shinyState[i] <= 0) shinyStateAnimation[i] = 1;
shinyState[i] += shinyStateAnimation[i];
SoftPWMSet(edgeLedPins[i], shinyState[i]);
}
shinyCounter++;
if (shinyCounter > 10) {
shinyCounter = 0;
for (byte r = random(1, 3); r > 0; r--) {
byte randomLed = random(0, 12);
if (randomLed < 6) SoftPWMSet(innerLedPins[random(0, 6)], 255);
else SoftPWMSet(middleLedPins[random(0, 6)], 255);
}
} else {
for (int i = 0; i < 6; i++) {
SoftPWMSet(innerLedPins[i], 20);
SoftPWMSet(middleLedPins[i], 20);
}
}
delay(30);
}
byte fadingState[] = {0, 100, 0, 100, 0, 100};
byte fadingStateAnimation[] = {1, 1, 1, 1, 1, 1};
void fadingAnimation() {
for (int i = 0; i < 6; i++) {
if (fadingState[i] >= 100) fadingStateAnimation[i] = -1;
else if (fadingState[i] <= 0) fadingStateAnimation[i] = 1;
fadingState[i] += fadingStateAnimation[i];
SoftPWMSet(edgeLedPins[i], fadingState[i]);
SoftPWMSet(middleLedPins[_nextIndex(i, 1)], fadingState[i]);
SoftPWMSet(innerLedPins[i], 50);
}
delay(20);
}
void _fill(byte value) {
for (int i = 0; i < 6; i++) {
SoftPWMSet(edgeLedPins[i], value);
SoftPWMSet(middleLedPins[i], value);
SoftPWMSet(innerLedPins[i], value);
}
}
byte _prevIndex(short index, byte step) {
index -= step;
while (index < 0) index += 6;
return index;
}
byte _nextIndex(short index, byte step) {
index += step;
while (index > 5) index -= 6;
return index;
}
Schema's
Productieproces
- De Deegroller: Van Etrusken tot Moderne Vakmanschap
- De Evolutie en Vakmanschap van Moderne Bowlingpinnen
- Van Oude Fibulae tot Moderne Fabricage: De Veiligheidsspeld Uitgelegd
- Een Raspberry Pi I/O Breakout-kabel & Plugboard-adapter Bouwen – Stap-voor-stap Handleiding
- Dynamische LED-driehoekkunst: Bouw een 45-LED RGB-display met Arduino UNO
- DHT11 Temperatuur- & Vochtigheidsensor Project met LED-indicatoren en Piezo-luidspreker
- Ball Grid Array (BGA): wat het is en waarom het belangrijk is
- Hoe Doppen Uw Projecten Precies Houden: Toepassingen in Meubels, Planken en Machines
- Materialen voor Doppen: Hout, Metaal en Kunststof Uitgelegd
- 5 Belangrijke Factoren Voor Het Selecteren Van De Juiste Dwangpennen
- TinyML: Ingenieurs verenigen om AI te standaardiseren voor ultra-laag-vermogen systemen