Оглавление:

Деревянные светодиодные часы - аналоговый стиль: 11 шагов (с изображениями)
Деревянные светодиодные часы - аналоговый стиль: 11 шагов (с изображениями)

Видео: Деревянные светодиодные часы - аналоговый стиль: 11 шагов (с изображениями)

Видео: Деревянные светодиодные часы - аналоговый стиль: 11 шагов (с изображениями)
Видео: БОКОВУШЕЧКА У ТУАЛЕТА в плацкарте 2024, Ноябрь
Anonim
Деревянные светодиодные часы - аналоговый стиль
Деревянные светодиодные часы - аналоговый стиль

Это деревянные светодиодные часы в аналоговом стиле. Я не знаю, почему я не видел ни одного из них раньше … хотя цифровые типы очень распространены. Anyhoo, поехали!

Шаг 1:

Изображение
Изображение
Изображение
Изображение

Проект фанерных часов начался как простой стартовый проект для фрезерного станка с ЧПУ. Я просматривал простые проекты в сети и нашел эту лампу (изображение выше). Я также видел цифровые часы, которые просвечивают сквозь деревянную облицовку (изображение выше). Итак, объединение двух проектов было очевидной идеей. Желая испытать себя, я решил использовать для этого проекта не шпон, а только кусок дерева.

Шаг 2: Дизайн

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

Я разработал часы в Inkscape (изображение выше). Дизайн очень простой по выбору. Я решил не прокладывать трассы для проводов, потому что на этом этапе я не был уверен, хочу ли я использовать радиальную разводку или проводку по периметру. (В конце концов я решил использовать проводку по периметру.) Один неопиксель входит в каждое из маленьких круглых отверстий для отображения минутного и часового времени с пятиминутной точностью. Круг в середине будет размечен для размещения электроники.

Шаг 3: ЧПУ

ЧПУ
ЧПУ
ЧПУ
ЧПУ
ЧПУ
ЧПУ
ЧПУ
ЧПУ

Я разработал траектории инструмента на MasterCAM и использовал technoRouter, чтобы вырезать часы из фанеры толщиной 3/4 дюйма. Для этого я использую кусок размером 15 x 15 дюймов с минимальными потерями. Хитрость заключается в том, чтобы вывести как можно больше древесины, не пробивая ее. Оставить 0,05 "-0,1" - хороший выбор для светлого дерева. Если вы не уверены, лучше оставить больше древесины, потому что вы всегда можете отшлифовать другую поверхность. В итоге я удалил слишком много дерева с некоторых деталей, но, к счастью, результаты не сильно пострадали из-за этого.

Примечание для пользователей без доступа к ЧПУ:

Этот проект легко выполнить с помощью сверлильного станка. Вам просто нужно установить упор в точке, где вы оставите около 0,1 дюйма древесины, оставшейся у основания. Вы должны быть точными, но не слишком точными. В конце концов, в идеале никто не увидит, что все светодиоды загорятся на в то же время, так что вы можете уйти немного помоями.

Шаг 4: Электроника

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

Электроника довольно проста. Есть 24 неопикселя, двенадцать для отображения часов и двенадцать для отображения минут с точностью до пяти минут. Arduino pro mini управляет неопикселями и получает точное время через модуль часов реального времени (RTC) DS3231. В модуле RTC в качестве резервного имеется монетная ячейка, поэтому он не теряет время даже при отключенном питании.

Материал:

Arduino pro mini (или любой другой Arduino, если на то пошло)

Коммутационная плата DS3231

Неопиксели в индивидуальных коммутационных платах

Шаг 5: Сборка электроники

Сборка электроники
Сборка электроники
Сборка электроники
Сборка электроники
Сборка электроники
Сборка электроники
Сборка электроники
Сборка электроники

Я соединил неопиксели в цепочку, используя 2,5-дюймовые провода для первых двенадцати светодиодов и четырехдюймовый провод для следующих двенадцати. Я мог бы использовать провода немного меньшей длины. суставы были хороши. Я добавил мгновенный переключатель, чтобы включить все светодиоды, просто чтобы покрасоваться.

Шаг 6: пробный запуск

Прогон, репетиция
Прогон, репетиция
Прогон, репетиция
Прогон, репетиция
Прогон, репетиция
Прогон, репетиция
Прогон, репетиция
Прогон, репетиция

Поэкспериментировав, вставив светодиоды в отверстия и включив их все, я остался доволен результатами. Поэтому я немного отшлифовал переднюю поверхность и нанёс полиуретановое покрытие. Позже мне пришлось отшлифовать пальто, но лучше оставить его, если оно вам не нравится с эстетической точки зрения.

Шаг 7: эпоксидная смола

Эпоксидная смола
Эпоксидная смола
Эпоксидная смола
Эпоксидная смола

После некоторого тестирования положения светодиода внутри отверстий, я решил, что наилучшее обсуждение достигается, когда светодиоды находятся на расстоянии около 0,2 дюйма от конца отверстия. Когда вы попробуете это сами, яркость светодиодов будет сильно отличаться от каждое отверстие. Не беспокойтесь об этом; мы исправим это в коде. Это из-за типа сверла, которое я использовал. Если бы я сделал это снова, я бы использовал сверло со сферической головкой для отверстий Но, в любом случае, чтобы получить расстояние, я смешал немного эпоксидной смолы и добавил немного в каждое отверстие.

Шаг 8: Собираем все вместе

Собираем все вместе
Собираем все вместе
Собираем все вместе
Собираем все вместе
Собираем все вместе
Собираем все вместе
Собираем все вместе
Собираем все вместе

Светодиоды будут размещены, начиная с положения часовой стрелки на 12 часов, двигаясь против часовой стрелки через все положения часовой стрелки, а затем к минутной стрелке, снова перемещаясь от отметки 60 минут, двигаясь против часовой стрелки. Это так, что когда мы смотрим спереди, светодиоды кажутся движущимися по часовой стрелке.

После того, как эпоксидная смола застыла в течение часа, я добавил еще немного эпоксидной смолы. На этот раз я поместил светодиоды в отверстия, убедившись, что провода и паяные соединения покрыты эпоксидной смолой. Это обеспечивает хорошее рассеивание света и закрепляет провода.

Шаг 9: Код

Код
Код

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

фанера

// Фанерные часы
// Автор: 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 level [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
в то время как (! серийный); // для Леонардо / Micro / Zero
#endif
// Это для Trinket 5V 16MHz, вы можете удалить эти три линии, если не используете Trinket
#if defined (_AVR_ATtiny85_)
если (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 now = 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 («Полоса очистки»);
Чисто();
strip.show ();
}
// # 3, 255 установит светодиод номер 3 на уровень 255, 255, 255
if (inputString [0] == '#') {
String temp;
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] == '$') {
String temp;
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 до»);
Serial.print (gIntensity);
Serial.print («от Б до»);
Serial.println (bIntensity);
strip.setPixelColor (pixNum, strip. Color (rIntensity, gIntensity, bIntensity));
strip.show ();
}
if (inputString [0] == 's') {
String temp;
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 now = rtc.now ();
hourPixel = now.hour ()% 12;
minutePixel = (now.minute () / 5)% 12 + 12;
Чисто();
// strip.setPixelColor (hourPixel, strip. Color (40 + 40 * уровень [hourPixel], 30 + 30 * level [hourPixel], 20 + 20 * level [hourPixel]));
// strip.setPixelColor (minutePixel, strip. Color (40 + 40 * уровень [minutePixel], 30 + 30 * level [minutePixel], 20 + 20 * level [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 час, int минута) {
hourPixel = час% 12;
minutePixel = (минута / 5)% 12 + 12;
Чисто();
// strip.setPixelColor (hourPixel, strip. Color (40 + 40 * уровень [hourPixel], 30 + 30 * level [hourPixel], 20 + 20 * level [hourPixel]));
// strip.setPixelColor (minutePixel, strip. Color (40 + 40 * уровень [minutePixel], 30 + 30 * level [minutePixel], 20 + 20 * level [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 (uint32_t color) {
for (uint16_t i = 0; i <strip.numPixels (); i ++) {
strip.setPixelColor (я, цвет);
}
strip.show ();
}
voidlightUpEven () {
for (uint16_t i = 0; i <strip.numPixels (); i ++) {
strip.setPixelColor (i, strip. Color (уровень , уровень , уровень ));
}
strip.show ();
}

просмотреть rawplywoodClock.ino, размещенный на ❤ от GitHub

Шаг 10: Компьютерное зрение - Калибровка

Компьютерное зрение - Калибровка
Компьютерное зрение - Калибровка
Компьютерное зрение - Калибровка
Компьютерное зрение - Калибровка

Я сделал сознательный выбор не использовать шпон в этом проекте. Если бы это было так, толщина дерева перед всеми светодиодами была бы одинаковой. Но поскольку у меня перед каждым светодиодом разная толщина дерева и цвет дерева также сильно различается, яркость каждого светодиода разная. Чтобы все светодиоды казались одинаковой яркости, я придумал хитрый трюк.

Я написал код обработки (на GitHub), который фотографирует часы и по очереди анализирует яркость каждого светодиода. Затем он изменяет мощность каждого светодиода, чтобы попытаться добиться такой же яркости, что и самый тусклый светодиод. Я знаю, что это перебор, но обработка изображений - это очень весело! И я надеюсь разработать код калибровки в виде библиотеки.

Вы можете увидеть яркость светодиода до и после калибровки на фотографиях выше.

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 acceptError = 3;
int сделано;
int numPixelsInLed;
long ledIntensity;
int ledPower;
long targetIntensity = 99999999;
voidsetup () {
сделано = newint [число];
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 (это, имя_порта, 9600);
размер (640, 480);
видео = newCapture (это, ширина, высота);
video.start ();
noStroke ();
гладкий; плавный();
задержка (1000); // Ждем открытия последовательного порта
}
voiddraw () {
if (video.available ()) {
if (done [ledNum] == 0) {
clearDisplay ();
задержка (1000);
video.read ();
изображение (видео, 0, 0, ширина, высота); // Рисуем видео с веб-камеры на экране
saveFrame ("данные / no_leds.jpg");
if (runNumber! = 0) {
if ((ledIntensity [ledNum] - targetIntensity) * 100 / targetIntensity> acceptError) {
ledPower [ledNum] - = pow (0,75, runNumber) * 100 + 1;
}
if ((targetIntensity - ledIntensity [ledNum]) * 100 / targetIntensity> acceptError) {
ledPower [ledNum] + = pow (0,75, runNumber) * 100 + 1;
}
if (abs (targetIntensity - ledIntensity [ledNum]) * 100 / targetIntensity <= acceptError) {
сделано [ledNum] = 1;
print ("Светодиод");
печать (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 (имя изображения);
String originalImageName = "данные / организация";
originalImageName + = str (ledNum);
originalImageName + = ". jpg";
if (runNumber == 0) {
saveFrame (originalImageName);
}
PImage noLedImg = loadImage ("данные / no_leds.jpg");
PImage ledImg = loadImage (имя изображения);
PImage originalImg = loadImage (originalImageName);
noLedImg.loadPixels ();
ledImg.loadPixels ();
originalImg.loadPixels ();
фон (0);
loadPixels ();
ledIntensity [ledNum] = 0;
numPixelsInLed [ledNum] = 0;
for (int x = 0; x <width; x ++) {
for (int y = 0; y <height; y ++) {
PxPGetPixelDark (x, y, noLedImg.pixels, width);
PxPGetPixelLed (x, y, ledImg.pixels, width);
PxPGetPixelOrg (x, y, originalImg.pixels, width);
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] ++;
} еще {
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);
Распечатать(', ');
печать (ledPower [ledNum]);
Распечатать(', ');
println (ledIntensity [ledNum]);
ledNum ++;
if (ledNum == numLed) {
int donezo = 0;
for (int i = 0; i <numLed; i ++) {
donezo + = сделано [я];
}
if (donezo == numLed) {
println ("ВЫПОЛНЕНО");
for (int i = 0; i <numLed; i ++) {
печать (я);
печать ("\ т");
println (ledPower );
}
печать ("уровень int [");
печать (ledNum);
печать ("] = {");
for (int i = 0; i <numLed-1; i ++) {
печать (ledPower );
Распечатать(', ');
}
печать (ledPower [numLed -1]);
println ("};");
lightUpEven ();
в то время как (правда);
}
print ("Целевая интенсивность:");
if (runNumber == 0) {
targetIntensity - = 1;
}
println (targetIntensity);
ledNum = 0;
runNumber ++;
}
}
}
voidPxPGetPixelOrg (intx, inty, int pixelArray, intpixelsWidth) {
int thisPixel = pixelArray [x + y * pixelWidth]; // получение цветов как int из пикселей
aOrg = (thisPixel >> 24) & 0xFF; // нам нужно сдвинуть и замаскировать, чтобы получить каждый компонент отдельно
rOrg = (thisPixel >> 16) & 0xFF; // это быстрее, чем вызывать red (), green (), blue ()
gOrg = (thisPixel >> 8) & 0xFF;
bOrg = thisPixel & 0xFF;
}
voidPxPGetPixelDark (intx, inty, int pixelArray, intpixelsWidth) {
int thisPixel = pixelArray [x + y * pixelWidth]; // получение цветов как 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 * pixelWidth]; // получение цветов как 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) {
а = (а << 24);
г = г << 16; // Мы упаковываем все 4 компонента в один int
г = г << 8; // поэтому нам нужно переставить их на свои места
цвет argb = a | г | г | б; // бинарная операция "или" складывает их все в один int
pixelArray [x + y * pixelWidth] = argb; // наконец, мы устанавливаем int с цветами te в пиксели
}

просмотреть rawcalibrateDispllay.pde, размещенный с ❤ на GitHub

Шаг 11: напутствие

Подводные камни, которых следует избегать:

* С древесиной вы получаете то, за что платите. Итак, получайте качественную древесину. Березовая фанера - хороший выбор; Подойдет и любое легкое массивное дерево. Я удешевил дрова и сожалею о своем решении.

* Лучше сверлить меньше, чем больше. Пара отверстий оказалась слишком глубокой для моего изделия. И эпоксидная смола просвечивает на лицевой стороне. Это очень заметно, как только вы это заметите.

* Используйте сверло со сферической головкой вместо прямого. Я не экспериментировал с долотой со сферической головкой, но уверен, что результаты будут намного лучше.

Я заигрываю с идеей продавать их на Etsy или tindie. Я был бы очень признателен, если бы вы могли прокомментировать ниже, если считаете, что это имеет смысл:)

Рекомендуемые: