How to display a voltmeter on a 0.96 inch 128x64 OLED?
How to Display a Voltmeter on a 0.96 Inch 128x64 OLED
To display a voltmeter on a 0.96 inch 128x64 OLED, you need to connect the OLED module to a microcontroller like an Arduino or ESP32, read an analog voltage using an ADC pin, and then update the display with the measured value. The most common approach uses the I2C interface, which only requires two wires (SDA and SCL) plus power and ground, making it ideal for compact projects. The specific module I recommend is the 0.96 inch 128x64 i2c oled display, which uses the SSD1306 driver chip. This driver supports a resolution of 128 pixels horizontally and 64 pixels vertically, with a pixel pitch of 0.21 mm and an active area of 21.7 mm by 10.8 mm. The display operates at 3.3V logic, but the I2C lines are 5V tolerant on most Arduino boards if you use a level shifter or pull-up resistors. The typical power consumption is around 20 mA when all pixels are on, but for a voltmeter display, you’ll only light up a fraction of the pixels, so expect around 5 to 10 mA average. The refresh rate of the SSD1306 is about 100 Hz when using hardware I2C, but for a voltmeter, updating at 10 Hz is more than sufficient to avoid flicker and reduce CPU load.
For the hardware setup, you need a voltage divider if you’re measuring voltages above the microcontroller’s ADC reference voltage. For example, an Arduino Uno’s ADC has a 10-bit resolution (0 to 1023) and a default reference of 5V. If you want to measure up to 12V, use a voltage divider with two resistors: R1 (top) = 10 kΩ and R2 (bottom) = 4.7 kΩ, giving a division factor of 1 + (R1/R2) = 3.13. The formula is V_in = V_measured * (R1 + R2) / R2. For a 12V input, the ADC pin sees 12V * 4.7k / (10k + 4.7k) = 3.84V, which is within the 5V limit. Use a 0.1 µF capacitor between the ADC pin and ground to filter noise. The OLED’s I2C address is typically 0x3C or 0x3D, and you can check it with an I2C scanner sketch. The SDA pin connects to A4 on Uno, SCL to A5. For ESP32, use GPIO 21 for SDA and GPIO 22 for SCL, and the ADC reference is 3.3V with 12-bit resolution (0 to 4095). The ESP32’s ADC is non-linear near the extremes, so calibrate it with a known voltage source. For the OLED, the maximum I2C clock speed is 400 kHz in fast mode, but 100 kHz works fine for a voltmeter update rate of 10 Hz. The display’s contrast is controlled by the SSD1306 command 0x81, and you can set it to 0x7F for a balanced brightness. The display also supports page addressing mode, which is useful for updating only the voltage value area without redrawing the entire screen.
Software-wise, you need the Adafruit SSD1306 library and the Adafruit GFX library. The GFX library provides functions like drawPixel(), drawLine(), drawRect(), and setCursor(). For a voltmeter, you can draw a bar graph or a numeric readout. A bar graph is more visually intuitive: draw a rectangle from (0, 40) to (128, 60) as the background, then fill it with white from the left to a position proportional to the voltage. For example, if the full scale is 12V, the bar width in pixels = (voltage / 12) * 128. Use fillRect(0, 40, barWidth, 20, WHITE) and clear the previous bar by drawing a black rectangle over it. For the numeric value, use setTextSize(2) and setCursor(10, 10) to display the voltage with one decimal place, like “12.3V”. The font size 2 uses a 12x16 pixel character box, so you can fit about 10 characters per line. The GFX library’s print() function handles floats, but you need to format the string with dtostrf() to avoid unnecessary decimal places. For example, dtostrf(voltage, 4, 1, buffer) creates a string like “12.3”. The display’s memory is organized as 8 pages of 128 bytes each, and the SSD1306 uses a 1 KB SRAM buffer. Updating the entire buffer takes about 2 ms over I2C at 400 kHz, but you can optimize by using display.display() only once per loop, not after every drawing command.
To improve accuracy, use the microcontroller’s internal bandgap reference or an external voltage reference like the TL431. The Arduino Uno’s internal reference is 1.1V, but it’s not very stable. A better approach is to use the AREF pin with a 2.5V reference like the LM4040, then set analogReference(EXTERNAL). For a 10-bit ADC with a 2.5V reference, the resolution is 2.5V / 1024 = 2.44 mV per step. With a voltage divider factor of 3.13, the effective resolution is 2.44 mV * 3.13 = 7.64 mV per step, so you can measure 12V with an accuracy of about ±0.08V. For higher precision, use a 12-bit ADC like the ADS1115, which has 16-bit resolution and a programmable gain amplifier. The ADS1115 communicates over I2C and can measure voltages from ±0.256V to ±6.144V. Connect it to the same I2C bus as the OLED, and set its address to 0x48. The ADS1115’s conversion time is 8 ms at 860 samples per second, so you can update the voltmeter at 100 Hz without aliasing. The OLED’s I2C bus can handle multiple devices, but the total capacitance should be below 400 pF for 400 kHz operation. If you use a long cable, add a 4.7 kΩ pull-up resistor on each line to 3.3V.
For a real-time voltmeter, consider the sampling rate and averaging. The ADC on an Arduino Uno takes about 100 µs per conversion, so you can sample 1000 times per second. For a stable reading, average 10 samples over 10 ms, then update the display. The formula for the average voltage is V_avg = (sum of ADC readings / number of samples) * (V_ref / 1024) * voltage_divider_factor. Use a moving average filter with a window of 10 to 20 samples to reduce noise without introducing lag. The display’s update rate should be at least 10 Hz to avoid visible flicker, but 20 Hz is smoother. The SSD1306’s internal oscillator runs at about 1.5 MHz, and the frame rate is limited by the I2C speed. At 400 kHz, a full screen update takes 2 ms, so you can do 500 full updates per second, but the human eye perceives flicker above 60 Hz, so 20 Hz is fine. For a bar graph, you can update only the bar area by using display.setCursor() and display.fillRect() on a specific region, which reduces the I2C traffic to about 20 bytes per update.
Power consumption is a key factor for portable voltmeters. The OLED draws 5 to 10 mA, the microcontroller draws 10 to 20 mA, and the voltage divider draws about 0.5 mA (12V / 14.7 kΩ). Total current is around 30 mA, which means a 2000 mAh battery can run the voltmeter for 66 hours continuously. To save power, use the OLED’s sleep mode by sending the command 0xAE to turn off the display, and wake it with 0xAF. The SSD1306 also supports a charge pump regulator that can be disabled with command 0x8D and data 0x10 to save 2 mA. For a battery-powered project, use a 3.3V microcontroller like the ESP32 or ATmega328P at 8 MHz, and set the ADC to single-shot mode instead of free-running. The ESP32’s deep sleep mode draws 10 µA, but you need to wake it every 100 ms to take a reading. The OLED’s wake-up time from sleep is about 100 µs, so you can turn it on, update the display, and turn it off in 10 ms, reducing average current to 0.5 mA.
Calibration is essential for accurate voltage measurement. Use a multimeter to measure the actual voltage at the test point, then adjust the voltage divider factor in the code. For example, if the actual voltage is 5.00V and the ADC reads 512, the factor is 5.00 / (512 * 5.0 / 1024) = 2.0. Store this factor in EEPROM so it persists across power cycles. The Arduino’s EEPROM has 1024 bytes, and you can store a float in 4 bytes. For the OLED, you can display the calibration factor on the screen during setup. The display’s contrast can also be calibrated by adjusting the command 0x81 with values from 0x00 to 0xFF. A higher contrast uses more power, so for a battery voltmeter, set it to 0x40 (64) to save 2 mA. The display’s viewing angle is 160 degrees, and the brightness is 100 cd/m² at 0x7F contrast. The pixel color is white on a blue or black background, depending on the OLED variant. The 0.96 inch 128x64 i2c oled display I mentioned earlier uses a white pixel color, which is easier to read in direct sunlight compared to blue.
For a more advanced voltmeter, add a graphical scale with tick marks. Draw a line from (0, 50) to (128, 50) as the base, then draw vertical ticks every 10 pixels, with labels like “0V”, “3V”, “6V”, “9V”, “12V”. Use drawLine() for the ticks and setTextSize(1) for the labels. The font size 1 uses a 6x8 pixel character box, so you can fit 21 characters per line. The tick marks should be 5 pixels tall, and the labels should be 2 pixels below the base line. The bar graph can be a filled rectangle that grows from the left to the right, with a color gradient if you use the drawFastHLine() function to draw multiple horizontal lines with different shades. However, the SSD1306 only supports monochrome, so you can simulate a gradient by varying the pixel density. For example, draw a solid bar for the first half, then a dotted bar for the second half. The GFX library’s drawFastVLine() and drawFastHLine() are faster than drawLine() because they avoid the Bresenham algorithm. For a 128-pixel wide bar, drawFastHLine() takes 0.5 ms, while fillRect() takes 1 ms.
Noise reduction is critical for a stable voltmeter reading. Use a 100 nF capacitor between the ADC pin and ground, and a 10 µF electrolytic capacitor between VCC and ground on the microcontroller. The OLED’s power supply should also be decoupled with a 10 µF capacitor. The I2C lines should be kept short, under 10 cm, to avoid parasitic capacitance. If you use a long cable, use shielded twisted pair wires for SDA and SCL, and connect the shield to ground. The ADC input should be a low-impedance source, so the voltage divider’s output impedance should be under 10 kΩ. The voltage divider’s resistors should be 1% tolerance for accuracy. For a 12V input, use a 10 kΩ and 4.7 kΩ resistor with 1% tolerance, giving a maximum error of 0.1V. The ADC’s internal reference temperature drift is about 50 ppm/°C, so for a 10°C change, the error is 0.05%. The OLED’s display is not affected by temperature, but the SSD1306’s contrast can drift by 10% over 100°C. Use a temperature sensor like the DS18B20 to compensate, but for a simple voltmeter, it’s overkill.
To test the voltmeter, connect a variable power supply to the voltage divider input. Set the power supply to 1V, 3V, 6V, 9V, and 12V, and record the displayed values. The error should be within ±0.1V for a 10-bit ADC. If the error is larger, check the voltage divider resistors with a multimeter. The OLED’s I2C address can be changed by soldering the address pin on the module. Some modules have a jumper that sets the address to 0x3C or 0x3D. If you have multiple I2C devices, use a multiplexer like the TCA9548A to avoid address conflicts. The TCA9548A can switch between 8 I2C buses, and it costs about $2. For a portable voltmeter, use a 9V battery with a 5V regulator like the 7805, but the regulator’s quiescent current is 5 mA, so use a low-dropout regulator like the MCP1700, which draws only 1.6 µA. The OLED’s power supply should be 3.3V, so use a 3.3V regulator like the HT7833. The total current for a 3.3V system is 30 mA, and a 9V battery has 500 mAh, so the runtime is 16 hours. For longer runtime, use a 18650 lithium-ion battery with a 3.3V regulator, giving 2000 mAh / 30 mA = 66 hours.
The software architecture for the voltmeter should be event-driven. Use a timer interrupt to read the ADC every 10 ms, then update a global variable. In the main loop, check if the variable has changed by more than 0.1V, and if so, update the display. This avoids unnecessary I2C traffic. The interrupt service routine should be short, so just read the ADC and store the value. The averaging can be done in the main loop using a circular buffer. For a 10-sample buffer, the memory usage is 20 bytes. The OLED’s buffer is 1024 bytes, so the total RAM usage is about 1.5 KB on an Arduino Uno, which has 2 KB of RAM. The program size is about 10 KB for the libraries and 2 KB for the user code, so it fits in the 32 KB flash. For an ESP32, the RAM is 520 KB, so no issues. The ESP32’s WiFi can be used to send the voltage data to a server, but for a standalone voltmeter, it’s not needed. The ESP32’s Bluetooth can also be used to display the voltage on a phone, but that adds complexity.
For a professional look, add a startup animation that draws the voltmeter scale from left to right over 2 seconds. Use delay(20) between each pixel to create a smooth animation. The animation can be a simple line that grows from 0 to 128 pixels. The SSD1306’s memory is static, so you can pre-calculate the animation frames in an array. For a 128-frame animation, the array size is 128 bytes, which fits in the Arduino’s flash. Use PROGMEM to store the array in flash memory. The animation can also include a logo for 2 seconds, then switch to the voltmeter display. The logo can be a 128x64 bitmap, which is 1024 bytes. Use a tool like LCD Assistant to convert an image to a byte array. The bitmap can be displayed with display.drawBitmap(). The startup sequence should take less than 3 seconds to avoid user frustration.
Finally, consider the user interface. Add a button to switch between DC and AC voltage measurement. For AC, you need a rectifier circuit and a low-pass filter. The ADC can read the peak voltage, and the RMS value is peak / sqrt(2) for a sine wave. The display can show “AC 12.0V” or “DC 12.0V”. The button can be a simple tactile switch connected to a digital pin with a pull-up resistor. Debounce the button with a 50 ms delay in software. The button can also be used to enter a calibration mode, where you adjust the voltage divider factor by pressing the button to increment or decrement the value. The calibration value can be stored in EEPROM. The OLED can display “CAL: 3.13” during calibration. The user can then press the button to save the value. This makes the voltmeter user-friendly without needing a computer. The entire project can be built on a perfboard, and the components cost less than $10, making it a practical DIY project for hobbyists.