How to display a graph on a 0.96 inch OLED display?
To display a graph on a 0.96 inch OLED display, you need to interface a microcontroller like an Arduino or ESP32 with the display module, typically using SPI or I2C communication, and then render pixel data for the graph. The 0.96 inch 128x64 spi i2c oled display is a common choice because it offers a 128x64 pixel resolution, which is sufficient for basic line graphs, bar charts, or scatter plots, provided you optimize the data density. The display uses an SSD1306 driver IC, which supports both SPI and I2C protocols, with SPI offering faster refresh rates (up to 10 MHz) compared to I2C (typically 400 kHz). For graphing, SPI is often preferred for real-time updates because it reduces latency, but I2C simplifies wiring with only two lines (SDA and SCL). The display’s active area is 21.7 mm x 10.8 mm, with each pixel measuring 0.17 mm x 0.17 mm, giving you a total of 8,192 pixels to work with. To display a graph, you must map your data points to pixel coordinates, for example, if you have 100 data points, you can plot them across the 128 horizontal pixels by averaging or downsampling, or use a scrolling window to show the latest 128 points. The vertical axis (64 pixels) can represent a range, say 0 to 1023 from an analog sensor, scaled to fit 0 to 63 pixels. This requires basic math: for a value V, the pixel row is 63 - (V * 63 / 1023). You then use the Adafruit SSD1306 library or similar to draw lines between points, which uses the Bresenham algorithm internally. The library supports functions like drawLine(), drawPixel(), and drawCircle(), but for a graph, you’ll primarily use drawLine() to connect data points. The refresh rate for a full screen update via SPI is about 30 frames per second (fps) when using a 16 MHz Arduino, but with I2C it drops to around 15 fps due to the slower clock. For real-time graphing, you can optimize by only updating changed pixels rather than redrawing the entire screen, which reduces the data transfer from 1,024 bytes (128x64/8) to as little as 10-20 bytes per update, depending on the graph complexity.
The hardware setup involves connecting the display to your microcontroller. For SPI, you need 7 pins: VCC (3.3V or 5V, depending on the module), GND, SCK (clock), MOSI (data), DC (data/command), CS (chip select), and RESET. For I2C, you only need 4 pins: VCC, GND, SDA, and SCL. The 0.96 inch 128x64 spi i2c oled display typically supports both modes, but you must check the module’s pinout; some boards have jumper pads to select I2C or SPI. For example, a common module like the one from Waveshare uses a 7-pin interface for SPI and a 4-pin for I2C. The supply voltage is 3.3V to 5V, with a typical current draw of 20 mA when all pixels are on, but for a graph with mostly black background, the current is lower, around 10-15 mA. The display’s contrast is adjustable via software, with a default contrast setting of 0x7F (127) in the SSD1306 register, which gives a good balance for indoor use. For outdoor readability, you might need to increase it to 0xFF (255), but this increases power consumption by about 5 mA. The viewing angle is 160 degrees, which is adequate for most applications, but direct sunlight can wash out the image due to the OLED’s lower brightness compared to LCDs.
When coding the graph, you need to consider the data type and update frequency. For a temperature sensor like the DS18B20, which outputs 12-bit data (0-4095), you scale it to the display’s 64 vertical pixels. For example, if the temperature range is 0°C to 100°C, you map 0°C to pixel 63 and 100°C to pixel 0, with each degree representing about 0.64 pixels. To improve readability, you can use a 2x or 3x vertical scaling, but this limits the range. A common approach is to use a scrolling graph where the latest data point is added to the rightmost column, and old data shifts left. This can be implemented by storing an array of 128 values (one per column) and updating the display buffer. The buffer itself is 1,024 bytes (128 x 64 / 8), and each byte represents 8 vertical pixels. For a line graph, you set the appropriate bit for the pixel row. For example, to draw a point at column 50, row 30, you set bit 6 (since row 0 is the top, and row 63 is the bottom) in the byte at buffer[50*8 + 30/8]. The Adafruit library handles this abstraction, but for performance, you can manipulate the buffer directly using the display’s getBuffer() function. This is especially useful for fast updates, as you can modify only the changed bytes and call display.display() to send the buffer via SPI or I2C. The SPI transfer speed for a full buffer is about 1.28 ms at 10 MHz (1,024 bytes * 8 bits / 10 MHz), but with overhead, it’s around 2-3 ms. For I2C, at 400 kHz, it takes about 20.5 ms (1,024 bytes * 9 bits / 400 kHz, including ACK), so real-time updates above 30 fps are not possible with I2C.
For practical implementation, let’s look at a typical Arduino sketch. You include the libraries: #include and #include . Define the display object: Adafruit_SSD1306 display(128, 64, &SPI, DC_PIN, CS_PIN, RST_PIN); for SPI, or Adafruit_SSD1306 display(128, 64, &Wire, OLED_RESET); for I2C. In setup(), you initialize with display.begin(SSD1306_SWITCHCAPVCC, 0x3C) for I2C (address 0x3C is common) or display.begin(SSD1306_SWITCHCAPVCC) for SPI. Then clear the buffer with display.clearDisplay(). In loop(), you read sensor data, map it to pixel coordinates, and draw the graph. For a simple line graph, you can use a static array of 128 ints for the y-values. When a new value comes in, shift all values left by one, add the new value to the end, then redraw the entire graph. This approach works but is inefficient for large datasets. A better method is to use a circular buffer and only update the new column. For example, maintain an index that increments every time you add a point, and draw only the line from the previous point to the new point. This reduces the number of drawLine() calls to one per update. The display’s drawLine() function uses integer arithmetic, so it’s fast, but for 128 points, full redraw takes about 10 ms on an Arduino Uno at 16 MHz. If you’re using an ESP32 at 240 MHz, the same operation takes under 1 ms.
The graph’s visual quality depends on the data resolution. With 128 horizontal pixels, you can display up to 128 data points at once. If you have more than 128 points, you need to downsample or use a scrolling window. For example, if you sample a sensor every 100 ms, you get 10 points per second, so you can fill the screen in 12.8 seconds. After that, you can either scroll or reset. For a scrolling graph, you shift the entire buffer left by one column each time, which requires copying 127 bytes of buffer data. This is a memmove operation that takes about 5 µs on an ESP32, but on an Arduino, it’s around 50 µs. The actual display update for scrolling is slower because you need to redraw the entire screen, but you can optimize by using the display’s hardware scrolling feature, which is supported by the SSD1306. The SSD1306 has a hardware horizontal scroll function that can shift the display content left or right at a set speed, but it only works for the entire frame, not partial updates. This is useful for scrolling text or simple patterns, but for a graph, you typically want to draw new data and keep old data, so hardware scrolling is less useful. Instead, you can use the display’s page addressing mode to update only the rightmost column, which reduces the data transfer to 8 bytes (one column of 64 pixels). This is done by setting the column address range to 127 and the page address range to 0-7 (since 64 pixels are 8 pages of 8 pixels each). Then you send 8 bytes of pixel data for that column. This method is very efficient for real-time graphing, as it only updates the new column without redrawing the entire screen. The I2C overhead for this is about 160 µs (8 bytes * 9 bits / 400 kHz), while SPI takes about 6.4 µs (8 bytes * 8 bits / 10 MHz). This allows for update rates of up to 6,250 fps for SPI and 6,250 fps for I2C theoretically, but in practice, the sensor reading and processing limit it to a few hundred fps.
The graph type also matters. For a line graph, you draw lines between consecutive points. For a bar chart, you draw vertical lines from the baseline to the data point. For a scatter plot, you draw individual pixels. The SSD1306’s pixel addressability makes all these possible, but line graphs are most common for time-series data. To improve readability, you can add grid lines, axis labels, and a title. Grid lines can be drawn by setting pixels at regular intervals, for example, every 10 pixels horizontally and every 8 pixels vertically. This requires drawing 64 horizontal lines (if you draw every row) or 8 horizontal lines (if you draw every 8 rows). Drawing 8 horizontal lines across 128 columns means 1,024 pixels, which takes about 1 ms on an Arduino. For axis labels, you need to use a font, like the 5x7 pixel font included in the Adafruit library. Displaying a number like “100” at 5x7 pixels takes 15 pixels horizontally, leaving 113 pixels for the graph. This reduces the effective graph area, so you might need to adjust the scaling. For example, if you reserve the left 20 pixels for the y-axis label and the bottom 10 pixels for the x-axis label, the graph area becomes 108x54 pixels. This still gives you 108 data points horizontally, which is reasonable. The font rendering uses the library’s drawChar() function, which is slow because it reads from a bitmap array. For a single number, it takes about 100 µs, but for multiple numbers, it can add up. To speed this up, you can pre-render the labels into the buffer and only update them when the data range changes, which is rare.
Power consumption is a key factor for battery-powered projects. The OLED display draws about 20 mA when all pixels are on, but for a graph with a black background (which is typical for OLEDs since they are emissive), the current is proportional to the number of lit pixels. For a line graph with 128 pixels lit (one pixel per column), the current is about 0.2 mA (since each pixel draws about 1.5 µA). However, the SSD1306 driver IC itself draws about 10 mA regardless of the pixel state, so the total is around 10.2 mA. If you use the display’s sleep mode, you can reduce this to 1 µA, but you need to wake it up to update the graph. For a sensor that updates every 10 seconds, you can put the display to sleep between updates, saving power. The wake-up time from sleep is about 100 ms, so you need to account for that. For a 10-second update interval, the average current is (10.2 mA * 100 ms + 1 µA * 9.9 s) / 10 s ≈ 0.102 mA, which is much lower than continuous operation. This makes it suitable for battery-powered IoT devices, like a weather station or a portable data logger.
The display’s temperature range is -40°C to 85°C, which covers most indoor and outdoor applications. However, the OLED material degrades over time, with a typical lifetime of 10,000 hours to 50% brightness for blue pixels, and 20,000 hours for white pixels. This is important for long-term graphing applications, as the graph will become dimmer over time. To mitigate this, you can reduce the brightness by lowering the contrast register, which extends the lifetime. For example, setting contrast to 0x40 (64) instead of 0x7F (127) reduces current by about 30% and doubles the lifetime. Also, avoid static images, as they cause burn-in. For a graph that updates frequently, the pixels change, so burn-in is less of an issue. But if you have a fixed grid, it might burn in after a few thousand hours. To prevent this, you can use a screensaver that moves the graph slightly every few minutes, or invert the display periodically.
For advanced users, you can implement anti-aliasing for smoother lines, but the SSD1306 is a 1-bit monochrome display, so you can only simulate grayscale by using dithering patterns. For example, a 2x2 dither pattern can give 5 levels of gray, but this reduces the effective resolution. For a graph, this is rarely needed because the data points are discrete. Another technique is to use sub-pixel rendering by drawing lines at fractional coordinates, but the display’s pixel grid is fixed, so this doesn’t improve accuracy. The main limitation is the 128x64 resolution, which is fine for simple graphs but not for detailed plots. If you need more resolution, you can use a 1.3-inch OLED with 128x64, which is larger but same pixel count, or a 1.5-inch OLED with 128x128. However, the 0.96 inch size is popular for its compactness, fitting into small enclosures like a handheld meter or a wearable device.
The communication protocol choice affects the wiring and performance. SPI uses more pins but offers faster updates, while I2C uses fewer pins but is slower. For a graph that updates at 10 fps, I2C is sufficient, but for 30 fps, SPI is better. The 0.96 inch 128x64 spi i2c oled display often comes with both interfaces, so you can choose based on your microcontroller’s pin availability. For example, an Arduino Nano has 14 digital pins, so SPI is fine, but for an ESP8266 with limited pins, I2C is preferred. The I2C address is typically 0x3C or 0x3D, and you can change it by soldering a resistor on the module. The default address 0x3C is used by most libraries. If you have multiple I2C devices, you need to use different addresses, but the SSD1306 only has two possible addresses, so you can only have two on the same bus. For SPI, you can use multiple CS pins to daisy-chain displays, but this is rare for a single graph.
To summarize the technical details, here’s a table comparing SPI and I2C for the 0.96 inch OLED display:
| Parameter | SPI | I2C |
| --- | --- | --- |
| Maximum clock speed | 10 MHz | 400 kHz (standard mode) |
| Typical full screen update time | 2-3 ms | 20-25 ms |
| Pins required | 7 (VCC, GND, SCK, MOSI, DC, CS, RST) | 4 (VCC, GND, SDA, SCL) |
| Maximum practical fps | 100+ | 30-40 |
| Wiring complexity | High | Low |
| Power consumption (active) | 10-15 mA | 10-15 mA (same driver) |
| Data transfer per full update | 1,024 bytes | 1,024 bytes |
| Partial update efficiency | Very high (can update single column) | High (but slower per byte) |
| Common microcontrollers | Arduino, ESP32, STM32 | Arduino, ESP8266, Raspberry Pi |
The choice between SPI and I2C also depends on the microcontroller’s hardware support. For example, the ESP32 has two I2C and two SPI controllers, so you can use either. The Arduino Uno has one SPI and one I2C, but the SPI pins are fixed (MOSI on pin 11, MISO on pin 12, SCK on pin 13, CS on pin 10), while I2C uses A4 (SDA) and A5 (SCL). For a graph project, I recommend SPI if you need high refresh rates, such as for real-time sensor monitoring at 30 fps or more. For slow updates like a temperature logger that updates every second, I2C is fine and simplifies the wiring.
The software libraries available for the SSD1306 are mature and well-documented. Besides Adafruit’s library, there’s the u8g2 library, which supports many fonts and graphics primitives. u8g2 is more memory-efficient for small microcontrollers, as it uses a page buffer instead of a full frame buffer. For a 128x64 display, the page buffer is 128 bytes (one page of 8 rows), which reduces RAM usage from 1,024 bytes to 128 bytes. This is critical for microcontrollers like the ATtiny85, which has only 512 bytes of RAM. With u8g2, you can still draw graphs, but you need to manage the page buffer manually. The library provides functions like u8g2.drawLine() and u8g2.drawPixel(), but the drawing is done in pages, so you need to call u8g2.firstPage() and u8g2.nextPage() in a loop. This adds overhead, but for a graph, it’s acceptable. The u8g2 library also supports hardware scrolling, which can be used for a scrolling graph without shifting the buffer. For example, you can use the u8g2.setScrollMode() function to enable horizontal scrolling, but again, it scrolls the entire display, not just the graph area.
For a practical example, let’s say you want to display a sine wave graph. You generate 128
Passez du Journal à votre projet