How to display a bitmap on a 1.77 inch screen?

To display a bitmap on a 1.77 inch screen, you first need to understand the hardware and software stack involved. The most common 1.77 inch display on the market is the 1.77 inch 128x160 tft display using the ST7735S driver IC, which communicates via SPI (Serial Peripheral Interface) with a 4-wire or 5-wire interface. The screen resolution is 128 pixels wide by 160 pixels tall, with a color depth of 16-bit RGB565 (65,536 colors). The pixel format is 2 bytes per pixel—5 bits for red, 6 bits for green, and 5 bits for blue. This means a full frame buffer requires 128 * 160 * 2 = 40,960 bytes of RAM. If your microcontroller has limited SRAM (like Arduino Uno with 2KB), you cannot store the entire bitmap in RAM—you must stream it from external storage like SPI flash or an SD card. For microcontrollers with at least 64KB of RAM (like ESP32 or STM32F4), you can allocate a full frame buffer in heap memory.

The physical connection between the microcontroller and the display is critical. The ST7735S requires at least 7 pins: CS (chip select), DC (data/command), RST (reset), MOSI (master out slave in), MISO (master in slave out, optional), SCK (serial clock), and VCC (3.3V). The backlight LED is typically driven by a separate PWM-capable pin to control brightness. The SPI clock speed should be set between 4 MHz and 20 MHz—higher speeds reduce screen update time but may introduce signal integrity issues on long wires. For a 1.77 inch 128x160 tft display, the maximum SPI clock is typically 16 MHz, giving a theoretical frame rate of about 30 fps for full-screen updates. In practice, you’ll get around 20-25 fps due to command overhead.

To display a bitmap, you must convert your image to the correct format. The bitmap must be in RGB565, little-endian byte order (low byte first, high byte second). Each pixel is stored as two consecutive bytes: byte 0 = (R[4:3]<<5 | G[5:3]<<2 | G[2]<<1 | B[4:3]), byte 1 = (R[4:0]<<3 | G[5:3]<<5 | B[4:0]). This is not the same as standard BMP files from Windows, which are stored in BGR565 and often have a 54-byte header. You must strip the header and reorder the bytes. A common approach is to use image processing tools like ImageMagick or a custom Python script to convert a PNG or JPEG to a raw RGB565 binary file. The raw file size for a full 128x160 image is exactly 40,960 bytes. If you store the bitmap in program memory (PROGMEM on AVR) or in a flash filesystem (SPIFFS on ESP32), you can access it directly.

Here is a step-by-step procedure for displaying a bitmap on the ST7735S using the Adafruit ST7735 library (Arduino IDE):

1. Initialize the display with tft.initR(INITR_BLACKTAB) for the 1.77 inch variant. This sets the column and row start addresses to 0,0 and the window to 128x160.

2. Set the drawing window using tft.setAddrWindow(x, y, w, h). For a full-screen bitmap, use tft.setAddrWindow(0, 0, 128, 160).

3. Push the pixel data using tft.pushColors(bitmap, 40960, false) where bitmap is a pointer to the RGB565 data array. The third parameter false indicates the data is not in big-endian order.

4. If you are using a library like TFT_eSPI (common on ESP32), the function is tft.pushImage(x, y, w, h, bitmap) which handles the window automatically.

For microcontrollers with limited RAM, you must use a streaming approach. Instead of loading the entire 40KB bitmap into memory, you read it in chunks of 256 bytes from an SD card or SPI flash. For example, using an SD card with FAT32, you can open the raw bitmap file and read 256 bytes at a time, then push them to the display using tft.pushColors in a loop. The SPI bus must be shared between the SD card and the display—use separate chip select pins and ensure proper deassertion. The total time to display a full bitmap from SD card at 8 MHz SPI is about 200-300 milliseconds, depending on the SD card speed.

Color calibration is important. The ST7735S has a gamma curve that can be adjusted via registers 0xE0 (positive gamma) and 0xE1 (negative gamma). The default gamma values are: 0x02, 0x1C, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2D, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10. If your bitmap appears washed out or too dark, you can tweak these registers. The color order is also configurable via register 0x3A (interface pixel format) and 0x36 (MADCTL). The MADCTL register controls the RGB order, screen orientation, and page/column order. The default value is 0xC8, which sets RGB order to BGR. To fix this, set MADCTL to 0x08 for RGB order. If you don’t adjust this, red and blue will be swapped in your bitmap.

Performance data: On an ESP32 at 240 MHz with 80 MHz SPI, pushing a full 128x160 bitmap takes approximately 12 milliseconds. On an Arduino Uno (16 MHz, 8 MHz SPI), it takes about 65 milliseconds. The bottleneck is almost always the SPI data transfer, not the CPU. If you need faster updates, you can use DMA (Direct Memory Access) on microcontrollers that support it (like STM32 or ESP32 with ESP-IDF). With DMA, the SPI transfer happens in the background, freeing the CPU for other tasks. The theoretical maximum throughput is SPI clock * 8 bits / 2 bytes per pixel = 16 MHz * 8 / 2 = 64 million pixels per second, but in practice you’ll get around 40-50 MB/s due to overhead.

Another factor is the display’s internal RAM. The ST7735S has a 132x162 pixel GRAM (graphics RAM), but only 128x160 is visible. The extra pixels are used for the border area. When you set the window, the driver automatically clips to the visible area. If you try to write outside the window, the data is ignored. This is useful for partial updates—you can update only a small region of the screen without affecting the rest. For example, to display a 32x32 icon, you set the window to (x, y, 32, 32) and push only 2048 bytes. This is 20 times faster than a full-screen update.

Bitmaps can also be stored in compressed formats like RLE (run-length encoding) or JPEG, but the ST7735S does not support hardware decompression. You must decompress on the CPU, which adds latency. For example, a JPEG image of 128x160 at 50% quality is about 4-6 KB, but decompressing it on an ESP32 takes about 50-100 milliseconds. This is slower than reading a raw bitmap from flash. For most applications, raw RGB565 is the best trade-off between storage and speed.

Power consumption is another consideration. The 1.77 inch 128x160 tft display draws about 20-30 mA with the backlight on at full brightness. If you are battery-powered, you can reduce power by turning off the backlight (PWM to 0) or by putting the display into sleep mode (command 0x10). In sleep mode, the display draws less than 1 mA. However, waking from sleep takes about 120 milliseconds, during which the display is blank. If you need to display a bitmap continuously, you can use a frame buffer in the display’s GRAM—the ST7735S retains the last written data even when the SPI bus is idle. This means you can write the bitmap once and then power down the microcontroller, leaving the display showing the static image.

For troubleshooting, common issues include: incorrect SPI wiring (especially MISO being left floating), wrong MADCTL settings causing inverted colors, and incorrect column/row start addresses. The 1.77 inch display often has a start column of 2 and start row of 1 due to the GRAM offset. This means pixel (0,0) in the GRAM is actually at column 2, row 1 on the physical panel. If your bitmap appears shifted by 2 pixels horizontally or 1 pixel vertically, adjust the window offset in the initialization. The Adafruit library handles this automatically with INITR_BLACKTAB, but if you are using a custom library, you must set the column and row start registers (0x2A and 0x2B) to 0x0002 and 0x0001 respectively.

If you are using a 1.77 inch 128x160 tft display from a specific vendor, check the datasheet for the exact initialization sequence. Some vendors use different MADCTL defaults or different gamma curves. The standard initialization sequence for the ST7735S is: software reset (0x01), wait 120 ms, sleep out (0x11), wait 120 ms, set pixel format (0x3A, 0x05 for 16-bit), set MADCTL (0x36, 0x08), set display inversion (0x21), set gamma curves (0xE0 and 0xE1), then display on (0x29). If you skip any of these steps, the bitmap may not display correctly. For example, without the gamma curve, colors will be too dark or too bright. Without the display inversion command, the image will be inverted (negative).

In terms of software libraries, the most popular are Adafruit ST7735 (for Arduino) and TFT_eSPI (for ESP32). TFT_eSPI is more flexible because it allows you to configure pin assignments, SPI speed, and color order via a configuration file (User_Setup.h). It also supports 16-bit parallel mode for faster updates, but that requires more pins. For the 1.77 inch display, SPI is sufficient. The library includes a function tft.drawBitmap() that takes a PROGMEM array, but it only supports 1-bit (monochrome) bitmaps. For 16-bit color, you must use tft.pushImage() or tft.pushColors(). The library also supports sprite objects (offscreen frame buffers) that can be drawn to the display with transparency. This is useful for animations or UI elements.

Data density: The 128x160 resolution at 16-bit color gives 40,960 bytes of pixel data. If you store 100 full-screen bitmaps on an SD card, that’s about 4 MB. On a 16 MB SPI flash chip, you can store about 400 bitmaps. The SPI flash read speed is typically 40-50 MB/s, so reading a full bitmap takes about 1 millisecond. The bottleneck is the SPI write to the display, which is limited by the display’s maximum SPI clock. For real-time applications, you can use double buffering: one buffer in RAM for the next frame, while the display shows the current frame. This smooths out the update and avoids tearing.

If you are working with a microcontroller that has a built-in LCD controller (like the ESP32-S3 with its own LCD peripheral), you can use the I80 parallel interface instead of SPI. This gives 8-bit or 16-bit parallel data transfer, which can be 10-20 times faster than SPI. However, the 1.77 inch display typically only has SPI pins, so you would need to use a different display module. For most hobbyist projects, SPI is adequate.

One more detail: the bitmap you want to display must be in the correct orientation. The ST7735S can be rotated via the MADCTL register. The four common orientations are: portrait (0x08), landscape (0x68), portrait flipped (0xC8), and landscape flipped (0xA8). If you are displaying a bitmap that was designed for a different orientation, you can either rotate the image in software before converting to RGB565, or change the MADCTL register. Rotating in software is more efficient because it avoids the overhead of changing the display configuration. For example, if you have a landscape bitmap but the display is in portrait mode, you can transpose the pixel array before pushing it. This adds a small amount of CPU time (about 2-3 milliseconds on ESP32) but saves you from re-converting the image.

Finally, the backlight control. The 1.77 inch display has a backlight LED that typically requires 3.3V and 20 mA. If you connect it directly to a GPIO pin, the pin may not be able to source enough current. Use a transistor or a dedicated backlight driver IC. The backlight can be PWM-controlled at 1 kHz to 10 kHz to adjust brightness. At 100% duty cycle, the display is at full brightness (about 250 cd/m²). At 50% duty cycle, it’s about 120 cd/m². The human eye perceives brightness logarithmically, so a linear PWM curve will appear to have more steps at low brightness. Use a gamma correction table for smoother dimming. For example, a 256-step PWM table with values computed as pow(i/255, 2.2) * 255 gives a more natural brightness curve.