Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions src/devices/CharacterLcd/Aip31068Lcd.cs
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();
Copy link
Member

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!

Copy link
Author

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.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, a pointer on the doc for maintenance would definitely help!

Copy link
Author

Choose a reason for hiding this comment

The 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));
}
}
}

1 change: 1 addition & 0 deletions src/devices/CharacterLcd/CharacterLcd.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<Compile Include="Hd44780.cs" />
<Compile Include="ICharacterLcd.cs" />
<Compile Include="CharacterLcdExtensions.cs" />
<Compile Include="Aip31068Lcd.cs" />
<Compile Include="Lcd1602.cs" />
<Compile Include="Lcd2004.cs" />
<Compile Include="LcdConsole.cs" />
Expand Down
12 changes: 12 additions & 0 deletions src/devices/CharacterLcd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
87 changes: 87 additions & 0 deletions src/devices/CharacterLcd/samples/Aip31068Sample.cs
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} ");
}
}
}

6 changes: 6 additions & 0 deletions src/devices/CharacterLcd/samples/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
// UsingGroveRgbDisplay();
// UsingHd44780OverI2C();
// UsingHd44780OverI2CAndArduino();
// UsingAip31068Lcd();
UsingShiftRegister();

void UsingGpioPins()
Expand Down Expand Up @@ -87,6 +88,11 @@ void UsingHd44780OverI2CAndArduino()
ExtendedSample.Test(hd44780);
}

void UsingAip31068Lcd()
{
Aip31068Sample.Run();
}

void UsingShiftRegister()
{
int registerSelectPin = 1;
Expand Down