How to Make a Digital Clock on a Vintage B&W TV using Arduino
2025-11-10 | By Mirko Pavleski
License: General Public License Arduino
These days, I accidentally came across this small retro black and white TV with a built-in radio, so I immediately decided to make a simple, useful device with it. Of course, first, I decided to check its current condition and functionality.
Let's examine the radio receiver. As far as I can see, this part of the device is functioning normally, and the only thing that needs to be cleaned is the volume potentiometer, possibly with some contact cleaner spray.
Now let's move on to the TV receiver. In a few seconds, a white raster appears on the screen, which is a sign that this part of the device is probably completely functional. We still have to examine the tuner part by bringing an analog signal to the antenna input. For this purpose, I will use the signal from cable television. This signal is in the VHF-III band, so now I will start searching for channels.
As I assumed, the TV receiver is completely functional, but unfortunately, nowadays it has no significant use value except as part of some retro TV collection.
I got the idea to "bring back to life" this small, cute device in a way that I would make some kind of clock that would display the time and date on the screen. And of course, the most suitable for that purpose is the Arduino microcontroller together with the TVout library, with the help of which a composite signal is generated through only two resistors.
The model of this device is "Inno-Hit TV128," but after a long search, I was unable to find any service manual or circuit diagram. Many years ago, I worked in a TV repair shop, and I have relatively extensive knowledge in this area.
By definition, the composite input should be located somewhere between the video amplifier and the sync section. After a detailed examination of the PCB and its components, I discovered that the video amplifier in this case is a TA7678AP IC, and the sync section is a µPC1379C IC.
After several hours of experimentation, I discovered that the composite input is located right after the video output from the TA7678AP IC. That video signal passes through this jumper J112.
So I need to remove this jumper so that the previous tuner circuit does not affect the signal and connect the composite input directly to the input of the sync section.
The composite signal generator consists of an Arduino Nano and two resistors, and there is also the DS3231 real-time clock module with a built-in battery from which the exact time is read and displayed on the TV screen.
Now, for testing, I will send a demo signal, actually an example from the TVout library. One important note. If you install the TVout library by default via "manage libraries" or by copying it to the library folder, you will get the error "fontALL.h: No such file or directory" when compiling.
To avoid this, we need to install the two libraries "TVout" and "TVoutfonts" separately, which are given at the end of the text.
Now let's start testing. We bring the composite signal generated by the Arduino to the appropriate input, which is connected in the manner explained previously. The switch should be in the "Composite" position. The content is clearly displayed on the screen.
Understandably, now the BAND switch has no function because the tuner section has been omitted. Now you can adjust the brightness and contrast of the image and, if necessary, horizontal or vertical synchronization. And as I mentioned earlier, I created a relatively simple code that will display the time, day of the week, and date in digital form. Initially, my idea was an analog clock, but due to the extremely low resolution that can be generated by the Arduino, the image was desperately bad.
The code is mainly based on the TVout library, so my goal was to introduce several different possibilities, such as drawing logos, rectangles, lines, and text borders, as well as black text on a white background.
The exact time and date are read from the DS3231 real-time clock module, which also has a built-in battery to save time when there is no power.
Otherwise, when modifying the TV, I installed a small switch that allows you to choose between standard TV mode and composite video mode.
And finally, a short conclusion. This project successfully transforms a vintage TV into a functional and stylish retro clock. It's a perfect fusion of classic hardware and modern microcontroller simplicity. Also, modifying the small TV without any documentation or schematics was a big challenge for me.
// Arduino CRT TV Clock by mircemk, September 2025
#include <TVout.h>
#include <fontALL.h>
#include "MyLogo.h"
#include <Wire.h>
#include <avr/pgmspace.h> // For PROGMEM
TVout TV;
#define DS3231_I2C_ADDRESS 0x68
long previousMillis = 0;
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
long interval = 1000; // Update every 1 second
byte lastDayOfWeek = 0; // To track changes in day of week
byte lastDayOfMonth = 0; // To track changes in date
// Array of day names in PROGMEM (1=Sunday, 2=Monday, ..., 7=Saturday)
const char day0[] PROGMEM = "Unknown";
const char day1[] PROGMEM = "Sunday";
const char day2[] PROGMEM = "Monday";
const char day3[] PROGMEM = "Tuesday";
const char day4[] PROGMEM = "Wednesday";
const char day5[] PROGMEM = "Thursday";
const char day6[] PROGMEM = "Friday";
const char day7[] PROGMEM = "Saturday";
const char *const dayNames[] PROGMEM = {day0, day1, day2, day3, day4, day5, day6, day7};
byte decToBcd(byte val) {
return ((val/10*16) + (val%10));
}
byte bcdToDec(byte val) {
return ((val/16*10) + (val%16));
}
void getDateDs3231() {
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0x00);
Wire.endTransmission();
Wire.requestFrom(DS3231_I2C_ADDRESS, 7);
second = bcdToDec(Wire.read() & 0x7f);
minute = bcdToDec(Wire.read());
hour = bcdToDec(Wire.read() & 0x3f);
dayOfWeek = bcdToDec(Wire.read());
dayOfMonth = bcdToDec(Wire.read());
month = bcdToDec(Wire.read());
year = bcdToDec(Wire.read());
}
void setup() {
Wire.begin();
getDateDs3231();
TV.begin(PAL, 130, 96);
TV.select_font(font8x8);
TV.set_cursor(15, 13);
TV.print("DIY Arduino");
TV.set_cursor(22, 40);
TV.print("CRT Clock");
TV.draw_rect(20, 38, 75, 13, 1, -1);
TV.draw_rect(17, 35, 81, 19, 1, -1);
TV.set_cursor(8, 67);
TV.print("TVout Library");
TV.delay(5000);
TV.clear_screen();
intro();
TV.set_cursor(12, 2);
TV.print("Time & Date:");
TV.draw_rect(6, 0, 105, 12, 1, 2);
TV.draw_line(15, 39, 103, 39, 1);
TV.draw_line(15, 61, 103, 61, 1);
// Initial display of day and date
TV.set_cursor(25, 45);
char buffer[10];
strcpy_P(buffer, (char *)pgm_read_word(&dayNames[dayOfWeek]));
TV.print(buffer);
TV.set_cursor(20, 68);
TV.print(char(dayOfMonth/10 + 0x30));
TV.print(char(dayOfMonth%10 + 0x30));
TV.print("/");
TV.print(char(month/10 + 0x30));
TV.print(char(month%10 + 0x30));
TV.print("/");
TV.set_cursor(68, 68);
TV.print("20");
TV.print(char(year/10 + 0x30));
TV.print(char(year%10 + 0x30));
lastDayOfWeek = dayOfWeek;
lastDayOfMonth = dayOfMonth;
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis > interval) {
previousMillis = currentMillis;
getDateDs3231();
#ifdef TVOUT_VBLANK
TV.wait_for_vblank(); // Synchronize with vertical blanking if available
#endif
printTime();
}
}
void printTime() {
// Clear and print time (changes every second)
TV.draw_rect(25, 25, 48, 8, 0, 1); // Clear time area (width for HH:MM:SS)
TV.set_cursor(25, 25);
TV.print(char(hour/10 + 0x30));
TV.print(char(hour%10 + 0x30));
TV.print(":");
TV.print(char(minute/10 + 0x30));
TV.print(char(minute%10 + 0x30));
TV.print(":");
TV.print(char(second/10 + 0x30));
TV.print(char(second%10 + 0x30));
// Print day of week only if it has changed
if (dayOfWeek != lastDayOfWeek && dayOfWeek >= 1 && dayOfWeek <= 7) {
TV.draw_rect(25, 45, 80, 8, 0, 1); // Clear day area (width for longest day name)
TV.set_cursor(25, 45);
char buffer[10];
strcpy_P(buffer, (char *)pgm_read_word(&dayNames[dayOfWeek]));
TV.print(buffer);
lastDayOfWeek = dayOfWeek;
}
// Print date only if dayOfMonth has changed
if (dayOfMonth != lastDayOfMonth) {
TV.draw_rect(10, 68, 64, 8, 0, 1); // Clear date area (width for DD/MM/YYYY)
TV.set_cursor(10, 68);
TV.print(char(dayOfMonth/10 + 0x30));
TV.print(char(dayOfMonth%10 + 0x30));
TV.print("/");
TV.print(char(month/10 + 0x30));
TV.print(char(month%10 + 0x30));
TV.print("/");
TV.set_cursor(58, 68);
TV.print("20");
TV.print(char(year/10 + 0x30));
TV.print(char(year%10 + 0x30));
lastDayOfMonth = dayOfMonth;
}
}
void intro() {
unsigned char w, l, wb;
int index;
w = pgm_read_byte(MyLogo);
l = pgm_read_byte(MyLogo+1);
if (w & 7)
wb = w/8 + 1;
else
wb = w/8;
index = wb*(l-1) + 2;
for (unsigned char i = 1; i < l; i++) {
TV.bitmap((TV.hres() - w)/2, 0, MyLogo, index, w, i);
index -= wb;
TV.delay(50);
}
for (unsigned char i = 0; i < (TV.vres() - l); i++) {
TV.bitmap((TV.hres() - w)/2, i, MyLogo);
TV.delay(50);
}
TV.delay(3000);
TV.clear_screen();
}

