Skip to content
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

Add SSD1309 Device using non-traditional SPI #2316

Merged
merged 5 commits into from
Aug 22, 2024
Merged
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
14 changes: 14 additions & 0 deletions src/Iot.Device.Bindings/CompatibilitySuppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@
<Right>lib/net6.0/Iot.Device.Bindings.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Iot.Device.Ssd13xx.Ssd13xx._i2cDevice</Target>
<Left>lib/net6.0/Iot.Device.Bindings.dll</Left>
<Right>lib/net6.0/Iot.Device.Bindings.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Iot.Device.Axp192.Axp192.SetGPIO0(Iot.Device.Axp192.Gpio0Behavior,System.Byte)</Target>
Expand Down Expand Up @@ -225,6 +232,13 @@
<Right>lib/netstandard2.0/Iot.Device.Bindings.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Iot.Device.Ssd13xx.Ssd13xx._i2cDevice</Target>
<Left>lib/netstandard2.0/Iot.Device.Bindings.dll</Left>
<Right>lib/netstandard2.0/Iot.Device.Bindings.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Iot.Device.Axp192.Axp192.SetGPIO0(Iot.Device.Axp192.Gpio0Behavior,System.Byte)</Target>
Expand Down
12 changes: 12 additions & 0 deletions src/devices/Ssd13xx/Commands/ISsd1309Command.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// 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.Ssd13xx.Commands
{
/// <summary>
/// Interface for all Ssd1309 commands
/// </summary>
public interface ISsd1309Command : ICommand
{
}
}
2 changes: 1 addition & 1 deletion src/devices/Ssd13xx/Ssd1306.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ private void SendCommand(ICommand command)
// Be aware there is a Continuation Bit in the Control byte and can be used
// to state (logic LOW) if there is only data bytes to follow.
// This binding separates commands and data by using SendCommand and SendData.
_i2cDevice.Write(writeBuffer);
I2cDevice?.Write(writeBuffer);
}

// Display size 128x32 or 128x64
Expand Down
227 changes: 227 additions & 0 deletions src/devices/Ssd13xx/Ssd1309.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
// 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.Collections.Generic;
using System.Device.Gpio;
using System.Device.Spi;
using System.Linq;
using System.Threading;
using Iot.Device.Graphics;
using Iot.Device.Ssd13xx.Commands;
using Iot.Device.Ssd13xx.Commands.Ssd1306Commands;

namespace Iot.Device.Ssd13xx
{
/// <summary>
/// A single-chip CMOS OLED/PLED driver with controller for organic/polymer
/// light emitting diode dot-matrix graphic display system.
/// </summary>
public class Ssd1309 : Ssd13xx
{
private readonly GpioPin _csGpioPin;
private readonly GpioPin _dcGpioPin;
private readonly GpioPin _rstGpioPin;

private GpioController _gpioController;

/// <summary>
/// Initializes new instance of Ssd13069 device that will communicate using SPI
/// in a non-traditional "4-wire SPI" mode that uses DC and RST GPIO pins to switch
/// between Data/Command instructions and reset behavior.
/// A single-chip CMOS OLED/PLED driver with controller for organic/polymer
/// light emitting diode dot-matrix graphic display system.
/// </summary>
/// <param name="spiDevice">The SPI device used for communication.</param>
/// <param name="width">Width of the display. Typically 128 pixels</param>
/// <param name="height">Height of the display</param>
/// <param name="gpioController">Instance of the boards GpioController</param>
/// <param name="csGpioPin">GPIO pin for chip-select. Active state is LOW. Does not guarantee support for multiple SPI bus devices.</param>
/// <param name="dcGpioPin">GPIO pin for Data/Command control</param>
/// <param name="rstGpioPin">GPIO pin for Reset behavior</param>
public Ssd1309(SpiDevice spiDevice, GpioController gpioController, int csGpioPin, int dcGpioPin, int rstGpioPin, int width, int height)
: base(spiDevice, width, height)
{
_gpioController = gpioController;

_csGpioPin = _gpioController.OpenPin(csGpioPin, PinMode.Output, PinValue.High);
_dcGpioPin = _gpioController.OpenPin(dcGpioPin, PinMode.Output, PinValue.Low);
_rstGpioPin = _gpioController.OpenPin(rstGpioPin, PinMode.Output, PinValue.High);

Reset();
Initialize();
}

/// <summary>
/// Sends command to the device
/// </summary>
/// <param name="command">Command being sent</param>
public virtual void SendCommand(ISsd1309Command command) => SendCommand((ICommand)command);

/// <summary>
/// Sends command to the device
/// </summary>
/// <param name="command">Command being sent</param>
public override void SendCommand(ISharedCommand command) => SendCommand(command);

/// <summary>
/// Send a command to the display controller.
/// </summary>
/// <param name="command">The command to send to the display controller.</param>
private void SendCommand(ICommand command)
{
Span<byte> commandBytes = command?.GetBytes();

if (commandBytes is not { Length: >0 })
{
throw new ArgumentNullException(nameof(command), "Argument is either null or there were no bytes to send.");
}

// TODO: Is this needed at all if data/command is controlled via GPIO instead of a control byte?
Span<byte> writeBuffer = SliceGenericBuffer(commandBytes.Length);

commandBytes.CopyTo(writeBuffer.Slice(0));

if (SpiDevice != null)
{
// Begin consuming SPI bus data
// Because the timing is not perfect, this may have adverse side-effects when using multiple SPI bus devices
_csGpioPin.Write(PinValue.Low);

// Enable command mode
_dcGpioPin.Write(PinValue.Low);
SpiDevice.Write(writeBuffer);

// Stop consuming SPI bus data
_csGpioPin.Write(PinValue.High);
}
else
{
throw new InvalidOperationException("No SPI device available or it has been disposed.");
}
}

/// <summary>
/// Sends data to the device
/// </summary>
/// <param name="data">Data being sent</param>
public override void SendData(Span<byte> data)
{
if (data.IsEmpty)
{
throw new ArgumentNullException(nameof(data));
}

if (SpiDevice != null)
{
// Begin consuming SPI bus data
// Because the timing is not perfect, this may have adverse side-effects when using multiple SPI bus devices
_csGpioPin.Write(PinValue.Low);

// Enable data mode
_dcGpioPin.Write(PinValue.High);
SpiDevice.Write(data);

// Stop consuming SPI bus data
_csGpioPin.Write(PinValue.High);
}
else
{
throw new InvalidOperationException("No SPI device available or it has been disposed.");
}
}

/// <summary>
/// Init 128x64
/// </summary>
protected virtual void Initialize()
{
SendCommand(new SetDisplayOff());
SendCommand(new SetDisplayClockDivideRatioOscillatorFrequency());
SendCommand(new SetMultiplexRatio());
SendCommand(new SetDisplayOffset());
SendCommand(new SetDisplayStartLine());
SendCommand(new SetMemoryAddressingMode(SetMemoryAddressingMode.AddressingMode.Horizontal));
SetStartAddress();
SendCommand(new SetSegmentReMap(true));
SendCommand(new SetComOutputScanDirection(false));
SendCommand(new SetComPinsHardwareConfiguration());
SendCommand(new SetContrastControlForBank0());
SendCommand(new SetPreChargePeriod());
SendCommand(new SetVcomhDeselectLevel());
SendCommand(new EntireDisplayOn(false));
SendCommand(new SetNormalDisplay());
SendCommand(new SetDisplayOn());
Thread.Sleep(200);
ClearScreen();
}

/// <summary>
/// Set the start address for the display
/// </summary>
protected override void SetStartAddress()
{
SendCommand(new SetColumnAddress());

if (ScreenHeight == 32)
{
SendCommand(new SetPageAddress(PageAddress.Page0, PageAddress.Page3));
}
else
{
SendCommand(new SetPageAddress());
}
}

/// <summary>
/// Reset device by cycling RST pin to LOW and resting at HIGH.
/// This will not re-initialize the device.
/// </summary>
protected virtual void Reset()
{
_rstGpioPin.Write(PinValue.Low);
Thread.Sleep(100);
_rstGpioPin.Write(PinValue.High);
Thread.Sleep(100);
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be useful to call Initialize() here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This method was intended to just execute the specific sequence to reset the controller according to the datasheet. But practically, it could be useful to include other reset-related tasks. Calling Reset and then Initialize separately in a "re-initialization" procedure feels more intuitive to me personally, but I can see advantages to both.

}

/// <summary>
/// Sends the image to the display
/// </summary>
/// <param name="image">Image to send to display</param>
public override void DrawBitmap(BitmapImage image)
{
if (!CanConvertFromPixelFormat(image.PixelFormat))
{
throw new InvalidOperationException($"{image.PixelFormat} is not a supported pixel format");
}

SetStartAddress();

int width = ScreenWidth;
int pages = image.Height / 8;
List<byte> buffer = new();

for (int page = 0; page < pages; page++)
{
for (int x = 0; x < width; x++)
{
int bits = 0;
for (byte bit = 0; bit < 8; bit++)
{
bits = bits << 1;
bits |= image[x, page * 8 + 7 - bit].GetBrightness() > BrightnessThreshold ? 1 : 0;
}

buffer.Add((byte)bits);
}
}

int chunk_size = 16;
for (int i = 0; i < buffer.Count; i += chunk_size)
{
SendData(buffer.Skip(i).Take(chunk_size).ToArray());
}
}
}
}
3 changes: 1 addition & 2 deletions src/devices/Ssd13xx/Ssd1327.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ private void Initialize()
public void SendCommand(byte command)
{
Span<byte> writeBuffer = new byte[] { Command_Mode, command };

_i2cDevice.Write(writeBuffer);
I2cDevice?.Write(writeBuffer);
}

/// <summary>
Expand Down
Loading