記事
How to display a bar graph on a 1.54 inch 128x64 OLED?
To display a bar graph on a 1.54 inch 128x64 oled display, you need to write code that maps data values to pixel coordinates, then draws filled rectangles representing each bar. This specific display uses a monochrome SSD1306 or SH1106 driver over SPI, with a resolution of 128 columns by 64 rows. The key is to treat the 128 horizontal pixels as your X-axis and the 64 vertical pixels as your Y-axis, where Y=0 is the top of the screen and Y=63 is the bottom. For a bar graph, you typically want bars to rise from the bottom, so you calculate the height of each bar based on your data range, then draw a filled rectangle from the bottom of the screen up to the calculated height. Let’s break this down with real numbers and practical steps.
First, understand the physical constraints. The 1.54 inch 128x64 oled display has a 1.54-inch diagonal, 128 pixels horizontally, 64 pixels vertically, and a pixel pitch of about 0.26mm. Each pixel is either on (white) or off (black) in monochrome mode. The display controller, typically SSD1306, supports a maximum SPI clock of 10 MHz, meaning you can update the entire screen in about 1.5 milliseconds if you send raw data. For a bar graph, you’ll be updating only portions of the frame buffer, which is faster. The frame buffer is 1024 bytes (128 columns * 64 rows / 8 bits per byte), stored in the controller’s internal RAM. You write to this buffer via SPI commands, and the display continuously refreshes from it at about 60 Hz.
To draw a bar graph, you need to decide on margins, bar width, and spacing. For a 128-pixel-wide display, if you want 10 bars, each bar can be 10 pixels wide with 2 pixels of spacing between bars, totaling 10*10 + 9*2 = 118 pixels, leaving 10 pixels for left and right margins (5 each). If you want 20 bars, each bar could be 5 pixels wide with 1 pixel spacing, totaling 20*5 + 19*1 = 119 pixels, leaving 9 pixels for margins. The Y-axis range is 0 to 63, but you’ll want a bottom margin for labels or just to leave a gap. For example, reserve the bottom 4 rows for a baseline, so usable height is 60 pixels. If your data values range from 0 to 100, each unit maps to 0.6 pixels. A value of 50 would produce a bar height of 30 pixels.
Here’s a concrete example using Arduino with the Adafruit SSD1306 library. First, initialize the display with SPI pins: CS, DC, RST, and MOSI/SCLK. The library handles the protocol. In your loop, you’d define an array of data, say `int data[10] = {23, 45, 67, 12, 89, 34, 56, 78, 90, 10};`. Then clear the display with `display.clearDisplay()`. For each bar, calculate the bar height: `int barHeight = map(data[i], 0, 100, 0, 60);` (assuming max value is 100). Then draw a filled rectangle: `display.fillRect(x, 63 - barHeight, barWidth, barHeight, WHITE);` where `x` is the starting X position (e.g., 5 + i*(barWidth+spacing)). The Y coordinate `63 - barHeight` places the bottom of the bar at row 63 (bottom of screen) and the top at row `63 - barHeight`. Finally, call `display.display()` to send the frame buffer to the OLED.
But there’s a nuance: the SSD1306’s coordinate system has Y=0 at the top, so you must invert the Y-axis. That’s why we use `63 - barHeight` as the top-left corner of the rectangle. The rectangle’s height is `barHeight`, so it extends down to row 63. If you want a baseline, draw a horizontal line at Y=63 using `display.drawLine(0, 63, 127, 63, WHITE);`. You can also add X-axis labels by writing text at the bottom, but the font size is limited. The Adafruit library includes a 5x7 pixel font, so each character is 5 pixels wide and 7 pixels tall. For a bar graph with 10 bars, you can fit 2-digit numbers under each bar if you have enough spacing. For example, if bar width is 10 pixels and spacing is 2, the total width per bar is 12 pixels, which is enough for a 2-digit number (10 pixels wide) with 1 pixel margin on each side.
Let’s talk about performance. The SPI bus speed is critical. At 10 MHz, sending 1024 bytes takes about 0.82 milliseconds (1024 * 8 bits / 10,000,000 bits per second). However, the library typically sends the entire frame buffer each time, even if only a few pixels change. For a bar graph that updates every second, this is fine. But if you’re animating or updating rapidly, you can optimize by only updating the changed regions. The SSD1306 supports page addressing mode, where you can set the column and page address before writing. Pages are 8-pixel tall strips. For a bar graph, you can update only the pages that contain bars that changed. For example, if a bar’s height changes from 30 to 40 pixels, you only need to rewrite pages 3 to 5 (since each page is 8 rows, and rows 24 to 40 span pages 3 to 5). This reduces SPI traffic by up to 80% for small changes.
Now, consider the physical layout of the display. The 1.54 inch 128x64 OLED typically has a viewing angle of 160 degrees and a contrast ratio of 2000:1. The brightness is around 100 cd/m², which is readable indoors but may need a shield in direct sunlight. The display consumes about 20 mA at 3.3V, so power is not a concern for most microcontrollers. The SPI interface uses 4 pins: MOSI (data), SCK (clock), DC (data/command), and CS (chip select). Some modules also require a reset pin. The maximum SPI frequency is 10 MHz, but many microcontrollers (like Arduino Uno) run at 16 MHz and can only achieve 8 MHz due to clock division. That’s still fast enough for real-time updates.
For a more robust implementation, you can use a look-up table for bar positions. Pre-calculate the X coordinates for each bar to avoid repeated multiplication. For example, if you have 10 bars with width 10 and spacing 2, the X positions are 5, 17, 29, 41, 53, 65, 77, 89, 101, 113. Store these in an array. Then in your draw loop, you just iterate and compute `barHeight` from your data. This reduces CPU overhead. Also, consider using a scaling factor that adapts to the maximum value in your data set. For example, if your data values vary, find the max value and scale all bars to fit within 60 pixels. This ensures the graph always uses the full height. The formula is: `int scaledHeight = (data[i] * 60) / maxValue;`. If maxValue is 0, handle the division by zero.
Another angle: you can add a grid for readability. Draw horizontal lines at 20-pixel intervals (e.g., at Y=43, Y=23, Y=3) using `display.drawLine(0, y, 127, y, WHITE);`. This gives a 3-level grid. The lines will be thin (1 pixel), so they won’t obscure the bars. For a more professional look, use dashed lines by drawing every other pixel. The SSD1306 doesn’t support line styles natively, but you can implement it with a loop. For example, for a dashed line at Y=43, iterate X from 0 to 127, and draw a pixel only when X%4 < 2. This creates a dotted line.
Let’s talk about data sources. You can feed the bar graph from a sensor like a temperature sensor (e.g., DS18B20) or an analog input (e.g., potentiometer). For a temperature sensor, the data range might be -40°C to 125°C. Map this to 0-60 pixels: `int barHeight = map(temperature, -40, 125, 0, 60);`. But negative temperatures would result in a bar height of 0, which is not useful. Instead, offset the baseline. For example, set the baseline at Y=63 and map the temperature to a range where the minimum value corresponds to a bar height of 0. If the minimum expected temperature is -10°C, you can map -10 to 0 and 50 to 60. The formula becomes: `int barHeight = (temperature - minTemp) * 60 / (maxTemp - minTemp);`. This ensures the bar graph is always positive.
For a multi-bar graph, you can also add labels on the X-axis using the `display.setCursor()` and `display.print()` functions. The 5x7 font means each character is 5 pixels wide. For a 2-digit number like “23”, you need 10 pixels plus 1 pixel spacing, so 11 pixels total. With 10 bars and 12 pixels per bar, you can fit the label centered under each bar. The Y position for the label is at row 56 (since the bottom 4 rows are reserved for the baseline). The X position is `x + (barWidth - textWidth) / 2`, where `textWidth` is the number of characters times 5. For a 2-digit number, `textWidth` is 10, so the X offset is `(10 - 10)/2 = 0`, meaning the label starts at the same X as the bar. But if the bar width is 10 and the label is 10, it fits exactly. For a 3-digit number, you’d need 15 pixels, so you’d need to increase bar width or reduce the number of bars.
Here’s a table of typical bar graph configurations for a 128x64 OLED:
| Number of Bars | Bar Width (pixels) | Spacing (pixels) | Total Width (pixels) | Left Margin (pixels) | Right Margin (pixels) |
|----------------|-------------------|------------------|----------------------|----------------------|-----------------------|
| 5 | 20 | 4 | 5*20 + 4*4 = 116 | 6 | 6 |
| 10 | 10 | 2 | 10*10 + 9*2 = 118 | 5 | 5 |
| 15 | 6 | 2 | 15*6 + 14*2 = 118 | 5 | 5 |
| 20 | 5 | 1 | 20*5 + 19*1 = 119 | 4 | 5 |
| 30 | 3 | 1 | 30*3 + 29*1 = 119 | 4 | 5 |
The table shows that you can fit up to 30 bars if you use 3-pixel-wide bars with 1-pixel spacing. However, with 3-pixel-wide bars, the labels become difficult to read because the font is 5 pixels wide. You’d need to omit labels or use a smaller font (if available). The SSD1306 can display custom fonts, but that requires more memory. For example, you can use a 3x5 pixel font for numbers, which fits in 3-pixel-wide bars. But implementing a custom font is beyond the scope of basic bar graphs.
Now, let’s discuss the software stack. The most common libraries are Adafruit_SSD1306 (for Arduino) and the u8g2 library (for C++). u8g2 supports more display controllers and has a built-in font system. For a bar graph, u8g2’s `drawBox()` function is similar to `fillRect()`. The u8g2 library also supports page buffering, which reduces memory usage on the microcontroller. For example, on an Arduino Uno with 2KB of RAM, the full frame buffer (1024 bytes) takes half the RAM. u8g2 can use a smaller buffer (e.g., 128 bytes) and update the display in pages, which is more memory-efficient. The trade-off is slower update speed because each page requires a separate SPI transaction. For a bar graph that updates once per second, this is acceptable.
For a real-world example, consider a weather station that displays temperature, humidity, and pressure as bar graphs. You can split the 128-pixel width into three sections: 42 pixels each for temperature and humidity, and 44 pixels for pressure (since 128/3 = 42.66). Each section has its own bar. For temperature, use a range of -10 to 40°C; for humidity, 0 to 100%; for pressure, 950 to 1050 hPa. The bars are drawn side by side with labels underneath. The code would be similar to the single-bar example but with three separate data arrays and scaling factors.
Another angle: you can use the OLED’s ability to invert pixels. For a bar graph, you can draw the background as black and the bars as white. But if you want to highlight a specific bar, you can invert its color (e.g., draw a black bar on a white background). The SSD1306 supports a `display.invertDisplay(true)` command that inverts the entire screen, but for individual bars, you need to set the pixel color manually. In the Adafruit library, you can use `drawRect()` with `WHITE` for the bar outline and `fillRect()` with `BLACK` for the fill, but that would make the bar black on a white background. To achieve a true inversion, you’d need to XOR the pixels, which is not directly supported. Instead, you can draw the bar as a hollow rectangle with a different color than the background.
Let’s talk about power consumption. The OLED display consumes about 20 mA when all pixels are on. For a bar graph, typically only a portion of the screen is lit, so power consumption is lower. For example, if 10 bars each cover 10% of the screen height, the total lit area is about 10% of 128*64 = 819 pixels, which is about 10% of the full screen. The power consumption scales roughly linearly with the number of lit pixels, so you’d be using about 2 mA. This is important for battery-powered projects. You can further reduce power by using the display’s sleep mode when not updating. The SSD1306 supports a `display.sleep()` command that reduces current to about 10 µA. Wake it up with `display.wake()` before drawing.
For a more advanced feature, you can add a moving average to smooth the bar graph. For example, if you’re reading a sensor every 100 ms, you can average the last 10 readings to reduce noise. The bar graph then updates every second with the average value. This is common in audio visualizers, where the bar graph shows the amplitude of different frequency bands. The FFT output is mapped to bar heights. For a 128x64 display, you can show 32 bars (each 4 pixels wide with 0 spacing) for a frequency spectrum. The code would involve an FFT library like ArduinoFFT, which computes the magnitude of each frequency bin. Then map the magnitudes to bar heights.
Finally, consider the physical mounting. The 1.54 inch OLED has a 0.1-inch pitch pin header, usually 4 or 7 pins. For a bar graph display, you’ll want to mount it on a breadboard or PCB with a microcontroller like ESP32 or STM32. The ESP32 has dual cores and can handle SPI at up to 40 MHz, which is faster than the 10 MHz limit of the SSD1306, so you can use the maximum speed. The STM32F103C8T6 (Blue Pill) also supports SPI at 18 MHz. The choice of microcontroller affects the refresh rate. For a bar graph that updates 10 times per second, any microcontroller is fine. But for a real-time audio visualizer, you need at least 30 FPS, which requires a faster MCU.
In summary, the process involves initializing the display, mapping data to pixel coordinates, drawing filled rectangles, and updating the frame buffer. The specific details of margins, scaling, and labels depend on your data and desired aesthetics. The 1.54 inch 128x64 OLED is a versatile display for bar graphs, with enough resolution for 10 to 20 bars with labels. The SPI interface is straightforward, and libraries like Adafruit_SSD1306 or u8g2 simplify the code. For performance, optimize by using pre-calculated positions and partial updates.