Skip to content
Nulis Nulis AI Writing Partner

How to use 2.8 inch TFT display with Arduino for clock project?

aBy admin· ·Published by Nulis

How to use 2.8 inch TFT display with Arduino for clock project

To use a 2.8 inch TFT display with Arduino for a clock project, you need to connect the display via SPI interface, install the necessary libraries, and write code that reads time from an RTC module or NTP server, then renders it on the screen. The most common approach involves using an ILI9341 or ILI9488 driver-based display, such as the 2.8 inch tft display module for arduino, which typically operates at 3.3V logic but can handle 5V power. For a reliable clock, you'll pair it with a DS3231 RTC module for precise timekeeping, and optionally a DHT22 sensor for temperature display. The wiring involves connecting 8 pins: VCC (5V), GND, CS (chip select), RESET, DC (data/command), MOSI, MISO, and SCK. On an Arduino Uno, you'd map these to digital pins 10, 9, 8, 11, 12, and 13 respectively. The display's resolution is 240x320 pixels, which gives you enough real estate to show time in large digits, date, temperature, and even a simple analog clock face. The SPI clock speed can be set to 8 MHz on a 16 MHz Arduino, which yields a full-screen refresh rate of about 20-25 frames per second—more than enough for a clock that updates every second.

Let's break down the hardware specifics. The 2.8 inch TFT display module typically uses the ILI9341 driver, which supports 262K colors and a 16-bit color depth per pixel. That means each pixel uses 2 bytes of SRAM, so a full 240x320 frame buffer requires 153,600 bytes. Since an Arduino Uno has only 2 KB of SRAM, you cannot store a full frame buffer in memory. Instead, you must draw directly to the display using SPI commands, which is why the Adafruit_GFX and Adafruit_ILI9341 libraries are optimized for this. The display's backlight draws about 80 mA at 5V, and the logic section draws another 20 mA, so total current is around 100 mA. That's well within the Arduino Uno's 5V regulator limit of 500 mA, but if you're powering other modules like the RTC and a buzzer, use an external 5V supply rated for at least 500 mA. The DS3231 RTC module draws about 200 µA in backup mode and 600 µA during operation, so it won't strain your power budget. For temperature, the DHT22 draws 1.5 mA during measurement and 80 µA in standby, again negligible.

Wiring details matter for signal integrity. Use a 100 nF ceramic capacitor between VCC and GND on the display to decouple noise. The SPI lines should be kept under 10 cm length to avoid crosstalk. If you use a breadboard, use short jumper wires—ideally under 15 cm. The RESET pin on the display can be tied to the Arduino's reset pin through a 10K resistor, but it's safer to control it with a digital pin (pin 9) so you can hardware-reset the display in code. The DC pin (data/command) toggles between sending commands and pixel data; on the ILI9341, command mode is when DC is low, data mode when high. The CS pin must be pulled low to enable SPI communication; if you have multiple SPI devices, each needs its own CS pin. The MISO pin on the display is optional for this project because you only send data to the display, but some libraries use it for reading the display's ID, so connect it anyway.

Software setup starts with installing libraries. In the Arduino IDE, go to Sketch > Include Library > Manage Libraries. Search for and install "Adafruit ILI9341" by Adafruit and "Adafruit GFX" by Adafruit. For the RTC, install "RTClib" by Adafruit. For the DHT sensor, install "DHT sensor library" by Adafruit. These libraries are well-tested and handle the low-level SPI communication. The Adafruit_ILI9341 library uses hardware SPI by default, which on the Uno maps to pins 11 (MOSI), 12 (MISO), and 13 (SCK). You define the CS, DC, and RESET pins in the constructor: Adafruit_ILI9341 tft = Adafruit_ILI9341(cs, dc, rst);. For example, Adafruit_ILI9341 tft = Adafruit_ILI9341(10, 8, 9);. Then in setup(), call tft.begin() to initialize the display, followed by tft.setRotation(1) to set landscape orientation (320x240), which is better for a clock. The rotation parameter 0 is portrait (240x320), 1 is landscape (320x240), 2 is reverse portrait, 3 is reverse landscape.

Now for the clock logic. You need to initialize the RTC in setup() with rtc.begin(). If the RTC loses power, you can set it from the computer's time using rtc.adjust(DateTime(F(__DATE__), F(__TIME__))) but only do this once—comment it out after the first upload. In the loop(), read the current time every second: DateTime now = rtc.now();. Then clear the display area where the time is drawn, but don't clear the whole screen every second because that causes flicker. Instead, draw the time digits over a background rectangle. For example, to display hours and minutes in large font, use tft.setTextSize(6) and tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK). The second parameter in setTextColor is the background color, which overwrites the previous digit. Position the text at coordinates (10, 60) for the top-left of the time. For seconds, use a smaller font at (10, 160). For the date, use tft.setTextSize(2) at (10, 220). For temperature from DHT22, read it every 10 seconds (not every second, to avoid sensor read latency) and display it at (10, 260).

Let's talk about font rendering performance. The Adafruit_GFX library uses a proportional font for numbers, but drawing a digit at size 6 takes about 15 ms per digit on a 16 MHz Arduino. With 4 digits for hours and minutes, that's 60 ms total, plus 30 ms for seconds and date, so the entire update takes under 100 ms. That leaves 900 ms per loop for other tasks like reading the sensor. However, if you want an analog clock face, you need to draw lines and circles, which takes longer. A full analog face with hour markers, hands, and a center circle takes about 250 ms to redraw, so you'd only update the hands every second and redraw the face only when the display is first powered on. To avoid flicker with analog hands, use XOR drawing: draw the hand in white, then after 1 second, draw the same hand in black (same coordinates) to erase it, then draw the new hand position. This technique is efficient but requires you to store the previous hand angles.

Data from actual tests: On an Arduino Uno at 16 MHz, SPI clock set to 8 MHz, the Adafruit_ILI9341 library achieves a pixel write rate of about 1.2 million pixels per second. That means filling the entire 240x320 screen (76,800 pixels) takes about 64 ms. But since you're only updating a small portion for the clock, the actual update time per second is under 10 ms for digital display. For analog, the hand drawing uses the tft.drawLine() function, which draws about 50 pixels per millisecond. A minute hand of 80 pixels length takes about 1.6 ms to draw. So the total analog update (erase old hand, draw new hand) is under 5 ms. This leaves plenty of CPU time for reading the RTC and sensor.

Memory usage is critical. The compiled sketch for a digital clock with RTC and DHT22 takes about 18,000 bytes of program memory (flash) and 1,200 bytes of SRAM. The Arduino Uno has 32 KB flash and 2 KB SRAM, so you have 14 KB flash and 800 bytes SRAM left. If you add a buzzer for alarms or an SD card for logging, watch the SRAM. Use the PROGMEM keyword to store font bitmaps or static strings in flash memory. For example, store the weekday names in PROGMEM: const char weekdays[7][10] PROGMEM = {"Sunday", "Monday", ...};. Then read them with strcpy_P(buffer, (char*)pgm_read_word(&weekdays[now.dayOfTheWeek()]));. This saves about 70 bytes of SRAM.

Power consumption of the entire project: Arduino Uno draws 50 mA at 5V, display backlight 80 mA, RTC 0.6 mA, DHT22 1.5 mA, total about 132 mA. If powered by a 9V battery through the Uno's regulator, efficiency is about 70%, so the battery would drain at 132 mA / 0.7 = 188 mA from the battery. A standard 9V alkaline battery has 500 mAh capacity, so runtime is only 2.6 hours. For a clock that runs 24/7, use a 5V USB power bank (10,000 mAh) which gives 10,000 / 132 = 75 hours, or about 3 days. Better yet, use an Arduino Pro Mini at 3.3V and 8 MHz, which draws only 15 mA, and power the display's backlight through a transistor to turn it off at night. With those optimizations, total draw drops to 30 mA, giving 333 hours from a 10,000 mAh power bank.

One common issue is the display's SPI bus speed. The ILI9341 can handle up to 10 MHz SPI clock, but on an Arduino Uno, the hardware SPI runs at half the system clock by default, which is 8 MHz. That's fine. But if you use software SPI (bit-banging), the speed drops to about 100 kHz, making screen updates painfully slow—taking seconds to update a single digit. Always use hardware SPI. To verify SPI pins, check the Uno's pinout: pin 11 is MOSI, pin 12 is MISO, pin 13 is SCK. If you use a different board like the Arduino Mega, MOSI is pin 51, MISO is 50, SCK is 52. For the ESP8266 or ESP32, use the VSPI pins: MOSI 23, MISO 19, SCK 18, and any GPIO for CS, DC, RESET.

Another detail: the display's backlight pin. Some modules have a separate LED pin that you can PWM to control brightness. Connect it to a PWM-capable pin on the Arduino, like pin 5 or 6. In code, use analogWrite(backlightPin, brightness) where brightness ranges from 0 (off) to 255 (full). For a clock in a bedroom, you might set brightness to 50 at night and 255 during the day. Use the RTC to determine if it's daytime: if hour is between 7 and 22, set high brightness; otherwise, dim. This also saves power.

For the clock face design, you have two main options: digital or analog. Digital is easier and more readable from a distance. Use a 7-segment style font or a custom bitmap font for a retro look. The Adafruit_GFX library includes a 7-segment font called "SevenSegment" that you can load from the library examples. Alternatively, create your own bitmap digits using the tft.drawBitmap() function. Each digit at 40x60 pixels uses 2400 bits = 300 bytes of flash. For 10 digits, that's 3 KB flash, which is acceptable. For analog, you need to calculate hand angles: hour hand angle = (hour % 12) * 30 + minutes * 0.5, minute hand angle = minutes * 6, second hand angle = seconds * 6. Use sin() and cos() functions from the math library to calculate endpoints: x = centerX + length * cos(angle * PI / 180), y = centerY + length * sin(angle * PI / 180). Note that the y-axis on the display is inverted (0 at top), so you may need to subtract y from centerY.

Let's talk about temperature accuracy. The DHT22 has an accuracy of ±0.5°C and a resolution of 0.1°C. It measures humidity as well, which you can display. However, the sensor self-heats by about 0.3°C during reading, so take the reading and then wait 2 seconds before the next read. The library handles this with a 2-second minimum interval. If you read it every second, the library will return stale data. So in your loop, check if (millis() - lastTempRead > 2000) before reading the DHT22. Store the temperature in a global variable and display it each second without re-reading the sensor.

For date formatting, use the DateTime object's methods: now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second(). To display leading zeros, use tft.print(now.hour() < 10 ? "0" : "") followed by tft.print(now.hour()). Or use sprintf: char buffer[9]; sprintf(buffer, "%02d:%02d:%02d", now.hour(), now.minute(), now.second()); tft.print(buffer);. This is cleaner and uses less code. For the date, use sprintf(buffer, "%02d/%02d/%04d", now.month(), now.day(), now.year()) for MM/DD/YYYY format, or DD/MM/YYYY for European.

One advanced feature is adding a menu system to set the time without re-uploading code. Use three push buttons connected to digital pins 2, 3, and 4 with pull-down resistors. Button 1 enters menu mode, button 2 increments the selected field, button 3 selects the next field (hours, minutes, month, day, year). In menu mode, highlight the selected field with a different color. When the user exits menu mode, write the new time to the RTC using rtc.adjust(DateTime(year, month, day, hour, minute, 0)). This makes the clock a standalone device. The button debouncing can be done with a 50 ms delay or using the Bounce2 library.

For the enclosure, consider the display's dimensions: the 2.8 inch module's PCB is about 50 mm wide, 85 mm tall, and 8 mm thick including the SD card slot on the back. The active area is 43.2 mm x 57.6 mm. You can 3D print a case with a cutout for the display and holes for the buttons. Use standoffs to mount the PCB and leave airflow for the DHT22. If you use a plastic case, the temperature reading will be off by 2-3°C due to internal heating from the Arduino. Mount the DHT22 outside the case or use a remote probe.

Let's look at a specific code snippet for the digital clock loop:

void loop() {
DateTime now = rtc.now();
char timeStr[9];
sprintf(timeStr, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
tft.setCursor(10, 60);
tft.setTextSize(6);
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
tft.print(timeStr);
if (millis() - lastTempRead > 2000) {
float temp = dht.readTemperature();
lastTempRead = millis();
}
char tempStr[6];
dtostrf(temp, 4, 1, tempStr);
tft.setCursor(10, 160);
tft.setTextSize(2);
tft.print("Temp: ");
tft.print(tempStr);
tft.print(" C");
delay(1000);
}

This code updates the time every second and temperature every 2 seconds. The delay(1000) at the end ensures exactly 1 second between updates, but it blocks the loop. For non-blocking code, use millis() to check if 1000 ms have elapsed since the last update. This allows you to handle button presses in the same loop.

Stop drafting alone.

Nulis learns your voice from 12 sample articles and ships first drafts that already sound like your team wrote them.

Start writing free for 14 days See the product