Guide for implementing support for a new segment LCD display not yet in SegLCDLib.
Overview
Adding a display involves 5 steps:
- Identify the controller (PCF85176, HT1621, HT1622, or VK0192)
- Test with RAW LCD class (prototype segment mapping)
- Create dedicated LCD class (implement display-specific driver)
- Create example sketch (demonstrate usage)
- Document the display (add to supported-lcds.md)
Estimated time: 1-2 hours for first-time implementation.
Step 1: Identify the Controller
Examine the LCD Module
Look for markings on the LCD module itself:
LCD Module (bottom view):
┌─────────────────┐
│ COB │ ← Integrated controller (HT1621, HT1622, VK0192)
│ HT1621 marked │
└─────────────────┘
Common scenarios:
| Controller Marked | Type | Pins | Notes |
| PCF85176 (separate IC) | I2C | 2 (SDA, SCL) | See separate IC on PCB |
| PCF8576 (separate IC) | I2C | 2 (SDA, SCL) | Variant of PCF85176 |
| None (COB) | Usually 3-wire | 3 (CLK, DATA, CS) | Integrated in module |
| HT1621 | 3-wire serial | 3 | Standard 6-digit, small |
| HT1622 | 3-wire serial | 3 | Larger RAM, 10+ digits |
| VK0192 | 3-wire serial | 3 | Irregular addressing |
Determine I2C Address (if PCF85176)
Use the standard I2C scanner:
#include <Wire.h>
void setup() {
Serial.begin(9600);
Wire.begin();
}
void loop() {
for (uint8_t addr = 0x00; addr < 0xFF; addr++) {
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0) {
Serial.print("Device found at 0x");
Serial.println(addr, HEX);
}
}
delay(5000);
}
Expected I2C addresses for PCF85176: 0x38 (SA0=0) or 0x39 (SA0=1)
Document Pinout
Create a pinout table:
LCD Module Pinout (My Display):
Pin Name Connect To
1 VCC 3.3V/5V
2 GND GND
3 SDA Arduino A4 (I2C)
4 SCL Arduino A5 (I2C)
Step 2: Test with RAW LCD Class
Use the appropriate RAW LCD class to prototype segment mapping without writing a full driver.
For PCF85176 (I2C)
#include <Wire.h>
#include "SegLCD_PCF85176_Raw.h"
SegLCD_PCF85176_Raw lcd(Wire);
void setup() {
Wire.begin();
Serial.begin(9600);
lcd.init();
Serial.println("RAW LCD initialized. Send segment data via Serial.");
}
void loop() {
if (Serial.available()) {
uint8_t addr = Serial.parseInt();
uint8_t value = Serial.parseInt();
lcd._ramBuffer[addr] = value;
lcd._writeRam();
Serial.print("Address 0x");
Serial.print(addr, HEX);
Serial.print(" = 0x");
Serial.println(value, HEX);
}
}
Send commands via Serial Monitor:
0 FF // Set address 0 to 0xFF (all segments on)
1 01 // Set address 1 to 0x01
For HT1621/1622/VK0192 (3-Wire)
const int CLK = 5, DATA = 6, CS = 7;
void setup() {
pinMode(CLK, OUTPUT);
pinMode(DATA, OUTPUT);
pinMode(CS, OUTPUT);
Serial.begin(9600);
lcd.init();
Serial.println("RAW LCD ready");
}
void loop() {
if (Serial.available()) {
uint8_t addr = Serial.parseInt();
uint8_t value = Serial.parseInt();
lcd._ramBuffer[addr] = value;
lcd._writeRam();
Serial.print("Address ");
Serial.print(addr, HEX);
Serial.print(" = ");
Serial.println(value, HEX);
}
}
Raw HT1621 LCD implementation for prototyping and testing.
Raw VK0192 LCD implementation for prototyping and testing.
Raw HT1621 LCD class for direct RAM access.
Definition SegLCD_HT1621_Raw.h:17
Mapping Process
- Enable all segments: Send 0xFF to each address in sequence
- Observe display: Note which segments light up for each address
- Record mapping: Create table: Address → Display Position
Example table for 6-digit display:
Address Digit Segments Binary Notes
0x00 1 a,b,c,d,e,f,g 11111110 Standard 7-segment
0x01 1 decimal point 00000001
0x02 1 colon 00000010
0x03 2 a,b,c,d,e,f,g 11111110
0x04 2 decimal point 00000001
...
Step 3: Create Dedicated LCD Class
File Structure
Create two files:
src/SegLCD_[CONTROLLER]_[DISPLAY].h (header)
src/SegLCD_[CONTROLLER]_[DISPLAY].cpp (implementation)
Example for PCF85176 6-digit display:
src/SegLCD_PCF85176_MyDisplay.h
src/SegLCD_PCF85176_MyDisplay.cpp
Header File Template
#ifndef SEGLCD_PCF85176_MYDISPLAY_H
#define SEGLCD_PCF85176_MYDISPLAY_H
#include "SegDriver_PCF85176.h"
public:
SegLCD_PCF85176_MyDisplay(TwoWire& i2c, uint8_t address = 0x38, uint8_t subaddress = 0);
virtual size_t write(uint8_t ch);
void setBattery(uint8_t level);
void setSignal(uint8_t bars);
private:
static const uint8_t _charMap[256];
void _mapSegments(uint8_t digit, uint8_t segments);
static const uint8_t _batteryAddr;
static const uint8_t _batteryBits[4];
static const uint8_t _signalAddr;
static const uint8_t _signalBits[5];
};
#endif
Implementation of the PCF85176 controllers.
Definition SegDriver_PCx85.h:144
virtual void init() override
Logical display sections that can be targeted by higher-level rendering logic.
Definition SegDriver_PCx85.cpp:10
virtual size_t write(uint8_t ch)
Definition SegLCDLib.h:89
Implementation File Template
#include "SegLCD_PCF85176_MyDisplay.h"
const uint8_t SegLCD_PCF85176_MyDisplay::_charMap[256] = {
0x3F,
0x06,
0x5B,
0x4F,
0x66,
0x6D,
0x7D,
0x07,
0x7F,
0x6F,
0x00
};
const uint8_t SegLCD_PCF85176_MyDisplay::_batteryAddr = 0x12;
const uint8_t SegLCD_PCF85176_MyDisplay::_batteryBits[4] = {
0x00,
0x01,
0x03,
0x07
};
SegLCD_PCF85176_MyDisplay::SegLCD_PCF85176_MyDisplay(uint8_t i2cAddress)
}
void SegLCD_PCF85176_MyDisplay::init() {
}
size_t SegLCD_PCF85176_MyDisplay::write(uint8_t ch) {
if (_cursorCol >= 6) {
return 0;
}
uint8_t segments = _charMap[ch];
_mapSegments(_cursorCol, segments);
_writeRam();
_cursorCol++;
return 1;
}
void SegLCD_PCF85176_MyDisplay::_mapSegments(uint8_t digit, uint8_t segments) {
uint8_t addr = digit;
_ramBuffer[addr] = segments;
}
void SegLCD_PCF85176_MyDisplay::setBattery(uint8_t level) {
if (level > 3) level = 3;
_ramBuffer[_batteryAddr] &= 0xF8;
_ramBuffer[_batteryAddr] |= _batteryBits[level];
_writeRam();
}
void SegLCD_PCF85176_MyDisplay::setSignal(uint8_t bars) {
if (bars > 4) bars = 4;
}
Key Implementation Notes
- Cursor Tracking: Track
_cursorRow and _cursorCol (inherit from base class)
- Character Map: Populate
_charMap[] array with 7-segment patterns for each character
- Segment Mapping: Implement
_mapSegments() based on your RAW LCD testing
- Feature Methods: Add
setBattery(), setSignal(), etc. for display-specific features
- RAM Buffering: Always write to
_ramBuffer[], then call _writeRam()
Step 4: Create Example Sketch
Place in examples/[CONTROLLER]/[DisplayType]/[DisplayType].ino
Example for PCF85176 6-digit display:
#include <Wire.h>
#include "SegLCD_PCF85176_MyDisplay.h"
SegLCD_PCF85176_MyDisplay lcd(Wire);
void setup() {
Wire.begin();
lcd.init();
lcd.clear();
Serial.begin(9600);
Serial.println("My Display Example Started");
}
void loop() {
static int counter = 0;
lcd.setCursor(0, 0);
lcd.print(counter);
lcd.setBattery(counter % 4);
lcd.setSignal((counter / 2) % 5);
counter++;
if (counter > 999999) counter = 0;
delay(1000);
}
Step 5: Document the Display
Update docs/supported-lcds.md
Add a section for your display under the appropriate controller:
### My Custom Display
**Specifications:**
- **Digits:** 6 7-segment digits
- **Features:** Battery level, Signal strength
- **Wiring:** I2C (SDA, SCL)
- **I2C Address:** 0x38 (SA0=0) or 0x39 (SA0=1)
- **Controller:** PCF85176
- **Purchase:** [Link to AliExpress/store]
- **Example:** `examples/PCF85176/MyDisplay/`
**Code Example:**
\`\`\`cpp
#include "SegLCD_PCF85176_MyDisplay.h"
SegLCD_PCF85176_MyDisplay lcd(Wire);
lcd.init();
lcd.print(123456);
lcd.setBattery(2);
lcd.setSignal(3);
\`\`\`
Troubleshooting
Segments Not Showing
- Verify I2C/3-wire communication:
- Test with RawLCD class first
- Check wiring (SDA/SCL for I2C, CLK/DATA/CS for 3-wire)
- Check segment mapping:
- Raw LCD shows all on at address X
- Your class shows nothing at digit X
- →
_mapSegments() has wrong address
- Verify character map:
write() receives character but doesn't display
- Check
_charMap[ch] has correct pattern
- Test with
0x3F (all segments on)
Display Garbled
- Mixed up digit order:
- Test each digit individually:
lcd.setCursor(0, 0); lcd.write(0xFF);
- Adjust address mapping in
_mapSegments()
- Wrong segment bits:
- Use RAW LCD to map each segment
- Verify
_charMap[] patterns match expected display
I2C Address Not Found
- Check physical connection:
- SDA → Arduino A4 (Uno), pin 20 (Mega)
- SCL → Arduino A5 (Uno), pin 21 (Mega)
- VCC, GND connected
- Scan for address:
- Run I2C scanner (code above)
- PCF85176 uses 0x38 (SA0=0) or 0x39 (SA0=1)
- SA0 pin setting:
- Look for SA0 pad on module (determines I2C address)
- A0, A1, A2 are subaddresses in protocol, not I2C address pins
Best Practices
RAM Optimization
Segment LCD display RAM is precious. Minimize memory usage:
const uint8_t _charMap[256] PROGMEM = { ... };
uint8_t charMap[] = { ... };
Buffer Management
lcd.print(12345);
lcd.setBattery(2);
lcd._writeRam();
lcd._writeRam();
lcd._writeRam();
lcd._writeRam();
Cursor Tracking
Always track cursor position:
size_t write(uint8_t ch) {
if (_cursorCol >= MAX_DIGITS) return 0;
_mapSegments(_cursorCol, segments);
_cursorCol++;
return 1;
}
Reference
Testing Checklist
- [ ] RAW LCD class maps all segments
- [ ] Pinout documented and verified
- [ ]
_charMap[] populated with patterns
- [ ]
_mapSegments() implemented correctly
- [ ] Digits display in correct order
- [ ]
write() advances cursor
- [ ] Feature methods (battery, signal) work
- [ ] Example sketch runs without errors
- [ ] Documentation added to
supported-lcds.md
Example Progression
If your display is complex, follow this order:
- Test RAW LCD: Does hardware work?
- Implement
init() and write(): Can I display digits?
- Add features: Battery, signal, symbols
- Optimize: RAM usage, buffer management
- Document: Add to supported-lcds.md
Each stage tests one aspect before moving to the next.