Skip to content

Conversation

@xas
Copy link
Contributor

@xas xas commented Oct 23, 2023

Description

  • add a driver for the M5 CoreInk screen
  • fix an issue with inherited font size properties

Motivation and Context

  • I have a CoreInk, I want to display data.

How Has This Been Tested?

  • run a sample project to verify it

Screenshots

CoreInk

Types of changes

  • Improvement (non-breaking change that improves a feature, code or algorithm)
  • Bug fix (non-breaking change which fixes an issue with code or algorithm)
  • New feature (non-breaking change which adds functionality to code)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Config and build (change in the configuration and build system, has no impact on code or features)
  • Dependencies (update dependencies and changes associated, has no impact on code or features)
  • Unit Tests (add new Unit Test(s) or improved existing one(s), has no impact on code or features)
  • Documentation (changes or updates in the documentation, has no impact on code or features)

Checklist:

  • My code follows the code style of this project (only if there are changes in source code).
  • My changes require an update to the documentation (there are changes that require the docs website to be updated).
  • I have updated the documentation accordingly (the changes require an update on the docs in this repo).
  • I have read the CONTRIBUTING document.
  • I have tested everything locally and all new and existing tests passed (only if there are changes in source code).
  • I have added new tests to cover my changes.

@nfbot nfbot added Type: bug Something isn't working Type: enhancement New feature or request labels Oct 23, 2023
@xas
Copy link
Contributor Author

xas commented Oct 23, 2023

Should we separate code into 3 package nuget ?

  • EPaper
  • GDEW0154x
  • SSD168x

(on draft state, as I am waiting some specs confirmation from M5 team)

Copy link
Member

@Ellerbach Ellerbach left a comment

Choose a reason for hiding this comment

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

Amazing start! And I love the fact that the interface makes sense :-) that's the first real different driver added here! So it's great to see! Few comments mainly about getting clarity for maintainability.
Also, you'll need to bump the minor version in the version file.

{
_spiDevice = spiDevice ?? throw new ArgumentNullException(nameof(spiDevice));
_gpioController = gpioController ?? throw new ArgumentNullException(nameof(gpioController));

Copy link
Member

Choose a reason for hiding this comment

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

can you add a "_shouldDispose" to check if the gpio controller should be disposed? See the pattern here for example:

private readonly bool _shouldDispose;

In short, you can pass a null GpioController and you'll create it, also you'll dispose it in this case. If it's not null and the shouldDispose is true, you'll dispose it otherwise no.

WaitMs(5);

SendCommand((byte)Command.DisplayRefresh);
WaitMs(500);
Copy link
Member

Choose a reason for hiding this comment

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

can you add a little comment on why to wait 5 ms and then 500 ms? Just a comment like "// 500 ms needed as per documentation page 5" will do it. So it makes it easier when maintaining and debugging all this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did, but I think there are some non-sense between samples I found on M5 repo and the one provided by the screen manufacturer...

}

SendCommand((byte)Command.PanelSetting);
SendData(0xdf);
Copy link
Member

Choose a reason for hiding this comment

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

can you point out where the initilization sequence is coming form? So it makes it easier for maintenance

/// <inheritdoc/>
public virtual void PerformFullRefresh()
{
WaitMs(10);
Copy link
Member

Choose a reason for hiding this comment

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

why 10 milliseconds?

public virtual void PowerOn()
{
HardwareReset();
WaitMs(1000);
Copy link
Member

Choose a reason for hiding this comment

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

few comments on the sequence will be greatly appreciated as well

// make sure we are setting data/command pin to low (command mode)
_dataCommandPin.Write(PinValue.Low);

foreach (var b in command)
Copy link
Member

Choose a reason for hiding this comment

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

maybe for performance reasons, it could be faster to write all the bytes at once? As every time, the data is sent independently with a low and high on the chip select. So it's much slower than sending in a single write with a buffer.

// set the data/command pin to high to indicate to the display we will be sending data
_dataCommandPin.Write(PinValue.High);

foreach (var @byte in data)
Copy link
Member

Choose a reason for hiding this comment

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

same comment as in the command one

{
while (_busyPin.Read() != pinValue)
{
WaitMs(5);
Copy link
Member

Choose a reason for hiding this comment

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

if waiting is a matter of few milliseconds you can use the Thread.Spin. If it's indeed much more, then keep the Thread.Sleep

{
while (_busyPin.Read() == PinValue.Low)
{
WaitMs(5);
Copy link
Member

Choose a reason for hiding this comment

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

same remark here as well

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did a little rewrite for the WaitBusy function as it was waiting indefinitely

@Ellerbach
Copy link
Member

Should we separate code into 3 package nuget ?

I guess it's ok to have one only for the moment (it simplify a bit things), the code overhead is not that high

Copy link
Member

@Ellerbach Ellerbach left a comment

Choose a reason for hiding this comment

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

better and better ;-)

{
while (_busyPin.Read() == PinValue.High)
int currentWait = 0;
while (currentWait < waitingTime && _busyPin.Read() == PinValue.High)
Copy link
Member

Choose a reason for hiding this comment

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

create a cancellation token, it's done for that. And you can use it in the loop as well to be non blocking

Copy link
Contributor

Choose a reason for hiding this comment

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

Is cancelling the wait on the busy pin a good idea here? If the waitingTime is set to 5 and after 25ms the display busy pin is still reading High, should we ignore that and move on with executing the rest of the commands knowing that they will not execute properly as the display is still stuck performing something else?

In my opinion, this will mask issues from the dev and might be harder to debug. If we must, I'd suggest throwing an exception so we have a clear signal to the caller/dev that the device has not responded in due time.

Copy link
Member

Choose a reason for hiding this comment

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

Something like (just did it from memory) this is much cleaner and you won't have to wait an extra 5 ms (which is ultimately going to be 20 ms) unnecessary. this shows as well how to set a default time.

public virtual bool WaitReady(CancellationToken waitingTime = default)
{
  if (waitingTime == default)
  {
    // With a default time of 5 seconds as an example
    waitingTime = new CancellationTokenSource(5000);
  }

  while(!waitingTime.IsCancelledRequest && _busyPin.Read() == PinValue.High)
  {
    waitingTime.WaitHandle.WaitOne(5, true);
  }

  return !waitingTime.IsCancelledRequest;
}

int dataCommandPin,
int width,
int height,
GpioController gpioController,
Copy link
Member

Choose a reason for hiding this comment

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

you can even set this one as null and create it internally if not given.

{
while (_busyPin.Read() == PinValue.High)
int currentWait = 0;
while (currentWait < waitingTime && _busyPin.Read() == PinValue.High)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is cancelling the wait on the busy pin a good idea here? If the waitingTime is set to 5 and after 25ms the display busy pin is still reading High, should we ignore that and move on with executing the rest of the commands knowing that they will not execute properly as the display is still stuck performing something else?

In my opinion, this will mask issues from the dev and might be harder to debug. If we must, I'd suggest throwing an exception so we have a clear signal to the caller/dev that the device has not responded in due time.


SendCommand((byte)Command.MasterActivation);
WaitReady();
WaitReady(_maxWaitingTime);
Copy link
Contributor

Choose a reason for hiding this comment

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

The return value of WaitReady is being ignored here. Would be good if it can be used to signal failure to the caller.

/// <summary>
/// Base class for GDEW0154-based ePaper devices.
/// </summary>
public abstract class Gdew0154x : IEPaperDisplay
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you rename the class to use the Controller model number instead which is 'JD79653A' ? GDEW0154x is an epaper product model number and not the controller name. As you can see in the other implemented driver, we're using SSD168x which is the controller name rather than the product number from GoodDisplay.

Also, please rename the namespace to match this.

/// <param name="sleepMode">The sleep mode to use when powering down the display.</param>
public virtual void PowerDown(SleepMode sleepMode = SleepMode.Normal)
{
SendCommand(0x50);
Copy link
Contributor

Choose a reason for hiding this comment

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

Since you have the Command enum, these byte values should go there too.

HardwareReset();
}

SendCommand(0x50);
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here. Please move the byte value to Command enum.

@xas
Copy link
Contributor Author

xas commented Nov 2, 2023

For the busy pin question, I am more on the solution of warn the developer, but not block him
The current implementation of WaitReady is a blocking thread, so if something happen on the screen side, the all app is blocked.

I think it should be the developer role to decide what to do if after a while the busy pin is still up.

I am also against raising an exception as you could crash the developer's app.

I am more on a warning policy: "hey be careful, seems there are still work on the screen, but continue with the rest"

@xas
Copy link
Contributor Author

xas commented Nov 2, 2023

BTW, is it really necessary Iot.Device.EPaper.Drivers.JD79653A.JD79653A ?

I don't think a class and a namespace should share a same name. Why the class Iot.Device.EPaper.Drivers.JD79653Ashould be wrong ?

@Ellerbach
Copy link
Member

BTW, is it really necessary Iot.Device.EPaper.Drivers.JD79653A.JD79653A ?

no it's not. Fine to use directly the Driver namespace!

@MrCSharp22
Copy link
Contributor

MrCSharp22 commented Nov 6, 2023

BTW, is it really necessary Iot.Device.EPaper.Drivers.JD79653A.JD79653A ?

Not necessary, no. What I was thinking about is how JD79653A seems to be part of a family of drivers. I found references to another epd driver JD796**32A** so my thinking is that the full namespace of the class becomes Iot.Device.EPaper.Drivers.JD796xx.JD79653A so if later on we add JD796**32A**, it will live there.

xas added 4 commits November 6, 2023 16:04
* add "shouldDispose" property
* fix data sending protocol
* clean up time delays
* improve data write buffering
* use of SpinWait for short time
* add a time limit to "WaitReady" to avoid infinite wait
* add a deep sleep power state
* namespace driver renaming
* fix missing command map
* allow gpioController parameter to be null
@xas xas marked this pull request as ready for review November 20, 2023 08:05
Copy link
Member

@Ellerbach Ellerbach left a comment

Choose a reason for hiding this comment

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

Couple of small details. Great to see the PR now real review :-)

/// <summary>
/// Base class for GDEW0154-based ePaper devices.
/// </summary>
public abstract class JD79653A : IEPaperDisplay
Copy link
Member

Choose a reason for hiding this comment

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

Technically the name should be Jd79653A to follow our guidelines. Same for the namespace Jd796xx

/// </summary>
public abstract class Ssd168x : IEPaperDisplay
{
private readonly int _maxWaitingTime = 500;
Copy link
Member

Choose a reason for hiding this comment

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

why not a const?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I want to leave the possibility that become a configurable property when instantiating it.

Copy link
Member

@Ellerbach Ellerbach left a comment

Choose a reason for hiding this comment

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

Thanks, just a little detail on the wait function using cancellation token

{
while (_busyPin.Read() == PinValue.High)
int currentWait = 0;
while (currentWait < waitingTime && _busyPin.Read() == PinValue.High)
Copy link
Member

Choose a reason for hiding this comment

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

Something like (just did it from memory) this is much cleaner and you won't have to wait an extra 5 ms (which is ultimately going to be 20 ms) unnecessary. this shows as well how to set a default time.

public virtual bool WaitReady(CancellationToken waitingTime = default)
{
  if (waitingTime == default)
  {
    // With a default time of 5 seconds as an example
    waitingTime = new CancellationTokenSource(5000);
  }

  while(!waitingTime.IsCancelledRequest && _busyPin.Read() == PinValue.High)
  {
    waitingTime.WaitHandle.WaitOne(5, true);
  }

  return !waitingTime.IsCancelledRequest;
}

@josesimoes josesimoes force-pushed the develop branch 2 times, most recently from 9de53d1 to c8ac54b Compare December 11, 2023 13:24
Copy link
Member

@Ellerbach Ellerbach left a comment

Choose a reason for hiding this comment

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

Looks all good now! Thanks!

@Ellerbach
Copy link
Member

@xas you still have to fix the versions between the nuget and the nfproj and what's missing. See the build result.

Copy link
Member

@Ellerbach Ellerbach left a comment

Choose a reason for hiding this comment

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

I think we're good to go :-) HUGE thanks for the work on this contribution! Great to see this additional epaper screen!

@Ellerbach Ellerbach merged commit cb8d99a into nanoframework:develop Jan 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: bug Something isn't working Type: enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants