Table des matières:

Horloge LED en bois - Style analogique : 11 étapes (avec photos)
Horloge LED en bois - Style analogique : 11 étapes (avec photos)

Vidéo: Horloge LED en bois - Style analogique : 11 étapes (avec photos)

Vidéo: Horloge LED en bois - Style analogique : 11 étapes (avec photos)
Vidéo: horloge murale 2024, Novembre
Anonim
Horloge LED en bois - Style analogique
Horloge LED en bois - Style analogique

C'est une horloge LED en bois de style analogique. Je ne sais pas pourquoi je n'en ai jamais vu un auparavant..même si les types numériques sont très courants. Quoi qu'il en soit, c'est parti !

Étape 1:

Image
Image
Image
Image

Le projet d'horloge en contreplaqué a commencé comme un simple projet de démarrage pour le routeur CNC. Je cherchais des projets simples en ligne et j'ai trouvé cette lampe (image ci-dessus). J'avais aussi vu des horloges numériques qui brillent à travers le placage de bois (image ci-dessus). Ainsi, combiner les deux projets était une idée évidente. Cherchant à me mettre au défi, j'ai décidé de ne pas utiliser de placage mais seulement un morceau de bois pour ce projet.

Étape 2: Concevoir

Concevoir
Concevoir
Concevoir
Concevoir

J'ai conçu l'horloge dans Inkscape (image ci-dessus). Le design est très simple par choix. J'ai décidé de ne pas acheminer les traces pour les fils car à ce stade, je ne savais pas si je voulais utiliser un câblage radial ou périphérique. (J'ai finalement décidé d'utiliser le câblage périphérique.) Un néopixel est inséré dans chacun des petits trous circulaires pour afficher les minutes et les heures, avec une précision de cinq minutes. Le cercle au milieu sera acheminé vers l'extérieur pour accueillir l'électronique.

Étape 3: CNC

CNC
CNC
CNC
CNC
CNC
CNC
CNC
CNC

J'ai conçu les parcours d'outils sur MasterCAM et utilisé un technoRouter pour fraiser l'horloge à partir de contreplaqué de 3/4 de pouce. J'utilise une pièce de 15"x15" pour cela, avec un minimum de gaspillage. L'astuce consiste à acheminer autant de bois que possible sans percer le bois. Laisser 0,05"-0,1" est un bon choix pour le bois clair. Si vous n'êtes pas sûr, il vaut mieux laisser plus de bois, car vous pouvez toujours poncer l'autre face. J'ai fini par retirer un peu trop de bois de certaines pièces, mais heureusement les résultats n'en souffrent pas trop à cause de cela.

Remarque pour les utilisateurs n'ayant pas accès à une CNC:

Ce projet peut facilement être réalisé avec une perceuse à colonne. Vous avez juste besoin de régler la butée à un point où vous laissez environ 0,1 de bois restant à la base. Vous devrez être précis, mais pas trop précis. Après tout, idéalement, personne ne verra toutes les LED s'allumer à en même temps, pour que vous puissiez vous en sortir avec un peu de slop.

Étape 4: Électronique

Électronique
Électronique
Électronique
Électronique
Électronique
Électronique

L'électronique est assez simple. Il y a 24 néopixels, douze pour afficher les heures et douze pour afficher les minutes, avec une précision de cinq minutes. Un Arduino pro mini contrôle les néopixels et obtient une heure précise grâce à un module d'horloge temps réel (RTC) DS3231. Le module RTC a une pile bouton comme sauvegarde, il ne perd donc pas de temps même lorsque l'alimentation est coupée.

Matériel:

Arduino pro mini (ou tout autre Arduino d'ailleurs)

Carte de dérivation DS3231

Néopixels dans des tableaux de répartition individuels

Étape 5: Assemblage électronique

Assemblage électronique
Assemblage électronique
Assemblage électronique
Assemblage électronique
Assemblage électronique
Assemblage électronique
Assemblage électronique
Assemblage électronique

J'ai connecté les néopixels dans une chaîne, en utilisant des fils de 2,5 pour les douze premières LED et un fil de quatre pouces pour les douze suivantes. J'aurais pu utiliser des longueurs de fil légèrement plus petites. Après avoir fabriqué la chaîne, je l'ai testée, en m'assurant que la soudure les joints étaient bons. J'ai ajouté un interrupteur momentané pour allumer toutes les leds, juste pour frimer.

Étape 6: Essai à sec

Essai à sec
Essai à sec
Essai à sec
Essai à sec
Essai à sec
Essai à sec
Essai à sec
Essai à sec

Après avoir expérimenté, mis des LED dans les trous et les avoir toutes allumées, j'étais satisfait des résultats. J'ai donc poncé un peu la face avant et appliqué une couche de PU. J'ai fini par poncer le manteau plus tard, mais c'est une bonne idée de le laisser si vous ne le trouvez pas esthétiquement déplaisant.

Étape 7: Époxy

Époxy
Époxy
Époxy
Époxy

Après quelques tests avec la position de la LED dans les trous, j'ai pensé que la meilleure discussion est obtenue lorsque les LED sont à environ 0,2 de l'extrémité du trou. Lorsque vous essayez cela vous-même, la luminosité des LED sera très différente dans chaque trou. Ne vous inquiétez pas à ce sujet; nous allons le corriger dans le code. C'est à cause du type de foret que j'ai utilisé. Si je devais le refaire, j'utiliserais un foret à bout sphérique pour les trous Mais, dans tous les cas, pour avoir la distance j'ai mélangé de l'époxy et j'en ai mis un peu dans chaque trou.

Étape 8: Tout assembler

Mettre tous ensemble
Mettre tous ensemble
Mettre tous ensemble
Mettre tous ensemble
Mettre tous ensemble
Mettre tous ensemble
Mettre tous ensemble
Mettre tous ensemble

Les LED seront placées à partir de la position de l'aiguille des heures à 12 heures en se déplaçant dans le sens inverse des aiguilles d'une montre à travers toutes les positions de l'aiguille des heures, puis à l'aiguille des minutes, en se déplaçant à nouveau à partir de la marque des 60 minutes en se déplaçant dans le sens inverse des aiguilles d'une montre. C'est ainsi que lorsque nous regardons de face, le motif LED apparaît dans le sens des aiguilles d'une montre.

Après que l'époxy ait durci pendant une heure, j'ai mis un peu plus d'époxy. Cette fois, j'ai placé les LED dans les trous, en veillant à recouvrir les fils et les joints de soudure avec de l'époxy. Cela permet une bonne diffusion de la lumière et sécurise les fils.

Étape 9: Coder

Code
Code

Le code est sur GitHub, n'hésitez pas à le modifier pour votre usage. Lorsque vous allumez toutes les LED au même niveau, la luminosité de la lumière qui traverse sera très différente dans chaque trou. Cela est dû à la différence d'épaisseur de bois dans les trous et à la différence de teinte du bois. Comme vous pouvez le voir, la couleur du bois varie beaucoup dans ma pièce. Pour remédier à cette différence de luminosité, j'ai réalisé une matrice de niveaux de luminosité des leds. Et a diminué la luminosité des LED les plus lumineuses. C'est un processus d'essais et d'erreurs qui peut prendre plusieurs minutes, mais les résultats en valent la peine.

contreplaquéClock.ino

// Horloge en contreplaqué
// Auteur: tinkrmind
// Attribution 4.0 International (CC BY 4.0). Vous êtes libre de:
// Partager - copier et redistribuer le matériel dans n'importe quel support ou format
// Adaptez - remixez, transformez et construisez sur le matériel à n'importe quelle fin, même commercialement.
// Hourra !
#comprendre
#include"RTClib.h"
RTC_DS3231 rtc;
#include"Adafruit_NeoPixel.h"
#ifdef _AVR_
#comprendre
#fin si
#definePIN6
Bande Adafruit_NeoPixel = Adafruit_NeoPixel (60, PIN, NEO_GRB + NEO_KHZ800);
int heurePixel = 0;
int minutePixel = 0;
non signélong lastRtcCheck;
Chaîne inputString = ""; // une chaîne pour contenir les données entrantes
chaîne booléenneComplete = false; // si la chaîne est complète
niveau int[24] = {31, 51, 37, 64, 50, 224, 64, 102, 95, 255, 49, 44, 65, 230, 80, 77, 102, 87, 149, 192, 67, 109, 68, 77};
void setup () {
#ifndef ESP8266
tandis que (!Série); // pour Leonardo/Micro/Zéro
#fin si
// Ceci est pour Trinket 5V 16MHz, vous pouvez supprimer ces trois lignes si vous n'utilisez pas de Trinket
#si défini (_AVR_ATtiny85_)
if (F_CPU == 16000000) clock_prescale_set(clock_div_1);
#fin si
// Fin du code spécial bibelot
Serial.begin(9600);
strip.begin();
strip.show(); // Initialise tous les pixels sur 'off'
si (! rtc.begin()) {
Serial.println("Impossible de trouver RTC");
tandis que (1);
}
pinMode(2, INPUT_PULLUP);
// rtc.adjust(DateTime(F(_DATE_), F(_TIME_)));
if (rtc.lostPower()) {
Serial.println("RTC a perdu l'alimentation, réglons l'heure !");
// la ligne suivante définit le RTC sur la date et l'heure auxquelles ce croquis a été compilé
rtc.adjust(DateTime(F(_DATE_), F(_TIME_)));
// Cette ligne définit le RTC avec une date et une heure explicites, par exemple pour définir
// Le 21 janvier 2014 à 3h du matin, vous appelleriez:
// rtc.adjust(DateTime(2017, 11, 06, 2, 49, 0));
}
// rtc.adjust(DateTime(2017, 11, 06, 2, 49, 0));
// lightUpEven();
// tandis que (1);
lastRtcCheck = 0;
}
boucle vide () {
if (millis() - lastRtcCheck >2000) {
DateHeure maintenant = rtc.now();
Serial.print(now.hour(), DEC);
Serial.print(':');
Serial.print(maintenant.minute(), DEC);
Serial.print(':');
Serial.print(now.second(), DEC);
Serial.println();
afficher l'heure();
lastRtcCheck = millis();
}
if (!digitalRead(2)) {
lightUpEven();
}
if (chaîneComplete) {
Serial.println(inputString);
if (inputString[0] == 'l') {
Serial.println("Niveau");
lightUpEven();
}
if (inputString[0] == 'c') {
Serial.println("Affichage de l'heure");
afficher l'heure();
strip.show();
}
if (inputString[0] == '1') {
Serial.println("Allumer toutes les LED");
lightUp(strip. Color(255, 255, 255));
strip.show();
}
if (inputString[0] == '0') {
Serial.println("Effacer la bande");
dégager();
strip.show();
}
// #3, 255 mettrait la led numéro 3 au niveau 255, 255, 255
if (inputString[0] == '#') {
Température de la chaîne;
temp = inputString.substring(1);
int pixNum = temp.toInt();
temp = inputString.substring(inputString.indexOf(', ') + 1);
int intensité = temp.toInt();
Serial.print("Paramètre ");
Serial.print(PixNum);
Serial.print(" au niveau ");
Serial.println(intensité);
strip.setPixelColor(pixNum, strip. Color(intensité, intensité, intensité));
strip.show();
}
// #3, 255, 0, 125 mettrait la led numéro 3 au niveau 255, 0, 125
if (inputString[0] == '$') {
Température de la chaîne;
temp = inputString.substring(1);
int pixNum = temp.toInt();
int rIndex = inputString.indexOf(', ') + 1;
temp = inputString.substring(rIndex);
int rIntensité = temp.toInt();
intgIndex = inputString.indexOf(', ', rIndex + 1) + 1;
temp = inputString.substring(gIndex);
intgIntensity = temp.toInt();
int bIndex = inputString.indexOf(', ', gIndex + 1) + 1;
temp = inputString.substring(bIndex);
int bIntensité = temp.toInt();
Serial.print("Paramètre ");
Serial.print(PixNum);
Serial.print(" R à ");
Serial.print(rIntensity);
Serial.print(" G à ");
Serial.print(gIntensity);
Serial.print(" B à ");
Serial.println(bIntensity);
strip.setPixelColor(pixNum, strip. Color(rIntensity, gIntensity, bIntensity));
strip.show();
}
if (inputString[0] == 's') {
Température de la chaîne;
int heure, minute;
temp = inputString.substring(1);
heure = temp.toInt();
int rIndex = inputString.indexOf(', ') + 1;
temp = inputString.substring(rIndex);
minute = temp.àInt();
Serial.print("Affichage de l'heure: ");
Serial.print(heure);
Serial.print(":");
Serial.print(minute);
showTime(heure, minute);
retard(1000);
}
chaîne d'entrée = "";
stringComplete = false;
}
// retard(1000);
}
voidserialEvent() {
while (Serial.available()) {
char inChar = (char)Serial.read();
inputString += inChar;
if (inChar == '\n') {
stringComplete = true;
}
retard(1);
}
}
voidclear() {
pour (uint16_t i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, strip. Color(0, 0, 0));
}
}
voidshowTime() {
DateHeure maintenant = rtc.now();
heurePixel = maintenant.heure() % 12;
minutePixel = (maintenant.minute() / 5) % 12 + 12;
dégager();
// strip.setPixelColor(hourPixel, strip. Color(40 + 40 * level[hourPixel], 30 + 30 * level[hourPixel], 20 + 20 * level[hourPixel]));
// strip.setPixelColor(minutePixel, strip. Color(40 + 40 * niveau[minutePixel], 30 + 30 * niveau[minutePixel], 20 + 20 * niveau[minutePixel]));
strip.setPixelColor(hourPixel, strip. Color(level[hourPixel], level[hourPixel], level[hourPixel]));
strip.setPixelColor(minutePixel, strip. Color(level[minutePixel], level[minutePixel], level[minutePixel]));
// lightUp(strip. Color(255, 255, 255));
strip.show();
}
voidshowTime(int heure, int minute) {
heurePixel = heure % 12;
minutePixel = (minute / 5) % 12 + 12;
dégager();
// strip.setPixelColor(hourPixel, strip. Color(40 + 40 * level[hourPixel], 30 + 30 * level[hourPixel], 20 + 20 * level[hourPixel]));
// strip.setPixelColor(minutePixel, strip. Color(40 + 40 * niveau[minutePixel], 30 + 30 * niveau[minutePixel], 20 + 20 * niveau[minutePixel]));
strip.setPixelColor(hourPixel, strip. Color(level[hourPixel], level[hourPixel], level[hourPixel]));
strip.setPixelColor(minutePixel, strip. Color(level[minutePixel], level[minutePixel], level[minutePixel]));
// lightUp(strip. Color(255, 255, 255));
strip.show();
}
voidlightUp (couleur uint32_t) {
pour (uint16_t i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, couleur);
}
strip.show();
}
voidlightUpEven() {
pour (uint16_t i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, strip. Color(level, level, level));
}
strip.show();
}

voir rawplywoodClock.ino hébergé avec ❤ par GitHub

Étape 10: Vision par ordinateur - Calibrage

Vision par ordinateur - Étalonnage
Vision par ordinateur - Étalonnage
Vision par ordinateur - Étalonnage
Vision par ordinateur - Étalonnage

J'ai fait un choix conscient de ne pas utiliser de placage dans ce projet. Si je l'avais fait, l'épaisseur du bois aurait été la même devant toutes les LED. Mais, parce que j'ai une épaisseur de bois différente devant chaque LED et parce que la couleur du bois varie également beaucoup, la luminosité de la LED est différente pour chaque LED. Pour que toutes les LED semblent avoir la même luminosité, j'ai imaginé une astuce astucieuse.

J'ai écrit un code de traitement (sur GitHub) qui prend une photo de l'horloge et analyse la luminosité de chaque LED à tour de rôle. Il fait ensuite varier la puissance de chaque LED pour essayer de les faire toutes avoir la même luminosité que la LED la plus faible. Maintenant, je sais que c'est exagéré, mais le traitement d'image est très amusant ! Et j'espère développer le code d'étalonnage en tant que bibliothèque.

Vous pouvez voir la luminosité de la LED avant et après l'étalonnage sur les photos ci-dessus.

calibrerAffichage.pde

importprocessing.video.*;
importprocessing.serial.*;
myPort série;
Capturer la vidéo;
finalint numLed =24;
int ledNum =0;
// vous devez avoir ces variables globales pour utiliser le PxPGetPixelDark()
int rDark, gDark, bDark, aDark;
int rLed, gLed, bLed, aLed;
int rOrg, gOrg, bOrg, aOrg;
int rTemp, gTemp, bTemp, aTemp;
PImage notreImage;
int runNumber =0;
int acceptableErreur =3;
int fait;
int numPixelsInLed;
long ledIntensité;
int ledPower;
cible longue Intensité =99999999;
void setup() {
done = newint[numLed];
numPixelsInLed =newint[numLed];
ledIntensity =newlong[numLed];
ledPower =newint[numLed];
pour (int i=0; i<numLed; i++) {
ledPower =255;
}
printArray(Serial.list());
String portName =Serial.list()[31];
myPort = newSerial(this, portName, 9600);
taille (640, 480);
video =newCapture(this, width, height);
video.start();
noStroke();
lisse();
retard(1000); // Attendez que le port série s'ouvre
}
voiddraw() {
si (vidéo.disponible()) {
if (done[ledNum] ==0) {
clearAffichage();
retard(1000);
video.read();
image(vidéo, 0, 0, largeur, hauteur); // Dessiner la vidéo de la webcam sur l'écran
saveFrame("data/no_leds.jpg");
if (runNumber !=0) {
if ((ledIntensity[ledNum] - targetIntensity)*100/targetIntensity > acceptableError) {
ledPower[ledNum] -=pow(0.75, runNumber)*100+1;
}
if ((targetIntensity - ledIntensity[ledNum])*100/targetIntensity > acceptableError) {
ledPower[ledNum] +=pow(0.75, runNumber)*100+1;
}
if (abs(targetIntensity - ledIntensity[ledNum])*100/targetIntensity <= acceptableError) {
fait[ledNum] =1;
print("Led ");
print(ledNum);
print("fait");
}
if (ledPower[ledNum] >255) {
ledPower[ledNum] =255;
}
if (ledPower[ledNum] <0) {
ledPower[ledNum]=0;
}
}
setLedPower(ledNum, ledPower[ledNum]);
retard(1000);
video.read();
image(vidéo, 0, 0, largeur, hauteur); // Dessiner la vidéo de la webcam sur l'écran
retard(10);
while (myPort.available() >0) {
int inByte = monPort.read();
//print(char(inByte));
}
String imageName ="data/";
imageName+=str(ledNum);
imageName +="_led.jpg";
saveFrame(nomImage);
String originalImageName ="data/org";
originalImageName+=str(ledNum);
NomImage d'origine +=".jpg";
if (runNumber ==0) {
saveFrame(originalImageName);
}
PImage noLedImg =loadImage("data/no_leds.jpg");
PImage ledImg =loadImage(imageName);
PImage originalImg =loadImage(originalImageName);
noLedImg.loadPixels();
ledImg.loadPixels();
originalImg.loadPixels();
arrière-plan (0);
loadPixels();
ledIntensité[ledNum] =0;
numPixelsInLed[ledNum] =0;
pour (int x =0; x<largeur; x++) {
for (int y =0; y<hauteur; y++) {
PxPGetPixelDark(x, y, noLedImg.pixels, width);
PxPGetPixelLed(x, y, ledImg.pixels, largeur);
PxPGetPixelOrg(x, y, originalImg.pixels, largeur);
if ((rOrg+gOrg/2+bOrg/3)-(rDark+gDark/2+bDark/3) >75) {
ledIntensity[ledNum] = ledIntensity[ledNum] +(rLed+gLed/2+bLed/3) -(rDark+gDark/2+bDark/3);
rTemp=255;
gTemp=255;
bTemp=255;
numPixelsInLed[ledNum]++;
} autre {
rTemp=0;
gTemp=0;
bTemp=0;
}
PxPSetPixel(x, y, rTemp, gTemp, bTemp, 255, pixels, largeur);
}
}
ledIntensity[ledNum] /= numPixelsInLed[ledNum];
if (targetIntensity > ledIntensity[ledNum] && runNumber ==0) {
targetIntensity = ledIntensity[ledNum];
}
updatePixels();
}
print(ledNum);
imprimer(', ');
print(ledPower[ledNum]);
imprimer(', ');
println(ledIntensity[ledNum]);
ledNum++;
if (ledNum == numLed) {
int donezo =0;
pour (int i=0; i<numLed; i++) {
donezo += fait;
}
if (donezo == numLed) {
println("FAIT");
for (int i=0; i<numLed; i++) {
imprimer(i);
print("\t");
println(ledPower);
}
print("int level[");
print(ledNum);
print("] = {");
pour (int i=0; i<numLed-1; i++) {
print(ledPower);
imprimer(', ');
}
print(ledPower[numLed -1]);
println("};");
lightUpEven();
tandis que (vrai);
}
print("Intensité cible: ");
if (runNumber ==0) {
cibleIntensité -=1;
}
println(cibleIntensité);
ledNum =0;
runNumber++;
}
}
}
voidPxPGetPixelOrg(intx, inty, int pixelArray, intpixelsWidth) {
int thisPixel=pixelArray[x+y*pixelsWidth]; // récupérer les couleurs sous forme d'entier à partir des pixels
aOrg = (ce Pixel >>24) &0xFF; // nous devons déplacer et masquer pour obtenir chaque composant seul
rOrg = (ce Pixel >>16) &0xFF; // c'est plus rapide que d'appeler red(), green(), blue()
gOrg = (ce pixel >>8) &0xFF;
bOrg = ce pixel &0xFF;
}
voidPxPGetPixelDark(intx, inty, int pixelArray, intpixelsWidth) {
int thisPixel=pixelArray[x+y*pixelsWidth]; // récupérer les couleurs sous forme d'entier à partir des pixels
aDark = (ce Pixel >>24) &0xFF; // nous devons déplacer et masquer pour obtenir chaque composant seul
rDark = (ce Pixel >>16) &0xFF; // c'est plus rapide que d'appeler red(), green(), blue()
gDark = (ce Pixel >>8) &0xFF;
bDark = thisPixel &0xFF;
}
voidPxPGetPixelLed(intx, inty, int pixelArray, intpixelsWidth) {
int thisPixel=pixelArray[x+y*pixelsWidth]; // récupérer les couleurs sous forme d'entier à partir des pixels
aLed = (ce Pixel >>24) &0xFF; // nous devons déplacer et masquer pour obtenir chaque composant seul
rLed = (ce Pixel >>16) &0xFF; // c'est plus rapide que d'appeler red(), green(), blue()
gLed = (ce Pixel >>8) &0xFF;
bLed = ce pixel &0xFF;
}
voidPxPSetPixel(intx, inty, intr, intg, intb, inta, int pixelArray, intpixelsWidth) {
a =(a <<24);
r = r <<16; // Nous emballons les 4 composants dans un seul int
g = g <<8; // nous devons donc les déplacer à leur place
couleur argb = a | r | g | b; // l'opération binaire "ou" les ajoute tous en un seul int
pixelArray[x+y*pixelsWidth]= argb; // enfin, nous définissons l'int avec les couleurs dans les pixels
}

voir rawcalibrateDispllay.pde hébergé avec ❤ par GitHub

Étape 11: Remarques de séparation

Pièges à éviter:

* Avec le bois, vous en avez pour votre argent. Alors, procurez-vous du bois de bonne qualité. Le contreplaqué de bouleau est un bon choix; tout bois massif léger fera également l'affaire. J'ai vendu du bois à bas prix et je regrette ma décision.

* Il vaut mieux forer moins que plus. Quelques trous sont devenus trop profonds pour ma pièce. Et l'époxy transparaît sur la face avant. C'est très perceptible une fois que vous le remarquez.

* Utilisez un foret à bout sphérique au lieu d'un bout droit. Je n'ai pas expérimenté le mors à rotule, mais je suis presque sûr que les résultats seront bien meilleurs.

Je flirte avec l'idée de les vendre sur Etsy ou Tindie. J'apprécierais vraiment si vous pouviez commenter ci-dessous si vous pensez que cela a du sens:)

Conseillé: