Upcycle the IKEA Obergränsad LED Lamp to a connected WiFi Clock!

In this article i describe how to convert a IKEA LED Lamp to a digital WIFI Wall Clock.
All you need is the IKEA Obergränsad and a Wemos D1 Mini Pro.
What is OBEGRÄNSAD ?
This is a LAMP, you can buy at your local IKEA shop in the lights corner. It is capable of displaying animated patterns.
In my opinion it is very annoying or boring after a few minutes. So i thought it would be a good idea to upcycle to something more useful..
The modification is based on a hack for the frequenz multimedia elements. These IKEA Elements have a display of 16×16 white LEDs. Basically the 16×16 pixels can be used for any kind of display. But the limitations of 16 by 16 LED and the non symetrical arrangement make it more difficult to find a useful application.
The X-Clock is 52×32 cm in size and has a good readability if used vertically.
As you can see a 16 Pixel wide Display will not be sufficient for a normal Clock.

So i arranged the 4 digits in two rows.

This is sufficient for a Clock Display.
To modify the OBEGRÄNSAD you have to open it, which is only possible by drilling the rivets heads off – this sounds complicated , but since these rivets are made of aluminium it is very easy.

Then you have to remove the built in microcontroller, which is located at the bottom board near a position marked J3. I marked it with a red Arrow. The simplest way is to use a hot air pistol. Alternatively you solder it off by placing a knife between board and chip applying preassure, and solder all pins one after another ..

Once it is gone, you attach the wires as displayed. Attach the Microcontroller as displayed with dual adhesive tape: You have to use long cables for the Pushbutton! The pushbutton wires are marked black/red. Power lines white(5V-VCC) and grey(GND 0).

Close up with chip U1 removed and new connections made to the Wemos D1 mini. We recommend a 1000uF Capaciator at the power line.

The connections are:
- D8 CLA
- D7 CLK
- D6 DIN
- D5 EN
- D4 Button IN
- VCC to 5V ! (not 3V3)
- 0 to GND
A SIMPLE ARDUINO SKETCH
A Basic Version of the Program, as a start for our own developement is printed here: Just copy the file into the Arduino IDE, change your SSID and Password and compile as Wemos Lolin D1 Mini Pro.
// THIS FILE IS A DEMO FOR IKEA OBERGRANSÄD/Frequenz KIT
// ANALOG NO WEB
// AS INTERNET CLOCK
// (C)2023 DR. ARMIN ZINK
// parts borrowed from
// By /u/frumperino
// goodwires.org
// FOR FREQUENZ H_FREKVENS
// FOR OBERGRÄNSAD H_OBEGRANSAD
#define H_OBEGRANSAD
#include <Arduino.h>
#include <pgmspace.h>
#include <ESP8266WiFi.h>
#include <WiFiManager.h> // https://github.com/tzapu/WiFiManager
// COLORS VALID FOR OBEGRÄNSAD ARICLE
#define P_EN D5 // ORANGE
#define P_DI D6 // YELLO
#define P_CLK D7 // BLUE
#define P_CLA D8 // LILA
#define P_KEY D4 // ROT
#define P_KEY_YELLOW D2 // YELLOW BUTTON
// SET YOUR TIMEZONE HERE
#define MY_NTP_SERVER "pool.ntp.org" // set the best fitting NTP server (pool) for your location
#define MY_TZ "CET-1CEST,M3.5.0/02,M10.5.0/03" // https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
long mil;
int brightness=220;
uint8_t sec;
uint8_t minute;
uint8_t hour;
time_t now; // this is the epoch
tm tm;
// Waiting Time for Shift Cycke, can go downto 1
#define TT 5
static const uint8_t System6x7[] PROGMEM = {
// Fixed width; char width table not used !!!!
// FIRST 32 Characters omitted
// font data
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // (space)
0x00, 0x00, 0x4f, 0x4f, 0x00, 0x00,
0x00, 0x00, 0x03, 0x00, 0x03, 0x00,
0x00, 0x0a, 0x3f, 0x0a, 0x3f, 0x0a,
0x24, 0x2a, 0x7f, 0x2a, 0x7f, 0x12,
0x23, 0x33, 0x18, 0x0c, 0x66, 0x62,
0x3e, 0x3f, 0x6d, 0x6a, 0x30, 0x48,
0x00, 0x00, 0x00, 0x00, 0x03, 0x00,
0x00, 0x1c, 0x3e, 0x63, 0x63, 0x00,
0x00, 0x00, 0x63, 0x63, 0x3e, 0x1c,
0x2a, 0x1c, 0x7f, 0x7f, 0x1c, 0x2a,
0x08, 0x08, 0x3e, 0x3e, 0x08, 0x08,
0x00, 0x00, 0xc0, 0xe0, 0x60, 0x00,
0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
0x00, 0x00, 0x60, 0x60, 0x00, 0x00,
0x20, 0x30, 0x18, 0x0c, 0x06, 0x02,
0x3e, 0x7f, 0x63, 0x63, 0x7f, 0x3e, // 0
0x00, 0x02, 0x7f, 0x7f, 0x00, 0x00, // 1
0x62, 0x73, 0x7b, 0x6b, 0x6f, 0x66, // 2
0x22, 0x63, 0x6b, 0x6b, 0x7f, 0x36, // 3
0x0f, 0x0f, 0x08, 0x08, 0x7f, 0x7f, // 4
0x2f, 0x6f, 0x6b, 0x6b, 0x7b, 0x3b, // 5
0x3e, 0x7f, 0x6b, 0x6b, 0x7b, 0x3a, // 6
0x03, 0x03, 0x7b, 0x7b, 0x0f, 0x07, // 7
0x36, 0x7f, 0x6b, 0x6b, 0x7f, 0x36, // 8
0x26, 0x6f, 0x6b, 0x6b, 0x7f, 0x3e, // 9
0x00, 0x00, 0x36, 0x36, 0x00, 0x00,
0x00, 0x00, 0x76, 0x36, 0x00, 0x00,
0x00, 0x18, 0x3c, 0x66, 0x42, 0x00,
0x00, 0x00, 0x36, 0x36, 0x36, 0x36,
0x00, 0x00, 0x42, 0x66, 0x3c, 0x18,
0x00, 0x02, 0x5b, 0x5b, 0x0f, 0x06,
0x3c, 0x42, 0x4a, 0x56, 0x5c, 0x00,
0x7e, 0x7f, 0x0b, 0x0b, 0x7f, 0x7e,
0x7f, 0x7f, 0x6b, 0x6b, 0x7f, 0x36,
0x3e, 0x7f, 0x77, 0x63, 0x63, 0x22,
0x7f, 0x7f, 0x63, 0x63, 0x7f, 0x3e,
0x7f, 0x7f, 0x6b, 0x6b, 0x63, 0x63,
0x7f, 0x7f, 0x0b, 0x0b, 0x03, 0x03,
0x3e, 0x7f, 0x63, 0x6b, 0x7b, 0x32,
0x7f, 0x7f, 0x08, 0x08, 0x7f, 0x7f,
0x00, 0x00, 0x7f, 0x7f, 0x00, 0x00,
0x00, 0x23, 0x63, 0x63, 0x7f, 0x3f,
0x7f, 0x7f, 0x1c, 0x36, 0x63, 0x41,
0x7f, 0x7f, 0x60, 0x60, 0x60, 0x60,
0x7f, 0x7f, 0x03, 0x06, 0x03, 0x7f,
0x7f, 0x7f, 0x0e, 0x18, 0x7f, 0x7f,
0x3e, 0x7f, 0x63, 0x63, 0x7f, 0x3e,
0x7f, 0x7f, 0x0b, 0x0b, 0x0f, 0x06,
0x3e, 0x7f, 0x63, 0x73, 0x3f, 0x5e,
0x7f, 0x7f, 0x1b, 0x3b, 0x7f, 0x6e,
0x26, 0x6f, 0x6b, 0x6b, 0x7b, 0x32,
0x03, 0x03, 0x7f, 0x7f, 0x03, 0x03,
0x3f, 0x7f, 0x60, 0x60, 0x7f, 0x3f,
0x0f, 0x7f, 0x78, 0x40, 0x7f, 0x0f,
0x3f, 0x7f, 0x60, 0x78, 0x60, 0x3f,
0x63, 0x77, 0x1c, 0x1c, 0x77, 0x63,
0x07, 0x0f, 0x78, 0x78, 0x0f, 0x07,
0x63, 0x73, 0x7b, 0x6f, 0x67, 0x63,
0x7d, 0x7e, 0x0b, 0x0b, 0x7e, 0x7d,
0x3d, 0x7e, 0x66, 0x66, 0x7e, 0x3d,
0x3d, 0x7d, 0x60, 0x60, 0x7d, 0x3d,
0x00, 0x04, 0x02, 0x01, 0x02, 0x04,
0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
0x7e, 0x7f, 0x0b, 0x0b, 0x7f, 0x7e,
0x7f, 0x7f, 0x6b, 0x6b, 0x7f, 0x36,
0x3e, 0x7f, 0x77, 0x63, 0x63, 0x22,
0x7f, 0x7f, 0x63, 0x63, 0x7f, 0x3e,
0x7f, 0x7f, 0x6b, 0x6b, 0x63, 0x63,
0x7f, 0x7f, 0x0b, 0x0b, 0x03, 0x03,
0x3e, 0x7f, 0x63, 0x6b, 0x7b, 0x32,
0x7f, 0x7f, 0x08, 0x08, 0x7f, 0x7f,
0x00, 0x00, 0x7f, 0x7f, 0x00, 0x00,
0x00, 0x23, 0x63, 0x63, 0x7f, 0x3f,
0x7f, 0x7f, 0x1c, 0x36, 0x63, 0x41,
0x7f, 0x7f, 0x60, 0x60, 0x60, 0x60,
0x7f, 0x7f, 0x03, 0x06, 0x03, 0x7f,
0x7f, 0x7f, 0x0e, 0x18, 0x7f, 0x7f,
0x3e, 0x7f, 0x63, 0x63, 0x7f, 0x3e,
0x7f, 0x7f, 0x0b, 0x0b, 0x0f, 0x06,
0x3e, 0x7f, 0x63, 0x73, 0x3f, 0x5e,
0x7f, 0x7f, 0x1b, 0x3b, 0x7f, 0x6e,
0x66, 0x6f, 0x6b, 0x6b, 0x7b, 0x32,
0x03, 0x03, 0x7f, 0x7f, 0x03, 0x03,
0x3f, 0x7f, 0x60, 0x60, 0x7f, 0x3f,
0x0f, 0x7f, 0x78, 0x40, 0x7f, 0x0f,
0x3f, 0x7f, 0x60, 0x78, 0x60, 0x3f,
0x63, 0x77, 0x1c, 0x1c, 0x77, 0x63,
0x07, 0x0f, 0x78, 0x78, 0x0f, 0x07,
0x63, 0x73, 0x7b, 0x6f, 0x67, 0x63,
0x7d, 0x7e, 0x0b, 0x0b, 0x7e, 0x7d,
0x3d, 0x7e, 0x66, 0x66, 0x7e, 0x3d,
0x3d, 0x7d, 0x60, 0x60, 0x7d, 0x3d,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// LUT For OBEGRÄNSAD
int lut[16][16] = {
{23, 22, 21, 20, 19, 18, 17, 16, 7, 6, 5, 4, 3, 2, 1, 0},
{24, 25, 26, 27, 28, 29, 30, 31, 8, 9, 10, 11, 12, 13, 14, 15},
{39, 38, 37, 36, 35, 34, 33, 32, 55, 54, 53, 52, 51, 50, 49, 48},
{40, 41, 42, 43, 44, 45, 46, 47, 56, 57, 58, 59, 60, 61, 62, 63},
{87, 86, 85, 84, 83, 82, 81, 80, 71, 70, 69, 68, 67, 66, 65, 64},
{88, 89, 90, 91, 92, 93, 94, 95, 72, 73, 74, 75, 76, 77, 78, 79},
{103, 102, 101, 100, 99, 98, 97, 96, 119, 118, 117, 116, 115, 114, 113, 112},
{104, 105, 106, 107, 108, 109, 110, 111, 120, 121, 122, 123, 124, 125, 126, 127},
{151, 150, 149, 148, 147, 146, 145, 144, 135, 134, 133, 132, 131, 130, 129, 128},
{152, 153, 154, 155, 156, 157, 158, 159, 136, 137, 138, 139, 140, 141, 142, 143},
{167, 166, 165, 164, 163, 162, 161, 160, 183, 182, 181, 180, 179, 178, 177, 176},
{168, 169, 170, 171, 172, 173, 174, 175, 184, 185, 186, 187, 188, 189, 190, 191},
{215, 214, 213, 212, 211, 210, 209, 208, 199, 198, 197, 196, 195, 194, 193, 192},
{216, 217, 218, 219, 220, 221, 222, 223, 200, 201, 202, 203, 204, 205, 206, 207},
{231, 230, 229, 228, 227, 226, 225, 224, 247, 246, 245, 244, 243, 242, 241, 240},
{232, 233, 234, 235, 236, 237, 238, 239, 248, 249, 250, 251, 252, 253, 254, 255}};
unsigned short _pLatch;
unsigned short _pClock;
unsigned short _pData;
uint8_t p_buf[16*16];
// -----------------------------------------------------------
void p_init(int p_latch, int p_clock, int p_data)
{
_pLatch = p_latch;
_pClock = p_clock;
_pData = p_data;
pinMode(_pLatch, OUTPUT);
pinMode(_pClock, OUTPUT);
pinMode(_pData, OUTPUT);
}
// -----------------------------------------------------------
// Clear the Panel Buffer
void p_clear(){
for (int i = 0; i < 256; i++) {
p_buf[i] = 0;
}
}
// -----------------------------------------------------------
// SCAN DISPLAY, output Bytes to Serial
void p_scan(uint8_t cmask){
analogWrite(P_EN,255);
delayMicroseconds(TT);
uint8_t w=0;
uint8_t w2=0;
for (int i = 256; i>0 ; i--) {
digitalWrite(_pData, cmask & p_buf[w++]);
digitalWrite(_pClock, HIGH);
digitalWrite(_pClock, LOW);
w2++;
if(w2 >15) {
w2=0;
digitalWrite(_pLatch, HIGH);
digitalWrite(_pLatch, LOW);
}
}
analogWrite(P_EN,brightness); // re enable brightmess
}
// -----------------------------------------------------------
void p_drawPixel(int8_t x, int8_t y, uint8_t color)
{
if((x<16) && (y<16)) {
#ifdef H_OBEGRANSAD
p_buf[lut[y][x]] = color;
#endif
#ifdef H_FREKVENS
if (x > 7) { y += 0x10; x &= 0x07; }
p_buf[(y*8+x)] = color ;
#endif
}
}
// -----------------------------------------------------------
void p_fillScreen(uint8_t col){
for (uint8_t x=0;x < 16; x++)
for (uint8_t y=0;y < 16; y++)
p_drawPixel(x, y, col);
}
// -----------------------------------------------------------
void test_display() {
for (int i = 0; i<2; i++) {
p_fillScreen(0xff); p_scan(1); analogWrite(P_EN,250); delay(300);
p_fillScreen(0x00); p_scan(1); analogWrite(P_EN,250); delay(300);
}
}
// -----------------------------------------------------------
void p_printChar(uint8_t xs, uint8_t ys, char ch) {
uint8_t d;
for (uint8_t x=0;x<6;x++) {
d = pgm_read_byte_near((ch-32)*6+ // Buchstabennummer (ASCII ) minus 32 da die ersten 32 Zeichen nicht im Font sind
x + // jede Spalte
System6x7); // Adrress of Font
if ((d&1) == 1) p_drawPixel(x+xs, 0+ys, 0xFF);else p_drawPixel(x+xs, 0+ys, 0);
if ((d&2) == 2) p_drawPixel(x+xs, 1+ys, 0xFF);else p_drawPixel(x+xs, 1+ys, 0);
if ((d&4) == 4) p_drawPixel(x+xs, 2+ys, 0xFF);else p_drawPixel(x+xs, 2+ys, 0);
if ((d&8) == 8) p_drawPixel(x+xs, 3+ys, 0xFF);else p_drawPixel(x+xs, 3+ys, 0);
if ((d&16 ) == 16) p_drawPixel(x+xs, 4+ys, 0xFF);else p_drawPixel(x+xs, 4+ys, 0);
if ((d&32 ) == 32) p_drawPixel(x+xs, 5+ys, 0xFF);else p_drawPixel(x+xs, 5+ys, 0);
if ((d&64 ) == 64) p_drawPixel(x+xs, 6+ys, 0xFF);else p_drawPixel(x+xs, 6+ys, 0);
}
}
// -----------------------------------------------------------
const char* getTimeString(void) {
static char acTimeString[32];
time_t now = time(nullptr);
ctime_r(&now, acTimeString);
size_t stLength;
while (((stLength = strlen(acTimeString))) &&
('\n' == acTimeString[stLength - 1])) {
acTimeString[stLength - 1] = 0; // Remove trailing line break...
}
return acTimeString;
}
// -----------------------------------------------------------
void set_clock(void) {
configTime(MY_TZ, MY_NTP_SERVER); // --> Here is the IMPORTANT ONE LINER needed in your sketch!
Serial.print("Waiting for NTP time sync: ");
time_t now = time(nullptr); // Secs since 01.01.1970 (when uninitialized starts with (8 * 3600 = 28800)
while (now < 8 * 3600 * 2) { // Wait for realistic value
Serial.print(".");
now = time(nullptr);
}
Serial.println("");
Serial.printf("Current time: %s\n", getTimeString());
}
// -----------------------------------------------------------
void set_clock_from_tm() {
time(&now); // read the current time
localtime_r(&now, &tm); // update the structure tm with the current time
// update time from struct
minute = tm.tm_min;
hour = tm.tm_hour;
}
// -----------------------------------------------------------
void setup() {
pinMode(P_KEY, INPUT_PULLUP); // RED KEY
pinMode(P_EN, OUTPUT); // Pseudo Analog out for FM Brightess
analogWrite(P_EN, brightness); // adjust brightness
p_init(P_CLA, P_CLK, P_DI); // init Display
test_display();
Serial.begin(74880); // Native Baud Rate of ESP
p_clear();
p_printChar(2,0,'A'); // Print "AP" while waiting for WiFi Manager
p_printChar(9,0,'P');
p_scan(1);
delay(1000);
WiFiManager wm;
wm.setMinimumSignalQuality(50);
bool res;
#ifdef H_FREKVENS
res = wm.autoConnect("Y-CLOCK");
#endif
#ifdef H_OBEGRANSAD
res = wm.autoConnect("X-CLOCK");
#endif
/*
// enable fo use without wifi manager
// start network
WiFi.mode(WIFI_STA);
WiFi.begin(STASSID, STAPSK);
*/
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
p_printChar(2,9,'O');
p_printChar(9,9,'K');
p_scan(1);
delay(500);
//if you get here you have connected to the WiFi
Serial.println("Wifi connected!");
// Sync clock
set_clock();
set_clock_from_tm() ;
print_time();
}
// -----------------------------------------------------------
void print_time() {
p_clear();
p_printChar(2,0,(hour / 10) +48);
p_printChar(9,0,(hour % 10) +48);
p_printChar(2,9,(minute / 10) +48);
p_printChar(9,9,(minute % 10) +48);
p_scan(1); // refreshes display
}
// -----------------------------------------------------------
/// MAIN LOOP
void loop() {
// JEDE SEKUNDE
if (millis()>mil+1000)
{
mil = millis();
sec ++;
// Jede Minute
if (sec>60) {
sec = 0;
print_time();
set_clock_from_tm() ;
set_clock();
}
//Serial.printf("Current time: %s\n", getTimeString());
}
// TASTE gedrückt ? Helligkeit einstellen. Max = 0, Min = 254
if (digitalRead(P_KEY) == 0) {
if(brightness>205) brightness =104; // 105,155,205,255
brightness+=25;
analogWrite(P_EN, brightness);
print_time();
delay(500);
Serial.print(brightness);
}
}
PRE BUILT FIRMWARE
To flash the controller you can use our pre – built firmware. It is available at o-clock.eu. It is most easy to flash it with a Chrome Browser, before soldering the ESP8266. It comes with some specials als built in Wifi Manager and is currently under development.
Expansion:
An expanded Version requires additional Hardware:

The pre-built Firmware will support that additional Hardware like the LDR. It features a Wifi Manager, Web Frontend, Animations, Timers, Auto Update and Brightness Control .

This is a picture of the advanced wiring. It features a brightness Sensor
Brightness Sensor
The brightness Sensor (pinkish white and red) is connected to A0. A Pulldown 100k is attached to A0 and GND. The other wire of the LDR is RED, goes to 3.3V.
FONT EDIT
You do not like the Font ? Want to edit Characters – No Problem! Download the bitmap below and make edits of your choice. Then convert to C-Code with the online Image2cpp converter.
Image2cpp converter
https://hurricanejoef.github.io/image2cpp/
Import the font below, Section 2: select: Invert image colors, Section 4: Select: Arduino Code , Horizontal 1 bit per Pixel.


Do you like the project ?

https://www.buymeacoffee.com/pixelkoenig
Example Font
Download this font and edit as you like with MS Paint, then convert it with Image2cpp into C-Source and copy into your Sketch.
