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;