-
Notifications
You must be signed in to change notification settings - Fork 606
Add AIP31068 LCD binding with contrast control, sample, and docs #2434
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| { | ||
| /// <summary> | ||
| /// 16x2 LCD controller based on the AIP31068, which exposes an HD44780 compatible interface with | ||
| /// an integrated I2C controller. | ||
| /// </summary> | ||
| 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); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="Aip31068Lcd"/> class using the specified I2C device. | ||
| /// </summary> | ||
| /// <param name="device">The I2C device used to communicate with the LCD controller.</param> | ||
| /// <param name="contrast">Initial contrast value. Valid range: 0-63.</param> | ||
| /// <param name="iconDisplayEnabled">True to enable the icon display, false to disable it.</param> | ||
| /// <param name="boosterEnabled">True to enable the internal voltage booster, false to disable it.</param> | ||
| 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(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="Aip31068Lcd"/> class using an existing LCD interface. | ||
| /// </summary> | ||
| /// <param name="lcdInterface">The LCD interface used to communicate with the controller.</param> | ||
| /// <param name="contrast">Initial contrast value. Valid range: 0-63.</param> | ||
| /// <param name="iconDisplayEnabled">True to enable the icon display, false to disable it.</param> | ||
| /// <param name="boosterEnabled">True to enable the internal voltage booster, false to disable it.</param> | ||
| 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(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the display contrast (0-63). | ||
| /// </summary> | ||
| public byte Contrast | ||
| { | ||
| get => _contrast; | ||
| set | ||
| { | ||
| byte normalized = NormalizeContrast(value); | ||
| if (_contrast == normalized) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| _contrast = normalized; | ||
| UpdateContrastConfiguration(); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets a value indicating whether the icon display is enabled. | ||
| /// </summary> | ||
| public bool IconDisplayEnabled | ||
| { | ||
| get => _iconDisplayEnabled; | ||
| set | ||
| { | ||
| if (_iconDisplayEnabled == value) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| _iconDisplayEnabled = value; | ||
| UpdateContrastConfiguration(); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets a value indicating whether the voltage booster is enabled. | ||
| /// </summary> | ||
| public bool BoosterEnabled | ||
| { | ||
| get => _boosterEnabled; | ||
| set | ||
| { | ||
| if (_boosterEnabled == value) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| _boosterEnabled = value; | ||
| UpdateContrastConfiguration(); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Performs the extended-instruction initialization sequence required by AIP31068L-compatible controllers. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// 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 | ||
| /// </remarks> | ||
| 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(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Writes the electronic volume (contrast) low and high bits while in the extended instruction set. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// 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 | ||
| /// </remarks> | ||
| private void SendContrastCommands() | ||
| { | ||
| SendCommandAndWait((byte)(ContrastSetCommand | (_contrast & 0x0F))); | ||
| byte value = (byte)(PowerIconContrastCommand | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here, a pointer on the doc for maintenance would definitely help! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Ellerbach Added an XML doc on SendContrastCommands with exact references: AIP31068L 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. Thanks! |
||
| | (_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)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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} "); | ||
| } | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you point where in the documentation this can be found? Will make it easier for maintenance. Including the delay. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I’ve added an XML doc on InitializeController with concrete refs to the AIP31068L Product Specification: Table 3. Instruction Table (p.18/28), Section 4.5 “INITIALIZING BY INSTRUCTION” (p.20/28), and Section 5.2 “BIAS VOLTAGE DIVIDE CIRCUIT” (p.23/28). Datasheet: https://www.orientdisplay.com/wp-content/uploads/2022/08/AIP31068L.pdf. Re: the delay, examples show short waits after Follower control; I kept a conservative 200 ms for VLCD stabilization across modules but can reduce to 2–10 ms if you prefer.