| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Format and display content on HD44780 LCDs (1602 / 2004) with scrolling, automatic pagination, and fixed prefix/postfix labels — all without the RPLCD dependency.
| Requirement | Version |
|---|---|
| Python | ≥ 3.7 |
| smbus2 | ≥ 0.4.1 |
| Hardware | HD44780 LCD (1602 or 2004) + PCF8574 I2C expander |
| OS | Linux with I2C enabled (e.g. Raspberry Pi OS) |
pip install lcd-content-formattersudo raspi-config # Interfacing Options → I2C → Enable
sudo reboot
i2cdetect -y 1 # confirm your display address (commonly 0x27 or 0x3F)from lcd_content_formatter import HD44780
with HD44780("PCF8574", 0x27, cols=20, rows=4) as lcd:
frame = lcd.Frame()
# Rows with static prefix and postfix
row_temp = frame.add("temp", "-", prefix="Temp: ", postfix=" °C")
row_hum = frame.add("hum", "-", prefix="Hum: ", postfix=" %")
row_time = frame.add("time", "-", prefix="Time: ")
row_date = frame.add("date", "-", prefix="Date: ")
import time
from datetime import date, datetime
while True:
row_temp.text = "23.5"
row_hum.text = "61"
row_time.text = datetime.now().strftime("%H:%M:%S")
row_date.text = str(date.today())
for row in (row_temp, row_hum, row_time, row_date):
frame.update_row(row)
lcd.scroll_frame(frame)
time.sleep(1)The following diagram illustrates how content is organised for a 20×4 display. The same model applies to 16×2 displays — only the physical dimensions change.
A Frame is a container that holds an ordered list of Frame Rows. When a Frame is passed to scroll_frame() or write_frame(), the library groups its rows into pages automatically — there is no manual page management.
When the number of Frame Rows exceeds the physical display height (e.g. 8 rows on a 4-row display), the library splits them into Pages. scroll_frame() iterates through every page in sequence.
Each Frame Row maps to one line on the display and has four parts:
| Part | Required | Description |
|---|---|---|
| id | optional | Unique key used to retrieve and update the row later. Omit with add_with_guid() for static rows. |
| prefix | optional | Static label before the value — e.g. "Temp: ". Never scrolls. |
| text | yes | The dynamic value you update at runtime — e.g. "23.5". This is the part that scrolls when it overflows. |
| postfix | optional | Static label after the value — e.g. " °C". Included in the scrolling window. |
The display renders each row as: prefix + text + postfix, padded or truncated to the column width.
┌────────────────────┐
│ Temp: 23.5 °C │ row 0 — prefix="Temp: " text="23.5" postfix=" °C"
│ Hum: 61 % │ row 1 — prefix="Hum: " text="61" postfix=" %"
│ Time: 14:32:01 │ row 2 — prefix="Time: " text="14:32:01"
│ Date: 2024-06-01 │ row 3 — prefix="Date: " text="2024-06-01"
└────────────────────┘
Page 1 of 2
┌────────────────────┐
│ IP eth0: 192.168.. │ row 4 — long text scrolls left automatically
│ IP wlan0: UNKNOWN │ row 5
│ CPU temp: 52.3 °C │ row 6
│ │ row 7 — empty row pads the last page
└────────────────────┘
Page 2 of 2
Main class. Constructor parameters:
| Parameter | Type | Description |
|---|---|---|
| i2c_expander | str | Expander type — "PCF8574" |
| address | int | I2C address, e.g. 0x27 |
| cols | int | Number of display columns (16 or 20) |
| rows | int | Number of display rows (2 or 4) |
| port | int | I2C bus number (default 1) |
| backlight | bool | Backlight state at startup |
Create a new, empty Frame object (also importable as from lcd_content_formatter import Frame).
Add a row with an explicit string ID. Raises DuplicateFrameRowError if the ID already exists.
Add a row with an auto-generated UUID as the ID. Use this for static labels that are never updated by ID.
Retrieve a row by ID. Creates an empty row when create_if_missing=True (default).
Replace the stored row whose id matches row.id with the updated object.
Remove a row by ID. Raises FrameRowNotFoundError if not found.
Remove all rows from the frame.
Render a single page of frame to the display. No scrolling — use scroll_frame for animations.
Display frame with optional horizontal scrolling.
| Parameter | Default | Description |
|---|---|---|
| scroll_in | False | Text enters from the right edge |
| scroll_to_blank | False | Text scrolls fully off the left before the next page |
| scroll_if_fit | False | Animate even rows that fit without scrolling |
| delay | 0.5 | Seconds between scroll steps (controls speed) |
| show_first_after_scroll | True | Reset display to page 1 after all pages finish |
Scroll mode combinations:
| scroll_in | scroll_to_blank | Effect |
|---|---|---|
| False | False | Standard — scrolls left until end of text is visible, then pauses |
| True | False | Text enters from the right, stops when fully visible |
| False | True | Text scrolls left until completely off screen |
| True | True | Text enters from the right and exits to the left |
Release the I2C bus. Called automatically when used as a context manager.
| Attribute | Type | Description |
|---|---|---|
| id | str | Unique identifier |
| text | str | Dynamic value (the part that changes) |
| prefix | str | Static label shown before text |
| postfix | str | Static label shown after text |
| full_text | str | Read-only: prefix + text + postfix |
| Exception | Inherits | Raised when |
|---|---|---|
| LCDError | Exception | Base class |
| FrameRowNotFoundError | LCDError, KeyError | Row ID not found in frame |
| DuplicateFrameRowError | LCDError, ValueError | Row ID already exists |
| I2CError | LCDError, OSError | I2C bus communication failure |
v2 removes the RPLCD dependency. The main changes are:
| v1 | v2 |
|---|---|
| pip install RPLCD | pip install smbus2 (done automatically) |
| from HD44780 import HD44780 | from lcd_content_formatter import HD44780 |
| lcd.Frame() | unchanged |
| frame.addWithGuid(...) | frame.add_with_guid(...) (old name still works) |
| frame.getFrame(id) | frame.get_row(id) (old name still works) |
| lcd.scrollFrame(...) | lcd.scroll_frame(...) (old name still works) |
| Constructor: HD44780(expander, addr, cols, rows) | same, positional order unchanged |
All v1 method names are kept as deprecated aliases so existing scripts continue to run.
Connect the PCF8574 I2C expander to your Raspberry Pi as shown below:
The default PCF8574 pin mapping assumed by this library:
| PCF8574 pin | HD44780 pin | Function |
|---|---|---|
| P0 | RS | Register Select |
| P1 | RW | Read/Write (always write) |
| P2 | EN | Enable |
| P3 | — | Backlight |
| P4–P7 | D4–D7 | 4-bit data bus |
A runnable demo is in the sample/ directory. It shows IP addresses, CPU temperature, a counter, date, time, and a long scrolling text across two pages on a 20×4 display.
Scroll animation examples:



Edit sample/config.py:
lcd_i2c_expander_type = "PCF8574"
lcd_i2c_address = 0x27 # use i2cdetect -y 1 to find your address
lcd_column_count = 20
lcd_row_count = 4python sample/sample.pyfrom lcd_content_formatter import HD44780
import config
import sample_functions
import time
from datetime import date, datetime
with HD44780(
config.lcd_i2c_expander_type,
config.lcd_i2c_address,
cols=config.lcd_column_count,
rows=config.lcd_row_count,
) as lcd:
frame = lcd.Frame()
# Rows with explicit IDs — updated in the loop by reference
row_ip_eth0 = frame.add("ip_eth0", "-", prefix="IP eth0: ")
row_ip_wlan0 = frame.add("ip_wlan0", "-", prefix="IP wlan0: ")
row_temp = frame.add("cpu_temp", "-", prefix="CPU temp: ", postfix=" °C")
row_counter = frame.add("counter", "-", prefix="Count: ", postfix=" iter.")
# These rows push the frame beyond 4 rows → page 2 is created automatically
row_date = frame.add_with_guid("-", prefix="Date: ")
row_time = frame.add_with_guid("-", prefix="Time: ")
row_long = frame.add_with_guid("Lorem ipsum dolor sit amet!", prefix="Text: ")
while True:
row_ip_eth0.text = sample_functions.get_ip_address("eth0")
row_ip_wlan0.text = sample_functions.get_ip_address("wlan0")
row_temp.text = str(sample_functions.get_cpu_temperature())
row_counter.text = str(config.sample_counter)
row_date.text = str(date.today())
row_time.text = datetime.now().strftime("%H:%M:%S")
for row in (row_ip_eth0, row_ip_wlan0, row_temp, row_counter, row_date, row_time):
frame.update_row(row)
# Change scroll_in / scroll_to_blank / scroll_if_fit to try different animations
lcd.scroll_frame(frame, scroll_in=False, scroll_to_blank=False)
config.sample_counter += 1See CHANGELOG.md.
MIT © 2021-2026 Phoeluga
| Back | FazBrowse Home | New Git URL |