How to display a tachometer on a 1.54 inch 128x64 OLED?
How to Display a Tachometer on a 1.54 inch 128x64 OLED
To display a tachometer on a 1.54 inch 128x64 OLED, you need to wire the OLED module to a microcontroller (like an Arduino Uno or ESP32), read RPM data from an engine or a simulated source, and then use a graphics library to draw the tachometer arc, needle, and numeric values on the screen. The 1.54 inch 128x64 oled display uses a 128x64 pixel matrix with a 0.96mm pixel pitch, giving you a 1.54-inch diagonal viewing area. It typically runs on a SSD1309 or SH1106 driver IC over SPI, which supports a maximum clock speed of 10 MHz, allowing for smooth 30+ frames per second updates even with complex graphics. The display’s 160-degree viewing angle and 10,000:1 contrast ratio make it readable in direct sunlight, which is critical for a tachometer in automotive or racing applications.
Start by connecting the OLED to your microcontroller. The SPI interface uses four pins: CS (chip select), DC (data/command), MOSI (master out slave in), and SCK (serial clock). For a standard Arduino Uno, map CS to pin 10, DC to pin 9, MOSI to pin 11, and SCK to pin 13. Power the module with 3.3V (some versions tolerate 5V logic, but check the datasheet; the typical supply current is 20 mA at 3.3V, and peak current during full-white display is 25 mA). Use a 10 µF capacitor between VCC and GND to filter noise from the microcontroller’s power rail, especially if you’re driving the display near its 10 MHz SPI clock limit. The OLED’s internal buffer is 128x64 bits, which is 1024 bytes—you can pre-render the tachometer frame in this buffer and flush it to the display via SPI in about 1.2 milliseconds at 10 MHz, leaving plenty of CPU time for RPM calculations.
For the tachometer graphic, you’ll need to draw a 180-degree arc (from 9 o’clock to 3 o’clock position) that represents the RPM range. The 128x64 pixel grid gives you a horizontal resolution of 128 pixels and vertical resolution of 64 pixels. A typical tachometer arc has a radius of 30 pixels, centered at (64, 50) on the display—this leaves 14 pixels at the bottom for digital RPM readout and 14 pixels at the top for the arc’s upper bound. The arc spans from 135 degrees to 45 degrees in a standard coordinate system (0 degrees at 3 o’clock, counterclockwise). To draw the arc, use Bresenham’s circle algorithm or a polygon approximation with 10-degree steps. For each step, calculate the x and y coordinates: x = center_x + radius * cos(angle), y = center_y + radius * sin(angle). Connect these points with line segments using the graphics library’s drawLine() function. The arc thickness should be at least 2 pixels for visibility; a 3-pixel thick arc uses about 180 pixels of memory for the arc itself, which is negligible compared to the 1024-byte buffer.
Mark RPM values on the arc. For a typical 8000 RPM tachometer, place tick marks at 0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, and 8000 RPM. Each tick mark is a 4-pixel line perpendicular to the arc. The arc’s angular span is 180 degrees (from 135 to 45 degrees), so each 1000 RPM step corresponds to 22.5 degrees. For example, the 0 RPM tick is at 135 degrees, and the 8000 RPM tick is at 45 degrees. Calculate the tick positions using the same trigonometric formulas but with a slightly shorter radius (e.g., 28 pixels) for the inner end of the tick. Add numeric labels next to each tick: for 0 RPM, place the text at (x-8, y+10) to avoid overlapping the arc; for 8000 RPM, place it at (x-8, y-10). The OLED’s font size is typically 5x7 pixels for a single character, so a 4-digit number like “8000” takes 20 pixels horizontally—ensure it fits within the 128-pixel width. The leftmost label (0 RPM) starts at pixel 24, and the rightmost label (8000 RPM) ends at pixel 104, leaving 12 pixels of margin on each side.
The needle is the most critical visual element. It should be a single line from the arc center to the arc’s current RPM position. Use a 2-pixel-thick line for the needle, and draw it in a contrasting color (white on black background, or black on white if you invert the display). The needle’s tip should extend 2 pixels beyond the arc’s outer radius (32 pixels from center) for visibility. Update the needle position every 10-20 milliseconds to simulate real-time RPM changes. To avoid flicker, use double buffering: render the entire frame (arc, ticks, labels, and needle) into a 1024-byte off-screen buffer, then copy it to the OLED’s internal buffer using the drawBitmap() or writeDisplay() function. The SSD1309 driver supports hardware vertical scrolling, but for a tachometer, you’ll want to disable scrolling and use page addressing mode for direct pixel control.
Now, the RPM data source. In a real engine, you’d read the RPM from a tachometer signal wire (typically a square wave from the ignition coil or ECU). Connect the signal wire to a digital input pin on the microcontroller. Use a pulseIn() function to measure the time between rising edges, then calculate RPM: RPM = 60,000 / (pulse width in milliseconds * number of pulses per revolution). For a 4-cylinder engine, the ignition fires twice per revolution, so divide by 2. For a simulated RPM, use a potentiometer connected to an analog input, or generate a sine wave with varying frequency using the microcontroller’s timer. The ESP32’s built-in hall effect sensor can also provide a variable frequency signal for testing. The OLED’s update rate must match the RPM update rate: at 8000 RPM, the period between pulses is 7.5 milliseconds (for a 4-cylinder engine), so you need to update the display at least every 7.5 milliseconds to avoid aliasing. The SPI transfer takes 1.2 milliseconds, leaving 6.3 milliseconds for RPM calculation and graphics rendering, which is feasible on a 16 MHz Arduino Uno or 240 MHz ESP32.
For the graphics library, use Adafruit_SSD1306 (for SSD1306/SSD1309 drivers) or u8g2 (for SH1106 drivers). The Adafruit library supports 128x64 resolution with a 1-bit color depth, meaning each pixel is either on or off. To draw the arc, use the drawCircle() function with a 30-pixel radius, but only draw the pixels within the 135-to-45-degree range. This requires a custom function that checks each pixel’s angle before drawing. Alternatively, use the u8g2 library’s drawArc() function, which natively supports arc drawing with start and end angles. For the needle, use drawLine() from the center to the calculated point. The library’s setFont() function lets you choose a 5x7 or 8x13 font for the RPM labels. The 8x13 font is more readable for the 8000 RPM label, but it takes 32 pixels horizontally (8 pixels per character), so you’ll need to adjust the label positions to avoid clipping at the display edges.
Memory management is crucial. The OLED’s buffer is 1024 bytes, but the microcontroller’s RAM is limited (2 KB on Arduino Uno). Pre-calculate the arc and tick positions as constants in PROGMEM to save RAM. For example, store the x and y coordinates of the 9 tick marks as two arrays of 9 bytes each (18 bytes total). The needle’s endpoint changes every frame, so calculate it on the fly. The u8g2 library uses a 1024-byte buffer by default, but you can reduce it to 128 bytes (one page) to save RAM, at the cost of slower rendering (you’ll need to call sendBuffer() 8 times per frame). For a 30 FPS target, this adds 8 * 1.2 ms = 9.6 ms per frame, still within the 33 ms budget.
Test the display with a static tachometer first. Upload a sketch that draws the arc, ticks, labels, and a needle at a fixed RPM (e.g., 4000 RPM). Verify the needle aligns with the 4000 RPM tick mark—the angular position should be 135 - (4000/8000)*180 = 45 degrees from the 0 RPM start. Adjust the angle calculation if your arc starts at a different position. Then, add the RPM input code. For a real signal, use an interrupt on the digital pin to measure pulse width with microsecond precision. For a simulated signal, read the analog pin and map the value (0-1023) to RPM (0-8000). Update the needle position and call display.display() to refresh the screen. If you notice ghosting (faint previous needle positions), clear the old needle by drawing a black line over it before drawing the new one. This is more efficient than redrawing the entire arc every frame.
Power consumption is another consideration. The OLED draws 20 mA typical, but the microcontroller may draw 50-200 mA depending on the model. For a portable tachometer, use a 3.7V lithium-ion battery with a boost converter to 3.3V. The display’s standby current is 0.1 mA, so you can put the microcontroller to sleep between RPM reads to save battery. The ESP32’s deep sleep mode consumes 10 µA, and you can wake it up with a timer every 10 ms. The OLED’s display off command reduces current to 0.1 mA, but you’ll need to reinitialize the display on wake-up, which takes 100 ms—acceptable for a 10 ms update interval.
For a racing application, you might want to add a redline indicator (e.g., a red arc segment from 7000 to 8000 RPM). Draw this segment with a thicker line (4 pixels) and a different color (if using a dual-color OLED, or use a pattern of alternating pixels for a grayscale effect on a monochrome display). The redline arc occupies 22.5 degrees of the 180-degree arc, which is about 12 pixels along the arc. Use a for loop to draw each pixel in the redline segment, and set the pixel value to 1 (white) for the arc and 0 (black) for the background. The contrast ratio of 10,000:1 ensures the redline stands out even in bright sunlight.
Finally, calibrate the tachometer with a known RPM source. Use a signal generator to output a square wave at 10 Hz to 200 Hz (corresponding to 600 to 12,000 RPM for a 4-cylinder engine). Compare the displayed RPM with the generator’s frequency. The OLED’s pixel accuracy is 0.7 degrees per pixel (since the arc spans 180 degrees over 128 pixels), so you can expect a reading accuracy of ±0.7% of full scale (8000 RPM), or ±56 RPM. This is sufficient for most automotive applications, but you can improve it by using a 256-pixel arc (if you scale the display to 2x zoom) or by interpolating the needle position between pixels using sub-pixel rendering (draw the needle at fractional pixel positions by adjusting the line thickness).