diff --git a/src/devices/CharacterLcd/Aip31068Lcd.cs b/src/devices/CharacterLcd/Aip31068Lcd.cs
new file mode 100644
index 0000000000..6e89a1e639
--- /dev/null
+++ b/src/devices/CharacterLcd/Aip31068Lcd.cs
@@ -0,0 +1,189 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Device;
+using System.Device.I2c;
+using System.Drawing;
+
+namespace Iot.Device.CharacterLcd
+{
+ ///
+ /// 16x2 LCD controller based on the AIP31068, which exposes an HD44780 compatible interface with
+ /// an integrated I2C controller.
+ ///
+ public class Aip31068Lcd : Hd44780
+ {
+ private const byte ContrastMask = 0x3F;
+ private const byte ContrastSetCommand = 0x70;
+ private const byte PowerIconContrastCommand = 0x50;
+ private const byte IconDisplayFlag = 0x08;
+ private const byte BoosterFlag = 0x04;
+ private const byte FollowerControlCommand = 0x6C;
+ private const byte InternalOscillatorCommand = 0x14;
+ private const byte DefaultContrast = 0x20;
+
+ private byte _contrast;
+ private bool _iconDisplayEnabled;
+ private bool _boosterEnabled;
+
+ // Static members must appear before non-static members (SA1204)
+ private static byte NormalizeContrast(byte value)
+ {
+ return value > ContrastMask ? ContrastMask : (byte)(value & ContrastMask);
+ }
+
+ ///
+ /// Initializes a new instance of the class using the specified I2C device.
+ ///
+ /// The I2C device used to communicate with the LCD controller.
+ /// Initial contrast value. Valid range: 0-63.
+ /// True to enable the icon display, false to disable it.
+ /// True to enable the internal voltage booster, false to disable it.
+ public Aip31068Lcd(I2cDevice device, byte contrast = DefaultContrast, bool iconDisplayEnabled = false, bool boosterEnabled = true)
+ : base(new Size(16, 2), LcdInterface.CreateI2c(device, true))
+ {
+ _contrast = NormalizeContrast(contrast);
+ _iconDisplayEnabled = iconDisplayEnabled;
+ _boosterEnabled = boosterEnabled;
+
+ InitializeController();
+ }
+
+ ///
+ /// Initializes a new instance of the class using an existing LCD interface.
+ ///
+ /// The LCD interface used to communicate with the controller.
+ /// Initial contrast value. Valid range: 0-63.
+ /// True to enable the icon display, false to disable it.
+ /// True to enable the internal voltage booster, false to disable it.
+ public Aip31068Lcd(LcdInterface lcdInterface, byte contrast = DefaultContrast, bool iconDisplayEnabled = false, bool boosterEnabled = true)
+ : base(new Size(16, 2), lcdInterface)
+ {
+ _contrast = NormalizeContrast(contrast);
+ _iconDisplayEnabled = iconDisplayEnabled;
+ _boosterEnabled = boosterEnabled;
+
+ InitializeController();
+ }
+
+ ///
+ /// Gets or sets the display contrast (0-63).
+ ///
+ public byte Contrast
+ {
+ get => _contrast;
+ set
+ {
+ byte normalized = NormalizeContrast(value);
+ if (_contrast == normalized)
+ {
+ return;
+ }
+
+ _contrast = normalized;
+ UpdateContrastConfiguration();
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the icon display is enabled.
+ ///
+ public bool IconDisplayEnabled
+ {
+ get => _iconDisplayEnabled;
+ set
+ {
+ if (_iconDisplayEnabled == value)
+ {
+ return;
+ }
+
+ _iconDisplayEnabled = value;
+ UpdateContrastConfiguration();
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the voltage booster is enabled.
+ ///
+ public bool BoosterEnabled
+ {
+ get => _boosterEnabled;
+ set
+ {
+ if (_boosterEnabled == value)
+ {
+ return;
+ }
+
+ _boosterEnabled = value;
+ UpdateContrastConfiguration();
+ }
+ }
+
+ ///
+ /// Performs the extended-instruction initialization sequence required by AIP31068L-compatible controllers.
+ ///
+ ///
+ /// Sequence (extended mode): enter extended instruction set; program bias/oscillator; set contrast low nibble
+ /// (0x70 | C[3:0]) and high bits with Ion/Bon (0x50 | Ion | Bon | C[5:4]); enable follower (Fon, Rab[2:0]);
+ /// wait for VLCD to stabilize; return to basic instruction set.
+ /// References (AIP31068L Product Specification):
+ /// - Table 3. Instruction Table (p.18/28)
+ /// - Section 4.5 "INITIALIZING BY INSTRUCTION" (p.20/28)
+ /// - Section 5.2 "BIAS VOLTAGE DIVIDE CIRCUIT" (p.23/28)
+ /// Datasheet: https://www.orientdisplay.com/wp-content/uploads/2022/08/AIP31068L.pdf
+ ///
+ private void InitializeController()
+ {
+ EnterExtendedInstructionSet();
+ SendCommandAndWait(InternalOscillatorCommand);
+ SendContrastCommands();
+ SendCommandAndWait(FollowerControlCommand);
+ DelayHelper.DelayMilliseconds(200, allowThreadYield: true);
+ ExitExtendedInstructionSet();
+
+ // Re-issue the standard configuration commands expected after extended setup.
+ SendCommandAndWait((byte)_displayControl);
+ Clear();
+ SendCommandAndWait((byte)_displayMode);
+ }
+
+ private void UpdateContrastConfiguration()
+ {
+ EnterExtendedInstructionSet();
+ SendContrastCommands();
+ ExitExtendedInstructionSet();
+ }
+
+ ///
+ /// Writes the electronic volume (contrast) low and high bits while in the extended instruction set.
+ ///
+ ///
+ /// Issues 0x70 | C[3:0] (low nibble) then 0x50 | Ion | Bon | C[5:4] (high bits and power/icon controls).
+ /// References (AIP31068L Product Specification): Table 3. Instruction Table (p.18/28) and
+ /// Section 4.5 "INITIALIZING BY INSTRUCTION" (p.20/28).
+ /// Datasheet: https://www.orientdisplay.com/wp-content/uploads/2022/08/AIP31068L.pdf
+ ///
+ private void SendContrastCommands()
+ {
+ SendCommandAndWait((byte)(ContrastSetCommand | (_contrast & 0x0F)));
+ byte value = (byte)(PowerIconContrastCommand
+ | (_iconDisplayEnabled ? IconDisplayFlag : 0)
+ | (_boosterEnabled ? BoosterFlag : 0)
+ | ((_contrast >> 4) & 0x03));
+ SendCommandAndWait(value);
+ }
+
+ private void EnterExtendedInstructionSet()
+ {
+ SendCommandAndWait((byte)(_displayFunction | DisplayFunction.ExtendedInstructionSet));
+ }
+
+ private void ExitExtendedInstructionSet()
+ {
+ SendCommandAndWait((byte)(_displayFunction & ~DisplayFunction.ExtendedInstructionSet));
+ }
+ }
+}
+
diff --git a/src/devices/CharacterLcd/CharacterLcd.csproj b/src/devices/CharacterLcd/CharacterLcd.csproj
index 4deb2de15a..02140f051f 100644
--- a/src/devices/CharacterLcd/CharacterLcd.csproj
+++ b/src/devices/CharacterLcd/CharacterLcd.csproj
@@ -8,6 +8,7 @@
+
diff --git a/src/devices/CharacterLcd/README.md b/src/devices/CharacterLcd/README.md
index 030c12567c..164e8f1bc0 100644
--- a/src/devices/CharacterLcd/README.md
+++ b/src/devices/CharacterLcd/README.md
@@ -44,6 +44,18 @@ using LcdRgb lcd = new LcdRgb(new Size(16, 2), i2cLcdDevice, i2cRgbDevice);
}
```
+AIP31068 based LCDs expose the same HD44780 compatible instruction set but require an extended
+initialization sequence. The `Aip31068Lcd` binding performs the necessary configuration and allows
+adjusting the display contrast:
+
+```csharp
+var i2cDevice = I2cDevice.Create(new I2cConnectionSettings(busId: 1, deviceAddress: 0x3E));
+using Aip31068Lcd lcd = new(i2cDevice);
+lcd.Clear();
+lcd.Write("Hello from AIP31068!");
+lcd.Contrast = 36; // optional: tune the contrast (0-63)
+```
+
PCF8574T/PCF8574AT Sample
The I2C backpack based on the PCF8574T/AT IC uses specific pin mapping, to consume this device binding on this backpack use like so
diff --git a/src/devices/CharacterLcd/samples/Aip31068Sample.cs b/src/devices/CharacterLcd/samples/Aip31068Sample.cs
new file mode 100644
index 0000000000..9c635ea933
--- /dev/null
+++ b/src/devices/CharacterLcd/samples/Aip31068Sample.cs
@@ -0,0 +1,87 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Device.I2c;
+using System.Threading;
+
+namespace Iot.Device.CharacterLcd.Samples
+{
+ internal static class Aip31068Sample
+ {
+ private const int DefaultBusId = 1;
+ private const int DefaultAddress = 0x3E;
+
+ public static void Run()
+ {
+ using I2cDevice device = I2cDevice.Create(new I2cConnectionSettings(DefaultBusId, DefaultAddress));
+ using Aip31068Lcd lcd = new(device);
+
+ lcd.Clear();
+ lcd.SetCursorPosition(0, 0);
+ lcd.Write("AIP31068 sample");
+ DisplayContrast(lcd);
+
+ Console.WriteLine("AIP31068 LCD sample ready.");
+ Console.WriteLine("Use '+' or '-' to adjust contrast, 'B' to toggle the booster, 'I' to toggle icons.");
+ Console.WriteLine("Press Esc to exit.");
+
+ while (true)
+ {
+ if (!Console.KeyAvailable)
+ {
+ Thread.Sleep(50);
+ continue;
+ }
+
+ ConsoleKeyInfo key = Console.ReadKey(intercept: true);
+ if (key.Key == ConsoleKey.Escape)
+ {
+ break;
+ }
+
+ switch (key.Key)
+ {
+ case ConsoleKey.Add:
+ case ConsoleKey.OemPlus:
+ AdjustContrast(lcd, +1);
+ break;
+ case ConsoleKey.Subtract:
+ case ConsoleKey.OemMinus:
+ AdjustContrast(lcd, -1);
+ break;
+ case ConsoleKey.B:
+ lcd.BoosterEnabled = !lcd.BoosterEnabled;
+ Console.WriteLine($"Booster {(lcd.BoosterEnabled ? "enabled" : "disabled")}.");
+ break;
+ case ConsoleKey.I:
+ lcd.IconDisplayEnabled = !lcd.IconDisplayEnabled;
+ Console.WriteLine($"Icon display {(lcd.IconDisplayEnabled ? "enabled" : "disabled")}.");
+ break;
+ default:
+ continue;
+ }
+
+ DisplayContrast(lcd);
+ }
+
+ lcd.Clear();
+ lcd.Write("Goodbye!");
+ }
+
+ private static void AdjustContrast(Aip31068Lcd lcd, int delta)
+ {
+ int contrast = lcd.Contrast + delta;
+ contrast = Math.Clamp(contrast, 0, 63);
+ lcd.Contrast = (byte)contrast;
+ Console.WriteLine($"Contrast set to {lcd.Contrast}.");
+ }
+
+ private static void DisplayContrast(Aip31068Lcd lcd)
+ {
+ lcd.SetCursorPosition(0, 1);
+ lcd.Write($"Contrast: {lcd.Contrast:D2} ");
+ }
+ }
+}
+
diff --git a/src/devices/CharacterLcd/samples/Program.cs b/src/devices/CharacterLcd/samples/Program.cs
index acb6a782f2..a50aa29c1e 100644
--- a/src/devices/CharacterLcd/samples/Program.cs
+++ b/src/devices/CharacterLcd/samples/Program.cs
@@ -19,6 +19,7 @@
// UsingGroveRgbDisplay();
// UsingHd44780OverI2C();
// UsingHd44780OverI2CAndArduino();
+// UsingAip31068Lcd();
UsingShiftRegister();
void UsingGpioPins()
@@ -87,6 +88,11 @@ void UsingHd44780OverI2CAndArduino()
ExtendedSample.Test(hd44780);
}
+void UsingAip31068Lcd()
+{
+ Aip31068Sample.Run();
+}
+
void UsingShiftRegister()
{
int registerSelectPin = 1;