未验证 提交 ce3e33e1 编写于 作者: P Patrick Grawehr 提交者: GitHub

New Ili9342 device, moved namespace accordingly (#2092)

* New Ili9342 device, moved namespace accordingly

* Reduce sample to minimum for now

* Updated compatibility suppressions

Ili9431 was moved to Ili943x

* Linkfix in Readme file

* Adjust documentation

* Review findings
上级 cbf35d22
......@@ -36,6 +36,13 @@
<Right>lib/net6.0/Iot.Device.Bindings.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Iot.Device.Ili9341.Ili9341</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>CP0001</DiagnosticId>
<Target>T:Iot.Device.Media.PixelFormat</Target>
......@@ -197,6 +204,13 @@
<Right>lib/netstandard2.0/Iot.Device.Bindings.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Iot.Device.Ili9341.Ili9341</Target>
<Left>lib/netcoreapp3.1/Iot.Device.Bindings.dll</Left>
<Right>lib/netstandard2.0/Iot.Device.Bindings.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Iot.Device.Media.PixelFormat</Target>
......@@ -239,6 +253,13 @@
<Right>lib/netstandard2.0/Iot.Device.Bindings.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Iot.Device.Ili9341.Ili9341</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>CP0001</DiagnosticId>
<Target>T:Iot.Device.Media.PixelFormat</Target>
......
......@@ -14,6 +14,7 @@
* [AGS01DB - MEMS VOC Gas Sensor](Ags01db/README.md)
* [AHT10/15/20 - Temperature and humidity sensor modules](Ahtxx/README.md)
* [AK8963 - Magnetometer](Ak8963/README.md)
* [AM2320 - Temperature and Humidity sensor](Am2320/README.md)
* [AMG88xx Infrared Array Sensor Family](Amg88xx/README.md)
* [APA102 - Double line transmission integrated control LED](Apa102/README.md)
* [AXP192 - Enhanced single Cell Li-Battery and Power System Management IC](Axp192/README.md)
......@@ -46,9 +47,11 @@
* [Holtek HT1632 - 32×8 & 24×16 LED Driver](Ht1632/README.md)
* [HT16K33 - LED Matrix Display Driver](Display/README.md)
* [HTS221 - Capacitive digital sensor for relative humidity and temperature](Hts221/README.md)
* [Ili9341 TFT LCD Controller](Ili9341/README.md)
* [Ili934x TFT LCD Controller](Ili934x/README.md)
* [INA219 - Bidirectional Current/Power Monitor](Ina219/README.md)
* [IP5306 - Power management](Ip5306/README.md)
* [IS31FL3730 -- LED Matrix Display Driver](Is31fl3730/README.md)
* [IS31FL3731 -- LED Matrix Display Driver](Is31fl3731/README.md)
* [Key Matrix](KeyMatrix/README.md)
* [LidarLiteV3 - LIDAR Time of Flight Sensor](LidarLiteV3/README.md)
* [LIS3DH - ultra-low-power high-performance 3-axis nano accelerometer](Lis3Dh/README.md)
......@@ -100,6 +103,7 @@
* [SHT4x - Temperature & Humidity Sensor](Sht4x/README.md)
* [SHTC3 - Temperature & Humidity Sensor](Shtc3/README.md)
* [Si7021 - Temperature & Humidity Sensor](Si7021/README.md)
* [SkiaSharp graphics library adapter](SkiaSharpAdapter/README.md)
* [SN74HC595 -- 8-bit shift register](Sn74hc595/README.md)
* [SocketCan - CAN BUS library (Linux only)](SocketCan/README.md)
* [Software PWM](SoftPwm/README.md)
......@@ -107,13 +111,13 @@
* [Solomon Systech Ssd1351 - CMOS OLED](Ssd1351/README.md)
* [Solomon Systech SSD13xx OLED display family](Ssd13xx/README.md)
* [SPI, GPIO and I2C drivers for Arduino with Firmata](Arduino/README.md)
* [SPI, GPIO and I2C drivers for FT232H](Ft232H/README.md)
* [SPI, GPIO and I2C drivers for FT232H, FT2232H, FT4232H](Ft232H/README.md)
* [SPI, GPIO and I2C drivers for FT4222](Ft4222/README.md)
* [Still image recording library](Media/README.md)
* [STUSB4500 - Autonomous USB-C PD controller for Power Sinks / UFP](StUsb4500/README.md)
* [System.Device.Model - attributes for device bindings](System.Device.Model/README.md)
* [TCA954X - TCA954X Low-Voltage Multi-Channel I2C Switch with Reset](Tca954x/README.md)
* [TCS3472x Sensors](Tcs3472x/README.md)
* [TCA954X Multiplexer](Tca954x/README.md)
* [TLC1543 - 10-bit ADC with 11 input channels](Tlc1543/README.md)
* [TM1637 - Segment Display](Tm1637/README.md)
* [TSL256x - Illuminance sensor](Tsl256x/README.md)
......
// 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.I2c;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Iot.Device.Ili934x
{
/// <summary>
/// Binding for Chipsemi CHSC6540 capacitive touch screen controller
/// Used for instance on the M5Tough in conjunction with an ILI9342 display controller.
/// Note: The M5Core2, while being very similar to the M5Tough otherwise, has a FT6336U instead.
/// The two chips appear to be similar, but the documentation is poor.
/// </summary>
public class Chsc6440 : IDisposable
{
/// <summary>
/// The default I2C address of this chip
/// </summary>
public const int DefaultI2cAddress = 0x2E;
private readonly int _interruptPin;
private readonly bool _shouldDispose;
private GpioController? _gpioController;
private I2cDevice _i2c;
private bool _wasRead;
private TimeSpan _interval;
private Point[] _lastPoints;
private Point _initialTouchPoint;
private Point[] _points;
private int _point0finger;
private int _activeTouches;
private int _lastActiveTouches;
private bool _dragging;
private bool _isPressed;
private Thread? _updateThread;
private bool _updateThreadActive;
private object _lock;
private AutoResetEvent _updateEvent;
/// <summary>
/// This event is fired when the user "clicks" a position
/// Call <see cref="EnableEvents"/> to use event handling
/// </summary>
public event Action<object, Point>? Touched;
/// <summary>
/// This event is fired repeatedly when the user drags over the screen
/// Call <see cref="EnableEvents"/> to use event handling.
/// </summary>
public event Action<object, DragEventArgs>? Dragging;
/// <summary>
/// The event that is fired when the user zooms (using two fingers)
/// Call <see cref="EnableEvents"/> to use event handling.
/// The second argument is the list of touch points (always 2 when this function is called), the third and fourth
/// argument are the old and the new distance between the points. So if the value decreases, zooming out is intended.
/// The values are always &gt; 0
/// </summary>
public event Action<object, Point[], int, int>? Zooming;
/// <summary>
/// Create a controller from the given I2C device
/// </summary>
/// <param name="device">An I2C device</param>
/// <param name="screenSize">Size of the screen. Used to filter out invalid readings</param>"/>
/// <param name="interruptPin">The interrupt pin to use, -1 to disable</param>
/// <param name="gpioController">The gpio controller the interrupt pin is attached to</param>
/// <param name="shouldDispose">True to dispose the gpio controller on close</param>
public Chsc6440(I2cDevice device, Size screenSize, int interruptPin = -1, GpioController? gpioController = null, bool shouldDispose = true)
{
ScreenSize = screenSize;
_i2c = device;
_interruptPin = interruptPin;
_gpioController = gpioController;
_shouldDispose = shouldDispose;
_wasRead = false;
_point0finger = 0;
_points = new Point[2];
_lastPoints = new Point[2];
_initialTouchPoint = Point.Empty;
_activeTouches = 0;
_lastActiveTouches = 0;
_dragging = false;
_interval = TimeSpan.FromMilliseconds(20);
_updateThread = null;
_lock = new object();
_updateEvent = new AutoResetEvent(false);
TouchSize = new Size(5, 5);
Span<byte> initData = stackalloc byte[2]
{
0x5A, 0x5A
};
_i2c.Write(initData);
if (_interruptPin >= 0)
{
if (_gpioController == null)
{
throw new ArgumentNullException(nameof(gpioController));
}
_gpioController.OpenPin(_interruptPin, PinMode.InputPullUp);
_gpioController.RegisterCallbackForPinValueChangedEvent(_interruptPin, PinEventTypes.Rising | PinEventTypes.Falling, OnInterrupt);
}
}
/// <summary>
/// Size of the screen
/// </summary>
public Size ScreenSize { get; }
/// <summary>
/// Sets the background thread update interval. Low values can impact performance, but increase the responsiveness.
/// </summary>
public TimeSpan UpdateInterval
{
get
{
return _interval;
}
set
{
_interval = value;
}
}
/// <summary>
/// The size of the rectangle that is considered a "touch". When the position changes more than this, it is considered a drag.
/// </summary>
public Size TouchSize
{
get;
set;
}
private void OnInterrupt(object sender, PinValueChangedEventArgs pinValueChangedEventArgs)
{
// The pin is low as long as the screen is being touched.
// So we still have to poll when the user drags
if (pinValueChangedEventArgs.PinNumber != _interruptPin)
{
return;
}
_isPressed = pinValueChangedEventArgs.ChangeType == PinEventTypes.Falling;
_updateEvent.Set();
}
/// <summary>
/// Returns true if the interrupt pin is set, meaning something is touching the display
/// </summary>
/// <returns>True if something presses the display, false if not. This queries the interrupt pin if available. Otherwise, an I2C request to the controller is required.</returns>
public bool IsPressed()
{
if (_gpioController != null)
{
return _isPressed;
}
// Need to query the device instead
Span<byte> register = stackalloc byte[1]
{
0x02
};
Span<byte> result = stackalloc byte[1];
_i2c.WriteRead(register, result);
return result[0] != 0;
}
private void ReadData()
{
lock (_lock)
{
// true if real read, not a "come back later"
_wasRead = false;
Span<Point> p = stackalloc Point[2];
p[0] = default;
p[1] = default;
byte pts = 0;
int p0f = 0;
Span<byte> register = stackalloc byte[1]
{
0x02
};
if (IsPressed())
{
Span<byte> data = stackalloc byte[11];
try
{
_i2c.WriteRead(register, data);
}
catch (TimeoutException)
{
// Try again next time
return;
}
pts = data[0];
if (pts > 2)
{
return;
}
if (pts > 0)
{
// Read the data. Never mind trying to read the "weight" and
// "size" properties or using the built-in gestures: they
// are always set to zero.
p0f = (data[3] >> 4 != 0) ? 1 : 0;
p[0].X = ((data[1] << 8) | data[2]) & 0x0fff;
p[0].Y = ((data[3] << 8) | data[4]) & 0x0fff;
if (pts == 2)
{
p[1].X = ((data[7] << 8) | data[8]) & 0x0fff;
p[1].Y = ((data[9] << 8) | data[10]) & 0x0fff;
}
}
if (p[0].X > ScreenSize.Width || p[0].X < 0 || p[0].Y > ScreenSize.Height || p[0].Y < 0
|| p[1].X > ScreenSize.Width || p[1].X < 0 || p[1].Y > ScreenSize.Height || p[1].Y < 0)
{
// Invalid data
return;
}
}
if (p[0].X < 0 || p[0].X >= ScreenSize.Width || p[0].Y < 0 || p[1].Y >= ScreenSize.Height)
{
// Drop invalid positions
_activeTouches = 0;
}
if (p[0] != _points[0] || p[1] != _points[1])
{
_points[0] = p[0];
_points[1] = p[1];
_point0finger = p0f;
_activeTouches = pts;
}
_wasRead = true;
}
}
/// <summary>
/// Gets the primary touch point or null if the screen is not being touched
/// </summary>
/// <returns>A point where the first finger is</returns>
public Point? GetPrimaryTouchPoint()
{
ReadData();
if (!_wasRead)
{
return null;
}
if (_activeTouches >= 1)
{
return _points[0];
}
return null;
}
/// <summary>
/// Enables event callback.
/// This starts an internal thread that will fire the <see cref="Touched"/> and <see cref="Dragging"/> events
/// </summary>
public void EnableEvents()
{
if (_updateThread != null)
{
return;
}
_updateThreadActive = true;
_updateThread = new Thread(UpdateLoop);
_updateThread.Name = "Touch Controller";
_updateThread.Start();
}
private void UpdateLoop()
{
while (_updateThreadActive)
{
_updateEvent.WaitOne(_interval);
if (!_isPressed && _lastActiveTouches == 0)
{
continue;
}
ReadData();
lock (_lock)
{
if (_activeTouches == 0 && _lastActiveTouches == 1 && !_dragging)
{
// Should not do this within the lock, but call is synchronous anyway, so if it takes to long, we
// are blocked either way
Touched?.Invoke(this, _lastPoints[0]);
}
else if (_activeTouches == 0)
{
_dragging = false;
if (_lastActiveTouches == 1)
{
Dragging?.Invoke(this, new DragEventArgs(false, true, _lastPoints[0], _lastPoints[0]));
}
}
if (_activeTouches == 1 && _lastActiveTouches == 0)
{
_initialTouchPoint = _points[0];
}
if (_activeTouches == 1 && _lastActiveTouches == 1)
{
if (_dragging || Math.Abs(_initialTouchPoint.X - _points[0].X) > TouchSize.Width || Math.Abs(_initialTouchPoint.Y - _points[0].Y) > TouchSize.Height)
{
Dragging?.Invoke(this, new DragEventArgs(!_dragging, false, _lastPoints[0], _points[0]));
_dragging = true;
}
}
if (_activeTouches == 2 && _lastActiveTouches == 2)
{
int oldXDiff = _lastPoints[0].X - _lastPoints[1].X;
int oldYDiff = _lastPoints[0].Y - _lastPoints[1].Y;
int oldDiff = (int)Math.Sqrt(oldYDiff * oldYDiff + oldXDiff * oldXDiff);
int newXDiff = _points[0].X - _points[1].X;
int newYDiff = _points[0].Y - _points[1].Y;
int newDiff = (int)Math.Sqrt(newXDiff * newXDiff + newYDiff * newYDiff);
if (oldDiff != 0 || newDiff != 0)
{
Zooming?.Invoke(this, _points, oldDiff, newDiff);
}
}
_lastActiveTouches = _activeTouches;
_lastPoints[0] = _points[0];
_lastPoints[1] = _points[1];
}
}
}
/// <summary>
/// Dispose of this instance and close connections
/// </summary>
public virtual void Dispose(bool disposing)
{
if (disposing)
{
_updateThreadActive = false;
_updateThread?.Join();
_updateThread = null;
if (_interruptPin >= 0 && _gpioController != null)
{
_gpioController.UnregisterCallbackForPinValueChangedEvent(_interruptPin, OnInterrupt);
_gpioController.ClosePin(_interruptPin);
if (_shouldDispose)
{
_gpioController.Dispose();
}
}
_gpioController = null;
_i2c?.Dispose();
_i2c = null!;
_updateEvent?.Dispose();
_updateEvent = null!;
}
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
// 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.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Iot.Device.Ili934x
{
/// <summary>
/// Event arguments for dragging (moving the finger over the screen)
/// </summary>
public class DragEventArgs : EventArgs
{
/// <summary>
/// Constructs a new instance of <see cref="DragEventArgs"/>
/// </summary>
public DragEventArgs(bool isDragBegin, bool isDragEnd, Point lastPoint, Point currentPoint)
{
IsDragBegin = isDragBegin;
IsDragEnd = isDragEnd;
LastPoint = lastPoint;
CurrentPoint = currentPoint;
}
/// <summary>
/// True if the dragging is starting
/// </summary>
public bool IsDragBegin
{
get;
init;
}
/// <summary>
/// True if the user has stopped dragging (no longer touching the screen)
/// </summary>
public bool IsDragEnd
{
get;
init;
}
/// <summary>
/// The previous point
/// </summary>
public Point LastPoint
{
get;
init;
}
/// <summary>
/// The current point. When <see cref="IsDragEnd"/> is true, this is equal to the last point
/// </summary>
public Point CurrentPoint
{
get;
init;
}
}
}
......@@ -7,8 +7,9 @@ using System.Device.Spi;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;
using Iot.Device.Graphics;
namespace Iot.Device.Ili9341
namespace Iot.Device.Ili934x
{
/// <summary>
/// The ILI9341 is a QVGA (Quarter VGA) driver integrated circuit that is used to control 240×320 VGA LCD screens.
......@@ -25,11 +26,9 @@ namespace Iot.Device.Ili9341
/// </summary>
public const SpiMode DefaultSpiMode = SpiMode.Mode3;
private const int ScreenWidthPx = 240;
private const int ScreenHeightPx = 320;
private const int DefaultSPIBufferSize = 0x1000;
private const byte LcdPortraitConfig = 8 | 0x40;
private const byte LcdLandscapeConfig = 44;
internal const byte LcdPortraitConfig = 8 | 0x40;
internal const byte LcdLandscapeConfig = 44;
private readonly int _dcPinId;
private readonly int _resetPinId;
......@@ -40,17 +39,23 @@ namespace Iot.Device.Ili9341
private SpiDevice _spiDevice;
private GpioController _gpioDevice;
private Rgb565[] _screenBuffer;
private Rgb565[] _previousBuffer;
private double _fps;
private DateTimeOffset _lastUpdate;
/// <summary>
/// Initializes new instance of ILI9341 device that will communicate using SPI bus.
/// Initializes new instance of ILI9342 device that will communicate using SPI bus.
/// </summary>
/// <param name="spiDevice">The SPI device used for communication. This Spi device will be displayed along with the ILI9341 device.</param>
/// <param name="dataCommandPin">The id of the GPIO pin used to control the DC line (data/command).</param>
/// <param name="resetPin">The id of the GPIO pin used to control the /RESET line (data/command).</param>
/// <param name="dataCommandPin">The id of the GPIO pin used to control the DC line (data/command). This pin must be provided.</param>
/// <param name="resetPin">The id of the GPIO pin used to control the /RESET line (RST). Can be -1 if not connected</param>
/// <param name="backlightPin">The pin for turning the backlight on and off, or -1 if not connected.</param>
/// <param name="spiBufferSize">The size of the SPI buffer. If data larger than the buffer is sent then it is split up into multiple transmissions. The default value is 4096.</param>
/// <param name="gpioController">The GPIO controller used for communication and controls the the <paramref name="resetPin"/> and the <paramref name="dataCommandPin"/>
/// If no Gpio controller is passed in then a default one will be created and disposed when ILI9341 device is disposed.</param>
/// <param name="shouldDispose">True to dispose the Gpio Controller</param>
/// <param name="shouldDispose">True to dispose the Gpio Controller when done</param>
public Ili9341(SpiDevice spiDevice, int dataCommandPin, int resetPin, int backlightPin = -1, int spiBufferSize = DefaultSPIBufferSize, GpioController? gpioController = null, bool shouldDispose = true)
{
if (spiBufferSize <= 0)
......@@ -64,9 +69,14 @@ namespace Iot.Device.Ili9341
_backlightPin = backlightPin;
_gpioDevice = gpioController ?? new GpioController();
_shouldDispose = shouldDispose || gpioController is null;
_fps = 0;
_lastUpdate = DateTimeOffset.UtcNow;
_gpioDevice.OpenPin(_dcPinId, PinMode.Output);
_gpioDevice.OpenPin(_resetPinId, PinMode.Output);
if (_resetPinId >= 0)
{
_gpioDevice.OpenPin(_resetPinId, PinMode.Output);
}
_spiBufferSize = spiBufferSize;
......@@ -81,6 +91,43 @@ namespace Iot.Device.Ili9341
SendCommand(Ili9341Command.SoftwareReset);
SendCommand(Ili9341Command.DisplayOff);
Thread.Sleep(10);
InitDisplayParameters();
SendCommand(Ili9341Command.SleepOut);
Thread.Sleep(120);
SendCommand(Ili9341Command.DisplayOn);
Thread.Sleep(100);
SendCommand(Ili9341Command.MemoryWrite);
_screenBuffer = new Rgb565[ScreenWidth * ScreenHeight];
_previousBuffer = new Rgb565[ScreenWidth * ScreenHeight];
// And clear the display
SendFrame(true);
}
/// <summary>
/// Width of the screen, in pixels
/// </summary>
/// <remarks>This is of type int, because all image sizes use int, even though this can never be negative</remarks>
public virtual int ScreenWidth => 240;
/// <summary>
/// Height of the screen, in pixels
/// </summary>
/// <remarks>This is of type int, because all image sizes use int, even though this can never be negative</remarks>
public virtual int ScreenHeight => 320;
/// <summary>
/// Returns the last FPS value (frames per second).
/// The value is unfiltered.
/// </summary>
public double Fps => _fps;
/// <summary>
/// Configure memory and orientation parameters
/// </summary>
protected virtual void InitDisplayParameters()
{
SendCommand(Ili9341Command.MemoryAccessControl, LcdPortraitConfig);
SendCommand(Ili9341Command.ColModPixelFormatSet, 0x55); // 16-bits per pixel
SendCommand(Ili9341Command.FrameRateControlInNormalMode, 0x00, 0x1B);
......@@ -89,83 +136,70 @@ namespace Iot.Device.Ili9341
SendCommand(Ili9341Command.PageAddressSet, 0x00, 0x00, 0x01, 0x3F); // height of the screen
SendCommand(Ili9341Command.EntryModeSet, 0x07);
SendCommand(Ili9341Command.DisplayFunctionControl, 0x0A, 0x82, 0x27, 0x00);
SendCommand(Ili9341Command.SleepOut);
Thread.Sleep(120);
SendCommand(Ili9341Command.DisplayOn);
Thread.Sleep(100);
SendCommand(Ili9341Command.MemoryWrite);
}
/// <summary>
/// Convert a color structure to a byte tuple representing the colour in 565 format.
/// Fill rectangle to the specified color
/// </summary>
/// <param name="color">The color to be converted.</param>
/// <returns>
/// This method returns the low byte and the high byte of the 16bit value representing RGB565 or BGR565 value
///
/// byte 11111111 00000000
/// bit 76543210 76543210
///
/// For ColorSequence.RGB
/// RRRRRGGG GGGBBBBB
/// 43210543 21043210
///
/// For ColorSequence.BGR
/// BBBBBGGG GGGRRRRR
/// 43210543 21043210
/// </returns>
private (byte Low, byte High) Color565(Color color)
/// <param name="color">The color to fill the rectangle with.</param>
/// <param name="x">The x co-ordinate of the point to start the rectangle at in pixels.</param>
/// <param name="y">The y co-ordinate of the point to start the rectangle at in pixels.</param>
/// <param name="w">The width of the rectangle in pixels.</param>
/// <param name="h">The height of the rectangle in pixels.</param>
public void FillRect(Color color, int x, int y, int w, int h)
{
// get the top 5 MSB of the blue or red value
UInt16 retval = (UInt16)(color.R >> 3);
// shift right to make room for the green Value
retval <<= 6;
// combine with the 6 MSB if the green value
retval |= (UInt16)(color.G >> 2);
// shift right to make room for the red or blue Value
retval <<= 5;
// combine with the 6 MSB if the red or blue value
retval |= (UInt16)(color.B >> 3);
return ((byte)(retval >> 8), (byte)(retval & 0xFF));
FillRect(color, x, y, w, h, false);
}
/// <summary>
/// Send filled rectangle to the ILI9341 display.
/// Fill rectangle to the specified color
/// </summary>
/// <param name="color">The color to fill the rectangle with.</param>
/// <param name="x">The x co-ordinate of the point to start the rectangle at in pixels.</param>
/// <param name="y">The y co-ordinate of the point to start the rectangle at in pixels.</param>
/// <param name="w">The width of the rectangle in pixels.</param>
/// <param name="h">The height of the rectangle in pixels.</param>
public void FillRect(Color color, uint x, uint y, uint w, uint h)
/// <param name="doRefresh">True to immediately update the screen, false to only update the back buffer</param>
private void FillRect(Color color, int x, int y, int w, int h, bool doRefresh)
{
Span<byte> colourBytes = stackalloc byte[2]; // create a short span that holds the colour data to be sent to the display
Span<byte> displayBytes = stackalloc byte[(int)(w * h * 2)]; // span used to form the data to be written out to the SPI interface
// set the colourbyte array to represent the fill colour
(colourBytes[0], colourBytes[1]) = Color565(color);
var c = Rgb565.FromRgba32(color);
// set the pixels in the array representing the raw data to be sent to the display
// to the fill color
for (int i = 0; i < w * h; i++)
for (int j = y; j < y + h; j++)
{
displayBytes[i * 2 + 0] = colourBytes[0];
displayBytes[i * 2 + 1] = colourBytes[1];
for (int i = x; i < x + w; i++)
{
_screenBuffer[i + j * ScreenWidth] = c;
}
}
// specify a location for the rows and columns on the display where the data is to be written
SetWindow(x, y, x + w - 1, y + h - 1);
if (doRefresh)
{
SendFrame();
}
}
// write out the pixel data
SendData(displayBytes);
/// <summary>
/// Clears the screen to a specific color
/// </summary>
/// <param name="color">The color to clear the screen to</param>
/// <param name="doRefresh">Immediately force an update of the screen. If false, only the backbuffer is cleared.</param>
public void ClearScreen(Color color, bool doRefresh = false)
{
FillRect(color, 0, 0, ScreenWidth, ScreenHeight, doRefresh);
}
/// <summary>
/// Clears screen
/// Clears the screen to black
/// </summary>
public void ClearScreen()
/// <param name="doRefresh">Immediately force an update of the screen. If false, only the backbuffer is cleared.</param>
public void ClearScreen(bool doRefresh = false)
{
FillRect(Color.Black, 0, 0, ScreenWidthPx, ScreenHeightPx);
FillRect(Color.FromArgb(0, 0, 0), 0, 0, ScreenWidth, ScreenHeight, doRefresh);
}
/// <summary>
......@@ -173,6 +207,11 @@ namespace Iot.Device.Ili9341
/// </summary>
public async Task ResetDisplayAsync()
{
if (_resetPinId < 0)
{
return;
}
_gpioDevice.Write(_resetPinId, PinValue.High);
await Task.Delay(20).ConfigureAwait(false);
_gpioDevice.Write(_resetPinId, PinValue.Low);
......@@ -207,7 +246,7 @@ namespace Iot.Device.Ili9341
_gpioDevice.Write(_backlightPin, PinValue.Low);
}
private void SetWindow(uint x0 = 0, uint y0 = 0, uint x1 = ScreenWidthPx - 1, uint y1 = ScreenWidthPx - 1)
private void SetWindow(int x0, int y0, int x1, int y1)
{
SendCommand(Ili9341Command.ColumnAddressSet);
Span<byte> data = stackalloc byte[4]
......@@ -235,7 +274,7 @@ namespace Iot.Device.Ili9341
/// </summary>
/// <param name="command">Command to send.</param>
/// <param name="commandParameters">parameteters for the command to be sent</param>
private void SendCommand(Ili9341Command command, params byte[] commandParameters)
internal void SendCommand(Ili9341Command command, params byte[] commandParameters)
{
SendCommand(command, commandParameters.AsSpan());
}
......@@ -245,7 +284,7 @@ namespace Iot.Device.Ili9341
/// </summary>
/// <param name="command">Command to send.</param>
/// <param name="data">Span to send as parameters for the command.</param>
private void SendCommand(Ili9341Command command, Span<byte> data)
internal void SendCommand(Ili9341Command command, Span<byte> data)
{
Span<byte> commandSpan = stackalloc byte[]
{
......@@ -295,17 +334,55 @@ namespace Iot.Device.Ili9341
while (index < data.Length); // repeat until all data sent.
}
/// <inheritdoc/>
public void Dispose()
/// <summary>
/// Creates an image with the correct size and color depth to be sent to the screen
/// </summary>
/// <returns>An image instance</returns>
public virtual BitmapImage CreateBackBuffer()
{
if (_shouldDispose)
return BitmapImage.CreateBitmap(ScreenWidth, ScreenHeight, PixelFormat.Format32bppArgb);
}
/// <inheritdoc cref="Dispose()"/>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_gpioDevice?.Dispose();
_gpioDevice = null!;
if (_gpioDevice != null)
{
if (_resetPinId >= 0)
{
_gpioDevice.ClosePin(_resetPinId);
}
if (_backlightPin >= 0)
{
_gpioDevice.ClosePin(_backlightPin);
}
if (_dcPinId >= 0)
{
_gpioDevice.ClosePin(_dcPinId);
}
if (_shouldDispose)
{
_gpioDevice?.Dispose();
}
_gpioDevice = null!;
}
_spiDevice?.Dispose();
_spiDevice = null!;
}
}
_spiDevice?.Dispose();
_spiDevice = null!;
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
......@@ -3,21 +3,34 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Iot.Device.Graphics;
#pragma warning disable CA1416 // Temporarily, will be removed after binding is updated
namespace Iot.Device.Ili9341
namespace Iot.Device.Ili934x
{
public partial class Ili9341
{
/// <summary>
/// Send a bitmap to the Ili9341 display specifying the starting position and destination clipping rectangle.
/// Send a bitmap to the Ili9341 display.
/// </summary>
/// <param name="bm">The bitmap to be sent to the display controller note that only Pixel Format Format32bppArgb is supported.</param>
public void SendBitmap(Bitmap bm)
/// <param name="bm">The bitmap to be sent to the display controller.</param>
public void DrawBitmap(BitmapImage bm)
{
SendBitmap(bm, new Point(0, 0), new Rectangle(0, 0, ScreenWidthPx, ScreenHeightPx));
int width = (int)ScreenWidth;
if (width > bm.Width)
{
width = bm.Width;
}
int height = (int)ScreenHeight;
if (height > bm.Height)
{
height = bm.Height;
}
DrawBitmap(bm, new Point(0, 0), new Rectangle(0, 0, width, height));
}
/// <summary>
......@@ -25,9 +38,9 @@ namespace Iot.Device.Ili9341
/// </summary>
/// <param name="bm">The bitmap to be sent to the display controller note that only Pixel Format Format32bppArgb is supported.</param>
/// <param name="updateRect">A rectangle that defines where in the display the bitmap is written. Note that no scaling is done.</param>
public void SendBitmap(Bitmap bm, Rectangle updateRect)
public void DrawBitmap(BitmapImage bm, Rectangle updateRect)
{
SendBitmap(bm, new Point(updateRect.X, updateRect.Y), updateRect);
DrawBitmap(bm, new Point(updateRect.X, updateRect.Y), updateRect);
}
/// <summary>
......@@ -36,62 +49,106 @@ namespace Iot.Device.Ili9341
/// <param name="bm">The bitmap to be sent to the display controller note that only Pixel Format Format32bppArgb is supported.</param>
/// <param name="sourcePoint">A coordinate point in the source bitmap where copying starts from.</param>
/// <param name="destinationRect">A rectangle that defines where in the display the bitmap is written. Note that no scaling is done.</param>
public void SendBitmap(Bitmap bm, Point sourcePoint, Rectangle destinationRect)
public void DrawBitmap(BitmapImage bm, Point sourcePoint, Rectangle destinationRect)
{
if (bm is null)
{
throw new ArgumentNullException(nameof(bm));
}
if (bm.PixelFormat != PixelFormat.Format32bppArgb)
FillBackBufferFromImage(bm, sourcePoint, destinationRect);
}
private void FillBackBufferFromImage(BitmapImage image, Point sourcePoint, Rectangle destinationRect)
{
if (image is null)
{
throw new ArgumentException($"Pixel format {bm.PixelFormat.ToString()} not supported.", nameof(bm));
throw new ArgumentNullException(nameof(image));
}
// get the pixel data and send it to the display
SendBitmapPixelData(GetBitmapPixelData(bm, new Rectangle(sourcePoint.X, sourcePoint.Y, destinationRect.Width, destinationRect.Height)), destinationRect);
Converters.AdjustImageDestination(image, ref sourcePoint, ref destinationRect);
Parallel.For(destinationRect.Y, destinationRect.Height + destinationRect.Y, y =>
{
var row = _screenBuffer.AsSpan(y * ScreenWidth, ScreenWidth);
for (int i = destinationRect.X; i < destinationRect.Width + destinationRect.X; i++)
{
int xSource = sourcePoint.X + i - destinationRect.X;
int ySource = sourcePoint.Y + y - destinationRect.Y;
row[i] = Rgb565.FromRgba32(image[xSource, ySource]);
}
});
}
/// <summary>
/// Convert a bitmap into an array of pixel data suitable for sending to the display
/// Updates the display with the current screen buffer.
/// <param name="forceFull">Forces a full update, otherwise only changed screen contents are updated</param>
/// </summary>
/// <param name="bm">The bitmap to be sent to the display controller note that only Pixel Format Format32bppArgb is supported.</param>
/// <param name="sourceRect">A rectangle that defines where in the bitmap data is to be converted from.</param>
public Span<byte> GetBitmapPixelData(Bitmap bm, Rectangle sourceRect)
public void SendFrame(bool forceFull = false)
{
BitmapData bmd;
byte[] bitmapData; // array that takes the raw bytes of the bitmap
byte[] outputBuffer; // array used to form the data to be written out to the SPI interface
if (bm is null)
if (forceFull)
{
throw new ArgumentNullException(nameof(bm));
SetWindow(0, 0, ScreenWidth, ScreenHeight);
SendSPI(MemoryMarshal.Cast<Rgb565, byte>(_screenBuffer));
}
if (bm.PixelFormat != PixelFormat.Format32bppArgb)
else
{
throw new ArgumentException($"Pixel format {bm.PixelFormat.ToString()} not supported.", nameof(bm));
int topRow = 0;
int bottomRow = ScreenHeight;
int w = ScreenWidth;
for (int y = 0; y < ScreenHeight; y++)
{
for (int x = 0; x < w; x++)
{
if (!Rgb565.AlmostEqual(_screenBuffer[x + y * w], _previousBuffer[x + y * w], 2))
{
topRow = y;
goto reverse;
}
}
}
// if we get here, there were no screen changes
UpdateFps();
return;
reverse:
for (int y = ScreenHeight - 1; y >= topRow; y--)
{
for (int x = 0; x < w; x++)
{
if (!Rgb565.AlmostEqual(_screenBuffer[x + y * w], _previousBuffer[x + y * w], 2))
{
bottomRow = y;
goto end;
}
}
}
end:
SetWindow(0, topRow, w, bottomRow);
// Send the given number of rows (+1, because including the end row)
var partialSpan = MemoryMarshal.Cast<Rgb565, byte>(_screenBuffer.AsSpan().Slice(topRow * w, (bottomRow - topRow + 1) * w));
SendSPI(partialSpan);
}
// allocate the working arrays.
bitmapData = new byte[sourceRect.Width * sourceRect.Height * 4];
outputBuffer = new byte[sourceRect.Width * sourceRect.Height * 2];
// get the raw pixel data for the bitmap
bmd = bm.LockBits(sourceRect, ImageLockMode.ReadOnly, bm.PixelFormat);
Marshal.Copy(bmd.Scan0, bitmapData, 0, bitmapData.Length);
bm.UnlockBits(bmd);
_screenBuffer.CopyTo(_previousBuffer.AsSpan());
UpdateFps();
}
// iterate over the source bitmap converting each pixle in the raw data
// to a format suitablle for sending to the display
for (int i = 0; i < bitmapData.Length; i += 4)
private void UpdateFps()
{
DateTimeOffset now = DateTimeOffset.UtcNow;
TimeSpan ts = now - _lastUpdate;
if (ts <= TimeSpan.FromMilliseconds(1))
{
(outputBuffer[i / 4 * 2 + 0], outputBuffer[i / 4 * 2 + 1]) = Color565(Color.FromArgb(bitmapData[i + 2], bitmapData[i + 1], bitmapData[i + 0]));
ts = TimeSpan.FromMilliseconds(1);
}
return (outputBuffer);
_fps = 1.0 / ts.TotalSeconds;
_lastUpdate = now;
}
/// <summary>
......@@ -99,10 +156,12 @@ namespace Iot.Device.Ili9341
/// </summary>
/// <param name="pixelData">The data to be sent to the display.</param>
/// <param name="destinationRect">A rectangle that defines where in the display the data is to be written.</param>
/// <remarks>This directly sends the data, circumventing the screen buffer</remarks>
public void SendBitmapPixelData(Span<byte> pixelData, Rectangle destinationRect)
{
SetWindow((uint)destinationRect.X, (uint)destinationRect.Y, (uint)(destinationRect.Right - 1), (uint)(destinationRect.Bottom - 1)); // specifiy a location for the rows and columns on the display where the data is to be written
SetWindow(destinationRect.X, destinationRect.Y, (destinationRect.Right - 1), (destinationRect.Bottom - 1)); // specifiy a location for the rows and columns on the display where the data is to be written
SendData(pixelData);
UpdateFps();
}
}
}
// 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.Text;
using System.Threading.Tasks;
namespace Iot.Device.Ili934x
{
/// <summary>
/// Ili9342 QVGA display
/// </summary>
public class Ili9342 : Ili9341
{
/// <summary>
/// Initializes new instance of ILI9342 device that will communicate using SPI bus.
/// </summary>
/// <param name="spiDevice">The SPI device used for communication. This Spi device will be displayed along with the ILI9341 device.</param>
/// <param name="dataCommandPin">The id of the GPIO pin used to control the DC line (data/command). This pin must be provided.</param>
/// <param name="resetPin">The id of the GPIO pin used to control the /RESET line (RST). Can be -1 if not connected</param>
/// <param name="backlightPin">The pin for turning the backlight on and off, or -1 if not connected.</param>
/// <param name="spiBufferSize">The size of the SPI buffer. If data larger than the buffer is sent then it is split up into multiple transmissions. The default value is 4096.</param>
/// <param name="gpioController">The GPIO controller used for communication and controls the the <paramref name="resetPin"/> and the <paramref name="dataCommandPin"/>
/// If no Gpio controller is passed in then a default one will be created and disposed when ILI9341 device is disposed.</param>
/// <param name="shouldDispose">True to dispose the Gpio Controller when done</param>
public Ili9342(SpiDevice spiDevice, int dataCommandPin, int resetPin, int backlightPin = -1, int spiBufferSize = 4096, GpioController? gpioController = null, bool shouldDispose = true)
: base(spiDevice, dataCommandPin, resetPin, backlightPin, spiBufferSize, gpioController, shouldDispose)
{
}
/// <inheritdoc />
public override int ScreenHeight => 240;
/// <inheritdoc />
public override int ScreenWidth => 320;
/// <summary>
/// Configure the Ili9342 (it uses a different color format than the 9341 and by default is used in landscape mode)
/// </summary>
protected override void InitDisplayParameters()
{
SendCommand(Ili9341Command.MemoryAccessControl, 0b1100); // Landscape orientation, inverted color order
SendCommand(Ili9341Command.ColModPixelFormatSet, 0x55); // 16-bits per pixel
SendCommand(Ili9341Command.FrameRateControlInNormalMode, 0x00, 0x1B);
SendCommand(Ili9341Command.GammaSet, 0x01);
SendCommand(Ili9341Command.ColumnAddressSet, 0x00, 0x00, 0x01, 0x3F); // 319 width of the screen
SendCommand(Ili9341Command.PageAddressSet, 0x00, 0x00, 0x00, 0xEF); // 239 height of the screen
SendCommand(Ili9341Command.EntryModeSet, 0x07);
SendCommand(Ili9341Command.DisplayFunctionControl, 0x0A, 0x82, 0x27, 0x00);
SendCommand(Ili9341Command.DisplayInversionOn); // When enabling display inversion, the colors work the same as for the ILI9341
}
}
}
......@@ -10,7 +10,12 @@
<ItemGroup>
<Compile Include="*.cs" />
<None Include="README.md" />
<PackageReference Include="System.Drawing.Common" Version="$(SystemDrawingCommonPackageVersion)" />
</ItemGroup>
<!-- Make the internal classes visible to the unit test assembly -->
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>$(AssemblyName).Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.33326.253
VisualStudioVersion = 17.0.32002.185
MinimumVisualStudioVersion = 15.0.26124.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{0D478BAB-AFEA-4AF1-866C-E3AC32E11C5A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ili9341.Samples", "samples\Ili9341.Samples.csproj", "{D9FEFE08-18E7-458A-8ECC-73A8D1E078F6}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ili934x.Samples", "samples\Ili934x.Samples.csproj", "{D9FEFE08-18E7-458A-8ECC-73A8D1E078F6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ili9341", "Ili9341.csproj", "{1398DC14-97F0-4048-965A-CCB3D44BFE06}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ili934x", "Ili934x.csproj", "{1398DC14-97F0-4048-965A-CCB3D44BFE06}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "..\Common\Common.csproj", "{4E04A275-4671-4262-B463-1DDD8E795E79}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arduino", "..\Arduino\Arduino.csproj", "{A8805363-31EC-45A3-B3C4-C21AE8F6445B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "..\Common\Common.csproj", "{B63EB5C0-F1AD-4348-BFEF-F38F178D33D7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharpAdapter", "..\SkiaSharpAdapter\SkiaSharpAdapter.csproj", "{DD919A7C-32DD-4D0B-B7B4-61285E188672}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{22FB5850-0D20-4703-80BA-5C902119F31E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ili934x.Tests", "tests\Ili934x.Tests.csproj", "{212B3113-99B9-4A53-8210-8C3512379A5F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
......@@ -45,23 +53,87 @@ Global
{1398DC14-97F0-4048-965A-CCB3D44BFE06}.Release|x64.Build.0 = Release|Any CPU
{1398DC14-97F0-4048-965A-CCB3D44BFE06}.Release|x86.ActiveCfg = Release|Any CPU
{1398DC14-97F0-4048-965A-CCB3D44BFE06}.Release|x86.Build.0 = Release|Any CPU
{4E04A275-4671-4262-B463-1DDD8E795E79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4E04A275-4671-4262-B463-1DDD8E795E79}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4E04A275-4671-4262-B463-1DDD8E795E79}.Debug|x64.ActiveCfg = Debug|Any CPU
{4E04A275-4671-4262-B463-1DDD8E795E79}.Debug|x64.Build.0 = Debug|Any CPU
{4E04A275-4671-4262-B463-1DDD8E795E79}.Debug|x86.ActiveCfg = Debug|Any CPU
{4E04A275-4671-4262-B463-1DDD8E795E79}.Debug|x86.Build.0 = Debug|Any CPU
{4E04A275-4671-4262-B463-1DDD8E795E79}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4E04A275-4671-4262-B463-1DDD8E795E79}.Release|Any CPU.Build.0 = Release|Any CPU
{4E04A275-4671-4262-B463-1DDD8E795E79}.Release|x64.ActiveCfg = Release|Any CPU
{4E04A275-4671-4262-B463-1DDD8E795E79}.Release|x64.Build.0 = Release|Any CPU
{4E04A275-4671-4262-B463-1DDD8E795E79}.Release|x86.ActiveCfg = Release|Any CPU
{4E04A275-4671-4262-B463-1DDD8E795E79}.Release|x86.Build.0 = Release|Any CPU
{A8805363-31EC-45A3-B3C4-C21AE8F6445B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A8805363-31EC-45A3-B3C4-C21AE8F6445B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A8805363-31EC-45A3-B3C4-C21AE8F6445B}.Debug|x64.ActiveCfg = Debug|Any CPU
{A8805363-31EC-45A3-B3C4-C21AE8F6445B}.Debug|x64.Build.0 = Debug|Any CPU
{A8805363-31EC-45A3-B3C4-C21AE8F6445B}.Debug|x86.ActiveCfg = Debug|Any CPU
{A8805363-31EC-45A3-B3C4-C21AE8F6445B}.Debug|x86.Build.0 = Debug|Any CPU
{A8805363-31EC-45A3-B3C4-C21AE8F6445B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A8805363-31EC-45A3-B3C4-C21AE8F6445B}.Release|Any CPU.Build.0 = Release|Any CPU
{A8805363-31EC-45A3-B3C4-C21AE8F6445B}.Release|x64.ActiveCfg = Release|Any CPU
{A8805363-31EC-45A3-B3C4-C21AE8F6445B}.Release|x64.Build.0 = Release|Any CPU
{A8805363-31EC-45A3-B3C4-C21AE8F6445B}.Release|x86.ActiveCfg = Release|Any CPU
{A8805363-31EC-45A3-B3C4-C21AE8F6445B}.Release|x86.Build.0 = Release|Any CPU
{B63EB5C0-F1AD-4348-BFEF-F38F178D33D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B63EB5C0-F1AD-4348-BFEF-F38F178D33D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B63EB5C0-F1AD-4348-BFEF-F38F178D33D7}.Debug|x64.ActiveCfg = Debug|Any CPU
{B63EB5C0-F1AD-4348-BFEF-F38F178D33D7}.Debug|x64.Build.0 = Debug|Any CPU
{B63EB5C0-F1AD-4348-BFEF-F38F178D33D7}.Debug|x86.ActiveCfg = Debug|Any CPU
{B63EB5C0-F1AD-4348-BFEF-F38F178D33D7}.Debug|x86.Build.0 = Debug|Any CPU
{B63EB5C0-F1AD-4348-BFEF-F38F178D33D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B63EB5C0-F1AD-4348-BFEF-F38F178D33D7}.Release|Any CPU.Build.0 = Release|Any CPU
{B63EB5C0-F1AD-4348-BFEF-F38F178D33D7}.Release|x64.ActiveCfg = Release|Any CPU
{B63EB5C0-F1AD-4348-BFEF-F38F178D33D7}.Release|x64.Build.0 = Release|Any CPU
{B63EB5C0-F1AD-4348-BFEF-F38F178D33D7}.Release|x86.ActiveCfg = Release|Any CPU
{B63EB5C0-F1AD-4348-BFEF-F38F178D33D7}.Release|x86.Build.0 = Release|Any CPU
{079BECB2-C2F8-4DC0-B9A2-111EA143A3C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{079BECB2-C2F8-4DC0-B9A2-111EA143A3C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{079BECB2-C2F8-4DC0-B9A2-111EA143A3C5}.Debug|x64.ActiveCfg = Debug|Any CPU
{079BECB2-C2F8-4DC0-B9A2-111EA143A3C5}.Debug|x64.Build.0 = Debug|Any CPU
{079BECB2-C2F8-4DC0-B9A2-111EA143A3C5}.Debug|x86.ActiveCfg = Debug|Any CPU
{079BECB2-C2F8-4DC0-B9A2-111EA143A3C5}.Debug|x86.Build.0 = Debug|Any CPU
{079BECB2-C2F8-4DC0-B9A2-111EA143A3C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{079BECB2-C2F8-4DC0-B9A2-111EA143A3C5}.Release|Any CPU.Build.0 = Release|Any CPU
{079BECB2-C2F8-4DC0-B9A2-111EA143A3C5}.Release|x64.ActiveCfg = Release|Any CPU
{079BECB2-C2F8-4DC0-B9A2-111EA143A3C5}.Release|x64.Build.0 = Release|Any CPU
{079BECB2-C2F8-4DC0-B9A2-111EA143A3C5}.Release|x86.ActiveCfg = Release|Any CPU
{079BECB2-C2F8-4DC0-B9A2-111EA143A3C5}.Release|x86.Build.0 = Release|Any CPU
{5277BF07-40B8-4E65-A777-20ECB2A420CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5277BF07-40B8-4E65-A777-20ECB2A420CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5277BF07-40B8-4E65-A777-20ECB2A420CF}.Debug|x64.ActiveCfg = Debug|Any CPU
{5277BF07-40B8-4E65-A777-20ECB2A420CF}.Debug|x64.Build.0 = Debug|Any CPU
{5277BF07-40B8-4E65-A777-20ECB2A420CF}.Debug|x86.ActiveCfg = Debug|Any CPU
{5277BF07-40B8-4E65-A777-20ECB2A420CF}.Debug|x86.Build.0 = Debug|Any CPU
{5277BF07-40B8-4E65-A777-20ECB2A420CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5277BF07-40B8-4E65-A777-20ECB2A420CF}.Release|Any CPU.Build.0 = Release|Any CPU
{5277BF07-40B8-4E65-A777-20ECB2A420CF}.Release|x64.ActiveCfg = Release|Any CPU
{5277BF07-40B8-4E65-A777-20ECB2A420CF}.Release|x64.Build.0 = Release|Any CPU
{5277BF07-40B8-4E65-A777-20ECB2A420CF}.Release|x86.ActiveCfg = Release|Any CPU
{5277BF07-40B8-4E65-A777-20ECB2A420CF}.Release|x86.Build.0 = Release|Any CPU
{DD919A7C-32DD-4D0B-B7B4-61285E188672}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DD919A7C-32DD-4D0B-B7B4-61285E188672}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD919A7C-32DD-4D0B-B7B4-61285E188672}.Debug|x64.ActiveCfg = Debug|Any CPU
{DD919A7C-32DD-4D0B-B7B4-61285E188672}.Debug|x64.Build.0 = Debug|Any CPU
{DD919A7C-32DD-4D0B-B7B4-61285E188672}.Debug|x86.ActiveCfg = Debug|Any CPU
{DD919A7C-32DD-4D0B-B7B4-61285E188672}.Debug|x86.Build.0 = Debug|Any CPU
{DD919A7C-32DD-4D0B-B7B4-61285E188672}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD919A7C-32DD-4D0B-B7B4-61285E188672}.Release|Any CPU.Build.0 = Release|Any CPU
{DD919A7C-32DD-4D0B-B7B4-61285E188672}.Release|x64.ActiveCfg = Release|Any CPU
{DD919A7C-32DD-4D0B-B7B4-61285E188672}.Release|x64.Build.0 = Release|Any CPU
{DD919A7C-32DD-4D0B-B7B4-61285E188672}.Release|x86.ActiveCfg = Release|Any CPU
{DD919A7C-32DD-4D0B-B7B4-61285E188672}.Release|x86.Build.0 = Release|Any CPU
{212B3113-99B9-4A53-8210-8C3512379A5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{212B3113-99B9-4A53-8210-8C3512379A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{212B3113-99B9-4A53-8210-8C3512379A5F}.Debug|x64.ActiveCfg = Debug|Any CPU
{212B3113-99B9-4A53-8210-8C3512379A5F}.Debug|x64.Build.0 = Debug|Any CPU
{212B3113-99B9-4A53-8210-8C3512379A5F}.Debug|x86.ActiveCfg = Debug|Any CPU
{212B3113-99B9-4A53-8210-8C3512379A5F}.Debug|x86.Build.0 = Debug|Any CPU
{212B3113-99B9-4A53-8210-8C3512379A5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{212B3113-99B9-4A53-8210-8C3512379A5F}.Release|Any CPU.Build.0 = Release|Any CPU
{212B3113-99B9-4A53-8210-8C3512379A5F}.Release|x64.ActiveCfg = Release|Any CPU
{212B3113-99B9-4A53-8210-8C3512379A5F}.Release|x64.Build.0 = Release|Any CPU
{212B3113-99B9-4A53-8210-8C3512379A5F}.Release|x86.ActiveCfg = Release|Any CPU
{212B3113-99B9-4A53-8210-8C3512379A5F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{D9FEFE08-18E7-458A-8ECC-73A8D1E078F6} = {0D478BAB-AFEA-4AF1-866C-E3AC32E11C5A}
{212B3113-99B9-4A53-8210-8C3512379A5F} = {22FB5850-0D20-4703-80BA-5C902119F31E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {691606E4-D339-49E7-9116-DD89DD6B8480}
EndGlobalSection
EndGlobal
......@@ -4,7 +4,7 @@
using System;
using System.Device.Gpio;
namespace Iot.Device.Ili9341
namespace Iot.Device.Ili934x
{
internal enum Ili9341Command : byte
{
......
# Ili9341 TFT LCD Controller
# Ili934x TFT LCD Controller
The ILI9341 is a QVGA (Quarter VGA) driver integrated circuit that is used to control 240×320 VGA LCD screens
This binding supports the ILI9341 and ILI9342 QVGA (Quarter VGA) driver integrated circuits that is used to control 240×320 VGA LCD screens.
The main difference between the two chips is that the ILI9341 is typically used with a screen in portrait mode, while the ILI9342 is used with
screens in landscape mode.
## Documentation
......@@ -9,11 +11,13 @@ The ILI9341 is a QVGA (Quarter VGA) driver integrated circuit that is used to co
## Device Family
- ILI9341 [datasheet](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf)
- ILI9342 [datasheet](https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/core/ILI9342C-ILITEK.pdf)
### Related Devices
- [2.4" TFT LCD with Touchscreen Breakout w/MicroSD Socket - ILI9341](https://www.adafruit.com/product/2478#technical-details)
- [2.2" TFT Display](https://learn.adafruit.com/2-2-tft-display)
- [M5 Though](https://docs.m5stack.com/en/core/tough) + various other devices from M5Stack
## Usage
......@@ -27,29 +31,50 @@ using Iot.Device.Ili9341;
const int pinID_DC = 25;
const int pinID_Reset = 24;
const int interruptPin = 39;
using Bitmap dotnetBM = new(240, 320);
using Graphics g = Graphics.FromImage(dotnetBM);
Chsc6440 touch = null;
using SpiDevice displaySPI = SpiDevice.Create(new SpiConnectionSettings(0, 0) { Mode = SpiMode.Mode3, DataBitLength = 8, ClockFrequency = 12_000_000 /* 12MHz */ });
using Ili9341 ili9341 = new(displaySPI, pinID_DC, pinID_Reset);
using var image = BitmapImage.CreateFromFile(@"images/Landscape.png");
using var backBuffer = _screen.CreateBackBuffer();
// if a touch controller is attached:
{
touch = new Chsc6440(I2cDevice.Create(new I2cConnectionSettings(0, Chsc6440.DefaultI2cAddress)), new Size(display.ScreenWidth, display.ScreenHeight), interruptPin);
touch.UpdateInterval = TimeSpan.FromMilliseconds(100);
touch.EnableEvents();
}
while (true)
{
foreach (string filepath in Directory.GetFiles(@"images", "*.png").OrderBy(f => f))
float factor = i / 10.0f;
if (Console.KeyAvailable || (_touch != null && _touch.IsPressed()))
{
using Bitmap bm = (Bitmap)Bitmap.FromFile(filepath);
g.Clear(Color.Black);
g.DrawImageUnscaled(bm, 0, 0);
ili9341.SendBitmap(dotnetBM);
Task.Delay(1000).Wait();
break;
}
IGraphics api = backBuffer.GetDrawingApi();
Rectangle newRect = Rectangle.Empty;
newRect.Width = (int)(image.Width * factor);
newRect.Height = (int)(image.Height * factor);
newRect.X = (backBuffer.Width / 2) - (newRect.Width / 2);
newRect.Y = (backBuffer.Height / 2) - (newRect.Height / 2);
api.DrawImage(image, new Rectangle(0, 0, image.Width, image.Height), newRect);
ili9341.DrawBitmap(backBuffer);
ili9341.SendFrame();
Thread.Sleep(100);
}
```
## Binding Notes
This binding currently only supports commands and raw data. Eventually, the plan is to create a graphics library that can send text and images to the device.
This binding currently only supports commands and raw data. Drawing needs to be done to a backbuffer, which then can be
forwarded to the device. See example above.
The following connection types are supported by this binding.
- [X] SPI
- [X] I2C (for the touch controller)
// 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.Drawing;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
namespace Iot.Device.Ili934x
{
/// <summary>
/// This is the image format used by the Ili934X internally. It's similar to the (meanwhile otherwise rather obsolete) 16-bit RGB format with
/// 5 bits for red, 6 bits for green and 5 bits for blue.
/// </summary>
internal struct Rgb565 : IEquatable<Rgb565>
{
private ushort _value;
public Rgb565(ushort packedValue)
{
_value = packedValue;
}
public Rgb565(ushort r, ushort g, ushort b)
{
_value = 0;
InitFrom(r, g, b);
}
public int R
{
get
{
// Ensure full black is a possible result
int lowByte = _value & 0xF8;
if (lowByte == 0)
{
return 0;
}
return lowByte | 0x7;
}
}
public int G
{
get
{
int gbyte = ((Swap(_value) & 0x7E0) >> 3);
if (gbyte == 0)
{
return 0;
}
return gbyte | 0x3;
}
}
public int B
{
get
{
int bbyte = (_value >> 5) & 0xF8;
if (bbyte == 0)
{
return 0;
}
return bbyte | 0x7;
}
}
private void InitFrom(ushort r, ushort g, ushort b)
{
// get the top 5 MSB of the blue or red value
UInt16 retval = (UInt16)(r >> 3);
// shift right to make room for the green Value
retval <<= 6;
// combine with the 6 MSB if the green value
retval |= (UInt16)(g >> 2);
// shift right to make room for the red or blue Value
retval <<= 5;
// combine with the 6 MSB if the red or blue value
retval |= (UInt16)(b >> 3);
_value = Swap(retval);
}
/// <summary>
/// Convert a color structure to a byte tuple representing the colour in 565 format.
/// </summary>
/// <param name="color">The color to be converted.</param>
/// <returns>
/// This method returns the low byte and the high byte of the 16bit value representing RGB565 or BGR565 value
///
/// byte 11111111 00000000
/// bit 76543210 76543210
///
/// For ColorSequence.RGB (inversed!, the LSB is the top byte)
/// GGGBBBBB RRRRRGGG
/// 43210543 21043210
/// </returns>
public static Rgb565 FromRgba32(Color color)
{
// get the top 5 MSB of the blue or red value
UInt16 retval = (UInt16)(color.R >> 3);
// shift right to make room for the green Value
retval <<= 6;
// combine with the 6 MSB if the green value
retval |= (UInt16)(color.G >> 2);
// shift right to make room for the red or blue Value
retval <<= 5;
// combine with the 6 MSB if the red or blue value
retval |= (UInt16)(color.B >> 3);
return new Rgb565(Swap(retval));
}
private static ushort Swap(ushort val) => (ushort)((val >> 8) | (val << 8));
public ushort PackedValue
{
get
{
return _value;
}
set
{
_value = value;
}
}
public bool Equals(Rgb565 other)
{
return _value == other._value;
}
public override bool Equals(object? obj)
{
return obj is Rgb565 other && Equals(other);
}
public override int GetHashCode()
{
return _value;
}
public static bool operator ==(Rgb565 left, Rgb565 right)
{
return left.Equals(right);
}
public static bool operator !=(Rgb565 left, Rgb565 right)
{
return !left.Equals(right);
}
/// <summary>
/// Returns true if the two colors are almost equal
/// </summary>
/// <param name="a">First color</param>
/// <param name="b">Second color</param>
/// <param name="delta">The allowed delta, in visible bits</param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public static bool AlmostEqual(Rgb565 a, Rgb565 b, int delta)
{
if (a.PackedValue == b.PackedValue)
{
return true;
}
if (Math.Abs(a.R - b.R) > (delta << 3))
{
return false;
}
if (Math.Abs(a.G - b.G) > (delta << 2))
{
return false;
}
if (Math.Abs(a.B - b.B) > (delta << 3))
{
return false;
}
return true;
}
public Color ToColor()
{
return Color.FromArgb(255, R, G, B);
}
}
}
......@@ -6,18 +6,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="$(SystemDrawingCommonPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../Ili9341.csproj" />
<ProjectReference Include="../Ili934x.csproj" />
<ProjectReference Include="../../Ft4222/Ft4222.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="images\*.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<ProjectReference Include="../../Arduino/Arduino.csproj" />
<ProjectReference Include="../../SkiaSharpAdapter/SkiaSharpAdapter.csproj" />
</ItemGroup>
</Project>
......@@ -9,7 +9,10 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Iot.Device.Ft4222;
using Iot.Device.Ili9341;
using Iot.Device.Graphics;
using Iot.Device.Graphics.SkiaSharpAdapter;
using Iot.Device.Ili934x;
#pragma warning disable CA1416 // Temporarily, will be removed after binding is updated
Console.WriteLine("Are you using Ft4222? Type 'yes' and press ENTER if so, anything else will be treated as no.");
bool isFt4222 = Console.ReadLine() == "yes";
......@@ -18,38 +21,43 @@ int pinDC = isFt4222 ? 1 : 23;
int pinReset = isFt4222 ? 0 : 24;
int pinLed = isFt4222 ? 2 : -1;
using Bitmap dotnetBM = new(240, 320);
using Graphics g = Graphics.FromImage(dotnetBM);
SkiaSharpAdapter.Register();
using SpiDevice displaySPI = isFt4222 ? GetSpiFromFt4222() : GetSpiFromDefault();
GpioController gpio = isFt4222 ? GetGpioControllerFromFt4222() : new GpioController();
using Ili9341 ili9341 = new(displaySPI, pinDC, pinReset, backlightPin: pinLed, gpioController: gpio);
while (true)
{
using var backBuffer = ili9341.CreateBackBuffer();
foreach (string filepath in Directory.GetFiles(@"images", "*.png").OrderBy(f => f))
{
Console.WriteLine($"Drawing {filepath}");
using Bitmap bm = (Bitmap)Bitmap.FromFile(filepath);
g.Clear(Color.Black);
g.DrawImage(bm, 0, 0, bm.Width, bm.Height);
ili9341.SendBitmap(dotnetBM);
using var image = BitmapImage.CreateFromFile(filepath);
var api = backBuffer.GetDrawingApi();
api.DrawImage(image, new Rectangle(0, 0, image.Width, image.Height), new Rectangle(0, 0, image.Width, image.Height));
ili9341.DrawBitmap(backBuffer);
ili9341.SendFrame(true);
Task.Delay(1000).Wait();
}
Console.WriteLine("FillRect(Color.Red, 120, 160, 60, 80)");
ili9341.FillRect(Color.Red, 120, 160, 60, 80);
ili9341.SendFrame(true);
Task.Delay(1000).Wait();
Console.WriteLine("FillRect(Color.Blue, 0, 0, 240, 320)");
ili9341.FillRect(Color.Blue, 0, 0, 240, 320);
ili9341.SendFrame(true);
Task.Delay(1000).Wait();
Console.WriteLine("ClearScreen()");
ili9341.ClearScreen();
ili9341.SendFrame(true);
Task.Delay(1000).Wait();
Console.WriteLine("FillRect(Color.Green, 0, 0, 120, 160)");
ili9341.FillRect(Color.Green, 0, 0, 120, 160);
ili9341.SendFrame(true);
Task.Delay(1000).Wait();
}
......
// 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.Spi;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ili934x.Tests
{
/// <summary>
/// An SPI driver that does nothing, except store the output data in a buffer for testing.
/// Useful for testing (the default SpiDevice is not mockable)
/// </summary>
internal class DummySpiDriver : SpiDevice
{
public DummySpiDriver()
{
ConnectionSettings = new SpiConnectionSettings(0, 1);
}
public override SpiConnectionSettings ConnectionSettings { get; }
public List<byte> Data { get; } = new List<byte>();
public override void Read(Span<byte> buffer)
{
buffer.Clear();
}
public override void Write(ReadOnlySpan<byte> buffer)
{
Data.AddRange(buffer.ToArray());
}
public override void TransferFullDuplex(ReadOnlySpan<byte> writeBuffer, Span<byte> readBuffer)
{
Data.AddRange(writeBuffer.ToArray());
readBuffer.Clear();
}
}
}
// 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.Gpio;
using System.Device.Spi;
using System.Drawing;
using Ili934x.Tests;
using Iot.Device.Graphics;
using Moq;
using Xunit;
namespace Iot.Device.Ili934x.Tests
{
public class Ili9342Test : IDisposable
{
private readonly DummySpiDriver _spiMock;
private readonly GpioController _gpioController;
private readonly Mock<MockableGpioDriver> _gpioDriverMock;
private readonly Mock<IImageFactory> _imageFactoryMock;
private Ili9342? _testee;
public Ili9342Test()
{
_spiMock = new DummySpiDriver();
_gpioDriverMock = new Mock<MockableGpioDriver>(MockBehavior.Loose);
_imageFactoryMock = new Mock<IImageFactory>(MockBehavior.Strict);
BitmapImage.RegisterImageFactory(_imageFactoryMock.Object);
_gpioDriverMock.CallBase = true;
_gpioController = new GpioController(PinNumberingScheme.Logical, _gpioDriverMock.Object);
}
public void Dispose()
{
_testee?.Dispose();
_testee = null!;
}
[Fact]
public void Init()
{
_gpioDriverMock.Setup(x => x.OpenPinEx(15));
_gpioDriverMock.Setup(x => x.IsPinModeSupportedEx(It.Is<int>(y => y == 15 || y == 2 || y == 3), PinMode.Output)).Returns(true);
_gpioDriverMock.Setup(x => x.OpenPinEx(2));
_gpioDriverMock.Setup(x => x.OpenPinEx(3));
_gpioDriverMock.Setup(x => x.WriteEx(15, It.IsAny<PinValue>()));
_testee = new Ili9342(_spiMock, 15, 2, 3, 4096, _gpioController, false);
Assert.NotEmpty(_spiMock.Data);
}
[Fact]
public void Size()
{
Init();
Assert.Equal(320, _testee!.ScreenWidth);
Assert.Equal(240, _testee.ScreenHeight);
}
[Fact]
public void SendImage()
{
Init();
_imageFactoryMock.Setup(x => x.CreateBitmap(It.IsAny<int>(), It.IsAny<int>(), PixelFormat.Format32bppArgb)).Returns(
new Func<int, int, PixelFormat, BitmapImage>((int w, int h, PixelFormat pf) =>
{
var m = new Mock<BitmapImage>(MockBehavior.Loose, w, h, w * 4, pf);
m.CallBase = true;
return m.Object;
}));
using var bmp = _testee!.CreateBackBuffer();
Assert.Equal(320, bmp.Width);
Assert.Equal(240, bmp.Height);
bmp.SetPixel(0, 0, Color.White);
_spiMock.Data.Clear();
_testee.DrawBitmap(bmp);
_testee.SendFrame(true);
// 11 bytes setup + 2 bytes per pixel (this is raw SPI data, not including any possible Arduino headers)
Assert.Equal(11 + (320 * 240 * 2), _spiMock.Data.Count);
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultSampleTfms)</TargetFramework>
<LangVersion>10</LangVersion>
<IsPackable>false</IsPackable>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../Ili934x.csproj" />
</ItemGroup>
</Project>
// 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.Gpio;
using System.Threading;
namespace Iot.Device.Ili934x.Tests;
/// <summary>
/// A wrapper class that exposes the internal protected methods, so that they can be mocked.
/// Note: To provide an expectation for this mock, be sure to set the <code>Callbase</code> property to true and then
/// set expectations on the Ex methods. Only loose behavior works.
/// </summary>
public abstract class MockableGpioDriver : GpioDriver
{
private PinChangeEventHandler? _event;
protected override int PinCount
{
get
{
return 28;
}
}
public void FireEventHandler(int forPin, PinEventTypes eventTypes)
{
_event?.Invoke(this, new PinValueChangedEventArgs(eventTypes, forPin));
}
public abstract int ConvertPinNumberToLogicalNumberingSchemeEx(int pinNumber);
protected override int ConvertPinNumberToLogicalNumberingScheme(int pinNumber)
{
return ConvertPinNumberToLogicalNumberingSchemeEx(pinNumber);
}
public abstract void OpenPinEx(int pinNumber);
protected override void OpenPin(int pinNumber)
{
OpenPinEx(pinNumber);
}
public abstract void ClosePinEx(int pinNumber);
protected override void ClosePin(int pinNumber)
{
ClosePinEx(pinNumber);
}
public abstract void SetPinModeEx(int pinNumber, PinMode mode);
protected override void SetPinMode(int pinNumber, PinMode mode)
{
SetPinModeEx(pinNumber, mode);
}
public abstract PinMode GetPinModeEx(int pinNumber);
protected override PinMode GetPinMode(int pinNumber)
{
return GetPinModeEx(pinNumber);
}
public abstract bool IsPinModeSupportedEx(int pinNumber, PinMode mode);
protected override bool IsPinModeSupported(int pinNumber, PinMode mode)
{
return IsPinModeSupportedEx(pinNumber, mode);
}
public abstract PinValue ReadEx(int pinNumber);
protected override PinValue Read(int pinNumber)
{
return ReadEx(pinNumber);
}
public abstract void WriteEx(int pinNumber, PinValue value);
protected override void Write(int pinNumber, PinValue value)
{
WriteEx(pinNumber, value);
}
public abstract WaitForEventResult WaitForEventEx(int pinNumber, PinEventTypes eventTypes, CancellationToken cancellationToken);
protected override WaitForEventResult WaitForEvent(int pinNumber, PinEventTypes eventTypes, CancellationToken cancellationToken)
{
return WaitForEventEx(pinNumber, eventTypes, cancellationToken);
}
public abstract void AddCallbackForPinValueChangedEventEx(int pinNumber, PinEventTypes eventTypes, PinChangeEventHandler callback);
protected override void AddCallbackForPinValueChangedEvent(int pinNumber, PinEventTypes eventTypes, PinChangeEventHandler callback)
{
_event = callback;
AddCallbackForPinValueChangedEventEx(pinNumber, eventTypes, callback);
}
public abstract void RemoveCallbackForPinValueChangedEventEx(int pinNumber, PinChangeEventHandler callback);
protected override void RemoveCallbackForPinValueChangedEvent(int pinNumber, PinChangeEventHandler callback)
{
RemoveCallbackForPinValueChangedEventEx(pinNumber, callback);
_event = null!;
}
}
// 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.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Iot.Device.Ili934x;
using Xunit;
namespace Iot.Device.Ili934x.Tests
{
public class Rgb565Test
{
[Fact]
public void Convert()
{
Rgb565 a = new Rgb565();
Assert.Equal(Color.FromArgb(0, 0, 0), a.ToColor());
Rgb565 b = Rgb565.FromRgba32(Color.White);
Assert.Equal(Color.FromArgb(255, 255, 255), b.ToColor());
b = Rgb565.FromRgba32(Color.Red);
Assert.Equal(Color.FromArgb(255, 0, 0), b.ToColor());
// Ensure the bits of green are right
b = Rgb565.FromRgba32(Color.FromArgb(0, 103, 0));
Assert.Equal(Color.FromArgb(0, 103, 0), b.ToColor());
b = Rgb565.FromRgba32(Color.FromArgb(0, 0, 103));
Assert.Equal(Color.FromArgb(0, 0, 103), b.ToColor());
Rgb565 d = new Rgb565(0xf800);
Assert.Equal(0xf800, d.PackedValue);
}
[Fact]
public void Init()
{
Rgb565 c = new Rgb565(255, 255, 255);
Assert.Equal(0xFFFF, c.PackedValue);
Rgb565 b = Rgb565.FromRgba32(Color.Red);
Assert.NotEqual(0, b.PackedValue);
Rgb565 d = new Rgb565(0xff, 0, 0);
Assert.Equal(b.PackedValue, d.PackedValue);
Assert.Equal(255, d.R);
Assert.Equal(0x00F8, d.PackedValue);
Rgb565 e = new Rgb565(0, 0xff, 0);
Assert.Equal(0xe007, e.PackedValue);
Assert.Equal(255, e.G);
Rgb565 f = new Rgb565(0, 0, 0xff);
Assert.Equal(0x1f00, f.PackedValue);
Assert.Equal(255, f.B);
}
[Fact]
public void EqualIsEqual()
{
Rgb565 a = new Rgb565(10, 52, 101);
Rgb565 b = new Rgb565(a.PackedValue);
Rgb565 c = new Rgb565(0x2345);
Assert.True(a == b);
Assert.True(a.Equals(b));
Assert.True(a != c);
Assert.True(!a.Equals(c));
}
[Fact]
public void AlmostEqual()
{
Rgb565 a = new Rgb565(10, 52, 101);
Rgb565 b = new Rgb565(12, 53, 102);
// The delta value is in visible bits, so these are anyway equal
Assert.True(Rgb565.AlmostEqual(a, b, 0));
a = new Rgb565(0, 52, 101);
b = new Rgb565(7, 53, 102);
Assert.True(Rgb565.AlmostEqual(a, b, 1));
}
}
}
......@@ -80,6 +80,7 @@ Our vision: the majority of .NET bindings are written completely in .NET languag
### Thermometers
* [AHT10/15/20 - Temperature and humidity sensor modules](Ahtxx/README.md)
* [AM2320 - Temperature and Humidity sensor](Am2320/README.md)
* [BMP180 - barometer, altitude and temperature sensor](Bmp180/README.md)
* [BMxx80 Device Family](Bmxx80/README.md)
* [Cpu Temperature](CpuTemperature/README.md)
......@@ -153,6 +154,7 @@ Our vision: the majority of .NET bindings are written completely in .NET languag
### Hygrometers
* [AHT10/15/20 - Temperature and humidity sensor modules](Ahtxx/README.md)
* [AM2320 - Temperature and Humidity sensor](Am2320/README.md)
* [BMxx80 Device Family](Bmxx80/README.md)
* [DHTxx - Digital-Output Relative Humidity & Temperature Sensor Module](Dhtxx/README.md)
* [HTS221 - Capacitive digital sensor for relative humidity and temperature](Hts221/README.md)
......@@ -194,12 +196,15 @@ Our vision: the majority of .NET bindings are written completely in .NET languag
* [Character LCD (Liquid Crystal Display)](CharacterLcd/README.md)
* [Holtek HT1632 - 32×8 & 24×16 LED Driver](Ht1632/README.md)
* [HT16K33 - LED Matrix Display Driver](Display/README.md)
* [Ili9341 TFT LCD Controller](Ili9341/README.md)
* [Ili934x TFT LCD Controller](Ili934x/README.md)
* [IS31FL3730 -- LED Matrix Display Driver](Is31fl3730/README.md)
* [IS31FL3731 -- LED Matrix Display Driver](Is31fl3731/README.md)
* [Lp55231 - Nine-Channel RGB, White-LED Driver](Lp55231/README.md)
* [Max7219 (LED Matrix driver)](Max7219/README.md)
* [PCD8544 - 48 × 84 pixels matrix LCD, famous Nokia 5110 screen](Pcd8544/README.md)
* [RGBLedMatrix - RGB LED Matrix](RGBLedMatrix/README.md)
* [Sense HAT](SenseHat/README.md)
* [SkiaSharp graphics library adapter](SkiaSharpAdapter/README.md)
* [Solomon Systech Ssd1351 - CMOS OLED](Ssd1351/README.md)
* [Solomon Systech SSD13xx OLED display family](Ssd13xx/README.md)
* [TM1637 - Segment Display](Tm1637/README.md)
......@@ -225,6 +230,7 @@ Our vision: the majority of .NET bindings are written completely in .NET languag
### Touch sensors
* [Adafruit Seesaw - extension board (ADC, PWM, GPIO expander)](Seesaw/README.md)
* [Ili934x TFT LCD Controller](Ili934x/README.md)
* [MPR121 - Proximity Capacitive Touch Sensor Controller](Mpr121/README.md)
### Wireless communication modules
......@@ -247,6 +253,8 @@ Our vision: the majority of .NET bindings are written completely in .NET languag
* [APA102 - Double line transmission integrated control LED](Apa102/README.md)
* [Explorer HAT Pro (Pimoroni)](ExplorerHat/README.md)
* [HT16K33 - LED Matrix Display Driver](Display/README.md)
* [IS31FL3730 -- LED Matrix Display Driver](Is31fl3730/README.md)
* [IS31FL3731 -- LED Matrix Display Driver](Is31fl3731/README.md)
* [Lp55231 - Nine-Channel RGB, White-LED Driver](Lp55231/README.md)
* [On-board LED driver](BoardLed/README.md)
* [Ws28xx / SK6812 LED drivers](Ws28xx/README.md)
......@@ -261,12 +269,13 @@ Our vision: the majority of .NET bindings are written completely in .NET languag
### Media libraries
* [Buzzer - Piezo Buzzer Controller](Buzzer/README.md)
* [SkiaSharp graphics library adapter](SkiaSharpAdapter/README.md)
* [Still image recording library](Media/README.md)
### USB devices
* [SPI, GPIO and I2C drivers for Arduino with Firmata](Arduino/README.md)
* [SPI, GPIO and I2C drivers for FT232H](Ft232H/README.md)
* [SPI, GPIO and I2C drivers for FT232H, FT2232H, FT4232H](Ft232H/README.md)
* [SPI, GPIO and I2C drivers for FT4222](Ft4222/README.md)
* [STUSB4500 - Autonomous USB-C PD controller for Power Sinks / UFP](StUsb4500/README.md)
......@@ -291,6 +300,14 @@ Our vision: the majority of .NET bindings are written completely in .NET languag
* [Sense HAT](SenseHat/README.md)
* [SensorHub - Environmental sensor](SensorHub/README.md)
### Bus and GPIO multiplexers
* [Charlieplex Segment binding](Charlieplex/README.md)
* [Generic shift register](ShiftRegister/README.md)
* [MBI5027 -- 16-bit shift register with error detection](Mbi5027/README.md)
* [SN74HC595 -- 8-bit shift register](Sn74hc595/README.md)
* [TCA954X - TCA954X Low-Voltage Multi-Channel I2C Switch with Reset](Tca954x/README.md)
### Protocols providers/libraries
* [1-wire](OneWire/README.md)
......@@ -303,7 +320,7 @@ Our vision: the majority of .NET bindings are written completely in .NET languag
* [Software PWM](SoftPwm/README.md)
* [Software SPI](SoftwareSpi/README.md)
* [SPI, GPIO and I2C drivers for Arduino with Firmata](Arduino/README.md)
* [SPI, GPIO and I2C drivers for FT232H](Ft232H/README.md)
* [SPI, GPIO and I2C drivers for FT232H, FT2232H, FT4232H](Ft232H/README.md)
* [SPI, GPIO and I2C drivers for FT4222](Ft4222/README.md)
* [System.Device.Model - attributes for device bindings](System.Device.Model/README.md)
......
......@@ -6,7 +6,7 @@ This folder contains the SkiaSharp adapter for Iot.Device.Bindings. It provides
[SkiaSharp](https://github.com/mono/SkiaSharp) multi-platform graphics library. This folder is compiled into its own .nuget file, so that no external dependencies
are included when they're not needed.
See [Ili934x](../Ili9341/Readme.md) for an usage example.
See [Ili934x](../Ili934x/Readme.md) for an usage example.
To use SkiaSharp as image library with Iot.Device.Bindings, reference (in addition to `Iot.Device.Bindings.nuget`) the `Iot.Device.Bindings.SkiaSharpAdapter.nuget`
and put a line with `SkiaSharpAdapter.Register()` somewhere at the beginning of your application.
......@@ -75,7 +75,7 @@ namespace Iot.Device.Graphics.SkiaSharpAdapter
}
/// <summary>
/// Draws another image into this one, at the given position and without scaling
/// Draws another image into this one, at the given position and with scaling
/// </summary>
/// <param name="graphics">The target bitmap</param>
/// <param name="source">The source bitmap</param>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册