How to Display a Logo on a 72x40 OLED
To display a logo on a 72x40 OLED, you need to convert the logo into a bitmap array that matches the display’s resolution, then use a microcontroller like an Arduino or ESP32 to send that data via I2C or SPI. The 72x40 OLED, such as the 0.42 inch 72x40 oled display, is a monochrome graphic display with 72 columns and 40 rows of pixels, each pixel individually addressable. This means your logo must be a 72x40 pixel monochrome image, typically black and white, since the OLED only supports on/off states per pixel. The display controller, often the SSD1306 or SH1106, uses a 128x64 buffer internally, so you’ll map your 72x40 data into the top-left corner of that buffer. For example, the SSD1306 has a 1024-byte buffer (128 columns * 64 rows / 8 bits per byte), but you only need 360 bytes for the 72x40 area (72 columns * 40 rows / 8 bits per byte). The I2C interface, running at 400 kHz typical, transfers 16 bytes per packet, so sending 360 bytes takes about 23 packets, each with a 9-bit overhead (address + read/write), totaling roughly 3.2 ms at 400 kHz. This is fast enough for static logos, but for animations, you’d need to optimize by using page-level updates.
Step 1: Prepare Your Logo Image
Start with a high-contrast logo, preferably a vector or PNG with sharp edges. Resize it to 72x40 pixels using software like GIMP or Photoshop. In GIMP, go to Image > Scale Image, set width to 72 and height to 40, then use the “Cubic” interpolation for smooth edges. Convert the image to grayscale, then to 1-bit (black and white) using Image > Mode > Indexed, selecting “Use black and white (1-bit) palette.” Export as a BMP or XBM file. For XBM, the format is a C-style array: for example, a 72x40 XBM file will have 360 bytes (72 bits per row, 40 rows, each row padded to 8 bytes because 72 bits = 9 bytes, but XBM aligns to 8-bit boundaries). Actually, 72 bits per row is 9 bytes, but XBM uses 8-bit alignment, so each row is 9 bytes, total 360 bytes. The array looks like: static unsigned char logo_bits[] = {0xFF, 0x00, ...};. Each bit represents a pixel: 1 for white (on) and 0 for black (off) on most OLEDs, but check your display’s polarity—some use 1 for black. The SSD1306 datasheet specifies that a 1 in the buffer turns the pixel on (white), assuming the display is configured for normal orientation.
Step 2: Convert to Byte Array
If you use a tool like LCD Assistant (from Microchip) or Image2Lcd, set the output to “C array (vertical)” for SSD1306. The vertical mode means bits are arranged column-wise: each byte represents 8 pixels in a column, starting from the top. For 72x40, you have 72 columns, each column has 40 rows, which is 5 bytes per column (40/8 = 5), so total bytes = 72 * 5 = 360. The array order is: first byte for column 0, rows 0-7; second byte for column 0, rows 8-15; up to column 71, rows 32-39. This matches the SSD1306’s page addressing mode, where each page is 8 rows. For example, page 0 covers rows 0-7, page 1 covers rows 8-15, etc. So the array is 5 pages * 72 columns = 360 bytes. In Image2Lcd, select “72x40”, “Monochrome”, “Vertical”, and “MSB first” (most significant bit is top pixel). The output will be a hex array like: 0x80, 0xC0, 0xE0, .... For a simple logo, say a circle, you might get: column 0: 0x00 (all off), column 1: 0x18 (bits 3 and 4 on), etc. You can verify by printing the array to serial and checking pixel positions.
Step 3: Upload to Microcontroller
Connect the OLED to your microcontroller. For I2C, wire SDA to A4 (Arduino Uno) or GPIO21 (ESP32), SCL to A5 or GPIO22, VCC to 3.3V or 5V (check datasheet—most 0.42-inch OLEDs run at 3.3V, but 5V tolerant via voltage regulator), and GND to ground. The I2C address is typically 0x3C or 0x3D, programmable via the SA0 pin. For the SSD1306, the default address is 0x3C if SA0 is low. Use the Adafruit SSD1306 library or the U8g2 library. In Arduino IDE, install the library, then include #include and #include . Define the display: Adafruit_SSD1306 display(72, 40, &Wire, -1); (the -1 disables the reset pin if not used). In setup(), call display.begin(SSD1306_SWITCHCAPVCC, 0x3C). Then, to display the logo, use display.clearDisplay(); and display.drawBitmap(0, 0, logo_bits, 72, 40, WHITE); where WHITE is 1 (on). The drawBitmap function expects the array in vertical format. If your array is in horizontal format (row-wise), you’ll see a scrambled image—you’d need to transpose it. For example, a horizontal array of 360 bytes (72 columns * 40 rows / 8) would be 40 rows * 9 bytes per row, but the SSD1306 expects column-wise. The U8g2 library handles this better: U8G2_SSD1306_72X40_ER_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8G2_PIN_NONE); then u8g2.firstPage(); do { u8g2.drawXBMP(0, 0, 72, 40, logo_bits); } while(u8g2.nextPage());. The U8g2 library supports both horizontal and vertical bitmaps with drawXBMP for XBM format.
Step 4: Optimize for Performance
If your logo is static, you can store it in PROGMEM to save RAM on AVR microcontrollers like Arduino Uno, which has only 2 KB SRAM. Use const unsigned char logo_bits[] PROGMEM = {...}; and read with pgm_read_byte. For ESP32, which has 512 KB SRAM, this isn’t necessary. The I2C speed can be increased to 1 MHz on some chips (e.g., ESP32 with Wire.setClock(1000000)), reducing the transfer time from 3.2 ms to 1.28 ms. However, the SSD1306 max I2C clock is 400 kHz per datasheet, but many clones work at 800 kHz. Test stability. If you need to display multiple logos, create a struct array: typedef struct { const unsigned char *data; uint8_t width; uint8_t height; } Logo; then loop through. For animations, use double buffering: draw to a buffer in RAM, then update the display with display.drawBitmap() or u8g2.sendBuffer(). The 360-byte buffer takes 0.36 KB, leaving 1.64 KB on Uno for other tasks.
Step 5: Troubleshoot Common Issues
If the logo appears mirrored or upside down, check the rotation parameter in U8G2_R0 (0 degrees) vs U8G2_R2 (180 degrees). The SSD1306 can also be rotated via display.setRotation(2) in Adafruit library. If pixels are off, confirm the bit order: some tools output MSB first (top pixel is bit 7), others LSB first. The SSD1306 expects MSB first for vertical mode. If the logo is too small, pad it with zeros: for a 72x40 display, a 32x32 logo can be centered at (20, 4) using display.drawBitmap(20, 4, logo32x32, 32, 32, WHITE). The coordinates are (x, y), where x is column (0-71) and y is row (0-39). The display’s active area is 72x40, but the controller’s buffer is 128x64, so you can place the logo anywhere in that 128x64 space. For example, display.drawBitmap(10, 5, logo_bits, 72, 40, WHITE) will draw at column 10, row 5, but the logo will be clipped if it exceeds 72 columns or 40 rows. The clipping is automatic—the library ignores pixels outside the display bounds.
Step 6: Reduce Power Consumption
The OLED draws about 20 mA at 3.3V when all pixels are on, but a logo with 50% white pixels draws about 10 mA. You can use display.ssd1306_command(SSD1306_DISPLAYON); and SSD1306_DISPLAYOFF to save power. The display’s contrast is adjustable via display.ssd1306_command(SSD1306_SETCONTRAST); display.ssd1306_command(0x7F); (0x00 to 0xFF, default 0x7F). Lower contrast reduces current draw by about 10-20%. For battery-powered projects, use sleep mode: display.ssd1306_command(SSD1306_DISPLAYOFF); and wake with SSD1306_DISPLAYON. The sleep current is under 10 µA. If you’re using an ESP32, you can also put the MCU to deep sleep, waking every few seconds to update the logo. The display retains its buffer in sleep, so the logo stays visible without drawing power from the MCU—just the OLED’s internal charge pump, which is about 1 µA in sleep.
Step 7: Real-World Example with Data
Let’s say you want to display a company logo that’s a 72x40 pixel monochrome image of a gear icon. You convert it using Image2Lcd with settings: 72x40, vertical, MSB first, output as C array. The array is 360 bytes. On an Arduino Uno at 16 MHz, the I2C transfer takes 3.2 ms, plus library overhead of about 1 ms, total 4.2 ms per update. If you update at 60 Hz (16.6 ms per frame), that’s 25% of CPU time, leaving room for sensor reading. On an ESP32 at 240 MHz, the same transfer takes 1.28 ms at 1 MHz I2C, and the CPU overhead is negligible. The display’s refresh rate is 100 Hz typical, but the SSD1306’s internal oscillator runs at 500 kHz, so the actual pixel update time is about 1.5 ms for a full frame. For a static logo, one update is enough. For scrolling text, you’d shift the bitmap by 1 pixel per frame, requiring 72 frames to scroll across, total 72 * 4.2 ms = 302 ms on Uno, smooth enough for a ticker.
Step 8: Alternative Methods
If you don’t want to use a bitmap, you can draw the logo using primitive shapes: display.drawCircle(36, 20, 10, WHITE); for a circle, display.fillRect(10, 10, 20, 20, WHITE); for a square. But for complex logos, bitmaps are faster. You can also use the Sprite class in U8g2 to draw multiple logos in a framebuffer, then flush to the display. The sprite size is limited by RAM: on Uno, a 72x40 sprite takes 360 bytes, which is fine. On ESP32, you can have multiple sprites. For example, create a sprite for a logo and another for text, then combine them with u8g2.drawSprite(). The U8g2 library supports up to 8 sprites, each with its own position and clipping. The sprite data is stored in PROGMEM or RAM, and you can animate them by changing the sprite’s position each frame. The performance is similar to bitmap drawing, but sprites allow hardware-accelerated scrolling on some OLED controllers (e.g., SSD1306 supports horizontal scrolling via command 0x26 or 0x27). For example, display.ssd1306_command(0x26); display.ssd1306_command(0x00); display.ssd1306_command(0x07); scrolls the entire display horizontally by 1 pixel per frame, but this only works for the whole screen, not a logo. For a logo-specific scroll, you’d need to redraw the bitmap at a new x position.
Step 9: Verify with Oscilloscope or Logic Analyzer
To ensure the I2C communication is correct, probe the SDA and SCL lines with a logic analyzer. The I2C packet for a write to the SSD1306 starts with the address byte (0x3C << 1 | 0 = 0x78), then a control byte (0x40 for data, 0x00 for command), then the data bytes. For a 360-byte logo, you’ll see 360 data bytes after the control byte. The clock frequency should be 400 kHz, with each byte taking 9 clock cycles (8 data + 1 ACK). The total time for 360 bytes is 360 * 9 / 400,000 = 8.1 ms, but the library may send multiple packets. The Adafruit library sends 16 bytes per packet, so 23 packets, each with 2 bytes overhead (address + control), total 0.58 ms overhead, plus 8.1 ms data = 8.68 ms. This matches the 3.2 ms estimate? Wait, recalc: 360 bytes at 400 kHz = 360 * 8 / 400,000 = 7.2 ms, plus 23 * 9 bits for address/control = 207 bits / 400,000 = 0.52 ms, total 7.72 ms. The earlier 3.2 ms was for 360 bytes at 1 MHz, which is 2.88 ms data + 0.21 ms overhead = 3.09 ms. So at 400 kHz, it’s 7.72 ms, which is still fine for static logos. If you need faster updates, use SPI mode, which can run at 10 MHz, reducing the transfer to 360 * 8 / 10,000,000 = 0.288 ms, plus overhead. But the SPI version of the 72x40 OLED is less common—most are I2C. Check the product page for your specific display: the 0.42 inch 72x40 oled display uses I2C, so stick with that.
Step 10: Advanced Technique – Inverse and XOR
You can invert the logo by using display.drawBitmap(0, 0, logo_bits, 72, 40, BLACK); if the library supports it, or XOR the buffer: display.xorDrawBitmap(0, 0, logo_bits, 72, 40); in U8g2. This flips pixels that are on to off and vice versa, useful for highlighting. For example, if you have a white logo on black background, XOR draws it as black on white. The SSD1306 also supports inverse display via command 0xA7 (normal) and 0xA6 (inverse), which flips the entire screen. For a partial inverse, you’d need to modify the buffer. The buffer is stored in RAM on the microcontroller, so you can do bitwise operations: for (int i = 0; i < 360; i++) buffer[i] ^= 0xFF; then redraw. This is fast on a 16 MHz MCU: 360 iterations take about 0.1 ms. The OLED’s internal buffer is updated only when you call display.display(), so you can batch changes.
Step 11: Handling Multiple Logos in a Menu
If you’re building a menu system with multiple logos, store each