How to display a bar chart on a 0.96 inch I2C OLED?
How to Display a Bar Chart on a 0.96 Inch I2C OLED
To display a bar chart on a 0.96 inch I2C OLED, you need to write firmware that reads data, maps values to pixel heights, and draws rectangles on the 128x64 monochrome screen. The most common approach is using an Arduino or ESP32 with the Adafruit SSD1306 library, which handles the I2C communication and graphics primitives. For example, if you have five data points representing sensor readings (like temperature or humidity), you can convert each value to a bar height between 0 and 48 pixels (leaving space for labels and axis). The key is to use the display.fillRect() function, specifying x, y, width, and height for each bar. A typical implementation takes about 30 lines of code, and the refresh rate can reach 10-15 frames per second on a 16 MHz Arduino Uno. The 0.96 inch 128x64 i2c oled display from DisplayModule is a solid choice because it uses the SSD1306 driver, supports 3.3V or 5V logic, and draws only 20 mA during active operation. You can find the exact module at 0.96 inch 128x64 i2c oled display.
Let’s break down the hardware side first. The 0.96 inch OLED has a resolution of 128 pixels horizontally and 64 pixels vertically. Each pixel is individually addressable, and the I2C interface uses two wires: SDA (data) and SCL (clock). The default I2C address is 0x3C, but some modules use 0x3D. Always check the back of the PCB or the datasheet. The display operates at 3.3V, but the logic pins are 5V tolerant, so you can connect it directly to an Arduino Uno. For power, the module draws about 20 mA when all pixels are on, and 10-15 mA for typical bar chart usage. That’s low enough to run from a coin cell battery for a few hours. The I2C bus speed is typically 100 kHz (standard mode) or 400 kHz (fast mode). Using 400 kHz, you can update the entire screen in about 8 ms, which is fast enough for real-time data visualization.
Now, the software side. You need the Adafruit SSD1306 library and the Adafruit GFX library. Install both via the Arduino Library Manager. The GFX library provides functions like drawRect(), fillRect(), and drawLine(). For a bar chart, you’ll use fillRect() extensively. The basic algorithm is: 1) clear the display, 2) draw a horizontal axis line at y=56 (leaving 8 pixels for labels), 3) for each data point, calculate the bar height as map(value, min, max, 0, 48), 4) draw a filled rectangle from (x, 56 - height) to (x + width, 56). The bar width depends on the number of bars. For 10 bars, each bar can be 10 pixels wide with 2 pixels gap, totaling 120 pixels (leaving 4 pixels margin on each side). For 5 bars, each bar can be 20 pixels wide with 4 pixels gap. Here’s a concrete example: if you have an array int data[5] = {10, 30, 50, 70, 90}, and you map 0-100 to 0-48 pixels, the heights become 4.8, 14.4, 24, 33.6, 43.2 pixels. Round to integers: 5, 14, 24, 34, 43. Then draw bars at x positions 10, 34, 58, 82, 106 (with bar width 20 and gap 4).
Let’s talk about the display buffer. The SSD1306 has 128 columns and 8 pages (each page is 8 rows). The Adafruit library uses a 1 KB buffer (128 * 64 / 8). You can write directly to the buffer using display.drawPixel() or display.fillRect(), then call display.display() to flush the buffer to the OLED. This double-buffering prevents flickering. If you update the buffer every 100 ms, you get a smooth 10 fps animation. For a static bar chart, you can update once per second. The library also supports text rendering with setTextSize() and setCursor(). You can label each bar with a number or a short string. However, the font is 5x7 pixels, so a 2-digit number takes 10 pixels width. For 10 bars, you might not have enough horizontal space for labels. A common workaround is to show labels only on the x-axis below the bars, using a smaller font or rotating the text (though the GFX library doesn’t support rotation natively). Alternatively, you can display the value above each bar as a small number.
Data scaling is critical. The OLED has only 64 vertical pixels, and you need at least 8 pixels for the x-axis label area, leaving 56 pixels for bars. If your data range is 0-1000, you must map it to 0-56. Use map(value, dataMin, dataMax, 0, 56). But be careful with outliers: if one value is 1000 and others are 10, the bar for 10 will be less than 1 pixel tall, making it invisible. You can use a logarithmic scale instead: height = log(value) / log(max) * 56. This compresses the range and makes small values visible. For example, with data 10, 100, 1000, the heights become 56 * log(10)/log(1000) = 18.6, 56 * log(100)/log(1000) = 37.3, 56 * log(1000)/log(1000) = 56. So the small value gets a visible bar. Another approach is to use a dynamic range: set the minimum and maximum based on the current dataset, not a fixed range. This makes the bars fill the screen better, but the scale changes with each update, which can be confusing for the user. You can also add a y-axis with tick marks. Draw a vertical line at x=0 from y=0 to y=56, then add horizontal ticks every 10 pixels. Label the ticks with values like 0, 20, 40, 60, 80, 100. This requires about 10 extra lines of code.
Let’s look at performance. The I2C bus speed is a bottleneck. At 100 kHz, transmitting 1 KB of data takes about 10 ms (1 KB * 10 bits per byte / 100 kHz). At 400 kHz, it’s 2.5 ms. The Arduino’s CPU time for drawing rectangles is negligible (a few microseconds). So the total update time is dominated by the I2C transfer. If you update the display 10 times per second, the I2C bus is busy 25% of the time at 400 kHz. This leaves room for other tasks like sensor reading. On an ESP32, you can run the I2C bus at 800 kHz, reducing transfer time to 1.25 ms. But the SSD1306 has a maximum clock frequency of 400 kHz, so don’t exceed that. Some cheap clones might not work reliably at 400 kHz; test at 100 kHz first.
Power consumption is another angle. The OLED draws 20 mA max, but the Arduino Uno itself draws 50 mA. Total system draw is about 70 mA. If you’re battery-powered, you can put the OLED to sleep using display.ssd1306_command(SSD1306_DISPLAYOFF). This reduces current to 1-2 µA. Then wake it up with SSD1306_DISPLAYON. You can also use the display’s charge pump: the internal DC-DC converter generates the 7-8V needed for the OLED pixels. If you disable the charge pump, the display won’t work. So keep it on. For a bar chart that updates every 5 seconds, you can turn off the display between updates, saving 80% power. But the user sees a blank screen most of the time. A better compromise is to dim the display: set the contrast to a lower value using display.ssd1306_command(SSD1306_SETCONTRAST) with a value of 0x10 (16 decimal) instead of 0x7F (127). This reduces current to about 10 mA while still being readable.
Now, let’s discuss multiple bar charts. You can split the 64-pixel height into two sections: top 32 pixels for one chart, bottom 32 pixels for another. But the bars will be half the height, making them less precise. Alternatively, you can use a scrolling display: show 5 bars at a time, and use buttons to scroll left or right. This requires a state machine to track the current window. The code becomes more complex, but it’s doable with a few hundred lines. The 128x64 resolution is limited, so you can’t show a 50-bar chart in one view. Practical maximum is 10-15 bars with labels, or 20 bars without labels. For a histogram with 100 bins, you’d need to downsample or use a different display.
Let’s talk about libraries beyond Adafruit’s. The u8g2 library is another option. It supports the SSD1306 and provides more fonts, including proportional fonts up to 22 pixels tall. It also has a drawBox() function for bars. But u8g2 uses a different API: you need to call u8g2.firstPage() and u8g2.nextPage() in a loop. This is a “page buffer” approach, which uses less RAM (only 128 bytes) but requires more CPU time. For a bar chart, the Adafruit library is simpler because you can draw directly to the buffer. However, u8g2 has better font support, which helps with labeling. For example, you can use a 10-pixel-tall font to show the bar value above each bar. The trade-off is RAM: Adafruit uses 1 KB, u8g2 uses 128 bytes. If you’re on an ATtiny85 with 512 bytes of RAM, u8g2 is the only option. But on an Arduino Uno with 2 KB, both work.
Here’s a practical code snippet for a 5-bar chart using Adafruit SSD1306:
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void setup() {
Serial.begin(9600);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
}
void loop() {
int data[5] = {10, 30, 50, 70, 90};
int barWidth = 20;
int gap = 4;
int xStart = 10;
int yAxis = 56;
display.clearDisplay();
// Draw axis
display.drawLine(0, yAxis, 127, yAxis, SSD1306_WHITE);
for (int i = 0; i < 5; i++) {
int height = map(data[i], 0, 100, 0, 48);
int x = xStart + i * (barWidth + gap);
display.fillRect(x, yAxis - height, barWidth, height, SSD1306_WHITE);
// Label bar
display.setCursor(x + 2, yAxis + 2);
display.print(data[i]);
}
display.display();
delay(1000);
}
This code works, but the labels are below the bars, which might overlap if the numbers are large. For a cleaner look, you can put labels above the bars: display.setCursor(x + 2, yAxis - height - 8);. But if the bar is short, the label might go off-screen. A safer approach is to print labels only if the bar height is greater than 8 pixels.
Let’s talk about real-world use cases. A common application is a battery monitor. You have voltage readings from an ADC, and you want to show the last 10 readings as a bar chart. The voltage range is 3.0V to 4.2V for a LiPo battery. Map 3.0-4.2 to 0-48 pixels. Each bar represents a reading every 10 seconds. The chart updates every 10 seconds, so the user sees the trend. Another use case is a temperature log from a DS18B20 sensor. The sensor reads every 5 seconds, and you store the last 15 readings. The temperature range is 0-40°C. Map to 0-48 pixels. The bars show how the temperature changes over 75 seconds. You can also add a threshold line at, say, 30°C, using display.drawLine(0, map(30, 0, 40, 0, 48), 127, map(30, 0, 40, 0, 48), SSD1306_WHITE). This is a dashed line if you set the style.
Now, let’s discuss the I2C bus issues. If you have multiple I2C devices (like a sensor and the OLED), you need to ensure they have different addresses. The OLED uses 0x3C or 0x3D. A typical BMP280 sensor uses 0x76 or 0x77. So they don’t conflict. But if you have two OLEDs, you can’t use the same address. Some modules have a jumper to change the address to 0x3D. Otherwise, you need an I2C multiplexer like the TCA9548A. This adds complexity but allows up to 8 OLEDs. For a bar chart, you probably don’t need multiple displays, but it’s possible.
Let’s talk about the display’s viewing angle and contrast. The 0.96 inch OLED has a 160-degree viewing angle, so it’s readable from any direction. The contrast is excellent: 10000:1 ratio, meaning black is truly black (no backlight bleed). This makes the bar chart look crisp. The display is monochrome, so all bars are white on black (or blue on black on some modules). You can invert the display using display.invertDisplay(true) to get black bars on white background. This might be easier to read in bright sunlight. The typical brightness is 100 cd/m², which is dimmer than a phone screen but fine for indoor use. In direct sunlight, you might need to shield the display.
Let’s look at the data sheet for the 0.96 inch 128x64 i2c oled display. The module from DisplayModule has a built-in 3.3V regulator, so you can power it from 5V directly. The I2C pins are 5V tolerant, but the SDA and SCL lines need pull-up resistors. The module includes 4.7kΩ pull-ups on the board, so you don’t need external ones. The operating temperature range is -40°C to +85°C, which is suitable for outdoor use. The display thickness is 1.2 mm, and the PCB is 13 mm x 28 mm. This makes it easy to mount in a small enclosure. The connector is a 4-pin header with 2.54 mm pitch. You can solder wires directly or use a female header.
Now, let’s talk about advanced features. You can draw a bar chart with gradient shading by using different fill patterns. The SSD1306 doesn’t support grayscale, but you can simulate it by dithering: draw every other pixel in a bar to make it look lighter. For example, for a bar with 50% intensity, draw a checkerboard pattern. The GFX library doesn’t have a built-in function for this, but you can write a loop: for (int y = 0; y < height; y++) { for (int x = 0; x < barWidth; x++) { if ((x + y) % 2 == 0) display.drawPixel(xPos + x, yPos - y, WHITE); } }. This creates a 50% pattern. You can use this to show different data series in the same bar (stacked bar chart) or to indicate uncertainty. Another advanced feature is animation: you can animate the bars growing from the bottom up. Start with height 0, then increment by 1 pixel every 10 ms until the final height. This takes 48 * 10 ms = 480 ms for a full bar. The user sees the bar grow smoothly. You can do this for all bars simultaneously or sequentially.