How to use a 0.96 inch OLED with a Raspberry Pi Pico?

By admin

How to use a 0.96 inch OLED with a Raspberry Pi Pico

You hook up a 0.96 inch OLED to a Raspberry Pi Pico by wiring the I2C lines (SDA and SCL) to the correct GPIO pins, installing the MicroPython firmware on the Pico, and then flashing a driver library like the ssd1306.py along with a simple script that initializes the display and sends pixel data. The most common OLED module you will encounter is the SSD1306-based 128x64 monochrome display, which communicates over I2C at a default address of 0x3C (or sometimes 0x3D). The Pico has two I2C peripherals: I2C0 on GP0 (SDA) and GP1 (SCL), and I2C1 on GP2 (SDA) and GP3 (SCL). You can use either, but I2C0 is the default in most tutorials. The display itself runs on 3.3V logic, which matches the Pico’s GPIO voltage perfectly, so you don’t need any level shifters. The OLED requires about 20mA during operation, well within the Pico’s 3.3V regulator output (which can supply up to 300mA). The display module typically has four pins: VCC (3.3V), GND, SDA, and SCL. Some breakout boards include an extra RESET pin, but you can leave it unconnected or tie it to 3.3V through a 10kΩ resistor if you want manual reset control. The I2C bus speed should be set to 400kHz (fast mode) for smooth updates, though the default 100kHz works fine for static text. The Pico’s RP2040 microcontroller has a 133MHz clock speed, so it can push data to the OLED at several frames per second even with a 128x64 buffer size of 1024 bytes (1KB). The SSD1306 controller inside the OLED has its own internal RAM of 128x64 bits, which is exactly 1KB. You write to this RAM through I2C commands, and the controller automatically refreshes the pixels at 100Hz or more, so you don’t need to worry about persistence of vision. The display’s contrast is adjustable via a command register, with a default value of 0x7F (127 decimal). You can increase it up to 0xFF (255) for brighter pixels, but the trade-off is slightly higher power consumption. The OLED’s lifetime is rated at 50,000 hours for typical use, with a brightness degradation of about 30% after that period. The viewing angle is 160 degrees, which is far superior to LCDs of the same size. The response time is under 10 microseconds, so you can display fast-moving data like scrolling text or simple animations without ghosting. The operating temperature range is -40°C to +85°C, making it suitable for outdoor projects. The display module itself weighs about 3 grams, so it adds negligible mass to your Pico setup. The physical dimensions of the OLED glass are 26.7mm x 19.26mm x 1.45mm, with the PCB breakout board adding about 2mm to the thickness. The total footprint is roughly 27mm x 20mm, which fits perfectly on a breadboard alongside the Pico. The Pico’s GPIO pins are 2.54mm pitch, and the OLED header pins are usually the same spacing, so you can use standard jumper wires or a small perfboard for a permanent connection. The I2C bus on the Pico supports multiple devices, so you can chain other sensors like a BME280 temperature/humidity sensor or an MPU6050 accelerometer on the same two wires, as long as they have unique addresses. The SSD1306’s I2C address is 0x3C for most modules, but some Chinese clones use 0x3D. You can scan the bus using a MicroPython script to confirm the address before coding. The I2C pull-up resistors are usually included on the OLED breakout board (4.7kΩ to 10kΩ), so you don’t need to add external ones. The Pico’s internal pull-ups are weak (50kΩ), so they won’t interfere. The maximum I2C bus capacitance is 400pF for 400kHz operation, and the OLED module adds about 10pF, so you can run long wires up to 1 meter without issues. The display’s pixel layout is column-major, meaning the first byte in the buffer corresponds to the top-left 8-pixel column. The SSD1306 supports horizontal, vertical, and page addressing modes, but page mode is the simplest for beginners. In page mode, you set the column and page start addresses, then send 128 bytes for one page (8 pixels tall). There are 8 pages total (128x64 / 8 = 8 pages). To draw a pixel at (x, y), you calculate the page number as y // 8, the bit position as y % 8, and then set that bit in the buffer. The buffer is a bytearray of size 1024. You update the entire display by sending the buffer over I2C in one shot, which takes about 2.5ms at 400kHz. For partial updates, you can set the column and page range to only update a region, which is useful for animations or text scrolling. The Pico’s DMA controller can handle the I2C transfer without CPU intervention, but MicroPython doesn’t expose that easily, so you’ll use the machine.I2C class. The Pico’s flash memory is 2MB, and the MicroPython firmware takes about 256KB, leaving plenty of space for your scripts and fonts. You can store custom fonts as bytearrays in flash, with each character defined as a 5x7 or 8x8 pixel matrix. A 5x7 font for all ASCII characters (95 characters) takes about 475 bytes. For Chinese characters, you’d need a much larger font file, but the 128x64 resolution can only display about 8 Chinese characters per line (using 16x16 font), so it’s best for simple text or graphics. The Pico’s RAM is 264KB, so you can easily hold multiple framebuffers for double-buffering, which eliminates tearing. Double-buffering uses two 1KB buffers: you draw to one while the other is being sent to the display, then swap. This is overkill for most projects, but useful for games or fast data visualization. The I2C bus on the Pico has a maximum speed of 1MHz in theory, but the SSD1306 datasheet specifies 400kHz max, so stick with that. The Pico’s default I2C frequency is 100kHz, which you can change using the freq parameter in the machine.I2C constructor. The OLED’s power consumption is about 20mA with all pixels on, and 0.1mA in sleep mode. You can put the display to sleep using the SSD1306_DISPLAYOFF command (0xAE), which saves power for battery-powered projects. The wake-up time from sleep is about 100ms, so you can’t toggle it rapidly. The display’s internal charge pump generates the 7V to 15V needed for the OLED pixels from the 3.3V supply. The charge pump efficiency is about 80%, so the 20mA at 3.3V translates to roughly 6.6mA at 10V for the OLED itself. The Pico’s 3.3V regulator can handle this easily. The display’s lifetime is affected by the pixel brightness: running at full contrast (0xFF) reduces the lifetime by about 20% compared to the default 0x7F. For most indoor use, 0x7F is plenty bright. The OLED’s contrast ratio is 2000:1, which means black pixels are truly off (no light), while LCDs have a backlight that bleeds through. This makes the OLED ideal for dark environments. The pixel size is 0.15mm x 0.15mm, with a pitch of 0.17mm, giving a pixel density of about 149 PPI. The display’s refresh rate is controlled by the internal oscillator, which runs at 450kHz to 500kHz. The frame rate is about 100Hz, meaning the display refreshes the pixels 100 times per second even if you don’t send new data. This is transparent to the user. The SSD1306 supports horizontal scrolling, vertical scrolling, and page scrolling through built-in commands. You can set the scroll direction, speed, and start/stop pages. This is useful for marquee text without CPU overhead. The scroll speed is set by a 5-bit value (0 to 31), with 0 being the fastest (about 2 frames per step) and 31 being the slowest (about 64 frames per step). The scroll effect is smooth and uses no extra RAM. The display also supports a “charge pump” setting that can be disabled for external voltage supply, but the default is fine for 3.3V. The I2C interface uses a 7-bit address, so the address byte is 0x3C << 1 = 0x78 for write operations. The read address is 0x79. The SSD1306 supports both write and read commands, but reading is rarely used because the display is write-only for most applications. The Pico’s I2C hardware handles the address and data framing automatically. The MicroPython ssd1306 library from the official repository is about 200 lines of code. It includes functions for pixel, hline, vline, rect, fill_rect, text, and scroll. The text function uses a built-in 8x8 font, but you can replace it with a custom font by modifying the framebuffer. The library’s performance is adequate: drawing a full screen of text (16 lines of 8 characters each) takes about 10ms, including the I2C transfer. For graphics, drawing a filled rectangle takes about 1ms for the buffer update plus 2.5ms for the transfer. The library uses a framebuffer in the Pico’s RAM, so you can draw off-screen and then blit to the display. The framebuffer is a bytearray of 1024 bytes, which is about 0.4% of the Pico’s total RAM. You can allocate multiple framebuffers if needed, but one is sufficient for most tasks. The Pico’s temperature sensor is internal, so you can read the die temperature and display it on the OLED. The sensor has an accuracy of ±2°C and a resolution of 0.1°C. You can also connect external sensors via the remaining GPIO pins. The Pico has 26 GPIO pins, and you only use 2 for the OLED, leaving 24 for other peripherals. The OLED’s I2C bus can be shared with other devices, but you must ensure the addresses don’t conflict. The SSD1306’s address can be changed by soldering a resistor on the back of the module, but most modules are fixed. The display’s contrast can be set per session, but the default is stored in the module’s non-volatile memory? No, the SSD1306 does not have non-volatile memory for contrast; it resets to 0x7F on power-up. You must set it in your initialization code. The initialization sequence for the SSD1306 is: turn off display, set mux ratio (64 for 128x64), set display offset (0), set start line (0), set segment remap (0 for normal, 1 for mirror), set COM pins hardware config, set contrast, enable charge pump, set display mode (normal), and turn on display. This sequence takes about 10 commands over I2C. The Pico’s MicroPython firmware includes a precompiled ssd1306 module in the latest versions, so you can import it directly without copying the file. Check your firmware version with `import sys; print(sys.version)`. If it’s 1.19 or later, the ssd1306 module is included. If not, download the file from the MicroPython GitHub repo and copy it to the Pico’s flash using Thonny or rshell. The Pico’s flash is accessible as a USB mass storage device when you hold the BOOTSEL button during power-up. You can drag and drop files. The ssd1306.py file is about 5KB, so it takes negligible space. The I2C pins on the Pico are 3.3V tolerant, but the GPIO pins are not 5V tolerant. Make sure you never connect 5V to the SDA or SCL lines. The OLED module’s VCC pin should be connected to 3.3V, not 5V, even though some modules claim 5V compatibility. The SSD1306 has an internal regulator that can handle 3.3V to 5V, but running at 5V increases power consumption and heat. The Pico’s 3.3V output is clean and sufficient. The display’s ground should be connected to the Pico’s GND, preferably the same ground plane to avoid noise. The I2C bus is sensitive to ground loops, so keep wires short. The OLED’s I2C lines have internal pull-ups to VCC, which is 3.3V. If you use a different voltage for the OLED, the pull-ups will be to that voltage, which could damage the Pico’s GPIO if the voltage exceeds 3.3V. So always use 3.3V. The Pico’s I2C pins are GP0 (SDA) and GP1 (SCL) for I2C0, and GP2 (SDA) and GP3 (SCL) for I2C1. You can also use any other GPIO pair by software bit-banging, but the hardware I2C is faster and more reliable. The hardware I2C on the Pico supports clock stretching, which the SSD1306 does not use, so it’s not an issue. The I2C bus speed can be set to 400kHz by passing `freq=400000` to the I2C constructor. The actual speed may be slightly lower due to clock division, but it’s fine. The Pico’s I2C peripheral has a 16-byte FIFO, so you can send up to 16 bytes in one burst without CPU intervention. The ssd1306 library sends the entire 1024-byte buffer in chunks of 128 bytes (one page), so it uses the FIFO efficiently. The transfer time for 128 bytes at 400kHz is about 1.3ms (128 bytes * 9 bits per byte / 400kHz = 2.88ms including start/stop, but actual measurements show ~2.5ms). The total time to update the full display is about 2.5ms for the command plus 2.5ms for the data, so about 5ms. This gives a theoretical frame rate of 200 FPS, but the Pico’s CPU overhead reduces it to about 100 FPS in practice. The OLED’s internal refresh rate is 100Hz, so there’s no benefit to updating faster than 10ms. The display’s persistence of vision is about 10ms, so updates at 100Hz look smooth. The Pico’s MicroPython interpreter is slower than C, but for the OLED, it’s fast enough. The main bottleneck is the I2C bus speed, not the CPU. If you need faster updates, use C or the Pico’s PIO (Programmable I/O) to drive the I2C bus. The PIO can run I2C at 1MHz, but the SSD1306 limits to 400kHz. The display’s power consumption can be measured with a multimeter in series with the VCC line. At idle with all pixels off, the display draws about 0.5mA. With all pixels on, it draws 20mA. With a typical mixed pattern (like text), it draws 10mA to 15mA. The Pico itself draws about 25mA at idle, so the total system draw is about 40mA to 50mA. A 2000mAh battery would last about 40 hours. The display’s contrast can be adjusted per pixel by using the “pre-charge” and “deselect” voltage settings, but that’s advanced. The default settings are fine. The display’s segment remap command allows you to mirror the display horizontally, which is useful if you mount the OLED upside down. The COM pins remap allows vertical mirroring. The combination gives you full 180-degree rotation. The display’s memory is organized as 128 columns by 8 pages. Each page is 8 pixels tall. The column address is set by the 7-bit column start and end registers. The page address is set by the 3-bit page start and end registers. The display supports “charge pump” regulation, which you can set to a fixed voltage or a ratio. The default is 0x14 (20 decimal), which is fine. The display’s “VCOMH” deselect level can be set to 0x00 (0.65x VCC), 0x20 (0.77x VCC), or 0x30 (0.83x VCC). The default is 0x20. Changing this can affect contrast uniformity. The display’s “pre-charge” period is set by a 4-bit value (0 to 15), with a default of 0x22 (2 DCLKs for phase 1, 2 DCLKs for phase 2). The “discharge” period is set by a 4-bit value. These are advanced settings that you can tweak to reduce ghosting or improve brightness. The display’s “contrast” is actually the current drive level, not the voltage. The SSD1306 uses a constant current drive, so the brightness is linear with the contrast register. The maximum current per segment is about 100µA, so the total current for all 128 segments is 12.8mA. The display’s “segment” current is set by the contrast register, with 0x7F giving about 50µA per segment. The display’s “common” current is about 10mA total. The power consumption is dominated by the segment current. The display’s “frame rate” is set by the internal oscillator frequency, which is trimmed at the factory. You can adjust it with the “clock divide” and “oscillator frequency” registers, but it’s not recommended. The default oscillator frequency is 450kHz, giving a frame rate of about 100Hz. The display’s “multiplex ratio” is set to 64 for 128x64 displays. This means 64 common lines