How to use a 0.96 inch OLED with an AVR?
How to use a 0.96 inch OLED with an AVR
To use a 0.96 inch 128x64 i2c oled display with an AVR microcontroller, you need to wire the I2C lines (SDA and SCL) to the AVR’s corresponding pins, install a library like the Adafruit SSD1306, and write initialization code. The display is monochrome, uses the SSD1306 driver, and operates at 3.3V or 5V logic, but the AVR’s I2C pins must be pulled up with 4.7kΩ resistors. For an ATmega328P (common on Arduino Uno), SDA is pin A4 and SCL is pin A5. The display’s resolution is 128x64 pixels, meaning you have 8192 pixels to control individually. The I2C address is typically 0x3C or 0x3D, depending on the module’s solder bridge. Power consumption is around 20mA when the display is active, but with the AVR in sleep mode, you can drop to under 1mA for battery projects. The display’s refresh rate can go up to 30 frames per second if you use DMA on some AVRs, but for basic text and graphics, 10-15 FPS is typical. The AVR’s internal oscillator is often 8MHz or 16MHz, and the I2C clock speed should be set to 100kHz or 400kHz; 400kHz works fine with the OLED, but you might need to adjust the AVR’s TWBR register for proper timing. For example, on an ATmega328P at 16MHz, setting TWBR to 12 gives 400kHz I2C. The display’s buffer size is 1024 bytes (128x64/8), which fits in the AVR’s SRAM (2KB on the 328P), but you should avoid using too many other variables to prevent stack overflow. The SSD1306 supports horizontal, vertical, and page addressing modes; page mode is the simplest for AVRs because you write 8-pixel blocks sequentially. The display’s contrast is adjustable via the command 0x81, with values from 0 to 255; 128 is typical for indoor use. The OLED’s lifetime is rated at 10,000 hours for full brightness, but you can reduce brightness to extend it. The module’s pinout is usually: VCC (3.3V or 5V), GND, SCL, SDA. Some modules have a RESET pin, but you can tie it to the AVR’s reset line or a GPIO.
For the AVR side, you need to initialize the I2C peripheral. On an ATmega328P, the TWI (Two-Wire Interface) module is used. Set the TWBR register for your desired clock speed. For 100kHz at 16MHz, TWBR = 72. For 400kHz, TWBR = 12. Then, send the SSD1306 initialization sequence: 0xAE (display off), 0xD5 (set display clock divide ratio), 0x80 (default), 0xA8 (set multiplex ratio), 0x3F (for 64 rows), 0xD3 (set display offset), 0x00, 0x40 (set start line), 0x8D (charge pump), 0x14 (enable), 0x20 (memory addressing mode), 0x00 (horizontal), 0xA1 (segment remap, for left-to-right), 0xC8 (COM output scan direction, top-to-bottom), 0xDA (COM pins hardware configuration), 0x12, 0x81 (contrast), 0xCF (default), 0xD9 (pre-charge period), 0xF1, 0xDB (VCOMH deselect level), 0x40, 0xA4 (display on resume), 0xA6 (normal display, not inverted), 0x2E (deactivate scroll), 0xAF (display on). This sequence is 25 bytes, which you send via I2C write operations. After that, you can send pixel data. The display’s buffer is organized as 8 pages (each page is 8 pixels tall) and 128 columns. So, to set a pixel at (x, y), you calculate the page as y/8 and the bit position as y%8. Then, you read the current byte from the buffer, set the bit, and write it back. This is slow if you do it per pixel, so it’s better to use a full buffer in RAM. The AVR’s 2KB SRAM can hold the 1024-byte buffer, leaving about 1KB for other variables. For graphics, you can use the Adafruit GFX library, which is designed for AVRs and supports lines, circles, text, and bitmaps. The library uses a 1024-byte buffer, and you call display.display() to flush the buffer to the OLED via I2C. The flush takes about 3ms at 400kHz I2C, so you can update the display 30 times per second without blocking the AVR too much. For text, the library includes a 5x7 font, which gives 21 characters per line and 8 lines on the 128x64 display. You can also use custom fonts, but they increase code size. The AVR’s flash memory is 32KB on the 328P, and the library takes about 8KB, leaving room for your application.
Power management is critical for battery-powered AVR projects. The OLED draws 20mA when active, but you can put it in sleep mode by sending 0xAE. The AVR can then enter deep sleep, drawing 0.1µA. To wake up, use an external interrupt on a button or a timer. The display’s wake-up time is about 100ms, so you need to wait after sending 0xAF. You can also reduce the display’s brightness via the contrast register to save power. For example, setting contrast to 10 reduces current to 5mA. The display’s internal charge pump can be disabled for 3.3V operation, but it’s usually enabled. If you use a 5V AVR, the OLED’s logic is 5V tolerant, but the I2C lines need pull-up resistors to 5V. The SSD1306 datasheet specifies a maximum 5V on SDA and SCL. For 3.3V AVRs, use 3.3V pull-ups. The display’s operating temperature range is -40°C to 85°C, so it works in most environments. The module’s PCB is 27.3mm x 27.8mm, and the viewing area is 21.7mm x 10.9mm. The pixel pitch is 0.17mm, so text is readable at 10cm. The contrast ratio is 2000:1, typical for OLEDs. The display’s driver IC is the SSD1306, which has 128x64 bits of RAM. The I2C protocol uses 7-bit addresses, so the display’s address is 0x3C (write) or 0x3D (read). You can change the address by soldering a jumper on the module. Some modules have a second address option.
For advanced use, you can implement double buffering by using two 1024-byte buffers in the AVR’s SRAM. This doubles the RAM usage, but it allows you to draw to one buffer while the other is being sent to the display. On the ATmega328P, 2KB SRAM is not enough for two buffers, so you need a larger AVR like the ATmega2560 (8KB SRAM). Alternatively, you can use the AVR’s EEPROM to store font data, but it’s slow. For animations, you can use the display’s hardware scrolling. The SSD1306 supports horizontal and vertical scrolling. To enable horizontal scrolling, send 0x26 (right scroll) or 0x27 (left scroll), followed by parameters for start page, end page, and speed. The speed is set by the frame rate: 0x00 for 2 frames, 0x01 for 3 frames, up to 0x07 for 128 frames. This is useful for scrolling text without CPU overhead. The display also supports vertical scrolling with 0x29. You can combine both for diagonal scrolling. The AVR can set up the scroll and then go to sleep, saving power. The display’s internal oscillator generates the timing, so the AVR doesn’t need to refresh the buffer. The scroll stops when you send 0x2E. For bitmaps, you can store them in the AVR’s flash memory using PROGMEM. For example, a 128x64 bitmap is 1024 bytes, which fits in flash. You can use the Adafruit library’s drawBitmap() function to display it. The library uses the AVR’s pgm_read_byte() to read from flash. This is slower than reading from RAM, but it saves SRAM. The flash read speed is about 1µs per byte, so a full bitmap transfer takes 1ms. The I2C transfer adds another 3ms, so total update time is 4ms. For multiple bitmaps, you can store them in flash and switch between them. The AVR’s flash is 32KB, so you can store 30 full-screen bitmaps. For text, you can use the library’s setTextSize() function to scale the font. Size 1 gives 5x7 pixels, size 2 gives 10x14, and so on. The display’s resolution limits text to size 4 (20x28 pixels) for a single character. You can also use the library’s setTextWrap() to wrap text to the next line. The library supports rotation via setRotation(), which rotates the display 0, 90, 180, or 270 degrees. This is done by remapping the buffer coordinates, so no extra RAM is needed. The rotation is handled in software, so it adds a small overhead to the draw functions.
For sensor data display, you can use the AVR’s ADC to read analog sensors and display the values. For example, a temperature sensor like the LM35 outputs 10mV per degree Celsius. The AVR’s ADC reads it as a 10-bit value. You can convert it to a string and display it using the library’s print() function. The display’s text functions are in the Adafruit GFX library, which uses the AVR’s sprintf() for formatting. This uses about 1KB of flash for the library. For graphs, you can draw lines using drawLine() or fillRect(). The library’s fillRect() is efficient for bar graphs. For example, a bar graph of 10 bars can be updated in 1ms. The display’s contrast can be adjusted per frame to create fading effects. Send 0x81 followed by the contrast value. The AVR can change the contrast in real-time. For user input, you can use buttons connected to the AVR’s GPIO. The AVR can debounce the buttons in software with a 50ms delay. The display can show menus with multiple pages. Each page is a different buffer state. You can use the AVR’s timer to update the display at a fixed rate. For example, set Timer1 to overflow every 100ms, and in the ISR, call display.display(). This ensures smooth updates. The display’s I2C bus can be shared with other devices, like an RTC or EEPROM. Each device has a unique address, so the AVR can communicate with them sequentially. The I2C bus speed must be the same for all devices. If you use a 400kHz bus, the OLED and other devices must support it. The AVR’s TWI module can handle multiple masters, but for simplicity, use a single master. The display’s SDA and SCL lines are open-drain, so the pull-up resistors are essential. The value of the pull-up resistors depends on the bus capacitance. For a 10cm bus, 4.7kΩ works. For longer buses, use 2.2kΩ. The AVR’s internal pull-ups are 20kΩ, which are too weak for I2C. Always use external resistors. The display’s power supply can be a 3.3V regulator from a 5V source. The AVR’s power consumption is about 5mA at 16MHz, so total system current is 25mA. For battery operation, use a 3.7V LiPo battery with a voltage regulator. The display’s charge pump can handle 3.3V to 5V input. If you use a 3.3V AVR, the display’s logic is 3.3V, so no level shifting is needed. For 5V AVRs, the display is 5V tolerant, but the I2C lines must be pulled up to 5V. The display’s maximum input voltage is 5.5V, so it’s safe. The display’s driver IC has a built-in DC-DC converter, so it can generate the 7V needed for the OLED pixels. The converter’s efficiency is about 80%. The display’s brightness is controlled by the contrast register and the pre-charge period. The pre-charge period is set by 0xD9, with values from 0x00 to 0xFF. Higher values increase brightness but also power consumption. The default is 0xF1. For low power, set it to 0x10. The display’s VCOMH deselect level is set by 0xDB, with values 0x00, 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0, 0xE0. The default is 0x40. This affects the pixel’s off-state voltage. For better contrast, use 0x80. The display’s multiplex ratio is set by 0xA8, with values from 0x0F to 0x3F. For 64 rows, use 0x3F. For a smaller display, you can reduce the ratio to save power. The display’s start line is set by 0x40, which can be from 0x00 to 0x3F. This allows you to shift the display vertically. The segment remap and COM scan direction allow you to flip the display. For example, 0xA0 and 0xC0 give a normal orientation. 0xA1 and 0xC8 give a flipped orientation. This is useful for mounting the display upside down. The display’s memory addressing mode is set by 0x20, with 0x00 for horizontal, 0x01 for vertical, and 0x02 for page. Page mode is the default for many libraries. In page mode, the display’s RAM is organized as 8 pages of 128 bytes. Each page corresponds to 8 rows of pixels. To write to a specific pixel, you set the page and column. The library handles this automatically. For custom graphics, you can use the display’s hardware acceleration for drawing lines and circles. The SSD1306 does not have hardware drawing, so it’s all software. The AVR’s performance is limited, so complex graphics may be slow. For example, drawing a circle takes about 100µs. For animations, pre-render frames in flash and display them sequentially. The AVR’s flash read speed is fast enough for 30 FPS. The display’s I2C speed is the bottleneck. At 400kHz, the maximum transfer rate is 50KB/s. A full frame is 1024 bytes, so the maximum frame rate is 50 FPS. In practice, the library overhead reduces it to 30 FPS. For text, the library uses a 5x7 font, which is stored in flash. The library’s print() function iterates over each character and draws it as a bitmap. This takes about 1ms per character. For a full screen of text (21 characters per line, 8 lines), the total time is 168ms, which is 6 FPS. To speed up text, use a larger font or pre-render the text as a bitmap. The library supports custom fonts via the setFont() function. You can store fonts in flash as arrays. The font data includes the character width, height, and bitmaps. The library’s drawChar() function reads the bitmap and draws it. For a 16x32 font, each character is 64 bytes, so a full screen of 8 characters takes 512 bytes. The display’s resolution limits the number of characters. For a 16x32 font, you can fit 8 characters per line and 2 lines. The library’s setCursor() function positions the text. The display’s rotation affects the coordinate system. In rotation 0, the origin is top-left. In rotation 90, the origin is top-right. The library handles this internally. For touch input, you can use a resistive touch panel over the display. The touch panel has four wires: X+, X-, Y+, Y-. The AVR reads the analog voltages from the touch panel using the ADC. The touch coordinates are mapped to the display’s coordinates. This is a common setup for menu systems. The touch panel’s resolution is 8-bit, so it’s accurate enough for button selection. The AVR’s ADC can read the touch panel in 100µs. The display’s update rate is 30 FPS, so the touch response is fast. For wireless communication, you can add an nRF24L01 module to the AVR. The AVR communicates with the nRF24L01 via SPI. The display can show the received data. The nRF24L01’s data rate is 2Mbps, so it’s fast enough for real-time data. The AVR’s SPI clock can be set to 8MHz. The display’s I2C bus is separate from the SPI bus, so no conflict. The AVR’s GPIO pins are limited on the ATmega328P, so you may need to use a multiplexer. For example, use a 74HC595 shift register to expand the outputs. The display’s I2C address is fixed, so it doesn’t conflict with other I2C devices. The AVR’s TWI module can handle up to 128 devices. The display’s power consumption is a concern for wireless projects. The nRF24L01 draws 13mA in transmit mode.