Съдържание:

Дървен LED часовник - аналогов стил: 11 стъпки (със снимки)
Дървен LED часовник - аналогов стил: 11 стъпки (със снимки)

Видео: Дървен LED часовник - аналогов стил: 11 стъпки (със снимки)

Видео: Дървен LED часовник - аналогов стил: 11 стъпки (със снимки)
Видео: Стоян колев се кара с яница 2024, Юли
Anonim
Дървен LED часовник - аналогов стил
Дървен LED часовник - аналогов стил

Това е дървен LED часовник в аналогов стил. Не знам защо не съм виждал някое от тези преди … въпреки че цифровите типове са много често срещани. Хайде, ето ни!

Етап 1:

Образ
Образ
Образ
Образ

Проектът за часовник от шперплат започна като прост стартов проект за CNC рутера. Гледах прости проекти онлайн и намерих тази лампа (изображението по -горе). Виждал съм и цифрови часовници, които блестят през дървения фурнир (изображението по -горе). Така че комбинирането на двата проекта беше очевидна идея. Търсейки да предизвикам себе си, реших да не използвам фурнир, а само парче дърво за този проект.

Стъпка 2: Дизайн

Дизайн
Дизайн
Дизайн
Дизайн

Проектирах часовника в Inkscape (изображението по -горе). Дизайнът е много прост по избор. Реших да не прокарвам следи за проводниците, защото в този момент не бях сигурен дали искам да отида с радиално или периметърно окабеляване. (Реших най-накрая да окабеля периметъра.) Всеки неопиксел влиза във всеки един от малките кръгли отвори за показване на минутното и часовото време с петминутна точност. Кръгът в средата ще бъде насочен, за да побере електрониката.

Стъпка 3: CNCing

CNCing
CNCing
CNCing
CNCing
CNCing
CNCing
CNCing
CNCing

Проектирах пътеки с инструменти на MasterCAM и използвах technoRouter за фрезоване на часовника от 3/4 инчов шперплат. Използвам парче 15 "x15" за това, с минимални загуби. Номерът е да изкарате възможно най -много дървесина, без да пробиете дървото. Оставянето на 0,05 "-0,1" е добър избор за светло дърво. Ако не сте сигурни, по -добре е да оставите повече дърва, защото винаги можете да шлайфате другото лице. В крайна сметка премахнах малко прекалено много дърво от някои части, но за щастие резултатите не страдат твърде много поради това.

Забележка за потребители без достъп до CNC:

Този проект може лесно да се осъществи с помощта на сеялка. Просто трябва да настроите спирането в точка, в която оставяте около 0,1 дървесина, останала в основата. Ще трябва да сте точни, но не прекалено точни. В крайна сметка в идеалния случай никой няма да види всички светодиоди да светят при едновременно, за да можете да се измъкнете с малко помия.

Стъпка 4: Електроника

Електроника
Електроника
Електроника
Електроника
Електроника
Електроника

Електрониката е доста проста. Има 24 неопиксела, дванадесет за показване на часовете и дванадесет за показване на минутите с петминутна точност. Arduino pro mini контролира неопикселите и получава точно време чрез модул за часовник в реално време DS3231 (RTC). Модулът RTC има резервна монета, така че не губи време дори когато захранването е изключено.

Материал:

Arduino pro mini (или всеки друг Arduino по този въпрос)

Пробивна платка DS3231

Неопиксели в отделни пробивни дъски

Стъпка 5: Монтаж на електроника

Сглобяване на електроника
Сглобяване на електроника
Сглобяване на електроника
Сглобяване на електроника
Сглобяване на електроника
Сглобяване на електроника
Сглобяване на електроника
Сглобяване на електроника

Свързах неопикселите в низ, като използвах 2.5 проводници за първите дванадесет светодиода и четири-инчов проводник за следващите дванадесет. Можех да използвам малко по-малки дължини на проводника. След като направих низ, го тествах, като се уверих, че спойката ставите бяха добри. Добавих моментен превключвател, за да включа всички светодиоди, само за да се изфука.

Стъпка 6: Бягане на сухо

Суха тренировка
Суха тренировка
Суха тренировка
Суха тренировка
Суха тренировка
Суха тренировка
Суха тренировка
Суха тренировка

След като експериментирах, сложих светодиоди в дупките и ги включих, останах доволен от резултатите. Затова шлайфах малко предната страна и нанесох PU слой. В крайна сметка шлифовах палтото по -късно, но е добра идея да го оставите, ако не ви се струва естетически неудобно.

Стъпка 7: Епоксидна смола

Епоксидна смола
Епоксидна смола
Епоксидна смола
Епоксидна смола

След известно тестване с позицията на светодиода в отворите, реших, че най -доброто обсъждане се постига, когато светодиодите са на около 0,2 инча от края на дупката. Когато опитате сами, яркостта на светодиодите ще бъде много различна в всяка дупка. Не се притеснявайте за това; ще го поправим в код. Това се дължи на типа свредло, което използвах. Ако трябваше да направя това отново, бих използвал свредло със сферичен край за дупките … Но във всеки случай, за да получа разстоянието, смесих малко епоксидна смола и сложих малко във всяка дупка.

Стъпка 8: Съберете всичко заедно

Събирайки всичко заедно
Събирайки всичко заедно
Събирайки всичко заедно
Събирайки всичко заедно
Събирайки всичко заедно
Събирайки всичко заедно
Събирайки всичко заедно
Събирайки всичко заедно

Светодиодите ще бъдат поставени, като се започне от позицията на часовата стрелка от 12 часа, движеща се обратно на часовниковата стрелка през всички позиции на часовата стрелка и след това до минутната стрелка, като отново ще се премести от 60-минутната отметка, движеща се обратно на часовниковата стрелка. Това е така, че когато гледаме отпред, LED моделът се появява по часовниковата стрелка.

След като епоксидът се втвърди в продължение на час, сложих още малко епоксид. Този път поставих светодиодите в отворите, като внимавах да покрия проводниците и спойките с епоксидната смола. Това осигурява добра дифузия на светлината и фиксира проводниците.

Стъпка 9: Код

Код
Код

Кодът е на GitHub, не се колебайте да го промените за ваша употреба. Когато включите всички светодиоди на едно и също ниво, яркостта на светещата светлина ще бъде много различна във всяка дупка. Това се дължи на различната дебелина на дървото в дупките и разликата в сянката на дървото. Както можете да видите, цветът на дървото варира доста в моето парче. За да поправя тази разлика в яркостта, направих матрица от нива на яркост на светодиода. И намали яркостта на по -ярките светодиоди. Това е процес на проба и грешка и може да отнеме няколко минути, но резултатите си заслужават.

plywoodClock.ino

// Часовник от шперплат
// Автор: tinkrmind
// Attribution 4.0 International (CC BY 4.0). Вие сте свободни да:
// Споделяне - копирайте и преразпределяйте материала във всеки носител или формат
// Адаптирайте - ремиксирайте, трансформирайте и надграждайте материала за всякакви цели, дори търговски.
// Ура!
#включва
#include "RTClib.h"
RTC_DS3231 rtc;
#include "Adafruit_NeoPixel.h"
#ifdef _AVR_
#включва
#endif
#definePIN6
Adafruit_NeoPixel strip = Adafruit_NeoPixel (60, PIN, NEO_GRB + NEO_KHZ800);
int hourPixel = 0;
int minutePixel = 0;
unsignedlong lastRtcCheck;
Низ inputString = ""; // низ за съхранение на входящи данни
логически stringComplete = false; // дали низът е пълен
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};
voidsetup () {
#ifndef ESP8266
while (! Сериен); // за Леонардо/Микро/Нула
#endif
// Това е за Trinket 5V 16MHz, можете да премахнете тези три реда, ако не използвате Trinket
#ако е дефиниран (_AVR_ATtiny85_)
if (F_CPU == 16000000) clock_prescale_set (clock_div_1);
#endif
// Край на дрънкулка специален код
Serial.begin (9600);
strip.begin ();
strip.show (); // Инициализиране на всички пиксели на „изключено“
if (! rtc.begin ()) {
Serial.println ("Не можах да намеря RTC");
докато (1);
}
pinMode (2, INPUT_PULLUP);
// rtc.adjust (DateTime (F (_ DATE_), F (_ TIME_)));
if (rtc.lostPower ()) {
Serial.println ("RTC загуби захранване, нека зададем часа!");
// следният ред задава RTC на датата и часа на съставяне на тази скица
rtc.adjust (DateTime (F (_ DATE_), F (_ TIME_)));
// Този ред задава RTC с изрична дата и час, например за задаване
// 21 януари 2014 г. в 3 часа сутринта бихте се обадили:
// rtc.adjust (DateTime (2017, 11, 06, 2, 49, 0));
}
// rtc.adjust (DateTime (2017, 11, 06, 2, 49, 0));
// lightUpEven ();
// while (1);
lastRtcCheck = 0;
}
voidloop () {
if (millis () - lastRtcCheck> 2000) {
DateTime сега = rtc.now ();
Serial.print (now.hour (), DEC);
Serial.print (':');
Serial.print (now.minute (), DEC);
Serial.print (':');
Serial.print (now.second (), DEC);
Serial.println ();
време за шоу();
lastRtcCheck = millis ();
}
if (! digitalRead (2)) {
lightUpEven ();
}
if (stringComplete) {
Serial.println (inputString);
if (inputString [0] == 'l') {
Serial.println ("Ниво");
lightUpEven ();
}
if (inputString [0] == 'c') {
Serial.println ("Показване на време");
време за шоу();
strip.show ();
}
if (inputString [0] == '1') {
Serial.println ("Включване на всички светодиоди");
lightUp (strip. Color (255, 255, 255));
strip.show ();
}
if (inputString [0] == '0') {
Serial.println ("Изчистваща лента");
clear ();
strip.show ();
}
// #3, 255 ще постави светодиод номер 3 на ниво 255, 255, 255
if (inputString [0] == '#') {
Низова температура;
temp = inputString.substring (1);
int pixNum = temp.toInt ();
temp = inputString.substring (inputString.indexOf (',') + 1);
int интензивност = temp.toInt ();
Serial.print ("Настройка");
Serial.print (pixNum);
Serial.print ("до ниво");
Serial.println (интензивност);
strip.setPixelColor (pixNum, strip. Color (интензивност, интензивност, интензивност));
strip.show ();
}
// #3, 255, 0, 125 ще зададе номер 3 на ниво 255, 0, 125
if (inputString [0] == '$') {
Низова температура;
temp = inputString.substring (1);
int pixNum = temp.toInt ();
int rIndex = inputString.indexOf (',') + 1;
temp = inputString.substring (rIndex);
int rIntensity = 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 bIntensity = temp.toInt ();
Serial.print ("Настройка");
Serial.print (pixNum);
Serial.print ("R до");
Serial.print (rIntensity);
Serial.print ("G to");
Serial.print (gIntensity);
Serial.print ("B to");
Serial.println (bIntensity);
strip.setPixelColor (pixNum, strip. Color (rIntensity, gIntensity, bIntensity));
strip.show ();
}
if (inputString [0] == 's') {
Низова температура;
int час, минута;
temp = inputString.substring (1);
час = temp.toInt ();
int rIndex = inputString.indexOf (',') + 1;
temp = inputString.substring (rIndex);
минута = temp.toInt ();
Serial.print ("Показване на време:");
Serial.print (час);
Serial.print (":");
Serial.print (минута);
showTime (час, минута);
забавяне (1000);
}
inputString = "";
stringComplete = false;
}
// забавяне (1000);
}
voidserialEvent () {
while (Serial.available ()) {
char inChar = (char) Serial.read ();
inputString += inChar;
if (inChar == '\ n') {
stringComplete = true;
}
забавяне (1);
}
}
voidclear () {
for (uint16_t i = 0; i <strip.numPixels (); i ++) {
strip.setPixelColor (i, strip. Color (0, 0, 0));
}
}
voidshowTime () {
DateTime сега = rtc.now ();
hourPixel = now.hour () % 12;
minutePixel = (now.minute () / 5) % 12 + 12;
clear ();
// strip.setPixelColor (hourPixel, strip. Color (40 + 40 * ниво [hourPixel], 30 + 30 * ниво [hourPixel], 20 + 20 * ниво [hourPixel]));
// strip.setPixelColor (minutePixel, strip. Color (40 + 40 * ниво [minutePixel], 30 + 30 * ниво [minutePixel], 20 + 20 * ниво [minutePixel]));
strip.setPixelColor (hourPixel, strip. Color (ниво [hourPixel], ниво [hourPixel], ниво [hourPixel]));
strip.setPixelColor (minutePixel, strip. Color (ниво [minutePixel], ниво [minutePixel], ниво [minutePixel]));
// lightUp (strip. Color (255, 255, 255));
strip.show ();
}
voidshowTime (int час, int минута) {
hourPixel = час % 12;
minutePixel = (минута / 5) % 12 + 12;
clear ();
// strip.setPixelColor (hourPixel, strip. Color (40 + 40 * ниво [hourPixel], 30 + 30 * ниво [hourPixel], 20 + 20 * ниво [hourPixel]));
// strip.setPixelColor (minutePixel, strip. Color (40 + 40 * ниво [minutePixel], 30 + 30 * ниво [minutePixel], 20 + 20 * ниво [minutePixel]));
strip.setPixelColor (hourPixel, strip. Color (ниво [hourPixel], ниво [hourPixel], ниво [hourPixel]));
strip.setPixelColor (minutePixel, strip. Color (ниво [minutePixel], ниво [minutePixel], ниво [minutePixel]));
// lightUp (strip. Color (255, 255, 255));
strip.show ();
}
voidlightUp (uint32_t цвят) {
for (uint16_t i = 0; i <strip.numPixels (); i ++) {
strip.setPixelColor (i, цвят);
}
strip.show ();
}
voidlightUpEven () {
for (uint16_t i = 0; i <strip.numPixels (); i ++) {
strip.setPixelColor (i, strip. Color (ниво , ниво , ниво ));
}
strip.show ();
}

вижте rawplywoodClock.ino, хоствано с ❤ от GitHub

Стъпка 10: Компютърно виждане - калибриране

Компютърно виждане - калибриране
Компютърно виждане - калибриране
Компютърно виждане - калибриране
Компютърно виждане - калибриране

Направих съзнателен избор да не използвам фурнир в този проект. Ако имах, дебелината на дървото щеше да е еднаква пред всички светодиоди. Но тъй като имам различна дебелина на дървото пред всеки светодиод и тъй като цветът на дървото също варира много, яркостта на светодиода е различна за всеки светодиод. За да изглежда, че всички светодиоди са с еднаква яркост, аз измислих чудесен трик.

Написах някакъв код за обработка (на GitHub), който прави снимка на часовника и анализира яркостта на всеки светодиод на свой ред. След това той променя мощността на всеки светодиод, за да се опита да накара всички да имат същата яркост като най -слабия светодиод. Знам, че това е прекалено много, но обработката на изображения е много забавно! Надявам се да разработя кода за калибриране като библиотека.

Можете да видите яркостта на LED преди и след калибриране на снимките по -горе.

calibrateDispllay.pde

importprocessing.video.*;
importprocessing.serial.*;
Сериен myPort;
Заснемане на видео;
finalint numLed = 24;
int ledNum = 0;
// трябва да имате тези глобални променливи, за да използвате PxPGetPixelDark ()
int rDark, gDark, bDark, aDark;
int rLed, gLed, bLed, aLed;
int rOrg, gOrg, bOrg, aOrg;
int rTemp, gTemp, bTemp, aTemp;
PImage ourImage;
int runNumber = 0;
int приемливаError = 3;
int направено;
int numPixelsInLed;
long ledIntensity;
int ledPower;
long targetIntensity = 99999999;
voidsetup () {
направено = newint [numLed];
numPixelsInLed = newint [numLed];
ledIntensity = newlong [numLed];
ledPower = newint [numLed];
for (int i = 0; i <numLed; i ++) {
ledPower = 255;
}
printArray (Serial.list ());
Низ portName = Serial.list () [31];
myPort = newSerial (this, portName, 9600);
размер (640, 480);
видео = newCapture (this, width, height);
video.start ();
noStroke ();
гладка ();
забавяне (1000); // Изчакайте отварянето на серийния порт
}
voiddraw () {
if (video.available ()) {
if (направено [ledNum] == 0) {
clearDisplay ();
забавяне (1000);
video.read ();
изображение (видео, 0, 0, ширина, височина); // Начертайте видеоклипа от уеб камерата на екрана
saveFrame ("data/no_leds.jpg");
if (runNumber! = 0) {
if ((ledIntensity [ledNum] - targetIntensity)*100/targetIntensity> приемлива грешка) {
ledPower [ledNum] -= pow (0.75, runNumber)*100+1;
}
if ((targetIntensity - ledIntensity [ledNum])*100/targetIntensity> приемлива грешка) {
ledPower [ledNum] += pow (0.75, runNumber)*100 +1;
}
if (abs (targetIntensity - ledIntensity [ledNum])*100/targetIntensity <= приемлива грешка) {
направено [ledNum] = 1;
печат ("Led");
печат (ledNum);
печат ("готово");
}
if (ledPower [ledNum]> 255) {
ledPower [ledNum] = 255;
}
if (ledPower [ledNum] <0) {
ledPower [ledNum] = 0;
}
}
setLedPower (ledNum, ledPower [ledNum]);
забавяне (1000);
video.read ();
изображение (видео, 0, 0, ширина, височина); // Начертайте видеоклипа от уеб камерата на екрана
забавяне (10);
while (myPort.available ()> 0) {
int inByte = myPort.read ();
// печат (char (inByte));
}
Низ imageName = "данни/";
imageName+= str (ledNum);
imageName += "_ led.jpg";
saveFrame (imageName);
Низ originalImageName = "данни/организация";
originalImageName+= str (ledNum);
originalImageName += ". jpg";
if (runNumber == 0) {
saveFrame (originalImageName);
}
PImage noLedImg = loadImage ("данни/no_leds.jpg");
PImage ledImg = loadImage (imageName);
PImage originalImg = loadImage (originalImageName);
noLedImg.loadPixels ();
ledImg.loadPixels ();
originalImg.loadPixels ();
фон (0);
loadPixels ();
ledIntensity [ledNum] = 0;
numPixelsInLed [ledNum] = 0;
for (int x = 0; x <ширина; x ++) {
for (int y = 0; y <височина; y ++) {
PxPGetPixelDark (x, y, noLedImg.пиксели, ширина);
PxPGetPixelLed (x, y, ledImg.пиксели, ширина);
PxPGetPixelOrg (x, y, originalImg.пиксели, ширина);
ако ((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] ++;
} else {
rTemp = 0;
gTemp = 0;
bTemp = 0;
}
PxPSetPixel (x, y, rTemp, gTemp, bTemp, 255, пиксели, ширина);
}
}
ledIntensity [ledNum] /= numPixelsInLed [ledNum];
if (targetIntensity> ledIntensity [ledNum] && runNumber == 0) {
targetIntensity = ledIntensity [ledNum];
}
updatePixels ();
}
печат (ledNum);
print (',');
печат (ledPower [ledNum]);
print (',');
println (ledIntensity [ledNum]);
ledNum ++;
if (ledNum == numLed) {
int donezo = 0;
for (int i = 0; i <numLed; i ++) {
donezo += направено ;
}
ако (donezo == numLed) {
println ("ГОТОВО");
for (int i = 0; i <numLed; i ++) {
печат (i);
print ("\ t");
println (ledPower );
}
print ("int ниво [");
печат (ledNum);
print ("] = {");
for (int i = 0; i <numLed-1; i ++) {
печат (ledPower );
print (',');
}
печат (ledPower [numLed -1]);
println ("};");
lightUpEven ();
while (вярно);
}
print ("Целева интензивност:");
if (runNumber == 0) {
targetIntensity -= 1;
}
println (targetIntensity);
ledNum = 0;
runNumber ++;
}
}
}
voidPxPGetPixelOrg (intx, inty, int pixelArray, intpixelsWidth) {
int thisPixel = pixelArray [x+y*pixelsWidth]; // получаване на цветовете като int от пикселите
aOrg = (thisPixel >> 24) & 0xFF; // трябва да сменим и маскираме, за да получим всеки компонент отделно
rOrg = (този пиксел >> 16) & 0xFF; // това е по -бързо от извикването на red (), green (), blue ()
gOrg = (този пиксел >> 8) & 0xFF;
bOrg = thisPixel & 0xFF;
}
voidPxPGetPixelDark (intx, inty, int pixelArray, intpixelsWidth) {
int thisPixel = pixelArray [x+y*pixelsWidth]; // получаване на цветовете като int от пикселите
aDark = (thisPixel >> 24) & 0xFF; // трябва да сменим и маскираме, за да получим всеки компонент отделно
rDark = (thisPixel >> 16) & 0xFF; // това е по -бързо от извикването на red (), green (), blue ()
gDark = (thisPixel >> 8) & 0xFF;
bDark = thisPixel & 0xFF;
}
voidPxPGetPixelLed (intx, inty, int pixelArray, intpixelsWidth) {
int thisPixel = pixelArray [x+y*pixelsWidth]; // получаване на цветовете като int от пикселите
aLed = (thisPixel >> 24) & 0xFF; // трябва да сменим и маскираме, за да получим всеки компонент отделно
rLed = (thisPixel >> 16) & 0xFF; // това е по -бързо от извикването на red (), green (), blue ()
gLed = (thisPixel >> 8) & 0xFF;
bLed = thisPixel & 0xFF;
}
voidPxPSetPixel (intx, inty, intr, intg, intb, inta, int pixelArray, intpixelsWidth) {
a = (a << 24);
r = r << 16; // Опаковаме всички 4 компонента в един int
g = g << 8; // така че трябва да ги преместим на местата им
цвят argb = a | r | g | b; // двоична операция "или" ги добавя всички в един int
pixelArray [x+y*pixelsWidth] = argb; // накрая задаваме int с te цветове в пикселите
}

вижте rawcalibrateDispllay.pde, хоствано с ❤ от GitHub

Стъпка 11: Забележки за раздяла

Подводни камъни, които трябва да се избягват:

* С дървото получавате това, за което плащате. Така че, вземете качествено дърво. Брезовият шперплат е добър избор; всяка лека масивна дървесина също ще се справи добре. Изкупих дървата и съжалявам за решението си.

* По -добре е да пробивате по -малко, отколкото повече. Няколко дупки станаха твърде дълбоки за моето парче. И епоксидната смола се вижда от предната страна. Това е много забележимо, след като го забележите.

* Използвайте свредло със сферичен край вместо прав край. Не съм експериментирал с края на топката, но съм сигурен, че резултатите ще бъдат много по -добри.

Флиртувам с идеята да ги продавам на Etsy или tindie. Наистина ще съм благодарен, ако можете да коментирате по -долу, ако смятате, че има смисъл:)

Препоръчано: