The 16X2 LCD display, also commonly known as the 1602 LCD, is one of the most widely used screens in electronics projects and embedded systems. This exact model is named by its 16 columns and 2 rows (16x2), meaning it can display up to 32 characters at a time. This guide will walk you through the features, wiring, and programming of both the 16x2 Parallel LCD and the 16x2 LCD display with I2C backpack (I2C support).
The Display
The display featuring a 16 character by 2 line configuration, providing space for displaying text and simple graphics. Each character is formed in a 5x8 pixel matrix, allowing for clear and legible text. Here are some key aspects of the display:
- Character Structure: Each of the 32 characters (16 per line) is composed of a 5x8 dot matrix. This structure allows for the detailed rendering of alphanumeric characters, punctuation, and even custom icons.
- Backlight: Most 16x2 LCDs come with an LED backlight, which significantly improves visibility in low-light conditions. The backlight can usually be controlled via software or by adjusting the current-limiting resistor.
- Contrast: The contrast of the display can be adjusted using the VO pin, which is typically connected to a potentiometer. This allows for fine-tuning of the display's readability based on ambient lighting conditions.
- Viewing Angle: The LCD is designed to offer a wide viewing angle, ensuring that the display is readable from various positions. This is particularly useful in embedded applications where the display's orientation might not always be directly in front of the user.
- Response Time: The response time of the 16x2 LCD is fast enough for most text display applications, making it suitable for real-time data display such as clocks, counters, and status messages.

Custom Characters
One of the standout features of the 16x2 LCD is its ability to display custom characters. This is particularly useful for creating special symbols or icons that are not part of the standard ASCII character set. Here’s how you can create and display custom characters:
- Define Custom Characters: Custom characters are defined using a byte array, where each byte represents a row of the 5x8 pixel matrix.
- Upload to CGRAM: The custom characters are uploaded to the LCD’s Character Generator RAM (CGRAM). The HD44780 controller allows for up to 8 custom characters to be stored in CGRAM.
- Display Custom Characters: Once defined and uploaded, custom characters can be displayed using their corresponding ASCII codes.
Pinout Diagram
A standard 16x2 character LCD has 16 pins. Here’s a brief overview of each pin:

| Pin No. | Name | Description |
| 1 | GND (Ground) | Connect to ground (0V). |
| 2 | VCC (Power Supply) | Connect to the LCD’s power supply 5V). |
| 3 | Vo (LCD Contrast) | Adjust the contrast using a potentiometer. |
| 4 | RS (Register Select) | Separates commands (e.g., setting the cursor, clearing the screen) from data. Set to LOW for commands and HIGH for data. |
| 5 | R/W (Read/Write) | Determines whether data is read from or written to the LCD. Typically held LOW for write operations. |
| 6 | E (Enable) | Enables the display. Set to LOW to ignore activity on other lines; set to HIGH to process incoming data. |
| 7-14 | D0-D7 (Data Bus) | These pins carry the 8-bit data sent to the display. For example, to display an uppercase ‘A’, set these pins to 0100 0001 (according to the ASCII table). |
| 15 | A (5V) | LED Backlight VCC |
| 16 | K (Ground) | LED Backlight Ground |
Communication Methods for 16x2 LCD Displays
The 16x2 LCD display supports more than one way of communicating with a microcontroller. The method you choose depends on how many pins you have available, how complex your project is, and how much control or performance you need. The two most common options are parallel communication and I2C serial communication, both of which are widely supported by Arduino and similar boards.
Parallel Communication Mode
The traditional 16x2 LCD is controlled via Parallel Communication Mode. In this mode, the microcontroller sends data directly to the display using multiple data and control lines at the same time. In parallel mode, the LCD uses a data bus along with control pins such as Register Select and Enable. Commands and characters are sent by setting the appropriate pins HIGH or LOW, then pulsing the Enable pin to tell the display to read the data. This method gives direct control over the display and does not require additional hardware.
There are two common parallel configurations.
- 8 bit mode - Uses 8 data pins along with several control pins. This allows each character or command to be sent in a single operation, making it slightly faster. However, it requires a large number of GPIO pins, which can be impractical on smaller microcontrollers.
-
4 bit mode - Reduces the number of data pins to 4 by sending each byte in two halves. While this is marginally slower, it is far more common because it significantly reduces pin usage. Most Arduino projects use 4 bit mode.
Parallel mode makes sense when you want maximum compatibility, minimal reliance on external components, or very fine control over timing. It is also useful in learning environments where understanding how the LCD works at a low level is important.
We've included a complete wiring and pinout diagram in a later section of this guide
The back of the parallel version LCD display with no I2C backpack looks like this:

I2C Serial Communication Mode Using an LCD Backpack
The 16x2 LCD wih I2C backpack simplifies the connection between a microcontroller and a 16x2 LCD by converting the parallel interface into a serial one. This is done using an I2C backpack, a small circuit board that attaches directly to the back of the LCD.
The I2C backpack handles all the low level parallel signaling internally. Instead of controlling many data and control pins, the microcontroller sends commands and characters over the I2C bus using just two lines.
Data is transmitted over
- SDA for serial data
- SCL for the clock signal
We've included a complete wiring and pinout diagram in a later section of this guide
Each I2C device on the bus uses its own address, with common LCD backpack addresses being 0x27 or 0x3F. If you are unsure which address your display is using, it can be identified by running a simple I2C scanner sketch from the Arduino IDE.
I2C is particularly well suited to larger or more complex projects where multiple modules need to be connected at once. Because all devices share the same SDA and SCL lines, pin usage stays low even as more peripherals are added, provided each device has a unique address. Even with this advantage, there are also some trade offs. I2C communication is slightly slower than direct parallel control, which can matter in timing sensitive applications. It also relies on compatible libraries, meaning incorrect or outdated libraries can cause issues.

Changing the I2C Address on an LCD Backpack
When using more than one I2C device on the same bus, each device must have its own unique address. The I2C address for an LCD is set directly on the I2C backpack attached to the rear of the display.
The backpack includes three address selection pads labelled A0, A1, and A2. These pads control part of the I2C address. By default, all three pads are left unconnected, which represents a logic value of 1. Changing the address is done by shorting one or more of these pads with solder, which sets that pad to a logic value of 0. Shorting a pad simply means creating a small solder bridge between the two exposed contacts for that address pin. Running an I2C scanner sketch after making changes is recommended, as it confirms the new address before continuing with your project.

The table below shows the full range of possible addresses. A value of 1 means the pad is left open, while 0 means the pad is shorted.
| A0 | A1 | A2 | Address |
|---|---|---|---|
| 0 | 0 | 0 | 0x20 |
| 0 | 0 | 1 | 0x21 |
| 0 | 1 | 0 | 0x22 |
| 0 | 1 | 1 | 0x23 |
| 1 | 0 | 0 | 0x24 |
| 1 | 0 | 1 | 0x25 |
| 1 | 1 | 0 | 0x26 |
| 1 | 1 | 1 | 0x27 |
When multiple LCDs are connected to the same I2C bus, ensure that each backpack is configured with a different address from the table above. This prevents address conflicts and allows all displays to operate reliably on the same SDA and SCL lines.
16x2 LCD Command Codes
This table is a quick reference for the most commonly used 16x2 LCD commands. These commands apply to displays based on the HD44780 controller and are useful when controlling the screen, cursor behaviour, and text position in Arduino and other microcontroller projects.
| Command (Hex) | Name | Description |
|---|---|---|
| 0x01 | Clear display | Clears all text and moves the cursor to the home position |
| 0x02 | Return home | Moves the cursor to the top left without clearing the display |
| 0x04 | Entry mode set | Cursor moves left after writing a character |
| 0x06 | Entry mode set | Cursor moves right after writing a character |
| 0x0C | Display on | Turns the display on with cursor and blink disabled |
| 0x0E | Display on cursor | Turns the display on with the cursor visible |
| 0x0F | Display on blink | Turns the display on with cursor blinking |
| 0x10 | Cursor move | Moves the cursor one position to the left |
| 0x14 | Cursor move | Moves the cursor one position to the right |
| 0x80 + col | Set cursor line 1 | Moves the cursor to a specific column on the first line |
| 0xC0 + col | Set cursor line 2 | Moves the cursor to a specific column on the second line |
Arduino Parallel Interface LCD Examples
The following examples follow the official Arduino LiquidCrystal library pinout. The parallel interface allows for any digital pins to be used. With a quick modification of the pin assignment in the code, any pins or compatible board can be used. To simplify connections, we'll use the 4-bit model.
Arduino Uno to 16x2 LCD Pinout & Wiring Diagram

| LCD Pin | Function | Arduino Uno Pin |
|---|---|---|
| RS | Register Select | 12 |
| E | Enable signal | 11 |
| D4 | Data 4 | 5 |
| D5 | Data 5 | 4 |
| D6 | Data 6 | 3 |
| D7 | Data 7 | 2 |
| R/W | Read/Write | GND |
| GND | Ground | GND |
| VCC (5V) | Power | 5V |
| A (5V) | Backlight Power | 220 Ohm resistor then 5V |
| Vo | Contrast | Middle wiper of 10K potentiometer then power |
Hello World Example for 16x2 LCD
This example sketch prints Hello World! to the LCD.
/* * Ethan Zaitchik * Arduino 16x2 LCD Hello World Example * Full guide: https://zaitronics.com.au/blogs/guides/how-to-use-16x2-lcd-parallel-i2c */ #include <LiquidCrystal.h> // Include the LiquidCrystal library // Initialize the library by associating LCD interface pins with Arduino pins const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2; LiquidCrystal lcd(rs, en, d4, d5, d6, d7); void setup() { lcd.begin(16, 2); // Set up the LCD's number of columns and rows lcd.print("hello, world!"); // Print a static message to the first row } void loop() { lcd.setCursor(0, 1); // Move cursor to column 0, row 1 (second row) lcd.print(millis() / 1000); // Print the number of seconds since reset }
Code Explanation
This Arduino sketch demonstrates the classic "Hello World" example using a 16x2 character LCD display in 4-bit mode with the built-in LiquidCrystal library.
Here is a detailed line-by-line explanation of the code:
#include <LiquidCrystal.h> // Include the LiquidCrystal library
- Includes the standard Arduino LiquidCrystal library, which provides simple functions for controlling HD44780-compatible character LCDs (the controller used in most 16x2 LCD modules).
// Initialize the library by associating LCD interface pins with Arduino pins
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
- Declares constant integers that map the LCD pins to Arduino Uno digital pins:
- rs (Register Select) → pin 12: Selects between command register (low) and data register (high).
- en (Enable) → pin 11: A high-to-low pulse latches the data/command into the LCD.
- d4 to d7 → pins 5, 4, 3, 2: The four data lines used for 4-bit communication.
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
- Creates an instance of the LiquidCrystal class named lcd.
- The constructor parameters match the pin assignments above and configure the library for 4-bit mode (only the upper four data pins are specified).
void setup()
{
lcd.begin(16, 2); // Set up the LCD's number of columns and rows
- setup() runs once when the Arduino powers on or resets.
- lcd.begin(16, 2) initializes the display as a 16-column by 2-row LCD and performs the required internal initialization sequence for the HD44780 controller.
lcd.print("hello, world!"); // Print a static message to the first row
}
- lcd.print() sends the string "hello, world!" to the LCD.
- By default, printing starts at position (0,0) - the top-left corner (first column, first row).
- The text appears in lowercase because the string literal uses lowercase letters.
void loop()
{
lcd.setCursor(0, 1); // Move cursor to column 0, row 1 (second row)
- loop() runs repeatedly after setup() completes.
- lcd.setCursor(0, 1) moves the cursor to the start of the second row (columns and rows are zero-indexed).
lcd.print(millis() / 1000); // Print the number of seconds since reset
}
- millis() returns the number of milliseconds since the Arduino started running the program.
- Dividing by 1000 converts it to seconds.
- lcd.print() displays this value on the second row.
- Since this is inside loop(), the seconds counter updates continuously.
lcd.print(" "); (16 spaces) or use more advanced formatting techniques.Autoscroll Example
This example sketch prints characters from 0 to 9 then moves the cursor to the bottom right, turns autoscroll on, and prints them again.
/* * Ethan Zaitchik * Arduino 16x2 LCD Autoscroll Example * Full guide: https://zaitronics.com.au/blogs/guides/how-to-use-16x2-lcd-parallel-i2c */ #include <LiquidCrystal.h> // Include the LiquidCrystal library // Initialize the library by associating LCD interface pins with Arduino pins const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2; LiquidCrystal lcd(rs, en, d4, d5, d6, d7); void setup() { lcd.begin(16, 2); // set up the LCD's number of columns and rows } void loop() { lcd.setCursor(0, 0); // set the cursor to (0,0): for (int thisChar = 0; thisChar < 10; thisChar++) { // print from 0 to 9: lcd.print(thisChar); delay(500); } lcd.setCursor(16, 1); // set the cursor to (16,1): lcd.autoscroll(); // set the display to automatically scroll: for (int thisChar = 0; thisChar < 10; thisChar++) { // print from 0 to 9: lcd.print(thisChar); delay(500); } lcd.noAutoscroll(); // turn off automatic scrolling lcd.clear(); // clear screen for the next loop: }
Code Explanation
This Arduino sketch demonstrates various features of a 16x2 LCD display using the built-in LiquidCrystal library in 4-bit parallel mode. It counts from 0 to 9 on the first row, then demonstrates the autoscroll() feature on the second row, and finally clears the screen before repeating.
Here is a detailed line-by-line explanation of the code:
#include <LiquidCrystal.h>
- Includes the standard Arduino LiquidCrystal library for controlling HD44780-based character LCDs.
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
- Defines the Arduino pins connected to the LCD in 4-bit mode:
- rs → pin 12 (Register Select)
- en → pin 11 (Enable)
- d4–d7 → pins 5, 4, 3, 2 (data lines)
- Creates a LiquidCrystal object named lcd with these pin assignments.
void setup() {
lcd.begin(16, 2);
}
- setup() runs once at startup.
- lcd.begin(16, 2) initializes the LCD as a 16-column, 2-row display and performs the necessary controller setup.
void loop() {
lcd.setCursor(0, 0);
for (int thisChar = 0; thisChar < 10; thisChar++) {
lcd.print(thisChar);
delay(500);
}
- loop() runs repeatedly.
- lcd.setCursor(0, 0) moves the cursor to the first column of the first row (top-left).
- A for loop counts from 0 to 9:
- lcd.print(thisChar) prints the current number.
- delay(500) pauses for 500 ms so each digit is visible for half a second.
- Result: "0123456789" appears sequentially on the first row.
lcd.setCursor(16, 1);
lcd.autoscroll();
- lcd.setCursor(16, 1) positions the cursor just off the right edge of the second row (column 16 is one position beyond the visible 16 columns).
- lcd.autoscroll() enables automatic scrolling mode: each new character printed shifts the entire display content to the left, making new characters appear from the right.
for (int thisChar = 0; thisChar < 10; thisChar++) {
lcd.print(thisChar);
delay(500);
}
- Another for loop prints digits 0 through 9 again.
- Because autoscroll is on and the cursor starts off-screen, each new digit appears to enter from the right, pushing previous digits leftward.
- This creates a nice scrolling effect on the second row.
lcd.noAutoscroll();
lcd.clear();
}
- lcd.noAutoscroll() disables automatic scrolling for future operations.
- lcd.clear() clears the entire display and moves the cursor back to (0,0).
- This prepares the LCD for the next iteration of loop(), so the sequence repeats cleanly.
• First row: Digits 0–9 appear one by one from left to right.
• Second row: Digits 0–9 appear to scroll in from the right.
• Screen clears, then the whole animation repeats indefinitely.
Arduino I2C Interface LCD Examples
The following examples follow the official Arduino LiquidCrystal library but introduce the common I2C interface, simplifying the wiring required. I2C requires SDA and SCL, which many boards have dedicated pins for. For the Arduino Uno, that is A4 for SDA and A5 for SCL. We've produced a reference guide that includes the I2C pinouts for many common boards.

| Pin | Type | Description |
|---|---|---|
| A4 (SDA) | Data | Carries data between master and slave devices |
| A5 (SCL) | Clock | Synchronizes data transfer |
Hello World Example for 16x2 LCD (I2C)
This example sketch prints Hello World! to the LCD using I2C.
/* * Ethan Zaitchik * Arduino 16x2 LCD Hello World Example (I2C) * Full guide: https://zaitronics.com.au/blogs/guides/how-to-use-16x2-lcd-parallel-i2c */ #include <Wire.h> // Include the Wire library for I2C #include <LiquidCrystal_I2C.h> // Include the I2C LCD library // Set the LCD I2C address (usually 0x27 or 0x3F) and size (16 columns, 2 rows) LiquidCrystal_I2C lcd(0x27, 16, 2); void setup() { lcd.init(); // Initialize the LCD lcd.backlight(); // Turn on the backlight lcd.print("hello, world!"); // Print a static message to the first row } void loop() { lcd.setCursor(0, 1); // Move cursor to column 0, row 1 (second row) lcd.print(millis() / 1000); // Print the number of seconds since reset }
Code Explanation (I2C)
This Arduino sketch demonstrates the classic "Hello World" example using a 16x2 character LCD display connected via I2C (using an I2C backpack module like PCF8574). It uses the popular LiquidCrystal_I2C library, which greatly reduces the number of required wires compared to parallel connection.
Here is a detailed line-by-line explanation of the code:
#include <Wire.h> // Include the Wire library for I2C
- Includes Arduino's built-in Wire library, which handles the low-level I2C communication (SDA and SCL lines).
#include <LiquidCrystal_I2C.h> // Include the I2C LCD library
- Includes the LiquidCrystal_I2C library (commonly by Frank de Brabander), which extends the standard LiquidCrystal functionality to work over I2C via a PCF8574 expander chip on the LCD backpack.
// Set the LCD I2C address (usually 0x27 or 0x3F) and size (16 columns, 2 rows)
LiquidCrystal_I2C lcd(0x27, 16, 2);
- Creates an instance of LiquidCrystal_I2C named lcd.
- The constructor takes three parameters:
- 0x27: The I2C address of the backpack (common values are 0x27 or 0x3F - check your module or run an I2C scanner if unsure).
- 16: Number of columns.
- 2: Number of rows.
void setup() {
lcd.init(); // Initialize the LCD
- setup() runs once at startup.
- lcd.init() (sometimes called lcd.begin() in other versions) initializes the LCD controller over I2C and prepares it for use.
lcd.backlight(); // Turn on the backlight
- lcd.backlight() turns on the LCD backlight (controlled through the I2C expander). Use lcd.noBacklight() to turn it off.
lcd.print("hello, world!"); // Print a static message to the first row
}
- lcd.print() displays the string "hello, world!" starting at the default cursor position (0,0) - the top-left corner of the first row.
void loop() {
lcd.setCursor(0, 1); // Move cursor to column 0, row 1 (second row)
- loop() runs repeatedly.
- lcd.setCursor(0, 1) positions the cursor at the beginning of the second row (rows and columns are zero-indexed).
lcd.print(millis() / 1000); // Print the number of seconds since reset
}
- millis() returns milliseconds since the Arduino started.
- Dividing by 1000 converts to seconds.
- lcd.print() displays the current seconds count on the second row, updating continuously.
lcd.print(" "); (16 spaces) before printing the new value, or use formatted printing techniques.0x27 to 0x3F (or run an I2C scanner sketch to find the correct address).Autoscroll Example (I2C)
This I2C version of an earlier example sketch prints characters from 0 to 9 then moves the cursor to the bottom right, turns autoscroll on, and prints them again.
Code Explanation (I2C)
This Arduino sketch demonstrates the autoscroll() feature on a 16x2 LCD display connected via I2C (using a PCF8574 backpack). It uses the LiquidCrystal_I2C library and shows counting from 0 to 9 normally on the first row, then with a scrolling effect on the second row, before clearing the screen and repeating.
Here is a detailed line-by-line explanation of the code (skipping the initial documentation comment):
#include <Wire.h>
#include <LiquidCrystal_I2C.h> // Include the I2C LCD library
- #include <Wire.h>: Includes Arduino's built-in library for I2C communication.
- #include <LiquidCrystal_I2C.h>: Includes the third-party library that allows control of the LCD over I2C.
// Common I2C address is 0x27 or 0x3F, adjust if needed
LiquidCrystal_I2C lcd(0x27, 16, 2);
- Creates an instance of LiquidCrystal_I2C named lcd.
- Parameters: I2C address 0x27 (change to 0x3F if your module uses that), 16 columns, 2 rows.
void setup() {
lcd.init(); // Initialize the LCD
lcd.backlight(); // Turn on the backlight
}
- setup() runs once at startup.
- lcd.init() initializes the LCD controller over I2C (equivalent to begin() in some library versions).
- lcd.backlight() turns on the LED backlight.
void loop() {
lcd.setCursor(0, 0); // Set the cursor to column 0, row 0
- loop() runs repeatedly.
- lcd.setCursor(0, 0) places the cursor at the top-left corner (first column, first row).
for (int thisChar = 0; thisChar < 10; thisChar++) { // Print from 0 to 9
lcd.print(thisChar);
delay(500);
}
- A for loop prints digits 0 through 9 on the first row.
- Each digit is displayed for 500 ms (delay(500)), so they appear sequentially from left to right.
- Result: "0123456789" builds up on the top row.
lcd.setCursor(16, 1); // Set the cursor just off the right edge of row 1
lcd.autoscroll(); // Enable automatic scrolling
- lcd.setCursor(16, 1) moves the cursor to column 16 on the second row — one position beyond the visible area (off the right edge).
- lcd.autoscroll() turns on autoscroll mode: every new character printed shifts the entire display content left, making the new character appear to enter from the right.
for (int thisChar = 0; thisChar < 10; thisChar++) { // Print from 0 to 9
lcd.print(thisChar);
delay(500);
}
- Prints digits 0 through 9 again.
- With autoscroll enabled and cursor starting off-screen, each new digit appears to slide in from the right, pushing previous digits left.
- This creates a smooth right-to-left scrolling effect on the second row.
lcd.noAutoscroll(); // Disable automatic scrolling
lcd.clear(); // Clear the display for the next loop
}
- lcd.noAutoscroll() turns off autoscroll mode.
- lcd.clear() erases everything on the display and returns the cursor to (0,0).
- This resets the LCD so the animation can start over cleanly on the next loop iteration.
• Top row: Digits 0–9 appear one by one from left to right.
• Bottom row: Digits 0–9 scroll in from the right, moving left across the screen.
• Screen clears, and the entire sequence repeats forever.
0x27 to 0x3F.Common Issues, FAQs & Troubleshooting Tips
Why is my LCD only showing blank squares?
This is the most common issue and usually means the LCD is powered but the contrast is not set correctly. Turn the potentiometer connected to the V0 (contrast) pin slowly until characters appear. You should first see faint blocks, then readable text. If using I2C backpack, turn the white/blue trimpot till the characters are clear.
Nothing appears on the screen at all
- Double-check VCC and GND wiring.
- Ensure the backlight pins (A and K) are connected correctly.
- If using I2C, confirm the correct I2C address (commonly
0x27or0x3F). - Run an I2C scanner sketch to detect your LCD module.
Text is garbled or random symbols appear
- Check that the correct pins are defined in your code.
- Make sure the LCD is initialised in the correct mode (4-bit or 8-bit).
- Loose jumper wires can also cause corrupted data.
Old characters remain when numbers change
This happens when shorter values overwrite longer ones (for example, going from 100 to 25). Clear the line before printing new data or add spaces after your text.
What is the difference between 4-bit and 8-bit mode?
| Mode | Data Pins Used | Speed | Pin Usage | Recommended For |
|---|---|---|---|---|
| 4-bit Mode | D4 – D7 | Slightly slower | Uses fewer Arduino pins | Most projects where pins are limited |
| 8-bit Mode | D0 – D7 | Faster data transfer | Uses many GPIO pins | High-speed or dedicated display setups |
In 4-bit mode, data is sent in two halves (nibbles), saving GPIO pins and making it ideal for Arduino projects. In 8-bit mode, all data bits are sent at once, making it faster but more hardware-intensive.
Backlight is on but text is invisible
This usually indicates a contrast issue or that the LCD has not been initialised correctly in code. Check the contrast potentiometer and ensure your lcd.begin(16, 2); line is present.
Screen flickers or updates slowly
- Avoid using
lcd.clear()repeatedly inside loops. - Only update parts of the display that actually change.
What power voltage input?
The display itself is rated for 5V, however the I2C version contains a converter to drive the chip from a lower voltage. Either version of the display can work with 3.3V, however the brightness would be low. This can be avoided by using USB power (VBUS) when powering from a 3.3V microcontroller.