-
Notifications
You must be signed in to change notification settings - Fork 2.1k
sys/disp_dev: add generic interface for display drivers #13262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
b4f2c26
to
0995ff6
Compare
0995ff6
to
5eb2a82
Compare
drivers/ili9341/ili9341_disp_dev.c
Outdated
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) |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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,
…
};
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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>, ...)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
babad69
to
29838af
Compare
29838af
to
e2c71e5
Compare
There was a problem hiding this 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!
d96f3ea
to
bccfaf0
Compare
@kaspar030, I moved the new |
|
||
USEMODULE += ili9341 | ||
USEMODULE += disp_dev | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
tests/disp_dev/main.c
Outdated
|
||
uint16_t color = 0; | ||
for (uint16_t x = 0; x < max_width; ++x) { | ||
for (uint16_t y = 0; y < max_height; ++y) { |
There was a problem hiding this comment.
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...)
.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
Are we good with this one @kaspar030 ? (and may I squash ?) |
Yes, please squash! I'll give it a last look and test run. |
ACK. LGTM, tested on pinetime. |
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:
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