Initial draft. Mostly works.

This commit is contained in:
devbotas
2021-12-11 11:04:16 +02:00
parent 9ee6593e5d
commit 50ecb6f77b
11 changed files with 647 additions and 2 deletions

5
.gitignore vendored
View File

@@ -260,5 +260,8 @@ paket-files/
__pycache__/
*.pyc
# Visual Studio Code
.vscode/
Build/output/*
.nuke/*
.nuke/*

View File

@@ -1 +1,21 @@
# basic-template
# St7735
## Summary
Provide a brief description on what the component is and its functionality.
## Device Family
Provide a list of component names and link to datasheets (if available) the binding will work with.
**[Family Name Here]**: [Datasheet link here]
## Binding Notes
Provide any specifics related to binding API. This could include how to configure component for particular functions and example code.
**NOTE**: Don't repeat the basics related to System.Device.API* (e.g. connection settings, etc.). This helps keep text/steps down to a minimum for maintainability.
## References
Provide any references to other tutorials, blogs and hardware related to the component that could help others get started.

166
src/Address.cs Normal file
View File

@@ -0,0 +1,166 @@
namespace Iot.Device.St7735.Register;
/// <summary>
/// Register address definitions for ST7735.
/// </summary>
public enum Address : byte {
/// <summary>
/// No operation.
/// </summary>
NOP = 0x00,
/// <summary>
/// Software reset.
/// </summary>
SwReset = 0x01,
/// <summary>
/// Read display ID.
/// </summary>
DisplayId = 0x04,
/// <summary>
/// Read display status.
/// </summary>
DisplayStatus = 0x09,
/// <summary>
/// Sleep in, booster off.
/// </summary>
SleepIn = 0x10,
/// <summary>
/// Sleep out, booster on.
/// </summary>
SleepOut = 0x11,
/// <summary>
/// Partial mode on, normal mode off.
/// </summary>
PartialModeOn = 0x12,
/// <summary>
/// Normal mode on, partial mode off.
/// </summary>
NormalModeOn = 0x13,
/// <summary>
/// No color inversion.
/// </summary>
DisplayInversionOff = 0x20,
/// <summary>
/// Colors are inverted.
/// </summary>
DisplayInversionOn = 0x21,
/// <summary>
/// Turn display off.
/// </summary>
DisplayOff = 0x28,
/// <summary>
/// Turn display on.
/// </summary>
DisplayOn = 0x29,
/// <summary>
/// Set column addresses (start and stop).
/// </summary>
ColumnAddressSet = 0x2A,
/// <summary>
/// Set row addresses (start and stop).
/// </summary>
RowAddressSet = 0x2B,
/// <summary>
/// Begin writing to graphics memory.
/// </summary>
MemoryWrite = 0x2C,
/// <summary>
/// Begin reading graphics memory.
/// </summary>
MemoryRead = 0x2E,
/// <summary>
/// Set partial start/end address.
/// </summary>
PartialAddressSet = 0x30,
/// <summary>
/// Set directions of how graphics memory is accessed.
/// </summary>
MemoryDataAccessControl = 0x36,
/// <summary>
/// Pixel color format.
/// </summary>
PixelFormat = 0x3A,
/// <summary>
/// Frame control (normal mode).
/// </summary>
FrameRateControl1 = 0xB1,
/// <summary>
/// Frame control (idle mode).
/// </summary>
FrameRateControl2 = 0xB2,
/// <summary>
/// Frame control (partial mode).
/// </summary>
FrameRateControl3 = 0xB3,
/// <summary>
/// Display inversion control.
/// </summary>
InversionControl = 0xB4,
/// <summary>
/// Display function settings.
/// </summary>
DisplayFunction = 0xB6,
/// <summary>
/// Power control for GVDD.
/// </summary>
PowerControl1 = 0xC0,
/// <summary>
/// Power control for VGH/VGL.
/// </summary>
PowerControl2 = 0xC1,
/// <summary>
/// Power control for amplifier and booster (normal mode).
/// </summary>
PowerControl3 = 0xC2,
/// <summary>
/// Power control for amplifier and booster (idle mode).
/// </summary>
PowerControl4 = 0xC3,
/// <summary>
/// Power control for amplifier and booster (partial mode).
/// </summary>
PowerControl5 = 0xC4,
/// <summary>
/// Power control VCOM.
/// </summary>
VcomControl = 0xC5,
/// <summary>
/// Gamma adjustment (positive polarity).
/// </summary>
GammaPositiveCorrection = 0xE0,
/// <summary>
/// Gamma adjustment (negative polarity).
/// </summary>
GammaNegativeCorrection = 0xE1,
}

26
src/Orientation.cs Normal file
View File

@@ -0,0 +1,26 @@
namespace Iot.Device.St7735;
/// <summary>
/// Orientation of the display.
/// </summary>
public enum Orientation {
/// <summary>
/// Native orientation, as designed by the manufacturer.
/// </summary>
Normal,
/// <summary>
/// Rotated CW by 90 degrees.
/// </summary>
Rotated90,
/// <summary>
/// Rotated CW by 180 degrees.
/// </summary>
Rotated180,
/// <summary>
/// Rotated CW by 270 degrees.
/// </summary>
Rotated270
}

105
src/PimoroniOled.cs Normal file
View File

@@ -0,0 +1,105 @@
using System.Device.Spi;
using System.Threading;
using Iot.Device.St7735.Register;
namespace Iot.Device.St7735;
/// <summary>
/// TODO
/// </summary>
public class PimoroniOled : ST7735 {
/// <summary>
/// TODO
/// </summary>
/// <param name="spiDevice">TODO</param>
/// <param name="controlPin">TODO</param>
public PimoroniOled(SpiDevice spiDevice, int controlPin)
: base(spiDevice, controlPin, 80, 160) {
}
/// <summary>
/// TODO
/// </summary>
public override void Initialize() {
// Rotation = rotation;
// switch (Rotation)
// {
// case Rotation.Normal:
// ActualWidth = NativeWidth;
// ActualHeight = NativeHeight;
// break;
// case Rotation.Clockwise180:
// ActualWidth = NativeHeight;
// ActualHeight = NativeWidth;
// break;
// }
SetRegister(Address.SwReset);
Thread.Sleep(200);
SetRegister(Address.SleepOut);
Thread.Sleep(200);
// Normal mode //Rate = fosc/(1x2+40) * (LINE+2C+2D)
SetRegister(Address.FrameRateControl1, new byte[] { 0x01, 0x2C, 0x2D });
// Idle mode //Rate = fosc/(1x2+40) * (LINE+2C+2D)
SetRegister(Address.FrameRateControl2, new byte[] { 0x01, 0x2C, 0x2D });
// Partial mode. // Dot inversion mode.// Line inversion mode.
SetRegister(Address.FrameRateControl3, new byte[] { 0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D });
// <-- No inversion
SetRegister(Address.InversionControl, 0x07);
// -4.6V, auto mode.
SetRegister(Address.PowerControl1, new byte[] { 0xA2, 0x02, 0x84 });
// Opamp current small, boost frequency.
SetRegister(Address.PowerControl2, new byte[] { 0x0A, 0x00 });
// BCLK / 2, Opamp current small & Medium low.
SetRegister(Address.PowerControl4, new byte[] { 0x8A, 0x2A });
SetRegister(Address.PowerControl5, new byte[] { 0x8A, 0xEE });
SetRegister(Address.VcomControl, 0x0E);
SetRegister(Address.DisplayInversionOn);
// switch (Rotation)
// {
// case Rotation.Normal:
// SetRegister(Address.MemoryDataAccessControl, 0x08 /* RGB mode */);
// OffsetLeft = (byte)((_sT7735_COLS - ActualWidth) / 2);
// OffsetTop = (byte)((_sT7735_ROWS - ActualHeight) / 2);
// SetRegion(0, 0, ActualWidth, ActualHeight);
// break;
// case Rotation.Clockwise180:
// SetRegister(Address.MemoryDataAccessControl, 0x80 /* Row order reversed */ + 0x20 /* Row/column switch */ + 0x08 /* RGB mode */);
// OffsetLeft = (byte)((_sT7735_ROWS - ActualWidth) / 2);
// OffsetTop = (byte)((_sT7735_COLS - ActualHeight) / 2);
// SetRegion(0, 0, ActualWidth, ActualHeight);
// break;
// }
SetRegister(Address.PixelFormat, 0x05); // 65k mode .
SetRegister(Address.GammaPositiveCorrection, new byte[] { 0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2e, 0x39, 0x00, 0x01, 0x03, 0x10 });
SetRegister(Address.GammaNegativeCorrection, new byte[] { 0x03, 0x1d, 0x07, 0x06, 0x2e, 0x2c, 0x29, 0x2d, 0x2e, 0x2e, 0x37, 0x3f, 0x00, 0x00, 0x02, 0x10 });
SetRegister(Address.NormalModeOn);
Thread.Sleep(10);
SetRegister(Address.DisplayOn);
Thread.Sleep(100);
SetOrientation(Orientation.Normal);
}
}

202
src/St7735.cs Normal file
View File

@@ -0,0 +1,202 @@
using System;
using System.Device.Gpio;
using System.Device.Spi;
using Iot.Device.St7735.Register;
namespace Iot.Device.St7735;
/// <summary>
/// Provides very basic access to ST7735 LCD controller.
/// </summary>
public class ST7735 {
private const byte _nativeNumberOfColumns = 132;
private const byte _nativeNumberOfRows = 162;
private readonly SpiDevice _spi;
private readonly GpioController _gpio = new();
private readonly int _controlPin;
private readonly int _backlightPin = 12;
private readonly byte[] _buffer1 = new byte[1];
private byte _offsetLeft;
private byte _offsetTop;
/// <summary>
/// Native width of the LCD display.This depends on the LCD matrix that is connected to ST7735 controller.
/// </summary>
public int NativeWidth { get; protected set; } = 80;
/// <summary>
/// Native height of the LCD display.This depends on the LCD matrix that is connected to ST7735 controller.
/// </summary>
public int NativeHeight { get; protected set; } = 160;
/// <summary>
/// Actual width of the usable LCD area. This may differ from <see cref="NativeWidth"/> because screen may be rotated, for example.
/// </summary>
public int ActualWidth { get; protected set; }
/// <summary>
/// Actual height of the usable LCD area. This may differ from <see cref="NativeHeight"/> because screen may be rotated, for example.
/// </summary>
public int ActualHeight { get; protected set; }
/// <summary>
/// Rotation of the LCD screen.
/// </summary>
public Orientation Orientation { get; protected set; }
/// <summary>
/// TODO
/// </summary>
public ST7735(SpiDevice spiDevice, int controlPin, int nativeWidth, int nativeHeight) {
_spi = spiDevice;
_controlPin = controlPin;
NativeWidth = nativeWidth;
NativeHeight = nativeHeight;
_gpio.OpenPin(_controlPin, PinMode.Output);
_gpio.OpenPin(_backlightPin, PinMode.Output);
Initialize();
}
/// <summary>
/// TODO
/// </summary>
/// <param name="orientation">TODO</param>
public void SetOrientation(Orientation orientation) {
Orientation = orientation;
switch (Orientation) {
case Orientation.Normal:
ActualWidth = NativeWidth;
ActualHeight = NativeHeight;
SetRegister(Address.MemoryDataAccessControl, 0x08 /* RGB mode */);
_offsetLeft = (byte)((_nativeNumberOfColumns - ActualWidth) / 2);
_offsetTop = (byte)((_nativeNumberOfRows - ActualHeight) / 2);
SetRegion(0, 0, ActualWidth, ActualHeight);
break;
case Orientation.Rotated180:
ActualWidth = NativeHeight;
ActualHeight = NativeWidth;
SetRegister(Address.MemoryDataAccessControl, 0x80 /* Row order reversed */ + 0x20 /* Row/column switch */ + 0x08 /* RGB mode */);
_offsetLeft = (byte)((_nativeNumberOfRows - ActualWidth) / 2);
_offsetTop = (byte)((_nativeNumberOfColumns - ActualHeight) / 2);
SetRegion(0, 0, ActualWidth, ActualHeight);
break;
}
}
/// <summary>
/// TODO
/// </summary>
public virtual void Initialize() {
}
/// <summary>
/// Set the register of the LCD controller (no payload).
/// </summary>
/// <param name="register">Address of the register.</param>
public void SetRegister(Address register) {
_buffer1[0] = (byte)register;
_gpio.Write(_controlPin, PinValue.Low);
_spi.Write(_buffer1);
}
/// <summary>
/// Set the register of the LCD controller (single-byte payload).
/// </summary>
/// <param name="register">Address of the register.</param>
/// <param name="value">Value of the register.</param>
public void SetRegister(Address register, byte value) {
_buffer1[0] = (byte)register;
_gpio.Write(_controlPin, PinValue.Low);
_spi.Write(_buffer1);
_buffer1[0] = value;
_gpio.Write(_controlPin, PinValue.High);
_spi.Write(_buffer1);
}
/// <summary>
/// Set the register of the LCD controller (multi-byte payload).
/// </summary>
/// <param name="register">Address of the register.</param>
/// <param name="value">Value of the register.</param>
public void SetRegister(Address register, byte[] value) {
var valueSpan = new Span<byte>(value);
var bytesSent = 0;
_buffer1[0] = (byte)register;
_gpio.Write(_controlPin, PinValue.Low);
_spi.Write(_buffer1);
_gpio.Write(_controlPin, PinValue.High);
while (bytesSent < value.Length) {
if (value.Length - bytesSent > 4096) {
_spi.Write(valueSpan.Slice(bytesSent, 4096));
bytesSent += 4096;
}
else {
_spi.Write(valueSpan.Slice(bytesSent, value.Length - bytesSent));
bytesSent += value.Length - bytesSent;
}
}
}
/// <summary>
/// Turns on the display.
/// </summary>
public void TurnOn() {
_gpio.Write(_backlightPin, PinValue.High);
}
/// <summary>
/// Turns off the display.
/// </summary>
public void TurnOff() {
_gpio.Write(_backlightPin, PinValue.Low);
}
/// <summary>
/// Sets clipping region of the display area. It will only be possible to change pixels in that region.
/// </summary>
/// <param name="x">Origin X of the clip region.</param>
/// <param name="y">Origin Y of the clip region.</param>
/// <param name="width">Width of the clip region.</param>
/// <param name="height">Height of the clip region.</param>
public void SetRegion(int x, int y, int width, int height) {
var x0 = x + _offsetLeft;
var x1 = x + width - 1 + _offsetLeft;
SetRegister(Address.ColumnAddressSet, new byte[] { (byte)(x0 >> 8), (byte)x0, (byte)(x1 >> 8), (byte)x1 });
var y0 = y + _offsetTop;
var y1 = y + height - 1 + _offsetTop;
SetRegister(Address.RowAddressSet, new byte[] { (byte)(y0 >> 8), (byte)y0, (byte)(y1 >> 8), (byte)y1 });
}
/// <summary>
/// TODO
/// </summary>
/// <param name="data">TODO</param>
public void SendBitmap(byte[] data) {
if (data == null) {
throw new ArgumentNullException(nameof(data));
}
SetRegister(Address.MemoryWrite, data);
}
}

28
src/St7735.csproj Normal file
View File

@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<!--Disabling default items so samples source won't get build by the main library-->
<EnableDefaultItems>false</EnableDefaultItems>
<LangVersion>9</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net6.0|AnyCPU'">
<LangVersion>10.0</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net6.0|AnyCPU'">
<LangVersion>10.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Include="*.cs" />
<Compile Remove="Color.cs" />
<Compile Remove="Graphics.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Device.Gpio" Version="1.5.0" />
</ItemGroup>
</Project>

31
src/St7735.sln Normal file
View File

@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31919.166
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "St7735", "St7735.csproj", "{A045CE34-AFFD-4A0E-B22E-F95DF59A21AD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "St7735.Samples", "samples\St7735.Samples.csproj", "{4218D778-859B-4A17-80F5-AA49EEFB0357}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A045CE34-AFFD-4A0E-B22E-F95DF59A21AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A045CE34-AFFD-4A0E-B22E-F95DF59A21AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A045CE34-AFFD-4A0E-B22E-F95DF59A21AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A045CE34-AFFD-4A0E-B22E-F95DF59A21AD}.Release|Any CPU.Build.0 = Release|Any CPU
{4218D778-859B-4A17-80F5-AA49EEFB0357}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4218D778-859B-4A17-80F5-AA49EEFB0357}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4218D778-859B-4A17-80F5-AA49EEFB0357}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4218D778-859B-4A17-80F5-AA49EEFB0357}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CA1F8195-2F31-474B-9E3F-A515EECD1BD5}
EndGlobalSection
EndGlobal

3
src/samples/README.md Normal file
View File

@@ -0,0 +1,3 @@
# TODO: This needs to be determined
Help Wanted Please

View File

@@ -0,0 +1,49 @@
using System;
using System.Device.Spi;
using System.Threading;
using Iot.Device.St7735;
Console.WriteLine("Hello, World!");
// Preparing SPI bus.
var spiSettings = new SpiConnectionSettings(0, 1);
spiSettings.ClockFrequency = 12_000_000;
var spiBus = SpiDevice.Create(spiSettings); // Ft4222Spi(new SpiConnectionSettings(0, 1) { ClockFrequency = 1_000_000, Mode = SpiMode.Mode0 });
// Pimoroni is using 0.96" 80x160 LCD displays for their Automation and Enviro pHats.
var lcd = new PimoroniOled(spiBus, 9);
lcd.SetOrientation(Orientation.Rotated90);
Console.WriteLine("Let's go!");
lcd.SetRegion(0, 0, lcd.ActualWidth, lcd.ActualHeight);
lcd.SendBitmap(new byte[2 * lcd.ActualWidth * lcd.ActualHeight]);
var randomNumber = new Random();
var whiteRectangleBuffer = new byte[2 * 10 * 5];
for (var i = 0; i < 2 * 10 * 5; i++) {
whiteRectangleBuffer[i] = 0xFF;
}
var greenRectangleBuffer = new byte[2 * 5 * 10];
for (var i = 0; i < 2 * 5 * 10; i += 2) {
greenRectangleBuffer[i] = 0b00011111;
}
while (true) {
Thread.Sleep(100);
var randomX = randomNumber.Next(lcd.ActualWidth - 10);
var randomY = randomNumber.Next(lcd.ActualHeight - 10);
lcd.SetRegion(randomX, randomY, 10, 5);
lcd.SendBitmap(whiteRectangleBuffer);
Thread.Sleep(100);
randomX = randomNumber.Next(lcd.ActualWidth - 10);
randomY = randomNumber.Next(lcd.ActualHeight - 10);
lcd.SetRegion(randomX, randomY, 5, 10);
lcd.SendBitmap(greenRectangleBuffer);
}

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../St7735.csproj" />
</ItemGroup>
</Project>