ST7735 1.8 inch TFT: Display 16-bit Color Images on Arduino
The ST7735 1.8-inch 128x160 color TFT is the perfect "step up" from a mono OLED. Cheap (~$3), bright, and supported by Adafruit_GFX. This post takes you from "blank screen" to "custom color image" using image2cpp's RGB565 output and Adafruit_ST7735.
Variants you'll encounter
| Variant | Resolution | Tab color (init) |
|---|---|---|
| ST7735R Red Tab | 128x160 | INITR_REDTAB |
| ST7735R Black Tab | 128x160 | INITR_BLACKTAB |
| ST7735R Green Tab | 128x128 or 128x160 (green offset) | INITR_GREENTAB / 144GREENTAB / 18GREENTAB |
| ST7735S | 80x160 | INITR_MINI160x80 |
Pick the wrong tab and you get garbled output, wrong colors, or a 1-2 pixel screen offset. The "tab" originally referred to the colored protective film on the panel during shipping — long lost when you bought it on AliExpress, so try each setting until the test sketch looks right.
Wiring
| ST7735 Pin | Arduino Uno | ESP32 |
|---|---|---|
| VCC | 3V3 or 5V (check module) | 3V3 |
| GND | GND | GND |
| CS | 10 | 5 |
| RESET | 9 | 4 |
| A0/DC | 8 | 2 |
| SDA (MOSI) | 11 | 23 |
| SCK | 13 | 18 |
| LED | 3V3 (or PWM for dimming) | 3V3 |
Verify wiring with the test sketch
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>
#define TFT_CS 10
#define TFT_RST 9
#define TFT_DC 8
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
void setup() {
tft.initR(INITR_BLACKTAB); // try INITR_GREENTAB if colors look wrong
tft.setRotation(0);
tft.fillScreen(ST77XX_BLACK);
tft.setTextColor(ST77XX_WHITE);
tft.setTextSize(2);
tft.setCursor(10, 60);
tft.print("Hello ST7735");
}
void loop() {}
Convert your image with image2cpp
For the 128x160 panel:
- Open pixel.hjlabs.in/converter
- Set canvas to 128x160 (or smaller for a sprite)
- Output: "Arduino code, 16-bit RGB565"
- Endianness: try little-endian first; flip if reds and blues are swapped
The resulting array is a uint16_t[] with 128 * 160 = 20,480 entries (40,960 bytes). That fits in an Arduino Uno's 32KB flash with about 8KB to spare for code — tight but workable. Move to ESP32 if you want multiple full-screen images.
Display the image
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>
Adafruit_ST7735 tft = Adafruit_ST7735(10, 8, 9);
const uint16_t myImage [] PROGMEM = {
0xF800, 0xF800, 0x07E0, /* ... 20,480 entries ... */
};
void setup() {
tft.initR(INITR_BLACKTAB);
tft.setRotation(0);
tft.fillScreen(ST77XX_BLACK);
tft.drawRGBBitmap(0, 0, myImage, 128, 160);
}
void loop() {}
Why drawRGBBitmap (and not drawBitmap)
Three Adafruit_GFX bitmap methods, three uses:
drawBitmap— 1-bit (mono), uses 1 color argument. For OLED-style logos.drawGrayscaleBitmap— 8-bit per pixel (256 grays). Rarely useful on a color TFT.drawRGBBitmap— 16-bit per pixel (RGB565). For color images. This is what you want on ST7735.
The endianness trap (different from TFT_eSPI)
Adafruit_ST7735 expects RGB565 in big-endian byte order (high byte first). image2cpp's default is little-endian. The fix is in image2cpp's output setting — pick "Big-endian" before generating, OR call tft.drawRGBBitmap() exactly as shown (Adafruit's drawRGBBitmap on AVR handles the swap internally; on ESP32/RP2040 it doesn't, and you may need a manual swap).
If reds and blues look swapped, that's the swap you need to fix. Re-export with the opposite endianness from image2cpp.
Smaller images and sprites
You rarely want full-screen. For a 32x32 icon at (48, 64):
tft.drawRGBBitmap(48, 64, smallIcon, 32, 32);
Memory cost: 32x32x2 = 2048 bytes per icon. You can fit dozens of icons in flash on an ESP32 or RP2040.
Transparent backgrounds
RGB565 has no alpha channel. To fake transparency, pick a "magic" color (e.g. magenta 0xF81F) and skip those pixels manually:
void drawSpriteWithKey(int x, int y, const uint16_t *spr, int w, int h, uint16_t key) {
for (int j = 0; j < h; j++) {
for (int i = 0; i < w; i++) {
uint16_t c = pgm_read_word(&spr[j * w + i]);
if (c != key) tft.drawPixel(x + i, y + j, c);
}
}
}
Slow (~30ms for a 64x64 sprite on AVR) but works. For real performance, use TFT_eSPI's sprite class on ESP32.
Animated GIFs — sort of
True animated GIF decoding on AVR is impractical (the LZW decoder won't fit). Instead, export individual frames as PNGs, convert each to RGB565, and cycle through them with millis():
const uint16_t* frames [] = {frame0, frame1, frame2, frame3};
unsigned long lastFrame = 0;
int frameIdx = 0;
void loop() {
if (millis() - lastFrame > 100) { // 10 fps
tft.drawRGBBitmap(20, 50, (uint16_t*)pgm_read_ptr(&frames[frameIdx]), 32, 32);
frameIdx = (frameIdx + 1) % 4;
lastFrame = millis();
}
}
Performance reality check
Adafruit_ST7735 on AVR pushes ~30 frames/sec for a 32x32 sprite, ~3 frames/sec for full-screen. On ESP32 with TFT_eSPI: 60+ FPS for full-screen redraws. If your animation feels sluggish, profile before optimizing — usually the bottleneck is SPI clock, not CPU.
Common pitfalls
- Image is shifted by 1-2 pixels. Wrong tab color in
initR(). Try INITR_GREENTAB or INITR_BLACKTAB. - Colors look inverted (negative). Some clones need
tft.invertDisplay(true). - Reds and blues swapped. Endianness mismatch. Re-export from image2cpp.
- Sketch too big to fit. Image is using all your flash. Drop to a smaller resolution or move to ESP32.
- "Stripey" diagonal artifacts. Loose wiring, especially at SPI clock > 8MHz on a breadboard.
Bottom line
ST7735 + Adafruit_GFX + image2cpp's RGB565 output = full-color images on a $3 display in 30 lines. Generate your array now, or read our Arduino tutorial if this is your first TFT project.
For ESP32 + 320x240 ILI9341, see our ILI9341 PROGMEM guide. Other hjLabs.in tools you might like: og.hjlabs.in for project README graphics.
Try image2cpp now — free, in browser
Drop in a PNG, JPG or BMP. Get a paste-ready C++ array for Arduino, ESP32 or RP2040. 100% client-side. Learn more about image2cpp or jump straight to the tool.
Open the converter →