How to display images from SD card on a 2.4 inch LCD
You can display images from an SD card on a 2.4 inch LCD by connecting the SD card module to a microcontroller (like an Arduino Uno or ESP32), reading the image file data in BMP or JPEG format, and then sending that pixel data to the LCD via SPI or parallel interface. The most common setup uses a 2.4 inch 240x320 ips display with an ILI9341 or ST7789 driver, paired with an SD card breakout board that communicates over SPI. For example, on an Arduino Uno, you connect the SD card module’s CS to pin 10, MOSI to pin 11, MISO to pin 12, and SCK to pin 13, then wire the LCD’s CS, DC, and RST to other digital pins. After formatting the SD card as FAT32 and saving a 240x320 pixel BMP image (24-bit color depth, no compression), you can use the Adafruit_ImageReader library to load and render it. Tests show that a 240x320 BMP file (~225 KB) takes about 1.2 seconds to display on an Arduino Uno at 8 MHz SPI clock, while an ESP32 at 40 MHz SPI clock cuts that to under 0.3 seconds. If you use JPEG, the decode time adds roughly 0.5 to 1 second depending on the microcontroller’s processing power. The key constraint is the LCD’s buffer size: most 2.4-inch displays have no frame buffer, so you must send pixel rows sequentially, which means the SD card reading speed directly impacts refresh rate. A class 10 SD card can sustain 10 MB/s read speed, but the SPI bus on an Arduino is limited to about 8 Mbps (1 MB/s), so the bottleneck is often the microcontroller, not the card. For faster performance, use an ESP32 with dual-core processing and hardware SPI, which can push 40 Mbps, reducing image load time to roughly 0.15 seconds for a 240x320 BMP. You can also use a dedicated SD card library like SdFat (by Bill Greiman) which offers faster read speeds than the standard SD library—benchmarks show SdFat can read 512-byte blocks at 4.5 MB/s on an Arduino Mega versus 2.1 MB/s with the stock library. When selecting a display, look for one with a built-in SD card slot to simplify wiring; many 2.4 inch 240x320 ips display modules include a microSD slot on the back, which shares the SPI bus with the LCD. This reduces pin count to 6 or 7 total. However, sharing the bus means you cannot read the SD card and update the LCD simultaneously—you must alternate chip select signals. A practical sequence is: initialize the SD card, open the image file, read the first row of pixels (320 bytes for 16-bit color), then select the LCD and write that row to its GRAM. Repeat for all 240 rows. The ILI9341 datasheet specifies a minimum write cycle time of 66 ns for 16-bit parallel mode, but in SPI mode, the maximum clock is 10 MHz for the ILI9341 (20 MHz for some variants). At 10 MHz, writing a full 240x320 frame (153,600 pixels at 16-bit each) takes about 0.123 seconds just for data transfer, excluding SD read time. So total display time for a BMP is SD read (1.2 s on Uno) plus LCD write (0.123 s) = ~1.32 seconds. With an ESP32, SD read drops to 0.15 s, total ~0.27 s. For JPEG, the decode step adds CPU load: on an ESP32, the JPEGDEC library can decode a 240x320 JPEG in about 0.4 seconds, making total time ~0.67 seconds. On an Arduino Uno, JPEG decode is impractical because of limited RAM (2 KB) and slow clock (16 MHz), so stick to BMP. The image format matters: 24-bit BMP files are larger but require no decoding, while 16-bit R5G6B5 BMPs (565 color) are half the size and map directly to the LCD’s pixel format, reducing SD read time by 50%. A 240x320 16-bit BMP is only 153,600 bytes, which an ESP32 can read in 0.1 seconds. To implement this, you need the following hardware: a 2.4-inch LCD with ILI9341 controller (SPI interface), an SD card module (SPI), a microcontroller (Arduino Uno, Mega, ESP32, or STM32), a 5V or 3.3V power supply (LCD and SD card both run at 3.3V logic, but Arduino Uno outputs 5V, so use level shifters or voltage dividers on the SPI lines), and a microSD card formatted as FAT32 with a cluster size of 32 KB for optimal read speed. Wiring details: connect the LCD’s VCC to 3.3V (or 5V if the module has a regulator), GND to ground, CS to digital pin 10, DC to pin 9, RST to pin 8, MOSI to pin 11, MISO to pin 12, SCK to pin 13. For the SD card module, connect CS to pin 4 (or any free pin), MOSI to pin 11, MISO to pin 12, SCK to pin 13. Note that MOSI, MISO, and SCK are shared between the LCD and SD card—this is standard for SPI devices on the same bus. You must set the chip select pins high for the inactive device. In code, you initialize the SD card first, then the LCD. The Adafruit_ILI9341 library provides a `drawBMP()` function that reads a BMP file from the SD card and draws it. But this function is blocking and uses a lot of RAM on the Arduino. A more efficient approach is to read the BMP header manually, extract the pixel offset and dimensions, then read pixel rows in a loop. Here’s a code snippet structure: `File bmpFile = SD.open("image.bmp");` then read the first 54 bytes to get the header, check that the file is 240x320 and 24-bit, then seek to the pixel data offset. Then for each row from bottom to top (BMP stores rows upside-down), read 320 pixels (each 3 bytes for 24-bit or 2 bytes for 16-bit), convert to 16-bit color if needed, and send to the LCD using `lcd.drawPixel()` or faster, `lcd.writeColor()` in a loop. For speed, use `lcd.startWrite()`, then write all pixels in the row with `lcd.writeColor(pixel, count)`, then `lcd.endWrite()`. This cuts SPI transaction overhead. Benchmarks: using `lcd.writeColor()` reduces row write time from 8 ms to 2 ms on an Arduino Uno. For a 240-row image, that saves 1.44 seconds. Another optimization: use DMA (Direct Memory Access) on an ESP32 or STM32 to transfer SPI data without CPU intervention. On an ESP32, the SPI library supports DMA, and you can achieve 40 Mbps transfer rates, reducing LCD write time for a full frame to 0.03 seconds. But DMA requires contiguous memory buffers, which can be tricky with SD card reads. A practical middle ground is to use an ESP32 with the TFT_eSPI library, which is optimized for ILI9341 and includes SD card support. TFT_eSPI can display a 240x320 BMP in about 0.2 seconds total. For JPEG, use the TJpg_Decoder library with TFT_eSPI, which decodes and draws in one pass, achieving 0.5 seconds for a full-screen JPEG. Power consumption: the LCD draws about 40 mA at 3.3V with backlight on, the SD card draws 20-30 mA during read, and the ESP32 draws 80 mA, so total is ~150 mA. Use a 500 mA regulator to be safe. If you need to display multiple images, consider pre-loading them into PSRAM (on ESP32-WROVER modules) for instant switching. A 16 MB PSRAM can hold about 100 16-bit BMPs (153 KB each). But reading from PSRAM is faster than SD card—0.05 seconds per image. For a production device, use an SD card with a SPI clock of 20 MHz and a microcontroller with hardware SPI and a FIFO buffer. The table below shows typical performance for different microcontrollers with a 2.4-inch 240x320 LCD and a class 10 SD card (16-bit BMP, 153 KB file):
| Microcontroller | SPI Clock (MHz) | SD Read Time (s) | LCD Write Time (s) | Total Time (s) |
|---|---|---|---|---|
| Arduino Uno (16 MHz) | 8 | 1.20 | 0.12 | 1.32 |
| Arduino Mega (16 MHz) | 8 | 0.90 | 0.12 | 1.02 |
| ESP32 (240 MHz) | 40 | 0.10 | 0.03 | 0.13 |
| STM32F4 (168 MHz) | 42 | 0.08 | 0.02 | 0.10 |
The table assumes a 16-bit BMP with no compression and a 240x320 resolution. Real-world results vary based on SD card fragmentation, SPI bus capacitance, and library overhead. For example, using the standard SD library on an Arduino Uno adds about 0.3 seconds of overhead compared to SdFat. Also, the LCD’s response time (typical 10 ms for ILI9341) is negligible. If you use a JPEG image, the decode time adds 0.4 to 1.5 seconds depending on the microcontroller’s MIPS. On an ESP32, JPEG decode using the JPEGDEC library takes 0.4 seconds for a 240x320 JPEG at quality 90 (file size ~30 KB). The trade-off is that JPEG files are smaller (30 KB vs 153 KB), so SD read time drops to 0.02 seconds, making total time 0.42 seconds—still slower than BMP on ESP32 (0.13 s) due to decode overhead. So for speed, use BMP; for storage efficiency, use JPEG. The LCD itself has a 240x320 resolution, which means 76,800 pixels. Each pixel is 16-bit (2 bytes) for 565 color, so the frame buffer would be 153,600 bytes. Most microcontrollers do not have that much RAM (Arduino Uno has 2 KB), so you cannot buffer the entire image. You must read and write row by row. This is why the SD card’s block read size matters: reading 512-byte blocks (32 pixels per block for 16-bit) is efficient because the SD card’s minimum read unit is a sector. So you read 512 bytes, which gives you 256 pixels (if 16-bit), then write those to the LCD. This matches the LCD’s row width of 320 pixels, so you need two reads per row (320 pixels = 640 bytes, which is 1.25 sectors). But you can read 1024 bytes (2 sectors) per row to simplify. The SdFat library allows multi-block reads, which can read 16 sectors (8 KB) at once, reducing overhead. For a 240-row image, that means 30 multi-block reads of 8 KB each, instead of 480 single-sector reads. This cuts SD read time by 40% on an Arduino Mega. On an ESP32, the SDMMC interface (if using the built-in SD card slot on ESP32 dev boards) can read at 20 MB/s in 4-bit mode, but that requires wiring 6 data lines plus clock and cmd. For simplicity, most hobbyists use SPI mode. If you use an ESP32 with an SD card in SPI mode, you can still achieve 10 MB/s at 40 MHz SPI clock. But the ESP32’s SPI controller has a 64-byte FIFO, so you can queue reads. The practical limit is the SD card’s internal speed. A class 10 card has a minimum write speed of 10 MB/s, but read speed can be 20-30 MB/s. However, SPI mode is slower than SD bus mode because of protocol overhead. A typical SPI read speed on an ESP32 with a class 10 card is 5-8 MB/s. For a 153 KB file, that’s 0.02 to 0.03 seconds. So the bottleneck becomes the LCD write. The ILI9341 in SPI mode at 40 MHz can write a pixel every 0.025 microseconds (25 ns per bit, 16 bits = 400 ns per pixel, plus command overhead). So 76,800 pixels take 30.7 ms for pure data. But you also need to send commands (CASET, PASET, RAMWR) which add about 1 ms. So total LCD write time is ~32 ms. Add SD read time of 30 ms, total ~62 ms. But in practice, library overhead doubles that. With TFT_eSPI, a 240x320 BMP takes about 120 ms on an ESP32 at 40 MHz. That’s fast enough for slideshow applications. For video, you need 30 fps, which means 33 ms per frame. That is not achievable with SD card reading and SPI LCD writing unless you use parallel interface (8-bit or 16-bit) and a fast microcontroller. The 2.4-inch LCD with a parallel interface can write a pixel in 66 ns, so a full frame takes 5 ms. But then you need a microcontroller with enough GPIO pins (16 for data, plus control). The ESP32 has enough pins, but you need to use the I2S peripheral to drive the parallel bus. This is advanced. For most users, the SPI interface is simpler and adequate for still images. The 2.4 inch 240x320 ips display with ILI9341 is widely available and well-supported by libraries. When you buy one, check the pinout: some modules have the SD card slot on the back, and the SD card’s CS pin is often labeled as “SD_CS” or “CS”. The default pin mapping for many breakout boards is: LCD CS = 10, DC = 9, RST = 8, SD CS = 4. But verify with your module’s datasheet. If the module has a built-in level shifter, you can use 5V logic. Otherwise, use 3.3V logic to avoid damaging the LCD or SD card. The Arduino Uno’s 5V outputs will damage the ILI9341 (rated for 3.3V max), so you must use a level shifter or a voltage divider on the SPI lines. A 1k ohm resistor in series with each line works as a current limiter, but a proper level shifter (like 74LVC245) is better. For the SD card, it is 3.3V only, so same issue. Many SD card modules have a built-in 3.3V regulator and level shifting for the CS and MOSI lines, but not all. Check the module’s schematic. If you use an ESP32 or STM32, they are 3.3V logic, so no level shifting needed. The power supply: the LCD backlight typically draws 20-30 mA at 3.3V. The SD card draws up to 100 mA during write, but read is 30 mA. Total current is under 200 mA, so a 3.3V regulator like AMS1117-3.3 can handle it. But if you use an Arduino Uno’s 3.3V output (rated for 50 mA), you will overload it. Use an external 3.3V regulator. The image file format: the BMP must be 240x320 pixels, 24-bit or 16-bit color. If you use a different resolution, the LCD will only display part of the image or cause artifacts. You can resize images on your computer using software like ImageMagick or GIMP. For batch conversion, use a script. The BMP file’s pixel data starts at offset 54 (for 24-bit) or 54 (for 16-bit, but 16-bit BMPs are less common). The ILI9341 expects 16-bit color in R5G6B5 format. If your BMP is 24-bit, you must convert each pixel: red = (pixel >> 16) & 0xFF, green = (pixel >> 8) & 0xFF, blue = pixel & 0xFF; then 16-bit = (red >> 3) << 11 | (green >> 2) << 5 | (blue >> 3). This conversion takes CPU time. On an ESP32, it adds about 5 ms for a full image. On an Arduino Uno, it adds 100 ms. So pre-converting to 16-bit BMP on your computer saves time. Tools like ImageMagick can do that: `convert input.png -resize 240x320 -depth 16 output.bmp`. But note that 16-bit BMP is not standard; many image editors save as 24-bit. You can use a custom converter. The SD card’s file system: FAT32 is required for files larger than 2 GB, but for small files, FAT16 works too. The cluster size should be 32 KB for optimal read speed. If you use a 4 GB card, format it with 32 KB clusters. On Windows, use the command `format /FS:FAT32 /A:32K X:` (where X is the drive letter). On Linux, use `mkfs.fat -F 32 -s 64 /dev/sdX1`. The number of files: the SD card can hold thousands of images, but the file system’s directory structure affects read speed. Keep images in the root directory for fastest access. If you have many files, use a flat structure. The microcontroller’s RAM: you need enough RAM to hold a 512-byte buffer for SD reads and a 320-byte buffer for