Arduino Projection Clock

29.09.2020

So I have been a fan of projection clocks at least 20 years (yeah... I'm quite old, no need to remind me ... :) When I took fancy in my later years for Arduino, I stumbled to Arduino project where this (https://hackaday.com/2018/05/05/simple-home-built-projection-clock-projects-time/) Richard guy had built Him self a nice projection clock...and from there the idea just kept on smoldering until...

It was clear from the start that I did not want to follow fully Richards footsteps , but more to build on his project and make it look mine. And base was to get it working with Node-Red. Node-Red is my chosen platform to integrate quite a lot of IoT sensors around my house.

So Idea and base was clear but the first problem was really to get the parts for the build. The Arduino platform uses NodeMCU as brains, The CCTV lens, 1-3W High-power lens were fairly easy to get from China, but the display proved to be a complex one to obtain. The originals project display was nowhere to be bought and I was getting quite desperate at one point and I actually bought quite several different models...(which I could not get to work) until finally I landed with the correct one (details later; what I used and where I bought it). You have to remember that ordering these things from china usually take 4-6 weeks these Covid-19 times so there was quite a lot of waiting on this project.

So about a month ago came the day that all parts were finally here and  the hands on time finally began.


Here's the part list. On top of the NodeMCU everything else is from Aliexpress except the display that I finally got from BuyDisplay.com.

And remember HEATSINK.

So what do you get when you put all these parts together, throw some arduino code and finalize it with parts out of your 3D Printer....

A Projection clock that connects to Wifi / Node Red with MQTT and currently shows; The Time, Out Temperature, IN Temperature and shows the current weather Icon via OpenWeather.com API. Brightness can be adjusted with the MOSFET chip. 

So was everything smooth sailing... NO. And here's what I have learned so take a note.

  • As said earlier the display was the hardest. You need a negative back light LCD display you can shine light through (project) and these were hard to come buy. I was originally looking for COG screen ( i.e. displays  with out boards) but the biggest problem really came that I could not get a proper ZIP/FPC connector (where my not so great soldering skills would not be a problem ) to be used with NodeMCU. My biggest / best idea came when prying one old LCD display from its back light as they are  typically connected only with some sort of tape ( like in the final display I used). So easy to use COB display and easy wire connection to NodeMCU.
  • HEATSINK. The original project by Richard did not mention anything about the HighPower LED getting HOT. Well you learn by doing...  AND EVER NEVER LEAVE YOUR PROTOTYPE WITH OUT SUPERVISON FOR THE FIRST 6HRS...

It did not burn, but my wife really did not like the smell of smoldering PLA.

  • On the Arduino Code the issue was the displays UC1609 controller which was not a "standard" one that a lot of Arduino libraries were built. Luckily after understanding that this display does would not  work with standard libraries I found the GitHub site where user KiLLAAA had created a Adafruit GFX compatible library, So Kudos to KiLLAAA ( https://github.com/KiLLAAA/LCD_UC1609) 
  • Finding also the correct Display PINS to connect to NodeMCU required some digging as original UC1609 library was built for UNO board. Look on the code I wrote them down to help you.
  • Also something curious also happened with the pins connected to MOSFET. The board would not boot correctly. Only pin where it worked correctly was D8... No idea though why.
  • Drawing text / pictures one pixel at the time is nightmare. Use this site to turn your images into byte arrays will simplify your task 1000% faster. https://javl.github.io/image2cpp/

Here's the Arduino Code (I'm not a professional coder...but hey...it works):


#include <Adafruit_GFX.h>

#include <LCD_UC1609.h>

#include <PubSubClient.h>

#include <ESP8266WiFi.h>

float IN;

float OUT;

String TIME;

int PC_PWR = 100;

String PC_WEATHER;

const int ledPin = D8;

const char* ssid = "XXXX";

const char* password = "XXXXX";

const char* mqttServer = "192.168.11.51";

const int mqttPort = 1883;

WiFiClient espClient;

PubSubClient client(espClient);

LCD_UC1609 display(D1, D0, D2); // DC, RST, CS

// NodeMCU SPI Fixed pins ;SDA(MOSI)/pin D7; SCK pin D5

void setup() {

Serial.begin(115200);

display.begin(); // initialize the LCD

display.clearDisplay(0x55); // this version with single param writes directly to the display, 0x55 (B01010101) makes horizontal strips

//delay(1000);

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {

delay(500);

Serial.println("Connecting to WiFi..");

}

Serial.println("Connected to the WiFi network");

client.setServer(mqttServer, mqttPort);

client.setCallback(callback);

while (!client.connected()) {

Serial.println("Connecting to MQTT...");

if (client.connect("ESP8266Client-")) {

Serial.println("connected");

} else {

Serial.print("failed with state ");

Serial.print(client.state());

delay(2000);

}

}

client.subscribe("OUT_TEMP8");

delay (500);

//client.loop();

client.subscribe("TEMP5",1);

delay (500);

//client.loop();

client.subscribe("TIME");

delay (500);

//client.loop();

client.subscribe("PC_PWR");

delay (500);

client.subscribe("PC_WEATHER");

delay (500);

}

void callback(char* topic, byte* payload, unsigned int length) {

if (strcmp(topic,"TIME")==0) {

delay (500);

Serial.print("Message arrived in topic: ");

Serial.println(topic);

Serial.print("Message:");

char buff_a[length];

for (int a = 0; a < length; a++)

{

// Serial.println((char)payload[i]);

buff_a[a] = (char)payload[a];

}

buff_a[length] = '\0';

String msg_a = String(buff_a);

// val = msg_p.toFloat(); //to float

// val = msg_p.toInt(); // to Int

TIME = msg_a;

Serial.println (TIME);

delay (500);

Serial.println("-----------------------");

Serial.println(WiFi.status());

// delay (3000);

}

if (strcmp(topic,"OUT_TEMP8")==0) {

delay (500);

Serial.print("Message arrived in topic: ");

Serial.println(topic);

Serial.print("Message:");

char buff_b[length];

for (int i = 0; i < length; i++)

{

// Serial.println((char)payload[i]);

buff_b[i] = (char)payload[i];

}

buff_b[length] = '\0';

String msg_b = String(buff_b);

// val = msg_p.toFloat(); //to float

// val = msg_p.toInt(); // to Int

OUT = msg_b.toFloat() ;

Serial.println (OUT,1);

delay (500);

Serial.println("-----------------------");

Serial.println(WiFi.status());

// delay (3000);

}

if (strcmp(topic,"TEMP5")==0) {

delay (500);

Serial.print("Message arrived in topic: ");

Serial.println(topic);

Serial.print("Message:");

char buff_c[length];

for (int i = 0; i < length; i++)

{

// Serial.println((char)payload[i]);

buff_c[i] = (char)payload[i];

}

buff_c[length] = '\0';

String msg_c = String(buff_c);

// val = msg_p.toFloat(); //to float

// val = msg_p.toInt(); // to Int

IN = msg_c.toFloat() ;

Serial.println (IN,1);

delay (500);

Serial.println("-----------------------");

Serial.println(WiFi.status());

//delay (3000);

}

if (strcmp(topic,"PC_PWR")==0) {

delay (500);

Serial.print("Message arrived in topic: ");

Serial.println(topic);

Serial.print("Message:");

char buff_c[length];

for (int i = 0; i < length; i++)

{

// Serial.println((char)payload[i]);

buff_c[i] = (char)payload[i];

}

buff_c[length] = '\0';

String msg_c = String(buff_c);

// val = msg_p.toFloat(); //to float

// val = msg_p.toInt(); // to Int

PC_PWR = msg_c.toInt() ;

Serial.println (PC_PWR);

delay (500);

Serial.println("-----------------------");

Serial.println(WiFi.status());

//delay (3000);

}

if (strcmp(topic,"PC_WEATHER")==0) {

delay (500);

Serial.print("Message arrived in topic: ");

Serial.println(topic);

Serial.print("Message:");

char buff_d[length];

for (int i = 0; i < length; i++)

{

// Serial.println((char)payload[i]);

buff_d[i] = (char)payload[i];

}

buff_d[length] = '\0';

String msg_d = String(buff_d);

// val = msg_p.toFloat(); //to float

// val = msg_p.toInt(); // to Int

PC_WEATHER = msg_d ;

Serial.println (PC_WEATHER);

delay (500);

Serial.println("-----------------------");

Serial.println(WiFi.status());

//delay (3000);

}

}

void text1() {

//display.selectedBuffer = &main_window; // set target buffer object

delay (1000);

display.clearDisplay();

display.setTextSize(1); // not required until other func changes it

display.setTextColor(WHITE);

// DRAW OUT TOP

display.drawPixel(94, 22, WHITE);

display.drawPixel(95, 22, WHITE);

display.drawPixel(96, 22, WHITE);

display.drawPixel(98, 22, WHITE);

display.drawPixel(100, 22, WHITE);

display.drawPixel(102, 22, WHITE);

display.drawPixel(103, 22, WHITE);

display.drawPixel(104, 22, WHITE);

display.drawPixel(94, 23, WHITE);

display.drawPixel(96, 23, WHITE);

display.drawPixel(98, 23, WHITE);

display.drawPixel(100, 23, WHITE);

display.drawPixel(103, 23, WHITE);

display.drawPixel(94, 24, WHITE);

display.drawPixel(96, 24, WHITE);

display.drawPixel(98, 24, WHITE);

display.drawPixel(100, 24, WHITE);

display.drawPixel(103, 24, WHITE);

display.drawPixel(94, 25, WHITE);

display.drawPixel(95, 25, WHITE);

display.drawPixel(96, 25, WHITE);

display.drawPixel(98, 25, WHITE);

display.drawPixel(99, 25, WHITE);

display.drawPixel(100, 25, WHITE);

display.drawPixel(103, 25, WHITE);

// END DRAW

display.setCursor(88, 30);

//display.print(":");

display.print(OUT,1);

//display.print(" C");

display.display();

delay (1000);

}

void text2() {

//display.selectedBuffer = &main_window; // set target buffer object

delay (1000);

display.clearDisplay();

display.setTextSize(1); // not required until other func changes it

display.setTextColor(WHITE);

display.setCursor(84, 28);

display.print(TIME);

display.display();

delay (1000);

}

void text3() {

//display.selectedBuffer = &main_window; // set target buffer object

delay (1000);

display.clearDisplay();

display.setTextSize(1); // not required until other func changes it

display.setTextColor(WHITE);

// DRAW IN

display.drawPixel(98, 22, WHITE);

display.drawPixel(98, 23, WHITE);

display.drawPixel(98, 24, WHITE);

display.drawPixel(98, 25, WHITE);

display.drawPixel(100, 22, WHITE);

display.drawPixel(103, 22, WHITE);

display.drawPixel(100, 23, WHITE);

display.drawPixel(101, 23, WHITE);

display.drawPixel(103, 23, WHITE);

display.drawPixel(100, 24, WHITE);

display.drawPixel(102, 24, WHITE);

display.drawPixel(103, 24, WHITE);

display.drawPixel(100, 25, WHITE);

display.drawPixel(103, 25, WHITE);

// END DRAW

display.setCursor(90, 30);

// display.print("I:");

display.print(IN,1);

//display.print(" C");

display.display();

delay (1000);

}

void text4() {

delay(100);

display.clearDisplay(0); // clear display memory

// create buffer with vertically addressed 16x16 bitmap data

uint8_t clouds [] = {

0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf0, 0xf0, 0xf8, 0xe4, 0xc4, 0xc4, 0xc4, 0x88, 0x10,

0x10, 0x60, 0x40, 0x80, 0x00, 0x0e, 0x3f, 0x3f, 0x3f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,

0x7f, 0x7f, 0x3f, 0x3f, 0x1e, 0x04, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

};

uint8_t sun [] = {

0x00, 0x00, 0x00, 0x08, 0xb0, 0xf0, 0x60, 0x30, 0x38, 0x1e, 0x38, 0x30, 0x60, 0xf0, 0xb0, 0x08,

0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x87, 0x6f, 0x7d, 0x30, 0x60, 0xe0, 0xc0, 0xe0, 0x60,

0x30, 0x7d, 0x6f, 0x87, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

};

uint8_t night_1 [] = {

0x00, 0x00, 0x08, 0x00, 0x00, 0xc0, 0x20, 0x20, 0x90, 0x50, 0x30, 0x10, 0x00, 0x00, 0x00, 0x84,

0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x07, 0x18, 0x20, 0x20, 0x47, 0x48, 0x50, 0x50,

0x28, 0x18, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

};

uint8_t night_2 [] = {

0x00, 0x00, 0x20, 0x00, 0x00, 0xc0, 0x20, 0x20, 0x90, 0x50, 0x30, 0x10, 0x00, 0x80, 0x00, 0x00,

0x00, 0x00, 0x02, 0x00, 0x00, 0x40, 0x00, 0x00, 0x07, 0x18, 0x20, 0x20, 0x47, 0x48, 0x50, 0x50,

0x28, 0x18, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

};

uint8_t rain_1 [] = {

0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf0, 0xf0, 0xf8, 0xe4, 0xc4, 0xc4, 0xc4, 0x88, 0x10,

0x10, 0x60, 0x40, 0x80, 0x00, 0x0e, 0x3f, 0x3f, 0x3f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,

0x7f, 0x7f, 0x3f, 0x3f, 0x1e, 0x04, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0x00,

0x08, 0x02, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

};

uint8_t rain_2 [] = {

0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf0, 0xf0, 0xf8, 0xe4, 0xc4, 0xc4, 0xc4, 0x88, 0x10,

0x10, 0x60, 0x40, 0x80, 0x00, 0x0e, 0x3f, 0x3f, 0x3f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,

0x7f, 0x7f, 0x3f, 0x3f, 0x1e, 0x04, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x02, 0x00,

0x04, 0x01, 0x00, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

};

uint8_t atmosphere [] = {

0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0x80, 0x00, 0x00,

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x16, 0x56, 0x56, 0x56, 0x56, 0x56,

0x56, 0x56, 0x12, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

};

uint8_t thunder [] = {

0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf0, 0xf0, 0xf8, 0xe4, 0xc4, 0xc4, 0xc4, 0x88, 0x10,

0x10, 0x60, 0x40, 0x80, 0x00, 0x0e, 0x3f, 0x3f, 0x7f, 0xdf, 0x0f, 0x87, 0x5f, 0x3f, 0x3f, 0x3f,

0x3f, 0x3f, 0x3f, 0x3f, 0x1e, 0x04, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00,

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

};

uint8_t snow [] = {

0x00, 0xa0, 0x40, 0xe8, 0xb0, 0xe8, 0x40, 0xa0, 0x00, 0x00, 0x22, 0x6b, 0x1c, 0x36, 0x1c, 0x6b,

0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x59, 0xe2, 0xb0, 0xe0, 0x58, 0x10, 0x00, 0x00,

0x28, 0x10, 0xba, 0x6c, 0xba, 0x10, 0x28, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x01, 0x00,

0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

};

uint8_t drizzle [] = {

0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xb0, 0xf0, 0xf0, 0xb8, 0xe4, 0xc4, 0xc4, 0xc4, 0x88, 0x10,

0x10, 0x60, 0x40, 0x80, 0x00, 0x0e, 0x3f, 0x3f, 0x3b, 0x6f, 0x7d, 0x7f, 0x7f, 0x77, 0x7f, 0x5d,

0x7f, 0x77, 0x3f, 0x3f, 0x1e, 0x04, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,

0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

};

display.clearDisplay(0); // clear display memory

display.displayBuffer(90, 23, 20, 20, (uint8_t*)atmosphere);

if (PC_WEATHER =="Clouds") {

display.displayBuffer(90, 23, 20, 20, (uint8_t*)clouds);

}

if (PC_WEATHER =="Sun") {

display.clearDisplay(0); // clear display memory

display.displayBuffer(90, 24, 20, 20, (uint8_t*)sun);

}

if (PC_WEATHER =="Night") {

display.clearDisplay(0); // clear display memory

display.displayBuffer(90, 23, 20, 20, (uint8_t*)night_1);

delay(500);

display.clearDisplay(0); // clear display memory

display.displayBuffer(90, 23, 20, 20, (uint8_t*)night_2);

delay(500);

display.clearDisplay(0); // clear display memory

display.displayBuffer(90, 23, 20, 20, (uint8_t*)night_1);

delay(500);

display.clearDisplay(0); // clear display memory

display.displayBuffer(90, 23, 20, 20, (uint8_t*)night_2);

}

if (PC_WEATHER =="Rain") {

display.clearDisplay(0); // clear display memory

display.displayBuffer(90, 23, 20, 20, (uint8_t*)rain_1);

delay(500);

display.clearDisplay(0); // clear display memory

display.displayBuffer(90, 23, 20, 20, (uint8_t*)rain_2);

delay(500);

display.clearDisplay(0); // clear display memory

display.displayBuffer(90, 23, 20, 20, (uint8_t*)rain_1);

delay(500);

display.clearDisplay(0); // clear display memory

display.displayBuffer(90, 23, 20, 20, (uint8_t*)rain_2);

}

if (PC_WEATHER =="Snow") {

display.clearDisplay(0); // clear display memory

display.displayBuffer(90, 23, 20, 20, (uint8_t*)snow);

}

if (PC_WEATHER =="Thunderstorm") {

display.clearDisplay(0); // clear display memory

display.displayBuffer(90, 23, 20, 20, (uint8_t*)thunder);

}

if (PC_WEATHER =="Drizzle") {

display.clearDisplay(0); // clear display memory

display.displayBuffer(90, 23, 20, 20, (uint8_t*)drizzle);

}

delay(500);

}

void loop() {

uint8_t bitmapBuffer[1536]; // 64x64 pixels take 512 bytes

////////////////////////////////////////////////////////////////

AdvancedBuffer main_window;

main_window.bitmap = (uint8_t*) &bitmapBuffer;

main_window.width = 192; // 192

main_window.height = 64; // 64

main_window.x = 0; // offset on display

display.selectedBuffer = &main_window;

//////////////////////////////// yes, it can be that simple!

// client.subscribe("OUT_TEMP8");

// client.subscribe("TEMP5",1);

// client.subscribe("TIME");

text1(); //OUT temp

client.loop();

//delay (1000);

text2(); //TIME

client.loop();

//delay (1000);

text3(); //IN temp

//delay (1000);

client.loop();

text4(); //WEATHER

client.loop();

//delay (1000);

analogWrite(ledPin, PC_PWR);

Serial.println(WiFi.status());

client.loop();

Serial.println ("LOOP");

}

Hope this will help you on your project. Have Fun.  Soiski71@gmail.com


Luo kotisivut ilmaiseksi! Tämä verkkosivu on luotu Webnodella. Luo oma verkkosivusi ilmaiseksi tänään! Aloita