To program a 72x40 OLED in C++, you need to interface with a monochrome graphic display that typically uses the SSD1306 or SH1106 driver over I2C or SPI. The specific model, like the 0.42 inch 72x40 oled display, operates at a resolution of 72 columns by 40 rows, which is non-standard compared to the more common 128x64 or 128x32 OLEDs. This means you cannot directly use generic libraries without adjusting the buffer size and initialization sequence. The display communicates via I2C, usually at address 0x3C or 0x3D, with a default clock speed of 100 kHz, though you can push it to 400 kHz in fast mode if your microcontroller supports it. The driver IC expects commands and data bytes sent in a specific order: first a control byte (0x00 for commands, 0x40 for data), then the payload. For C++ on embedded platforms like Arduino, ESP32, or STM32, you write a custom class that handles the I2C bus, manages a frame buffer of 72 * 40 / 8 = 360 bytes (since each pixel is 1 bit), and sends updates via page addressing mode. The OLED's physical layout is 72 segments (columns) and 4 pages (each page is 8 rows high), so you map pixels in memory as a 2D array of bytes, where each byte represents 8 vertical pixels. For example, to set pixel (x, y) to white, you do buffer[x + (y / 8) * 72] |= 1 << (y % 8). To clear it, you AND with a mask. The initialization sequence for the SSD1306-based 72x40 display involves sending specific commands: 0xAE (display off), 0xD5 (set oscillator frequency), 0x80 (default), 0xA8 (set multiplex ratio), 0x27 (40 rows - 1 = 39 in hex), 0xD3 (set display offset), 0x00, 0x40 (set start line to 0), 0x8D (enable charge pump), 0x14, 0x20 (set memory addressing mode), 0x00 (horizontal mode), 0xA1 (set segment remap to column 127, but for 72x40 you might need to adjust), 0xC8 (COM scan direction), 0xDA (set COM pins), 0x12, 0x81 (set contrast), 0xCF (default), 0xD9 (set pre-charge period), 0xF1, 0xDB (set VCOMH deselect level), 0x40, 0xA4 (display on resume), 0xA6 (normal display), 0x2E (deactivate scroll), 0xAF (display on). However, the 72x40 panel might use a variant driver like SH1106, which has a different RAM layout—SH1106 uses 132 columns internally, so you must set the column start address to 32 (0x20) to center the 72 columns. Data from DisplayModule's datasheet for the 0.42 inch model indicates the active area is 18.5 mm x 10.2 mm, with a pixel pitch of 0.257 mm, and the I2C interface requires pull-up resistors of 4.7 kΩ on SDA and SCL lines. Power consumption is around 20 mA at 3.3V when all pixels are on, but typical usage with text draws 5-10 mA. In C++, you can optimize the buffer update by only sending changed bytes using dirty rectangle tracking—maintain a boolean array of 360 flags, set true when a byte is modified, then in the refresh loop, iterate through pages and send only non-zero rows. For fonts, you need a custom bitmap font table since standard 5x7 fonts assume 8-pixel height; for 40 rows, you can fit 5 lines of 8-pixel tall characters or 8 lines of 5-pixel tall characters. A common approach is to store font data as PROGMEM (on AVR) or in flash (on ESP32) to save RAM, using an array of 5 bytes per character for 5x7 fonts. For example, the letter 'A' in 5x7 is {0x7E, 0x11, 0x11, 0x11, 0x7E} (columns from left to right). To draw a character at (x, y), you iterate over 5 columns and for each column, read the byte and shift it to align with the y offset. If y is not a multiple of 8, you need to handle partial bytes by splitting the pixel data across two pages. This is where performance matters—on an 8-bit Arduino Uno at 16 MHz, updating the entire 360-byte buffer via I2C takes about 4 ms at 400 kHz, but if you draw many small shapes, the overhead of I2C transactions adds up. For an ESP32 at 240 MHz, you can use the I2C library's non-blocking mode or even use SPI if your display supports it (some 72x40 OLEDs come in SPI variants with CS, DC, and RES pins). The SPI version uses 4-wire protocol: CS (chip select), DC (data/command), RES (reset), and SCLK/MOSI. The initialization sequence for SPI is similar but you must toggle DC and CS manually. In C++, you can abstract the interface with a base class that has virtual methods like sendCommand(uint8_t cmd) and sendData(uint8_t data), then derive I2C and SPI implementations. For the 0.42 inch model, the I2C address is fixed at 0x3C, but you can change it by ordering a different variant. One tricky part is the column mapping: the SSD1306 driver expects a 128-column RAM, but your display only has 72 columns visible. You must set the column start and end addresses via commands 0x21 (set column address) with parameters 0x00 and 0x47 (72-1 = 71 in decimal, 0x47). For page address, use 0x22 with 0x00 and 0x03 (4 pages). If you use the Adafruit SSD1306 library, it assumes 128x64 by default, so you need to modify the buffer size and the initialization code. Instead, writing your own driver gives you full control. For example, on an STM32 with HAL library, you can use HAL_I2C_Mem_Write to send commands and data. The C++ code might look like this: class OLED72x40 { private: uint8_t buffer[360]; TwoWire &wire; uint8_t addr; public: void init() { sendCommand(0xAE); sendCommand(0xD5); sendCommand(0x80); // ... rest of init } void setPixel(uint8_t x, uint8_t y, bool on) { if (x >= 72 || y >= 40) return; uint16_t idx = x + (y / 8) * 72; if (on) buffer[idx] |= (1 << (y % 8)); else buffer[idx] &= ~(1 << (y % 8)); } void display() { for (uint8_t page = 0; page < 4; page++) { sendCommand(0xB0 + page); sendCommand(0x00); // low column nibble sendCommand(0x10); // high column nibble (usually 0x10 for start) for (uint8_t col = 0; col < 72; col++) { sendData(buffer[col + page * 72]); } } } }. Note that the column low and high nibble commands set the start column pointer; for 72x40, you set low to 0 and high to 0x10 (which means column 0). But some displays require an offset—check the datasheet. The SH1106 driver, for instance, has a 132-column RAM and you need to set the column start to 32 via command 0x21 with parameters 32 and 103 (since 32+72-1=103). Another detail: the OLED's contrast is controlled by command 0x81 followed by a value from 0x00 to 0xFF. At 0x00, the display is off; at 0xFF, it's max brightness. For battery-powered devices, set contrast to 0x40 to save power. The charge pump command 0x8D with 0x14 enables it; without it, the display stays blank. In terms of real-world performance, a 72x40 OLED can update at 60 Hz if you use double buffering and only send changed pages. For a weather station display, you might update temperature every 10 seconds, so a simple blocking I2C write is fine. But for animations, you need to optimize. One technique is to use DMA on STM32 or ESP32 to send I2C data in the background. On ESP32, you can use the i2c_master_write_raw function with a buffer of 360+1 bytes (the first byte is 0x40 for data mode). The I2C transaction time for 361 bytes at 400 kHz is about 9 ms, leaving 16 ms for other tasks at 60 Hz. For text rendering, you can pre-render strings into a separate buffer and blit them onto the frame buffer using bitwise OR operations. For example, to draw a string at (10, 0), you copy the font bitmaps into buffer[10 + (0/8)*72] etc. If you use a proportional font, you need a lookup table for character widths. The 72-pixel width limits you to 14 characters of a 5x7 font (with 1 pixel spacing) per line, or 12 characters of a 6x8 font. For a 40-pixel height, you can fit 5 lines of 8-pixel tall fonts with 0 spacing, or 4 lines with 2-pixel spacing. A practical example: a clock display showing time in 24-hour format uses 6 digits (HH:MM) with a colon, each digit 6 pixels wide, total 42 pixels, centered on the screen. You can draw the colon by setting a few pixels at the midpoint. For graphics, like a battery icon, you define a 16x8 bitmap and use a nested loop to set pixels. The C++ code for drawing a bitmap is: for (uint8_t y=0; y<8; y++) { for (uint8_t x=0; x<16; x++) { if (bitmap[y*2 + x/8] & (1 << (7 - x%8))) setPixel(x0+x, y0+y, true); } }. This assumes the bitmap is stored as 2 bytes per row (16 pixels). Another aspect is power management: the OLED can be put to sleep via command 0xAE, and woken up with 0xAF. The sleep current is less than 1 µA, while active is 5-20 mA depending on pixels on. For a battery-powered sensor node, you can turn off the display between readings. The I2C bus must be pulled high, so if you share the bus with other devices, ensure the OLED doesn't interfere. On the hardware side, the 0.42 inch 72x40 OLED typically comes with a 4-pin header (VCC, GND, SDA, SCL) for I2C, or 6-pin for SPI. The logic voltage is 3.3V, but some modules have a built-in regulator that accepts 5V. Check the datasheet: the absolute maximum rating for VCC is 3.6V, so using 5V without a regulator can damage the display. For C++ programming on Arduino, you need to include Wire.h and use Wire.begin() with your SDA/SCL pins (default on Uno is A4/A5). On ESP32, you can specify any pins: Wire.begin(21, 22) for SDA and SCL. The I2C clock speed is set with Wire.setClock(400000L). For STM32, use HAL_I2C_Init with Timing register calculated for 400 kHz. One common bug is forgetting to set the I2C address shift: the 7-bit address 0x3C becomes 0x78 for 8-bit write (left-shifted by 1). In Arduino Wire library, you use Wire.beginTransmission(0x3C) and it handles the shift. For the 72x40 display, the page addressing mode is the simplest, but you can also use horizontal or vertical addressing modes. In horizontal mode, after each data byte, the column pointer increments automatically, wrapping to next page. This allows you to send the entire buffer in one burst: send command 0x20, 0x00 (horizontal), then set column range 0x21, 0x00, 0x47, and page range 0x22, 0x00, 0x03, then send 360 data bytes. This is faster than per-page loops because it reduces command overhead. The total I2C transaction becomes: start, control byte 0x00, command 0x21, 0x00, 0x47, command 0x22, 0x00, 0x03, then restart, control byte 0x40, then 360 data bytes. On Arduino, you can do this with Wire.write() in a single transaction. The timing: at 400 kHz, 360 bytes take 9 ms, plus 1 ms for commands, total 10 ms per frame. For a smooth 60 fps, you need 16.6 ms per frame, so you have 6.6 ms for drawing. If you draw complex shapes, you might need to reduce frame rate to 30 fps. For the SH1106 variant, the column range is different: you set start column 32, end column 103. Also, the SH1106 does not support hardware scrolling like the SSD1306, but you can implement software scrolling by shifting the buffer. The 72x40 resolution is often used in small wearables or smart home displays where space is limited. For example, a fitness tracker might show steps, heart rate, and time on three lines. The font size can be 8x8 for clarity, but you only get 9 characters per line. A better approach is to use a 6x8 font to fit 12 characters. For icons, you can use 16x16 bitmaps for a heart rate symbol. The C++ code for drawing a heart icon involves setting pixels in a pattern: for (y=0; y<16; y++) for (x=0; x<16; x++) if (heart[y*2 + x/8] & (1 << (7 - x%8))) setPixel(x, y, true);. The heart bitmap can be stored as 32 bytes (16 rows * 2 bytes per row). Another consideration is the display orientation. The SSD1306 allows flipping the display via commands 0xA0 (segment remap) and 0xC0 (COM scan direction). For landscape mode, you set 0xA1 (remap columns) and 0xC8 (reverse COM). But for portrait mode, use 0xA0 and 0xC0. The 72x40 OLED is usually mounted in landscape orientation, but you can rotate it 180 degrees with commands 0xA1 and 0xC8. If you need to mirror, use 0xA0 and 0xC0. The physical pixel arrangement is row 0 to row 39 from top to bottom, and column 0 to 71 from left to right. In the buffer, column 0 corresponds to the leftmost column. For the SH1106, the column mapping is offset by 32, so you might need to adjust your drawing coordinates. A common mistake is to draw at x=0 and see nothing because the display starts at column 32. To fix, either set the column start to 0 via command 0x21 with 0x00 and 0x47, but some SH1106 ignore that and require hardware offset. The datasheet for the 0.42 inch 72x40 OLED from DisplayModule specifies that the SH1106 variant has a 132x64 RAM but only 72x40 is visible, so you must set the display start line to 0 and column offset to 32. The command sequence for SH1106 initialization includes: 0xAE, 0xD5, 0x80, 0xA8, 0x27, 0xD3, 0x00, 0x40, 0x8D, 0x14, 0x20, 0x00, 0xA1, 0xC8, 0xDA, 0x12, 0x81, 0xCF, 0xD9, 0xF1, 0xDB, 0x40, 0xA4, 0xA6, 0x2E, 0xAF. Then set column address range via 0x21 with 32 and 103. For page address, 0x22 with 0 and 3. This maps the 72 columns to the center of the 132-column RAM. If you don't set the offset, the display will show garbage or be blank. In C++, you can encapsulate this in a function initSH1106(). For the SSD1306 variant, the column range is 0 to 71. To detect which driver you have, you can read the display's ID register (if supported) or just try both initialization sequences. Some 72x40 OLEDs use the SSD1306B or SSD1306Z which have slightly different command sets. The 0.42 inch model typically uses SSD1306 for I2C and SH1106 for SPI, but always check the product page. For the I2C version, the address can be 0x3C or 0x3D depending on the RST pin level. If you have multiple OLEDs on the same bus, you can use a multiplexer like TCA9548A. For C++ on a Raspberry Pi Pico, you can use the Pico SDK's I2C functions with a 256-byte FIFO. The Pico's I2C can run at 1 MHz, but the OLED's maximum is 400 kHz, so set the baudrate accordingly. The code: i2c_init(i2c0, 400000); gp