Skip to content

Conversation

aabadie
Copy link
Contributor

@aabadie aabadie commented Feb 2, 2020

Contribution description

This PR takes out the generic display driver interface introduced in #13124 and extends it with set/get method. So for the moment, the API exposes 3 functions:

  • map: to display a rectangle based on 4 coordinates and an array of colors.
  • get: to retrieve state/parameter of the underlying display driver
  • set: to set state/parameter to the underlying display driver

For the moment, there's only an adaption provided for the ili9341 driver because it's the only LCD display driver supported by RIOT.

Suggestion for improvements/extension are welcome (maybe add an init function to the API ?).
It would be interesting to see if it generalize well with the driver implemented in #12509.

Testing procedure

Issues/PRs references

Based on #13261, required by #13124

@aabadie aabadie added the Area: drivers Area: Device drivers label Feb 2, 2020
@aabadie aabadie requested a review from bergzand February 2, 2020 18:50
@aabadie aabadie added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Feb 4, 2020
ili9341_pixmap(ili9341, x1, x2, y1, y2, color);
}

static int _ili9341_get(disp_dev_t *disp_dev, disp_opt_t opt, void *value, size_t max_len)
Copy link
Contributor

Choose a reason for hiding this comment

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

I wish we had an easy way to compare this mechanism with a corresponding function pointer based version.

I mean, if there'd be inline wrappers like:

static int disp_dev_get_max_width(dev)
{
  if !(dev->ops->get_max_width) {
    return -ENOTSUP;
  }
  return dev->ops->get_max_width(dev);
}

(this would be shared by all).

As the ops struct is constant, only supported functions would need to be mentioned in this:

const disp_dev_driver_t ili9341_disp_dev_driver = {
    .map = _ili9341_map,
    .get = _ili9341_get,
    .set = _ili9341_set,
};

I'd assume that the code would be of similar size, but always fully type checked.
(Unless we espect a lot of not implemented operations.)

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, if a function is implemented (by implementing the corresponding static int _ili9341_scroll_down() or whatever, the compiler comlplains about an unused function if it is not added to the ops struct.

Copy link
Member

@bergzand bergzand Feb 11, 2020

Choose a reason for hiding this comment

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

So if I understand correctly you want to test it against something like:

const disp_dev_driver_t ili9341_disp_dev_driver = {
    .map = _ili9341_map,
    .get_width = _ili9341_get_width,
    .get_heigth = _ili9341_get_heigth,
    .set_brightness = _ili9341_set_brightness,
    …
};

?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, thats what I mean. I know we use this in netdev, but there it is also meant to work over netapi / msgs.

I'll try to sketch it, to get an idea of the overhead of an implemented / unimplemented function.

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 would prefer to keep the ops struct as it is implemented in the PR and add wrapper functions around each of them.
I see a benefit in API usage. e.g.

disp_dev_map(dev, ...)
disp_dev_get(dev, DISP_OPT_<option name>, ...)
disp_dev_set(dev, DISP_OPT_<option name>, ...)

instead of:

dev->driver->map(dev, ...)
dev->driver->get(dev, DISP_OPT_<option name>, ...)
dev->driver->set(dev, DISP_OPT_<option name>, ...)

Copy link
Contributor

@kaspar030 kaspar030 Feb 13, 2020

Choose a reason for hiding this comment

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

disp_dev_get(dev, DISP_OPT_<option name>, ...)
disp_dev_set(dev, DISP_OPT_<option name>, ...)

I think the detail is in the .... As proposed, this would be:

disp_dev_get(disp_dev_t *disp_dev, disp_opt_t opt, void *value, size_t max_len);

The user needs to manually typecheck the pointer and max_len, or check the result. Sure, the asserts catch that, but it is clumsy.

E.g., getting DISP_OPT_MAX_WIDTH:

int max_with;
int res = disp_dev_get(dev, DISP_OPT_MAX_WIDTH, &max_width, sizeof(max_width));

Do you know if this works as expected on a 32bit platform? The compiler won't check any type.

That can surely be wrapped:

static inline uint16_t disp_dev_get_max_width(dev)
{
    int16_t max_width;
    int res = disp_dev_get(dev, DISP_OPT_MAX_WIDTH, &max_width, sizeof(max_width));
   if (res) return 0;
   return max_width;
}

... but at that point my proposal is much less hassle.

Again, using pointers & max_len was a kludge in netdev, in order to allow calling the API directly over netapi / messages edit as the values need to be packed into pointers / raw data anyways.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know how this would affect code size. I could imagine that having an extra function for returning a single value might be a waste. Then again, with a static inline getter, that can easily be changed to be a static field, for common values. (static inline disp_dev_get_max_width(dev) return dev->params->max_width;).

@aabadie do you see my point? I can try changing the branch towards fully function pointer based, to get an idea of the code size implications.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you know if this works as expected on a 32bit platform? The compiler won't check any type.

I'm not sure to understand what you mean. Is it because the example you provide uses int instead of uint16_t ?

The problem with specific wrappers is that it contraints the available common functions. And not all display device will provide get/set_brightness, get/set_color_depth, etc. The only common minimum is about width/height attributes.

If you agree and if I understand it correctly, I can try your proposed approach, and give some comparison.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure to understand what you mean. Is it because the example you provide uses int instead of uint16_t ?

yes.

The problem with specific wrappers is that it contraints the available common functions.

This I don't understand.

And not all display device will provide get/set_brightness, get/set_color_depth, etc. The only common minimum is about width/height attributes.

The price for a not supported function is the size of one pointer, in flash. Unless we'd have a high ratio of those, it could be acceptable.

If you agree and if I understand it correctly, I can try your proposed approach, and give some comparison.

If you factor out the functions of the current switch-based approach, it could be easy to switch.

e.g.:

switch (option):
    case DISP_DEP_GET_FOO:
      ...
      ((uint16_t*)ptr) = dev->foo;
    break;

->


static uint16_t _get_foo(dev) {
  return dev->foo;
}

switch (option) {
   case DISP_DEP_GET_FOO:
      ...
      ((uint16_t*)ptr) = _get_foo(dev);
    break;
    ...

Up to here it should actually result in the same code, but now _get_foo() can directly be used in the drivers struct.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kaspar030, I tried your proposal and it's 60B smaller than the current one. I'll push the changes in a moment.

Copy link
Contributor

@kaspar030 kaspar030 left a comment

Choose a reason for hiding this comment

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

first round. Looking quite nice already!

@benpicco benpicco added CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR and removed CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR labels Feb 25, 2020
@aabadie
Copy link
Contributor Author

aabadie commented Feb 26, 2020

@kaspar030, I moved the new disp_dev module in drivers. I think it makes more sense to put it there.


USEMODULE += ili9341
USEMODULE += disp_dev

Copy link
Contributor

@kaspar030 kaspar030 Mar 2, 2020

Choose a reason for hiding this comment

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

I guess this needs a blacklist for CI tests? Or can a display tests be reasonably tested on headless nodes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A display can be wired to any board with SPI.

Copy link
Contributor

Choose a reason for hiding this comment

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

So they can be compile tested, but why run the test script?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's no test script :)

Copy link
Contributor

Choose a reason for hiding this comment

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

I see. So we avoid the issue (and convention: no new test application without test script).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So we avoid the issue (and convention: no new test application without test script)

It's the same for almost all driver. You can't automate the test unless the hardware is wired to the board.


uint16_t color = 0;
for (uint16_t x = 0; x < max_width; ++x) {
for (uint16_t y = 0; y < max_height; ++y) {
Copy link
Contributor

@kaspar030 kaspar030 Mar 2, 2020

Choose a reason for hiding this comment

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

Seeing this is a bit painful.

It screams for a "fill(x,x2,y,y2, color)" that doesn't do a map() for every pixel. But we can leave that to a followup.

Iterating linewise (first all x=0 y=0, 1, 2, ... then all x=1, y=0, 1, 2, ...) is also a textbook example for missing optimization. Please change to for (y, ...) for (x...).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please change to for (y, ...) for (x...)

Done but doesn't make much difference in speed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It screams for a "fill(x,x2,y,y2, color)"

Indeed, we also had this discussion with @bergzand IRL. We came to the conclusion that map can also be added in this situation with an input buffer. The input doesn't necessarily need to have a size equal to the size of the screen but then that would require rectangle size computation and multiple calls to map in users code (so not very handy, but more generic).

Copy link
Member

Choose a reason for hiding this comment

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

The only way to speed this up is to use a buffer with the pixels for the selected color and transmit those. Even a buffer of only 32 pixels (64 bytes) already speeds this up tremendously.

This because (at least for the ili9341), each map() first sends x1, x2, y1, y2 (8 bytes) to the display and then the pixels (another 2 byte). That's 10 bytes of SPI data for a single transfer. Transmitting a buffer of 32 pixels (64 byte + 8 byte coordinates = 72 bytes) at once is a lot more efficient in terms of SPI transfers.

@aabadie
Copy link
Contributor Author

aabadie commented Mar 3, 2020

Are we good with this one @kaspar030 ? (and may I squash ?)

@kaspar030
Copy link
Contributor

Yes, please squash! I'll give it a last look and test run.

@aabadie aabadie added CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR and removed CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR labels Mar 3, 2020
@kaspar030
Copy link
Contributor

ACK. LGTM, tested on pinetime.

@aabadie aabadie merged commit b050608 into RIOT-OS:master Mar 3, 2020
@aabadie aabadie deleted the pr/sys/disp_dev branch March 3, 2020 12:25
@leandrolanzieri leandrolanzieri added this to the Release 2020.04 milestone Mar 13, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: drivers Area: Device drivers CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants