未验证 提交 46f0ceb5 编写于 作者: L Laurent Ellerbach 提交者: GitHub

Adding PCD8544, famous Nokia 5110 LCD screen (#1392)

* Initial commit

* Adjusting based on PR feedback

* sln update

* Adjusting based on PR feedback

* Adjusting image size

* Removing System.Drawing, adding ImageSharp

* Adjusting CharacterLcd to ImageSharp, adding ImageSharp to main project

* byte array to Span and stackalloc

* Adding support for cursor and in sample to demonstrate. Implemening ICharacterLcd

* Adding cursor support, adjusting for ICharacterLcd

* adjusting example, fixing enum naming

* Adding support for LcdConsole, fixing Write for span byte

* Adjusting ILcdCharacter and adding fonts

* Adding support for external fonts

* removing static generated font

* adjusting nit in comments

* fixing #1437

* Refactoring the 5x8 font, moving LcdRncoding and font to common

* Adjusting for project reference

* Fixing references and stylecop
上级 49d04d35
......@@ -13,5 +13,6 @@
<SystemMemoryPackageVersion>4.5.4</SystemMemoryPackageVersion>
<SystemRuntimeInteropServicesWindowsRuntimePackageVersion>4.3.0</SystemRuntimeInteropServicesWindowsRuntimePackageVersion>
<UnitsNetPackageVersion>4.77.0</UnitsNetPackageVersion>
<SixLaborsImageSharpPackageVersion>1.0.2</SixLaborsImageSharpPackageVersion>
</PropertyGroup>
</Project>
......@@ -29,6 +29,7 @@
<ProjectReference Include="$(MainLibraryPath)System.Device.Gpio.csproj" />
<PackageReference Include="UnitsNet" Version="$(UnitsNetPackageVersion)" />
<PackageReference Include="System.Management" Version="$(SystemManagementPackageVersion)" />
<PackageReference Include="SixLabors.ImageSharp" Version="$(SixLaborsImageSharpPackageVersion)" />
</ItemGroup>
<!-- This target will call into each device binding project to get out the source files for the framework we are building
......
......@@ -7,6 +7,7 @@ using System.Device.Gpio;
using System.Globalization;
using System.Text;
using Iot.Device.CharacterLcd;
using Iot.Device.Graphics;
namespace Iot.Device.Arduino.Sample
{
......
......@@ -6,7 +6,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Memory" Version="4.5.3" />
<Compile Include="*.cs" />
<Compile Include="Record\*.cs" />
</ItemGroup>
......
......@@ -13,8 +13,6 @@
<Compile Include="CharacterLcdExtensions.cs" />
<Compile Include="Lcd1602.cs" />
<Compile Include="Lcd2004.cs" />
<Compile Include="LcdCharacterEncoding.cs" />
<Compile Include="LcdCharacterEncodingFactory.cs" />
<Compile Include="LcdConsole.cs" />
<Compile Include="LcdInterface.cs" />
<Compile Include="LcdInterface.Gpio.cs" />
......@@ -27,6 +25,7 @@
<ItemGroup>
<ProjectReference Include="$(MainLibraryPath)System.Device.Gpio.csproj" />
<ProjectReference Include="../Common/CommonHelpers.csproj" />
<None Include="README.md" />
</ItemGroup>
......
......@@ -3,7 +3,7 @@
using System;
using System.Buffers;
using System.Drawing;
using SixLabors.ImageSharp;
namespace Iot.Device.CharacterLcd
{
......@@ -144,6 +144,12 @@ namespace Iot.Device.CharacterLcd
/// <param name="values">Data to be send to the device</param>
protected void SendData(ReadOnlySpan<byte> values) => _lcdInterface.SendData(values);
/// <summary>
/// Sends data to the device
/// </summary>
/// <param name="values">Data to be send to the device</param>
protected void SendData(ReadOnlySpan<char> values) => _lcdInterface.SendData(values);
/// <summary>
/// Send commands to the device
/// </summary>
......@@ -358,7 +364,7 @@ namespace Iot.Device.CharacterLcd
/// </remarks>
/// <param name="location">Should be between 0 and 7</param>
/// <param name="characterMap">Provide an array of 8 bytes containing the pattern</param>
public void CreateCustomCharacter(byte location, ReadOnlySpan<byte> characterMap)
public void CreateCustomCharacter(int location, ReadOnlySpan<byte> characterMap)
{
if (location >= NumberOfCustomCharactersSupported)
{
......@@ -402,7 +408,7 @@ namespace Iot.Device.CharacterLcd
/// Used if character translation already took place
/// </summary>
/// <param name="text">Text to print</param>
public void Write(ReadOnlySpan<byte> text) => SendData(text);
public void Write(ReadOnlySpan<char> text) => SendData(text);
/// <summary>
/// Releases unmanaged resources used by Hd44780
......
......@@ -2,8 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Text;
using SixLabors.ImageSharp;
namespace Iot.Device.CharacterLcd
{
......@@ -35,7 +34,7 @@ namespace Iot.Device.CharacterLcd
/// <summary>
/// Returns the size of the display.
/// </summary>
System.Drawing.Size Size { get; }
Size Size { get; }
/// <summary>
/// Returns the number of custom characters for this display.
......@@ -89,7 +88,7 @@ namespace Iot.Device.CharacterLcd
/// </remarks>
/// <param name="location">Should be between 0 and <see cref="NumberOfCustomCharactersSupported"/>.</param>
/// <param name="characterMap">Provide an array of 8 bytes containing the pattern</param>
void CreateCustomCharacter(byte location, ReadOnlySpan<byte> characterMap);
void CreateCustomCharacter(int location, ReadOnlySpan<byte> characterMap);
/// <summary>
/// Moves the cursor to an explicit column and row position.
......@@ -116,6 +115,6 @@ namespace Iot.Device.CharacterLcd
/// Used if character translation already took place.
/// </summary>
/// <param name="text">Text to print</param>
void Write(ReadOnlySpan<byte> text);
void Write(ReadOnlySpan<char> text);
}
}
......@@ -3,7 +3,7 @@
using System.Device.Gpio;
using System.Device.I2c;
using System.Drawing;
using SixLabors.ImageSharp;
namespace Iot.Device.CharacterLcd
{
......
......@@ -3,7 +3,7 @@
using System.Device.Gpio;
using System.Device.I2c;
using System.Drawing;
using SixLabors.ImageSharp;
namespace Iot.Device.CharacterLcd
{
......
......@@ -2,13 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Drawing;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Linq;
using System.Threading.Tasks;
using System.Globalization;
using SixLabors.ImageSharp;
using Iot.Device.Graphics;
namespace Iot.Device.CharacterLcd
{
......@@ -412,7 +413,7 @@ namespace Iot.Device.CharacterLcd
for (int i = 0; i < Size.Height; i++)
{
_lcd.SetCursorPosition(0, i);
byte[] buffer = MapChars(_currentData[i].ToString());
char[] buffer = MapChars(_currentData[i].ToString());
_lcd.Write(buffer);
}
......@@ -443,7 +444,7 @@ namespace Iot.Device.CharacterLcd
// Replace the existing chars at the given position with the new text
_currentData[CursorTop].Remove(CursorLeft, line.Length);
_currentData[CursorTop].Insert(CursorLeft, line);
byte[] buffer = MapChars(line);
char[] buffer = MapChars(line);
_lcd.Write(buffer);
CursorLeft += line.Length;
}
......@@ -524,21 +525,33 @@ namespace Iot.Device.CharacterLcd
}
}
private byte[] MapChars(string line)
private char[] MapChars(string line)
{
byte[] buffer = new byte[line.Length];
char[] buffer = new char[line.Length];
if (_characterEncoding is null)
{
for (int i = 0; i < line.Length; i++)
{
buffer[i] = (byte)line[i];
buffer[i] = line[i];
}
return buffer;
}
else
{
return _characterEncoding.GetBytes(line);
byte[] buff = _characterEncoding.GetBytes(line);
if (buff is not object)
{
return new char[0];
}
char[] encoded = new char[buff.Length];
for (int i = 0; i < buff.Length; i++)
{
encoded[i] = (char)buff[i];
}
return encoded;
}
}
......
......@@ -185,6 +185,15 @@ namespace Iot.Device.CharacterLcd
}
}
public override void SendData(ReadOnlySpan<char> values)
{
_controller.Write(_rsPin, PinValue.High);
foreach (byte value in values)
{
SendByte(value);
}
}
private void SendByte(byte value)
{
if (_dataPins.Length == 8)
......
......@@ -68,7 +68,8 @@ namespace Iot.Device.CharacterLcd
{
Span<byte> buffer = stackalloc byte[]
{
0x00, command
0x00,
command
};
_device.Write(buffer);
}
......@@ -93,7 +94,8 @@ namespace Iot.Device.CharacterLcd
{
Span<byte> buffer = stackalloc byte[]
{
(byte)ControlByteFlags.RegisterSelect, value
(byte)ControlByteFlags.RegisterSelect,
value
};
_device.Write(buffer);
}
......@@ -115,6 +117,31 @@ namespace Iot.Device.CharacterLcd
_device.Write(buffer.Slice(0, currentValues.Length + 1));
}
}
public override void SendData(ReadOnlySpan<char> values)
{
// There is a limit to how much data the controller can accept at once. Haven't found documentation
// for this yet, can probably iterate a bit more on this to find a true "max". 40 was too much.
const int MaxCopy = 20;
Span<byte> buffer = stackalloc byte[MaxCopy + 1];
buffer[0] = (byte)ControlByteFlags.RegisterSelect;
Span<byte> bufferData = buffer.Slice(1);
while (values.Length > 0)
{
ReadOnlySpan<char> buff = values.Slice(0, values.Length > MaxCopy ? MaxCopy : values.Length);
// As we are in a while loop, we can't use stackalloc
Span<byte> currentValues = new byte[buff.Length];
for (int i = 0; i < buff.Length; i++)
{
currentValues[i] = (byte)buff[i];
}
values = values.Slice(currentValues.Length);
currentValues.CopyTo(bufferData);
_device.Write(buffer.Slice(0, currentValues.Length + 1));
}
}
}
}
}
......@@ -128,6 +128,15 @@ namespace Iot.Device.CharacterLcd
Write4Bits((byte)(REGISTERSELECT | ((c << 4) & 0xF0)));
}
}
public override void SendData(ReadOnlySpan<char> values)
{
foreach (var c in values)
{
Write4Bits((byte)(REGISTERSELECT | (c & 0xF0)));
Write4Bits((byte)(REGISTERSELECT | ((c << 4) & 0xF0)));
}
}
}
}
}
......@@ -69,6 +69,12 @@ namespace Iot.Device.CharacterLcd
/// <param name="values">Bytes to be send to the device</param>
public abstract void SendData(ReadOnlySpan<byte> values);
/// <summary>
/// Sends data to the LCD device
/// </summary>
/// <param name="values">Char to be send to the device</param>
public abstract void SendData(ReadOnlySpan<char> values);
/// <summary>
/// Send commands to the LCD device
/// </summary>
......
......@@ -3,7 +3,8 @@
using System;
using System.Device.I2c;
using System.Drawing;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
namespace Iot.Device.CharacterLcd
{
......@@ -81,9 +82,10 @@ namespace Iot.Device.CharacterLcd
/// <param name="color">The color to set.</param>
private void ForceSetBacklightColor(Color color)
{
SetRgbRegister(RgbRegisters.REG_RED, color.R);
SetRgbRegister(RgbRegisters.REG_GREEN, color.G);
SetRgbRegister(RgbRegisters.REG_BLUE, color.B);
var col = color.ToPixel<Rgba32>();
SetRgbRegister(RgbRegisters.REG_RED, col.R);
SetRgbRegister(RgbRegisters.REG_GREEN, col.G);
SetRgbRegister(RgbRegisters.REG_BLUE, col.B);
}
/// <summary>
......
......@@ -36,7 +36,7 @@ Here is a Hello World example of how to consume Grove LCD RGB Backlight binding:
```csharp
var i2cLcdDevice = I2cDevice.Create(new I2cConnectionSettings(busId: 1, deviceAddress: 0x3E));
var i2cRgbDevice = I2cDevice.Create(new I2cConnectionSettings(busId: 1, deviceAddress: 0x62));
using (var lcd = LcdRgb(new System.Drawing.Size(16, 2), i2cLcdDevice, i2cRgbDevice))
using LcdRgb lcd = new LcdRgb(new Size(16, 2), i2cLcdDevice, i2cRgbDevice);
{
lcd.Write("Hello World!");
lcd.SetBacklightColor(Color.Azure);
......
......@@ -4,12 +4,12 @@
using System;
using System.Device.I2c;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Timers;
using System.Globalization;
using Iot.Device.Mcp23xxx;
using SixLabors.ImageSharp;
namespace Iot.Device.CharacterLcd.Samples
{
......@@ -204,7 +204,7 @@ namespace Iot.Device.CharacterLcd.Samples
foreach (var color in colors)
{
lcd.Clear();
lcd.Write(color.Name);
lcd.Write(color.ToHex());
colorLcd.SetBacklightColor(color);
System.Threading.Thread.Sleep(1000);
......
......@@ -8,6 +8,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Iot.Device.CharacterLcd;
using Iot.Device.Graphics;
namespace Iot.Device.CharacterLcd.Samples
{
......
......@@ -5,10 +5,10 @@ using System;
using System.Device.Gpio;
using System.Device.I2c;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Timers;
using Iot.Device.Pcx857x;
using SixLabors.ImageSharp;
namespace Iot.Device.CharacterLcd.Samples
{
......@@ -216,7 +216,7 @@ namespace Iot.Device.CharacterLcd.Samples
foreach (var color in colors)
{
lcd.Clear();
lcd.Write(color.Name);
lcd.Write(color.ToHex());
lcd.SetBacklightColor(color);
System.Threading.Thread.Sleep(1000);
......
......@@ -4,10 +4,10 @@
using System;
using System.Device.Gpio;
using System.Device.I2c;
using System.Drawing;
using Iot.Device.Mcp23xxx;
using Iot.Device.CharacterLcd;
using Iot.Device.CharacterLcd.Samples;
using SixLabors.ImageSharp;
// Choose the right setup for your display:
// UsingGpioPins();
......
......@@ -3,7 +3,7 @@
using System;
using System.Globalization;
using Iot.Device.CharacterLcd;
using Iot.Device.Graphics;
using Xunit;
namespace CharacterLcd.Tests
......
......@@ -4,68 +4,70 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Iot.Device.Graphics
{
/// <summary>
/// Represents BDF font
/// Represents Bitmap Distribution Format (BDF) font, partial implementation of specifications.
/// Specifications can be found here: https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5005.BDF_Spec.pdf
/// </summary>
public class BdfFont
{
/// <summary>
/// Character width
/// </summary>
public int Width { get; private set; }
public int Width { get; protected set; }
/// <summary>
/// Character height
/// </summary>
public int Height { get; private set; }
public int Height { get; protected set; }
/// <summary>
/// X displacement of the character
/// </summary>
public int XDisplacement { get; private set; }
public int XDisplacement { get; protected set; }
/// <summary>
/// Y Displacement of the character
/// </summary>
public int YDisplacement { get; private set; }
public int YDisplacement { get; protected set; }
/// <summary>
/// Default character
/// </summary>
public int DefaultChar { get; private set; }
public int DefaultChar { get; protected set; }
/// <summary>
/// Number of characters
/// </summary>
public int CharsCount { get; private set; }
public int CharsCount { get; protected set; }
// GlyphMapper is mapping from the character number to the index of the character bitmap data in the buffer GlyphUshortData.
private Dictionary<int, int>? GlyphMapper { get; set; }
/// <summary>
/// GlyphMapper is mapping from the character number to the index of the character bitmap data in the buffer GlyphUshortData.
/// </summary>
protected Dictionary<int, int>? GlyphMapper { get; set; }
private int BytesPerGlyph { get; set; }
private ushort[]? GlyphUshortData { get; set; }
private static readonly string s_fontBoundingBox = "FONTBOUNDINGBOX ";
private static readonly string s_charSet = "CHARSET_REGISTRY ";
private static readonly string s_isoCharset = "\"ISO10646\"";
private static readonly string s_defaultChar = "DEFAULT_CHAR ";
private static readonly string s_Chars = "CHARS ";
private static readonly string s_startChar = "STARTCHAR ";
private static readonly string s_encoding = "ENCODING ";
// private static readonly string s_sWidth = "SWIDTH";
// private static readonly string s_dWidth = "DWIDTH";
private static readonly string s_bbx = "BBX ";
// private static readonly string s_vVector = "VVECTOR";
private static readonly string s_endChar = "ENDCHAR";
private static readonly string s_bitmap = "BITMAP";
/// <summary>
/// The buffer containing all the data
/// </summary>
protected ushort[]? GlyphUshortData { get; set; }
private static readonly string FontBoundingBoxString = "FONTBOUNDINGBOX ";
private static readonly string CharSetString = "CHARSET_REGISTRY ";
private static readonly string IsoCharsetString = "\"ISO10646\"";
private static readonly string DefaultCharString = "DEFAULT_CHAR ";
private static readonly string CharsString = "CHARS ";
private static readonly string StartCharString = "STARTCHAR ";
private static readonly string EncodingString = "ENCODING ";
// Those next ones are comments as not implemented but for further usage
// private static readonly string SWidthString = "SWIDTH";
// private static readonly string DWidthString = "DWIDTH";
// private static readonly string VVectorString = "VVECTOR";
private static readonly string BbxString = "BBX ";
private static readonly string EndCharString = "ENDCHAR";
private static readonly string BitmapString = "BITMAP";
/// <summary>
/// Loads BdfFont from a specified path
......@@ -79,31 +81,31 @@ namespace Iot.Device.Graphics
while (!sr.EndOfStream)
{
ReadOnlySpan<char> span = sr.ReadLine().AsSpan().Trim();
if (span.StartsWith(s_fontBoundingBox, StringComparison.Ordinal))
if (span.StartsWith(FontBoundingBoxString, StringComparison.Ordinal))
{
span = span.Slice(s_fontBoundingBox.Length).Trim();
span = span.Slice(FontBoundingBoxString.Length).Trim();
font.Width = ReadNextDecimalNumber(ref span);
font.Height = ReadNextDecimalNumber(ref span);
font.XDisplacement = ReadNextDecimalNumber(ref span);
font.YDisplacement = ReadNextDecimalNumber(ref span);
font.BytesPerGlyph = (int)Math.Ceiling(((double)font.Width) / 8);
}
else if (span.StartsWith(s_charSet, StringComparison.Ordinal))
else if (span.StartsWith(CharSetString, StringComparison.Ordinal))
{
span = span.Slice(s_charSet.Length).Trim();
if (span.CompareTo(s_isoCharset, StringComparison.Ordinal) != 0)
span = span.Slice(CharSetString.Length).Trim();
if (span.CompareTo(IsoCharsetString, StringComparison.Ordinal) != 0)
{
throw new NotSupportedException("We only support ISO10646 for now.");
}
}
else if (span.StartsWith(s_defaultChar, StringComparison.Ordinal))
else if (span.StartsWith(DefaultCharString, StringComparison.Ordinal))
{
span = span.Slice(s_defaultChar.Length).Trim();
span = span.Slice(DefaultCharString.Length).Trim();
font.DefaultChar = ReadNextDecimalNumber(ref span);
}
else if (span.StartsWith(s_Chars, StringComparison.Ordinal))
else if (span.StartsWith(CharsString, StringComparison.Ordinal))
{
span = span.Slice(s_Chars.Length).Trim();
span = span.Slice(CharsString.Length).Trim();
font.CharsCount = ReadNextDecimalNumber(ref span);
if (font.Width == 0 || font.Height == 0 || font.CharsCount <= 0)
......@@ -175,8 +177,8 @@ namespace Iot.Device.Graphics
public void GetCharData(char character, out ReadOnlySpan<ushort> charData)
{
if (GlyphMapper is object &&
(GlyphMapper.TryGetValue((int)character, out int index) ||
GlyphMapper.TryGetValue((int)DefaultChar, out index)))
(GlyphMapper.TryGetValue((int)character, out int index) ||
GlyphMapper.TryGetValue((int)DefaultChar, out index)))
{
charData = GlyphUshortData.AsSpan().Slice(index, Height);
}
......@@ -260,19 +262,19 @@ namespace Iot.Device.Graphics
for (int i = 0; i < CharsCount; i++)
{
ReadOnlySpan<char> span = sr.ReadLine().AsSpan().Trim();
if (!span.StartsWith(s_startChar, StringComparison.Ordinal))
if (!span.StartsWith(StartCharString, StringComparison.Ordinal))
{
throw new InvalidDataException(
"The font data is not well formed. expected STARTCHAR tag in the beginning of glyoh data.");
}
span = sr.ReadLine().AsSpan().Trim();
if (!span.StartsWith(s_encoding, StringComparison.Ordinal))
if (!span.StartsWith(EncodingString, StringComparison.Ordinal))
{
throw new InvalidDataException("The font data is not well formed. expected ENCODING tag.");
}
span = span.Slice(s_encoding.Length).Trim();
span = span.Slice(EncodingString.Length).Trim();
int charNumber = ReadNextDecimalNumber(ref span);
GlyphMapper.Add(charNumber, index);
......@@ -280,9 +282,9 @@ namespace Iot.Device.Graphics
{
span = sr.ReadLine().AsSpan().Trim();
}
while (!span.StartsWith(s_bbx, StringComparison.Ordinal));
while (!span.StartsWith(BbxString, StringComparison.Ordinal));
span = span.Slice(s_bbx.Length).Trim();
span = span.Slice(BbxString.Length).Trim();
if (ReadNextDecimalNumber(ref span) != Width ||
ReadNextDecimalNumber(ref span) != Height ||
ReadNextDecimalNumber(ref span) != XDisplacement ||
......@@ -293,7 +295,7 @@ namespace Iot.Device.Graphics
}
span = sr.ReadLine().AsSpan().Trim();
if (span.CompareTo(s_bitmap, StringComparison.Ordinal) != 0)
if (span.CompareTo(BitmapString, StringComparison.Ordinal) != 0)
{
throw new InvalidDataException("The font data is not well formed. expected BITMAP tag.");
}
......@@ -317,7 +319,7 @@ namespace Iot.Device.Graphics
}
span = sr.ReadLine().AsSpan().Trim();
if (!span.StartsWith(s_endChar, StringComparison.Ordinal))
if (!span.StartsWith(EndCharString, StringComparison.Ordinal))
{
throw new InvalidDataException(
"The font data is not well formed. expected ENDCHAR tag in the beginning of glyph data.");
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
namespace Iot.Device.Graphics
{
/// <summary>
/// The specific PCD8544 font for Nokia 5110
/// </summary>
public class Font5x8 : BdfFont
{
/// <summary>
/// ASCII Font specific to the PCD8544 Nokia 5110 screen but can be used as a generic 5x8 font.
/// Font characters are column bit mask.
/// Font size is 5 pixels width and 8 pixels height. Each byte represent a vertical column for the character.
/// </summary>
private static readonly byte[][] Ascii = new byte[][]
{
new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00 }, // 20
new byte[] { 0x00, 0x00, 0x5f, 0x00, 0x00 }, // 21 !
new byte[] { 0x00, 0x07, 0x00, 0x07, 0x00 }, // 22 "
new byte[] { 0x14, 0x7f, 0x14, 0x7f, 0x14 }, // 23 #
new byte[] { 0x24, 0x2a, 0x7f, 0x2a, 0x12 }, // 24 $
new byte[] { 0x23, 0x13, 0x08, 0x64, 0x62 }, // 25 %
new byte[] { 0x36, 0x49, 0x55, 0x22, 0x50 }, // 26 &
new byte[] { 0x00, 0x05, 0x03, 0x00, 0x00 }, // 27 '
new byte[] { 0x00, 0x1c, 0x22, 0x41, 0x00 }, // 28 (
new byte[] { 0x00, 0x41, 0x22, 0x1c, 0x00 }, // 29 )
new byte[] { 0x14, 0x08, 0x3e, 0x08, 0x14 }, // 2a *
new byte[] { 0x08, 0x08, 0x3e, 0x08, 0x08 }, // 2b +
new byte[] { 0x00, 0x50, 0x30, 0x00, 0x00 }, // 2c ,
new byte[] { 0x08, 0x08, 0x08, 0x08, 0x08 }, // 2d -
new byte[] { 0x00, 0x60, 0x60, 0x00, 0x00 }, // 2e .
new byte[] { 0x20, 0x10, 0x08, 0x04, 0x02 }, // 2f /
new byte[] { 0x3e, 0x51, 0x49, 0x45, 0x3e }, // 30 0
new byte[] { 0x00, 0x42, 0x7f, 0x40, 0x00 }, // 31 1
new byte[] { 0x42, 0x61, 0x51, 0x49, 0x46 }, // 32 2
new byte[] { 0x21, 0x41, 0x45, 0x4b, 0x31 }, // 33 3
new byte[] { 0x18, 0x14, 0x12, 0x7f, 0x10 }, // 34 4
new byte[] { 0x27, 0x45, 0x45, 0x45, 0x39 }, // 35 5
new byte[] { 0x3c, 0x4a, 0x49, 0x49, 0x30 }, // 36 6
new byte[] { 0x01, 0x71, 0x09, 0x05, 0x03 }, // 37 7
new byte[] { 0x36, 0x49, 0x49, 0x49, 0x36 }, // 38 8
new byte[] { 0x06, 0x49, 0x49, 0x29, 0x1e }, // 39 9
new byte[] { 0x00, 0x36, 0x36, 0x00, 0x00 }, // 3a :
new byte[] { 0x00, 0x56, 0x36, 0x00, 0x00 }, // 3b ;
new byte[] { 0x08, 0x14, 0x22, 0x41, 0x00 }, // 3c <
new byte[] { 0x14, 0x14, 0x14, 0x14, 0x14 }, // 3d =
new byte[] { 0x00, 0x41, 0x22, 0x14, 0x08 }, // 3e >
new byte[] { 0x02, 0x01, 0x51, 0x09, 0x06 }, // 3f ?
new byte[] { 0x32, 0x49, 0x79, 0x41, 0x3e }, // 40 @
new byte[] { 0x7e, 0x11, 0x11, 0x11, 0x7e }, // 41 A
new byte[] { 0x7f, 0x49, 0x49, 0x49, 0x36 }, // 42 B
new byte[] { 0x3e, 0x41, 0x41, 0x41, 0x22 }, // 43 C
new byte[] { 0x7f, 0x41, 0x41, 0x22, 0x1c }, // 44 D
new byte[] { 0x7f, 0x49, 0x49, 0x49, 0x41 }, // 45 E
new byte[] { 0x7f, 0x09, 0x09, 0x09, 0x01 }, // 46 F
new byte[] { 0x3e, 0x41, 0x49, 0x49, 0x7a }, // 47 G
new byte[] { 0x7f, 0x08, 0x08, 0x08, 0x7f }, // 48 H
new byte[] { 0x00, 0x41, 0x7f, 0x41, 0x00 }, // 49 I
new byte[] { 0x20, 0x40, 0x41, 0x3f, 0x01 }, // 4a J
new byte[] { 0x7f, 0x08, 0x14, 0x22, 0x41 }, // 4b K
new byte[] { 0x7f, 0x40, 0x40, 0x40, 0x40 }, // 4c L
new byte[] { 0x7f, 0x02, 0x0c, 0x02, 0x7f }, // 4d M
new byte[] { 0x7f, 0x04, 0x08, 0x10, 0x7f }, // 4e N
new byte[] { 0x3e, 0x41, 0x41, 0x41, 0x3e }, // 4f O
new byte[] { 0x7f, 0x09, 0x09, 0x09, 0x06 }, // 50 P
new byte[] { 0x3e, 0x41, 0x51, 0x21, 0x5e }, // 51 Q
new byte[] { 0x7f, 0x09, 0x19, 0x29, 0x46 }, // 52 R
new byte[] { 0x46, 0x49, 0x49, 0x49, 0x31 }, // 53 S
new byte[] { 0x01, 0x01, 0x7f, 0x01, 0x01 }, // 54 T
new byte[] { 0x3f, 0x40, 0x40, 0x40, 0x3f }, // 55 U
new byte[] { 0x1f, 0x20, 0x40, 0x20, 0x1f }, // 56 V
new byte[] { 0x3f, 0x40, 0x38, 0x40, 0x3f }, // 57 W
new byte[] { 0x63, 0x14, 0x08, 0x14, 0x63 }, // 58 X
new byte[] { 0x07, 0x08, 0x70, 0x08, 0x07 }, // 59 Y
new byte[] { 0x61, 0x51, 0x49, 0x45, 0x43 }, // 5a Z
new byte[] { 0x00, 0x7f, 0x41, 0x41, 0x00 }, // 5b [
new byte[] { 0x02, 0x04, 0x08, 0x10, 0x20 }, // 5c ¥
new byte[] { 0x00, 0x41, 0x41, 0x7f, 0x00 }, // 5d ]
new byte[] { 0x04, 0x02, 0x01, 0x02, 0x04 }, // 5e ^
new byte[] { 0x40, 0x40, 0x40, 0x40, 0x40 }, // 5f _
new byte[] { 0x00, 0x01, 0x02, 0x04, 0x00 }, // 60 `
new byte[] { 0x20, 0x54, 0x54, 0x54, 0x78 }, // 61 a
new byte[] { 0x7f, 0x48, 0x44, 0x44, 0x38 }, // 62 b
new byte[] { 0x38, 0x44, 0x44, 0x44, 0x20 }, // 63 c
new byte[] { 0x38, 0x44, 0x44, 0x48, 0x7f }, // 64 d
new byte[] { 0x38, 0x54, 0x54, 0x54, 0x18 }, // 65 e
new byte[] { 0x08, 0x7e, 0x09, 0x01, 0x02 }, // 66 f
new byte[] { 0x0c, 0x52, 0x52, 0x52, 0x3e }, // 67 g
new byte[] { 0x7f, 0x08, 0x04, 0x04, 0x78 }, // 68 h
new byte[] { 0x00, 0x44, 0x7d, 0x40, 0x00 }, // 69 i
new byte[] { 0x20, 0x40, 0x44, 0x3d, 0x00 }, // 6a j
new byte[] { 0x7f, 0x10, 0x28, 0x44, 0x00 }, // 6b k
new byte[] { 0x00, 0x41, 0x7f, 0x40, 0x00 }, // 6c l
new byte[] { 0x7c, 0x04, 0x18, 0x04, 0x78 }, // 6d m
new byte[] { 0x7c, 0x08, 0x04, 0x04, 0x78 }, // 6e n
new byte[] { 0x38, 0x44, 0x44, 0x44, 0x38 }, // 6f o
new byte[] { 0x7c, 0x14, 0x14, 0x14, 0x08 }, // 70 p
new byte[] { 0x08, 0x14, 0x14, 0x18, 0x7c }, // 71 q
new byte[] { 0x7c, 0x08, 0x04, 0x04, 0x08 }, // 72 r
new byte[] { 0x48, 0x54, 0x54, 0x54, 0x20 }, // 73 s
new byte[] { 0x04, 0x3f, 0x44, 0x40, 0x20 }, // 74 t
new byte[] { 0x3c, 0x40, 0x40, 0x20, 0x7c }, // 75 u
new byte[] { 0x1c, 0x20, 0x40, 0x20, 0x1c }, // 76 v
new byte[] { 0x3c, 0x40, 0x30, 0x40, 0x3c }, // 77 w
new byte[] { 0x44, 0x28, 0x10, 0x28, 0x44 }, // 78 x
new byte[] { 0x0c, 0x50, 0x50, 0x50, 0x3c }, // 79 y
new byte[] { 0x44, 0x64, 0x54, 0x4c, 0x44 }, // 7a z
new byte[] { 0x00, 0x08, 0x36, 0x41, 0x00 }, // 7b
new byte[] { 0x00, 0x00, 0x7f, 0x00, 0x00 }, // 7c |
new byte[] { 0x00, 0x41, 0x36, 0x08, 0x00 }, // 7d
new byte[] { 0x10, 0x08, 0x08, 0x10, 0x08 }, // 7e ←
new byte[] { 0x78, 0x46, 0x41, 0x46, 0x78 }, // 7f →
};
/// <summary>
/// Constructor for Font 5x8
/// </summary>
public Font5x8()
{
Width = 5;
Height = 8;
XDisplacement = 0;
YDisplacement = 0;
DefaultChar = 0x20;
CharsCount = Ascii.Length;
GlyphMapper = new Dictionary<int, int>();
GlyphUshortData = new ushort[CharsCount * Height];
for (int i = 0; i < CharsCount; i++)
{
var font8 = LcdCharacterEncodingFactory.ConvertFont5to8bytes(Ascii[i]);
for (int j = 0; j < 8; j++)
{
GlyphUshortData[i * 8 + j] = font8[j];
}
GlyphMapper.Add(i + DefaultChar, i * 8);
}
}
}
}
......@@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Text;
namespace Iot.Device.CharacterLcd
namespace Iot.Device.Graphics
{
/// <summary>
/// Provides the character encoding for an LCD display.
......@@ -67,7 +67,7 @@ namespace Iot.Device.CharacterLcd
/// <summary>
/// This is internally set to false if we already know that we won't be able to display all required characters
/// </summary>
protected internal bool AllCharactersSupported { get; set; }
public bool AllCharactersSupported { get; set; }
/// <summary>
/// Specified Name of the hardcoded character memory set for which this Encoding is intended. An encoding shall only be loaded to
......
......@@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
namespace Iot.Device.CharacterLcd
namespace Iot.Device.Graphics
{
/// <summary>
/// Factory for creating Encodings that support different cultures on different LCD Displays.
......@@ -906,5 +906,59 @@ namespace Iot.Device.CharacterLcd
/// </example>
protected byte[] CreateCustomCharacter(byte byte0, byte byte1, byte byte2, byte byte3, byte byte4, byte byte5, byte byte6, byte byte7) =>
new byte[] { byte0, byte1, byte2, byte3, byte4, byte5, byte6, byte7 };
/// <summary>
/// Convert a 8 bytes array with 5 lower bit character representation into a
/// 5 bytes array with all bit character representation vertically ordered.
/// </summary>
/// <param name="font8">A span of bytes, must be 8 bytes length</param>
/// <returns>A 5 bytes array containing the character</returns>
public static byte[] ConvertFont8to5bytes(ReadOnlySpan<byte> font8)
{
if (font8.Length != 8)
{
throw new ArgumentException("Font size must be 8 bytes");
}
byte[] font5 = new byte[5];
for (int i = 0; i < 5; i++)
{
byte font = 0x00;
for (int j = 0; j < 8; j++)
{
font |= (byte)(((font8[j] >> (4 - i)) & 0x01) << j);
}
font5[i] = font;
}
return font5;
}
/// <summary>
/// Convert a 5 bytes array with 8 bits vertically encoded character representation into a
/// 8 bytes array with the lower 5 bits.
/// </summary>
/// <param name="font5">A span of bytes, must be 5 bytes length</param>
/// <returns>A 8 bytes array containing the character</returns>
public static byte[] ConvertFont5to8bytes(ReadOnlySpan<byte> font5)
{
if (font5.Length != 5)
{
throw new ArgumentException("Font size must be 5 bytes");
}
byte[] font8 = new byte[8];
for (int i = 0; i < 5; i++)
{
for (int j = 1; j < 8; j++)
{
font8[7 - j] = (byte)(font8[7 - j] << 1 | ((font5[i] >> (7 - j)) & 1));
}
}
return font8;
}
}
}
......@@ -15,6 +15,7 @@
<ItemGroup>
<PackageReference Include="UnitsNet" Version="$(UnitsNetPackageVersion)" />
<PackageReference Include="SixLabors.ImageSharp" Version="$(SixLaborsImageSharpPackageVersion)" />
<ProjectReference Include="$(SystemDeviceModelPath)" Condition="'$(MSBuildProjectName)' != '$(SystemDeviceModelProjectName)'" />
</ItemGroup>
</Project>
......@@ -29,12 +29,6 @@ namespace Iot.Device.Display
/// </summary>
private const int MaxNumberOfDigits = 4;
/// <summary>
/// This display does not support dot bits for each digit,
/// so the first bit should be masked before flushing to
/// the device
/// </summary>
private const byte SegmentMask = 0b0111_1111;
#endregion
#region Enums
......@@ -168,7 +162,7 @@ namespace Iot.Device.Display
foreach (byte digit in digits)
{
_displayBuffer[(int)s_digitAddressList[startAddress++]] = (byte)(digit & SegmentMask);
_displayBuffer[(int)s_digitAddressList[startAddress++]] = (byte)(digit);
}
AutoFlush();
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
namespace Iot.Device.Display.Pcd8544Enums
{
[Flags]
internal enum FunctionSet
{
PowerOn = 0b0010_0000,
PowerOff = 0b0010_0100,
ExtendedMode = 0b0010_0001,
HorizontalAddressing = 0b0010_0000,
VerticalAddressing = 0b0010_0010,
}
}
此差异已折叠。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net5.0;netcoreapp2.1</TargetFrameworks>
<EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>
<ItemGroup>
<Compile Include="*.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../Common/CommonHelpers.csproj" />
<ProjectReference Include="../CharacterLcd/CharacterLcd.csproj" />
<ProjectReference Include="$(MainLibraryPath)System.Device.Gpio.csproj" />
</ItemGroup>
</Project>

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30804.86
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pcd8544", "Pcd8544.csproj", "{11BF29C0-463C-4423-B028-92833F068001}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pcd8544.samples", "samples\Pcd8544.samples.csproj", "{E0E5A91B-1F56-4E66-8885-C15C0C8E6597}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CharacterLcd", "..\CharacterLcd\CharacterLcd.csproj", "{F35AD83F-7414-4B89-AC3C-EDF9C4425D17}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CharacterLcd.Samples", "..\CharacterLcd\samples\CharacterLcd.Samples.csproj", "{03985059-6DE5-4BB5-947C-70493DAC1BC9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonHelpers", "..\Common\CommonHelpers.csproj", "{85E17B98-0745-407A-894B-214F25BAE20F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CharacterLcd.Tests", "..\CharacterLcd\tests\CharacterLcd.Tests.csproj", "{9395365B-927A-4E84-9294-AD243BB167D8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{11BF29C0-463C-4423-B028-92833F068001}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{11BF29C0-463C-4423-B028-92833F068001}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11BF29C0-463C-4423-B028-92833F068001}.Release|Any CPU.ActiveCfg = Release|Any CPU
{11BF29C0-463C-4423-B028-92833F068001}.Release|Any CPU.Build.0 = Release|Any CPU
{E0E5A91B-1F56-4E66-8885-C15C0C8E6597}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E0E5A91B-1F56-4E66-8885-C15C0C8E6597}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E0E5A91B-1F56-4E66-8885-C15C0C8E6597}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E0E5A91B-1F56-4E66-8885-C15C0C8E6597}.Release|Any CPU.Build.0 = Release|Any CPU
{F35AD83F-7414-4B89-AC3C-EDF9C4425D17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F35AD83F-7414-4B89-AC3C-EDF9C4425D17}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F35AD83F-7414-4B89-AC3C-EDF9C4425D17}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F35AD83F-7414-4B89-AC3C-EDF9C4425D17}.Release|Any CPU.Build.0 = Release|Any CPU
{03985059-6DE5-4BB5-947C-70493DAC1BC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{03985059-6DE5-4BB5-947C-70493DAC1BC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{03985059-6DE5-4BB5-947C-70493DAC1BC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03985059-6DE5-4BB5-947C-70493DAC1BC9}.Release|Any CPU.Build.0 = Release|Any CPU
{85E17B98-0745-407A-894B-214F25BAE20F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{85E17B98-0745-407A-894B-214F25BAE20F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{85E17B98-0745-407A-894B-214F25BAE20F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{85E17B98-0745-407A-894B-214F25BAE20F}.Release|Any CPU.Build.0 = Release|Any CPU
{9395365B-927A-4E84-9294-AD243BB167D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9395365B-927A-4E84-9294-AD243BB167D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9395365B-927A-4E84-9294-AD243BB167D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9395365B-927A-4E84-9294-AD243BB167D8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8DC70770-3307-4CF2-9EEC-1D1582189439}
EndGlobalSection
EndGlobal
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Iot.Device.Display.Pcd8544Enums
{
internal enum PcdDisplayControl
{
DisplayBlank = 0b0000_1000,
NormalMode = 0b0000_1100,
AllSegmentsOn = 0b0000_1001,
InverseVideoMode = 0b0000_1101,
}
}
# PCD8544 - 48 × 84 pixels matrix LCD, famous Nokia 5110 screen
This is the famous screen that Nokia 5110 used. It's a SPI based device. This Nokia 5110 was very popular and many of us used to have it in their hands and that young generation have seen it in pictures. This LCD is quite cheap and easy to use.
## Usage
The screen is most of the time presented like this:
![Nokia 5110 screen](./samples/Nokia-5110-LCD-Pinout.png)
It uses SPI and you can add PWM to control the 4 leds brightness which are around the screen. The pin have various names like BL or LED.
It requires as well a GPIO controller on top of the SPI device with a pin called Data Control, most often presented as DC.
In this schema, the Chip Select (active low) is most of the time called CE. The SPI Clock has to be connected to CLK or SCLK. And the MOSI pin to DIN or MOSI. Names for the pins varies a bit.
Reset pin is sometimes called RST.
Supply voltage can vary, 3.3 or 5V, up to 7V is acceptable. Make sure that there is a current limiting resistor between the brightness led and the controller you'll plug. Most of the kits you buy have it. If you don't add one, you will damage the external brightness leds.
The following example shows how to setup the screen with a PWM to control the brightness:
```csharp
SpiConnectionSettings spiConnection = new(0, 1) { ClockFrequency = 5_000_000, Mode = SpiMode.Mode0, DataFlow = DataFlow.MsbFirst, ChipSelectLineActiveState = PinValue.Low };
PwmChannel pwmChannel = PwmChannel.Create(0, 0);
SpiDevice spi = SpiDevice.Create(spiConnection);
Pcd8544 lcd = new(27, 22, spi, pwmChannel);
```
If you don't want neither a PWM neither a reset pin, you can then pass a negative pin number for reset and null for the PWM:
```csharp
lcd = new(27, spi, -1, null);
```
Not that there is as well the possibility to pass a normal pin number for the backlight. In this case, the light will be on once the `BacklightBrightness` property if more then 0.5, otherwise off.
## Displaying text
Like for other screen bindings in this repository you have `Write` and `SetCursorPosition` to write text and set the cursor position.
A specific font is available and optimized for the screen. The font only contains a limited number of characters and will just ignore any characters that are unknown.
```csharp
lcd.SetCursorPosition(0, 0);
lcd.WriteLine("First line");
lcd.Write("Second one");
lcd.SetCursorPosition(0, 5);
lcd.Write("last line");
```
The position of the cursor moves with the text you write. The screen has 6 lines in total. And a character is 5 pixels width and 8 pixels height.
You can as well provide your own character using the `Write(ReadOnlySpan<byte> text)` function.
## Drawing lines, rectangles and points
Point, line and rectangle primitives are available:
```csharp
lcd.DrawPoint(5, 5, true);
lcd.DrawLine(0, 0, 15, 35, true);
lcd.DrawRectangle(10, 30, 10, 20, true, true);
lcd.DrawRectangle(12, 32, 6, 16, false, true);
// You should not forget to draw what you have in memory
lcd.Draw();
```
Each can take `Point`, `Size` and `Rectangle` as well as input. You have to decide if you want to have the point on or off and if you fill or not the rectangle.
Also you should `Refresh` the screen once you'll finish your drawing. All drawing are in the memory and needs to be pushed to the screen.
## Displaying raw images
The function SetByteMap allows you to draw anything, you'll just need to provide the raw buffer. Size is 504 bytes representing from the top left part columns of 8 pixels up to the right up to the next raw. A total of 6 raws are available with 84 columns each.
Here is an example on how you can convert an existing image to extract the raw data:
```csharp
using Image<Rgba32> bitmapNokia = (Image<Rgba32>)Image.Load(Path.Combine("nokia_bw.bmp"));
var bitmap2 = BitmapToByteArray(bitmapNokia);
lcd.SetByteMap(bitmap2);
lcd.Draw();
byte[] BitmapToByteArray(Image<Rgba32> bitmap)
{
if (bitmap is not object)
{
throw new ArgumentNullException(nameof(bitmap));
}
if ((bitmap.Width != Pcd8544.PixelScreenSize.Width) || (bitmap.Height != Pcd8544.PixelScreenSize.Height))
{
throw new ArgumentException($"{nameof(bitmap)} should be same size as the screen {Pcd8544.PixelScreenSize.Width}x{Pcd8544.PixelScreenSize.Height}");
}
byte[] toReturn = new byte[Pcd8544.ScreenBufferByteSize];
int width = Pcd8544.PixelScreenSize.Width;
Rgba32 colWhite = new(255, 255, 255);
for (int position = 0; position < Pcd8544.ScreenBufferByteSize; position++)
{
byte toStore = 0;
for (int bit = 0; bit < 8; bit++)
{
toStore = (byte)(toStore | ((bitmap[position % width, position / width * 8 + bit] == colWhite ? 0 : 1) << bit));
}
toReturn[position] = toStore;
}
return toReturn;
}
```
In case you want to convert existing images which have a different size than the 84x48 screen size, you have to resize the picture like this using ImageSharp and convert it to Black and White:
```csharp
// Open a non bitmap and resize it
using Image<Rgba32> bitmapLarge = (Image<Rgba32>)Image.Load(Path.Combine("nonbmp.jpg"));
bitmapLarge.Mutate(x => x.Resize(Pcd8544.PixelScreenSize));
bitmapLarge.Mutate(x => x.BlackWhite());
var bitmap3 = BitmapToByteArray(bitmapLarge);
```
Note: you may want to reverse the colors first depending on what you want.
## Advance functions
You can adjust couple of factors like `Bias`, `Temperature`, `Contrast` and `Brightness`. The [samples](./samples/Program.cs) will run thru all of them so you can understand the impact of each of them. The `Bias` will increase the voltage and darken the screen.
In general, it is recommended to leave Temperature to the 0 coefficient, if you are in normal conditions. The Bias can be left to 4 as well as a default value for normal conditions.
Then you can use the Contrast property to properly adjust the contrast. A value around 0x30 is usually a good one for normal conditions.
But you may have to adjust those depending on the conditions of lights, temperature you are in.
### Brightness
The brightness is controlling the PWM. If you have not passed any PWM controller, this has no effect on the screen.
Brightness goes from 0.0 to 0.1f.
```csharp
lcd.BacklightBrightness = 0.2f;
```
### InvertedColors
An invert mode is available, it just revert the screen colors. So white pixels become black and vice versa
```csharp
lcd.InvertedColors = true;
```
### Enabled
You can switch on or off the screen fully with the `Enabled` property:
```csharp
lcd.Enabled = true;
```
## Reference
- Data sheet: https://www.sparkfun.com/datasheets/LCD/Monochrome/Nokia5110.pdf
\ No newline at end of file
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Iot.Device.Display.Pcd8544Enums
{
/// <summary>
/// Temperature set for the PCD8544
/// </summary>
public enum ScreenTemperature
{
/// <summary>Temperature Coefficient 0</summary>
Coefficient0 = 0b0000_0100,
/// <summary>Temperature Coefficient 1</summary>
Coefficient1 = 0b0000_0101,
/// <summary>Temperature Coefficient 2</summary>
Coefficient2 = 0b0000_0110,
/// <summary>Temperature Coefficient 3</summary>
Coefficient3 = 0b0000_0111,
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Iot.Device.Display.Pcd8544Enums
{
internal enum SetAddress
{
YAddress = 0b0100_0000,
XAddress = 0b1000_0000,
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="$(MainLibraryPath)System.Device.Gpio.csproj" />
<ProjectReference Include="..\Pcd8544.csproj" />
<ProjectReference Include="..\..\Ft4222\Ft4222.csproj" />
<ProjectReference Include="..\..\SoftPwm\SoftwarePwm.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="me.bmp">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="nokia_bw.bmp">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="nonbmp.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
\ No newline at end of file
// 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.Spi;
using System.Device.Gpio;
using System.Device.Pwm.Drivers;
using System.IO;
using System.Device.Pwm;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Globalization;
using Iot.Device.Display;
using Iot.Device.Ft4222;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Iot.Device.CharacterLcd;
Console.WriteLine("Hello PCD8544, screen of Nokia 5110!");
Console.WriteLine("Please select the platform you want to use:");
Console.WriteLine(" 1. Native device like a Raspberry Pi");
Console.WriteLine(" 2. FT4222");
var platformChoice = Console.ReadKey();
Console.WriteLine();
if (platformChoice is not object or { KeyChar: not ('1' or '2') })
{
Console.WriteLine("You have to choose a valid platform");
return;
}
SpiConnectionSettings spiConnection = new(0, 1) { ClockFrequency = 5_000_000, Mode = SpiMode.Mode0, DataFlow = DataFlow.MsbFirst, ChipSelectLineActiveState = PinValue.Low };
Pcd8544 lcd;
int pwmPin = -1;
Console.WriteLine("Which pin/channel do you want to use for PWM? (use negative number for none)");
var pinChoice = Console.ReadLine();
try
{
pwmPin = Convert.ToInt32(pinChoice);
}
catch
{
Console.WriteLine("Can't convert the pin number, will assume you don't want to use PWM.");
}
int resetPin = -1;
Console.WriteLine("Which pin do you want to use for Reset? (use negative number for none)");
pinChoice = Console.ReadLine();
try
{
resetPin = Convert.ToInt32(pinChoice);
}
catch
{
Console.WriteLine("Can't convert the pin number, will assume you don't want to use Reset.");
}
int dataCommandPin = -1;
Console.WriteLine("Which pin do you want to use for Data Command?");
pinChoice = Console.ReadLine();
try
{
dataCommandPin = Convert.ToInt32(pinChoice);
}
catch
{
}
if (dataCommandPin < 0)
{
Console.WriteLine("Can't continue as Data Command pin has to be valid.");
return;
}
GpioController? gpio = null;
if (platformChoice.KeyChar == '1')
{
PwmChannel? pwmChannel = pwmPin >= 0 ? PwmChannel.Create(0, pwmPin) : null;
SpiDevice spi = SpiDevice.Create(spiConnection);
lcd = new(dataCommandPin, spi, resetPin, pwmChannel);
}
else
{
gpio = new(PinNumberingScheme.Logical, new Ft4222Gpio());
Ft4222Spi ft4222Spi = new(spiConnection);
SoftwarePwmChannel? softPwm = pwmPin >= 0 ? new(pwmPin, 1000, usePrecisionTimer: true, controller: gpio, shouldDispose: false, dutyCycle: 0) : null;
lcd = new(dataCommandPin, ft4222Spi, resetPin, softPwm, gpio, false);
}
if (lcd is not object)
{
Console.WriteLine("Something went wrong, can't initialize PCD8544");
return;
}
lcd.Enabled = true;
Console.WriteLine("Choose the demonstration you want to see:");
Console.WriteLine(" 1. All");
Console.WriteLine(" 2. Brightness, Contrast, Temperature, Bias");
Console.WriteLine(" 3. Display text, change cursor position");
Console.WriteLine(" 4. Display images, resize images");
Console.WriteLine(" 5. Display lines, points, rectangles");
Console.WriteLine(" 6. Use the LcdConsole and display texts");
var demoChoice = Console.ReadKey();
Console.WriteLine();
if (demoChoice is not object or { KeyChar: < '1' or > '6' })
{
Console.WriteLine("You have to choose a demonstration");
return;
}
switch (demoChoice.KeyChar)
{
case '1':
BrightnessContrastTemperatureBias();
DisplayTextChangePositionBlink();
DisplayingBitmap();
DisplayLinesPointsRectabngles();
LcdConsole();
break;
case '2':
BrightnessContrastTemperatureBias();
break;
case '3':
DisplayTextChangePositionBlink();
break;
case '4':
DisplayingBitmap();
break;
case '5':
DisplayLinesPointsRectabngles();
break;
case '6':
LcdConsole();
break;
}
void BrightnessContrastTemperatureBias()
{
Console.WriteLine("Adjusting brightness from 0 to 1.0");
for (int i = 0; i < 10; i++)
{
lcd.SetCursorPosition(0, 0);
lcd.WriteLine("Test for brightness");
lcd.Write($"{i * 10} %");
lcd.BacklightBrightness = i / 10.0f;
Thread.Sleep(1000);
}
lcd.Clear();
Console.WriteLine("Reseting brightness to 0.2 (20%)");
lcd.BacklightBrightness = 0.2f;
Console.WriteLine("Adjusting contrast from 0 to 127");
for (byte i = 0; i < 128; i++)
{
lcd.SetCursorPosition(0, 0);
lcd.WriteLine("Test for contrast");
lcd.Write($"{i}");
lcd.Contrast = i;
Thread.Sleep(100);
}
Console.WriteLine("Reseting contrast to recommended value 40");
lcd.Contrast = 40;
lcd.Clear();
Console.WriteLine("Testing the 4 different temperature modes");
lcd.WriteLine("Test temp 0");
lcd.Temperature = Iot.Device.Display.Pcd8544Enums.ScreenTemperature.Coefficient0;
Thread.Sleep(1500);
lcd.WriteLine("Test temp 1");
lcd.Temperature = Iot.Device.Display.Pcd8544Enums.ScreenTemperature.Coefficient1;
Thread.Sleep(1500);
lcd.WriteLine("Test temp 2");
lcd.Temperature = Iot.Device.Display.Pcd8544Enums.ScreenTemperature.Coefficient2;
Thread.Sleep(1500);
lcd.WriteLine("Test temp 3");
lcd.Temperature = Iot.Device.Display.Pcd8544Enums.ScreenTemperature.Coefficient3;
Thread.Sleep(1500);
lcd.Temperature = Iot.Device.Display.Pcd8544Enums.ScreenTemperature.Coefficient0;
lcd.Clear();
Console.WriteLine("Adjusting bias from 0 to 6");
// Adjusting the bias
for (byte i = 0; i < 7; i++)
{
// Adjusting the bias
lcd.Bias = i;
lcd.SetCursorPosition(0, 0);
lcd.WriteLine("Adjusting bias");
lcd.Write($"bias = {i}");
Thread.Sleep(2000);
lcd.Clear();
}
Console.WriteLine("Reseting bias to recommended value 4");
lcd.Bias = 4;
}
void DisplayTextChangePositionBlink()
{
Console.WriteLine("Display is on and will switch on and off");
lcd.Write("Display is on and will switch on and off");
for (int i = 0; i < 5; i++)
{
Thread.Sleep(1000);
lcd.Enabled = !lcd.Enabled;
}
lcd.Enabled = true;
lcd.Clear();
Console.WriteLine("Displaying multi line with WriteLine");
lcd.SetCursorPosition(0, 0);
lcd.Write("First line");
lcd.SetCursorPosition(0, 1);
lcd.Write("Second one");
lcd.SetCursorPosition(0, 2);
lcd.Write("3rd");
lcd.SetCursorPosition(0, 3);
lcd.Write("Guess!");
lcd.SetCursorPosition(0, 4);
lcd.Write("One more...");
lcd.SetCursorPosition(0, 5);
lcd.Write("last line");
Thread.Sleep(1500);
Console.WriteLine("Inverting the color screen");
// this will blink the screen
for (int i = 0; i < 6; i++)
{
lcd.InvertedColors = !lcd.InvertedColors;
Thread.Sleep(1000);
}
Console.WriteLine("Activating the cursor, writting numbers in a raw");
lcd.Clear();
lcd.SetCursorPosition(0, 0);
lcd.UnderlineCursorVisible = true;
for (int i = 0; i < 50; i++)
{
lcd.Write($"{i}");
Thread.Sleep(500);
}
lcd.Clear();
Console.WriteLine("Testing backspace to remove character");
lcd.Write("Basckspace");
for (int i = 0; i < 5; i++)
{
Thread.Sleep(2000);
lcd.Write("\b");
}
Console.WriteLine("Displaying more text and moving the cursor around");
lcd.Clear();
lcd.Write("More text");
Thread.Sleep(1500);
lcd.SetCursorPosition(0, 0);
Thread.Sleep(1500);
lcd.SetCursorPosition(5, 0);
Thread.Sleep(1500);
lcd.SetCursorPosition(0, 5);
Thread.Sleep(1500);
lcd.UnderlineCursorVisible = false;
Console.WriteLine("This will display a line of random bits");
lcd.Clear();
lcd.WriteLine("This will display a line of random characters");
Thread.Sleep(1500);
char[] textToSend = new char[lcd.Size.Height * lcd.Size.Width];
var rand = new Random(123456);
for (int i = 0; i < textToSend.Length; i++)
{
textToSend[i] = (char)rand.Next(0, 255);
}
lcd.Clear();
lcd.SetCursorPosition(0, 0);
lcd.Write(textToSend);
Thread.Sleep(1000);
lcd.Clear();
}
void DisplayingBitmap()
{
Console.WriteLine("Displaying bitmap, text, resizing them");
using Image<Rgba32> bitmapMe = (Image<Rgba32>)Image.Load(Path.Combine("me.bmp"));
var bitmap1 = BitmapToByteArray(bitmapMe);
using Image<Rgba32> bitmapNokia = (Image<Rgba32>)Image.Load(Path.Combine("nokia_bw.bmp"));
var bitmap2 = BitmapToByteArray(bitmapNokia);
// Open a non bitmap and resize it
using Image<Rgba32> bitmapLarge = (Image<Rgba32>)Image.Load(Path.Combine("nonbmp.jpg"));
bitmapLarge.Mutate(x => x.Resize(Pcd8544.PixelScreenSize));
bitmapLarge.Mutate(x => x.BlackWhite());
var bitmap3 = BitmapToByteArray(bitmapLarge);
for (byte i = 0; i < 2; i++)
{
lcd.Clear();
lcd.Write(" Bonjour .NET IoT!");
Thread.Sleep(2000);
lcd.Clear();
lcd.SetCursorPosition(0, 0);
lcd.Write("This is me");
Thread.Sleep(1000);
// Shows the first bitmap
lcd.SetByteMap(bitmap1);
lcd.Draw();
Thread.Sleep(1500);
lcd.SetCursorPosition(0, 0);
lcd.WriteLine("A nice");
lcd.WriteLine("picture with");
lcd.WriteLine("a heart");
lcd.WriteLine("displayed");
lcd.WriteLine("on the screen");
Thread.Sleep(1000);
// Shows the second bitmap
lcd.SetByteMap(bitmap2);
lcd.Draw();
Thread.Sleep(1500);
lcd.SetCursorPosition(0, 0);
lcd.WriteLine("Large picture");
lcd.WriteLine("resized and");
lcd.WriteLine("changed to");
lcd.WriteLine("monochrome");
Thread.Sleep(1000);
// Shows the second bitmap
lcd.SetByteMap(bitmap3);
lcd.Draw();
Thread.Sleep(1500);
}
}
void DisplayLinesPointsRectabngles()
{
Console.WriteLine("Drawing point, line and rectangles");
lcd.DrawPoint(5, 5, true);
lcd.DrawLine(0, 0, 15, 35, true);
lcd.DrawRectangle(10, 30, 10, 20, true, true);
lcd.DrawRectangle(12, 32, 6, 16, false, true);
// You should not forget to refresh to draw everything
lcd.Draw();
Thread.Sleep(2000);
lcd.Clear();
Console.WriteLine("Drawing 4 points at the 4 edges");
lcd.DrawPoint(0, 0, true);
lcd.DrawPoint(Pcd8544.PixelScreenSize.Width - 1, 0, true);
lcd.DrawPoint(Pcd8544.PixelScreenSize.Width - 1, Pcd8544.PixelScreenSize.Height - 1, true);
lcd.DrawPoint(0, Pcd8544.PixelScreenSize.Height - 1, true);
lcd.Draw();
Thread.Sleep(2000);
Console.WriteLine("Drawing a rectangle at 2 pixels from the edge");
lcd.DrawRectangle(2, 2, Pcd8544.PixelScreenSize.Width - 4, Pcd8544.PixelScreenSize.Height - 4, true, false);
lcd.Draw();
Thread.Sleep(2000);
lcd.Clear();
Console.WriteLine("Drawing 2 diagonal lines");
lcd.DrawLine(0, 0, Pcd8544.PixelScreenSize.Width - 1, Pcd8544.PixelScreenSize.Height - 1, true);
lcd.DrawLine(0, Pcd8544.PixelScreenSize.Height - 1, Pcd8544.PixelScreenSize.Width - 1, 0, true);
lcd.Draw();
Thread.Sleep(2000);
}
void LcdConsole()
{
LcdConsole console = new LcdConsole(lcd, string.Empty, false);
console.LineFeedMode = LineWrapMode.Truncate;
Console.WriteLine("Nowrap test:");
console.Write("This is a long text that should not wrap and just extend beyond the display");
console.WriteLine("This has CRLF\r\nin it and should \r\n wrap.");
console.Write("This goes to the last line of the display");
console.WriteLine("This isn't printed, because it's off the screen");
Thread.Sleep(1500);
Console.WriteLine("Autoscroll test:");
console.LineFeedMode = LineWrapMode.Wrap;
console.WriteLine();
console.WriteLine("Now the display should move up.");
console.WriteLine("And more up.");
for (int i = 0; i < 20; i++)
{
console.WriteLine($"This is line {i + 1}/{20}, but longer than the screen but you really have to add a lot of text to make it big enough");
Thread.Sleep(500);
}
console.LineFeedMode = LineWrapMode.Wrap;
console.WriteLine("Same again, this time with full wrapping.");
for (int i = 0; i < 20; i++)
{
console.Write($"This is string {i + 1}/{20} longer than the screen but you really have to add a lot of text to make it big enough");
Thread.Sleep(500);
}
Thread.Sleep(1500);
Console.WriteLine("Intelligent wrapping test");
console.LineFeedMode = LineWrapMode.WordWrap;
console.WriteLine("Now intelligent wrapping should wrap this long sentence at word borders and ommit spaces at the start of lines.");
Console.WriteLine("Not wrappable test");
Thread.Sleep(1500);
console.WriteLine("NowThisIsOneSentenceInOneWordThatCannotBeWrappedButStillAppearAllOverUpToTheEnd");
Thread.Sleep(1500);
Console.WriteLine("Individual line test");
console.Clear();
console.LineFeedMode = LineWrapMode.Truncate;
console.ReplaceLine(0, "This is all garbage that will be replaced");
console.ReplaceLine(0, "Running clock test, press enter to continue");
int left = console.Size.Width;
Task? alertTask = null;
// Let the current time move trought the display on line 1
while (!Console.KeyAvailable)
{
DateTime now = DateTime.Now;
String time = String.Format(CultureInfo.CurrentCulture, "{0}", now.ToLongTimeString());
string printTime = time;
if (left > 0)
{
printTime = new string(' ', left) + time;
}
else if (left < 0)
{
printTime = time.Substring(-left);
}
console.ReplaceLine(1, printTime);
left--;
// Each full minute, blink the display (but continue writing the time)
if (now.Second == 0 && alertTask is null)
{
alertTask = console.BlinkDisplayAsync(3);
}
if (alertTask is object && alertTask.IsCompleted)
{
// Ensure we catch any exceptions (there shouldn't be any...)
alertTask.Wait();
alertTask = null;
}
Thread.Sleep(500);
// Restart when the time string has left the display
if (left < -time.Length)
{
left = console.Size.Width;
}
}
alertTask?.Wait();
Console.ReadKey();
console.Dispose();
}
Console.WriteLine("Thank you for your attention!");
lcd.WriteLine("Thank you for your attention!");
Thread.Sleep(2000);
lcd.Dispose();
// In case we're using FT4222, gpio needs to be disposed after the screen
gpio?.Dispose();
byte[] BitmapToByteArray(Image<Rgba32> bitmap)
{
if (bitmap is not object)
{
throw new ArgumentNullException(nameof(bitmap));
}
if ((bitmap.Width != Pcd8544.PixelScreenSize.Width) || (bitmap.Height != Pcd8544.PixelScreenSize.Height))
{
throw new ArgumentException($"{nameof(bitmap)} should be same size as the screen {Pcd8544.PixelScreenSize.Width}x{Pcd8544.PixelScreenSize.Height}");
}
byte[] toReturn = new byte[Pcd8544.ScreenBufferByteSize];
int width = Pcd8544.PixelScreenSize.Width;
Rgba32 colWhite = new(255, 255, 255);
for (int position = 0; position < Pcd8544.ScreenBufferByteSize; position++)
{
byte toStore = 0;
for (int bit = 0; bit < 8; bit++)
{
toStore = (byte)(toStore | ((bitmap[position % width, position / width * 8 + bit] == colWhite ? 0 : 1) << bit));
}
toReturn[position] = toStore;
}
return toReturn;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册