An LCD custom character is a user-defined glyph that you can draw, store, and display on a character LCD beyond the standard ASCII font. Instead of accepting only letters, numbers, and a handful of symbols, many HD44780-compatible displays let you define your own icons, arrows, battery bars, smileys, logos, or non-English letters. That flexibility turns a simple 16x2 or 20x4 display into a more expressive interface for embedded projects.

For makers and firmware developers, learning how LCD custom characters work is a practical skill. It helps you save screen space, communicate status quickly, and add polish to menus, sensors, and control panels without upgrading to a graphical display. This guide walks through the memory model, the 5x8 pixel grid, Arduino-style code, and the real limits you should know before designing a full icon set.

How LCD Custom Characters Work
Most character LCDs use an HD44780-compatible controller or a clone. That controller contains a character generator ROM, usually called CGROM, which holds the built-in font. It also contains a small character generator RAM, or CGRAM, where user-defined patterns live. The CGROM gives you the standard characters; the CGRAM gives you the custom ones.

Because CGRAM is RAM, the custom patterns can be changed while the program is running. You are not permanently burning new letters into the display. You can upload one set of icons at startup, replace a slot later, or animate a small symbol by rewriting its bytes in a loop. This makes LCD custom characters useful for both static icons and simple motion effects.
The 5x8 Pixel Matrix and CGRAM

Each custom character is built from a small pixel grid, typically 5 columns wide and 8 rows tall. In CGRAM, one custom character occupies eight bytes. Each byte represents one row, and the lower five bits of that byte decide which pixels are on. A bit value of 1 turns a pixel on, while 0 leaves it off. The display reads the rows from top to bottom, so the first byte is the top row and the eighth byte is the bottom row.
Consider a simple heart pattern. The rows might be 00000, 01010, 11111, 11111, 01110, 00100, 00000, and 00000. Converted to hexadecimal, those rows become 0x00, 0x0A, 0x1F, 0x1F, 0x0E, 0x04, 0x00, and 0x00. That compact byte array is all the controller needs to draw a recognizable heart on the LCD.
Addressing and Displaying Custom Characters

Custom character slots are normally numbered 0 through 7, giving you eight user-defined patterns at a time. After you store a pattern in slot 0, you display it by writing character code 0 to the LCD. In the Arduino LiquidCrystal library, that usually means calling lcd.createChar(0, heart); during setup and then lcd.write(byte(0)); where you want the icon to appear.
On I2C LCDs, the same underlying HD44780 controller is still doing the work. The I2C backpack only reduces the number of pins required, so libraries such as LiquidCrystal_I2C still support createChar. If you are driving the display directly, you send commands to set the CGRAM address and then write the eight data bytes. The result is the same: a custom character becomes available just like a normal font character.
Creating and Using LCD Custom Characters in Practice

To design a custom character, start with a 5x8 pixel grid and sketch the shape you want. Online LCD custom character generators are popular because they let you click pixels and automatically convert the drawing into a byte array. You can also draw the pattern by hand on graph paper, but keep the design simple. At only 5 pixels wide, fine details disappear quickly, so bold outlines and clear silhouettes work best.
Once the pattern is converted into eight bytes, your code uploads it to the display controller. The LCD must be initialized before you create the custom characters, and each pattern should be assigned to a unique slot. If your project needs more than eight icons, you can reuse slots dynamically. For example, you might show a battery icon in slot 0, then redefine slot 0 later as a temperature icon when the screen changes.










Step-by-Step Arduino LCD Custom Character Example
Start by defining byte arrays for each icon. A heart might look like byte heart[8] = {0b00000, 0b01010, 0b11111, 0b11111, 0b01110, 0b00100, 0b00000, 0b00000};. A smiley can follow the same structure with different rows. Clear names make the code easier to maintain, especially when you have several custom characters in one project.
In the setup function, initialize the LCD, then upload the patterns. For a standard parallel LCD, the sequence might be lcd.begin(16, 2); lcd.createChar(0, heart); lcd.createChar(1, smiley);. To display them, set the cursor and write the slot number: lcd.setCursor(0, 0); lcd.write(byte(0)); lcd.setCursor(1, 0); lcd.write(byte(1));. In the loop, you can update sensor readings or menu states while leaving the custom icons in place.
Practical Applications and Limitations
LCD custom characters are ideal for battery indicators, signal strength bars, progress meters, arrows, locks, clocks, thermometers, and simple logos. They also help with languages that need accented letters or symbols not present in the display's built-in font. In a weather station, for instance, you might combine a custom raindrop, sun, and cloud icon with numeric readings to create a clearer dashboard.
The main limitations are memory and resolution. Most HD44780-compatible LCDs allow only eight custom characters at once, and each one is limited to a 5x8 monochrome grid. There is no grayscale or anti-aliasing, so curves can look blocky. Also, redefining a slot changes every instance of that character code currently on the screen. You can animate by rewriting CGRAM quickly, but excessive updates can cause flicker or timing issues on slower interfaces.
With a few byte patterns and a bit of experimentation, you can turn a basic character LCD into a more expressive interface. Try sketching a custom icon for your next project, upload it to CGRAM, and see how much personality a small 5x8 grid can add. The best custom character set is usually the one shaped around your specific display layout and the information your users need at a glance.