Arduino TFT libraries compatibility

ChrisMicro
Wed Jun 21, 2017 12:02 pm
Hello together,

because I like to use Arduino programs on different platforms I realized a problem: Many examples are written for the ILI9341 TFT. But sometimes I use the F746 disco or F429 disco and sometimes a BluePill with a ILI9341.

But the most of the time there are different names for the same functionality so the code has to be rewritten.
Why not having one interface for all this displays?

Even on the Arduino site for the TFT library itself they make this software design errors:

#include <TFT.h> // Hardware-specific library
#include <SPI.h>
#include <Esplora.h>

void setup(){
EsploraTFT.begin();
EsploraTFT.background(0,0,0); // clear the screen with black
delay(1000); // pause for dramatic effect
}

void loop(){
EsploraTFT.stroke(255, 0, 0); // set the stroke color to red
EsploraTFT.line(0, 10, EsploraLCD.width(), 10); // draw a line across the screen
delay(1000);


zoomx
Wed Jun 21, 2017 12:18 pm
Unfortunately it happens also in other libraries.

But the Esplora let you to use the old commands
The Arduino TFT library extends the Adafruit GFX, and Adafruit ST7735 libraries that it is based on.
.....
The library is backwards compatible, which means you can still use the Adafruit functions described here.


david.prentice
Wed Jun 21, 2017 3:28 pm
The beauty of C++ is that you can inherit classes. e.g. many TFT, OLED, GLCD libraries inherit all the Adafruit_GFX methods which in turn inherit the Print methods.

So I can expect tft.println(“Hello World”) to work just like Serial.println(“Hello World”);
Or tft.drawRect(0, 10, 5, 20, WHITE) to work like any other drawRect()

As a matter of convention you might name the object tft whatever TFT controller library you are using.
Much like most 16×2 objects are called lcd.

It seems a little odd to call the object EsploraTFT but it looks as if it is from a “stroke” type of graphics class rather than an Adafruit_GFX class.

Yes, you do tend to get a few anomalies. e.g. some TFT libraries have begin(void) and some have begin(uint16_t ID)
But the bulk of a TFT graphics program will work on an SPI ST7735 or a parallel ILI9341 if the specific libraries share the GFX methods. i.e. you just need specific include and constructor.

David.


RogerClark
Thu Jun 22, 2017 1:18 am
[david.prentice – Wed Jun 21, 2017 3:28 pm] –
So I can expect tft.println(“Hello World”) to work just like Serial.println(“Hello World”);
Or tft.drawRect(0, 10, 5, 20, WHITE) to work like any other drawRect()

I doubt even if you always used libraries by the same company e.g. Adafruit, whether tft.println() would work the same across all their libs or whether it worked the same as Serial.println

From what I can tell, libs by the same company e.g. Adafruit are often written by different people and don’t confirm to any style guide.

In my day job I use libs on various platforms from various sources, and they all behave differently

I think the only time you can expect the sort of coherence to a fixed API style and functionality is if you single source your SDK / API / Libs from one company as a commercial product e.g. All Apple iOS API’s strongly follow a style, but this is a heavily funded commercial team.


ChrisMicro
Thu Jun 22, 2017 10:01 am
I want to make an easy to use and simple GUI library which supports several TFT displays.
What interface technique would your recommend to attach the different drivers?

david.prentice
Thu Jun 22, 2017 10:30 am
[RogerClark – Thu Jun 22, 2017 1:18 am] –

[david.prentice – Wed Jun 21, 2017 3:28 pm] –
So I can expect tft.println(“Hello World”) to work just like Serial.println(“Hello World”);
Or tft.drawRect(0, 10, 5, 20, WHITE) to work like any other drawRect()

I doubt even if you always used libraries by the same company e.g. Adafruit, whether tft.println() would work the same across all their libs or whether it worked the same as Serial.println

From what I can tell, libs by the same company e.g. Adafruit are often written by different people and don’t confirm to any style guide.

In my day job I use libs on various platforms from various sources, and they all behave differently

I think the only time you can expect the sort of coherence to a fixed API style and functionality is if you single source your SDK / API / Libs from one company as a commercial product e.g. All Apple iOS API’s strongly follow a style, but this is a heavily funded commercial team.

Rubbish. If one class inherits from Print.h it will inherit all the Print.h methods.
This is a feature of C++ and most OO languages.

Yes, the new class can implement new methods. It can even overwrite some of the inherited methods. But as a general rule, you extend methods or add new methods rather than change behaviour.

e.g. you might add tft.blueCircle(x, y, radius) to draw a circle in blue.
e.g. it is possible that you overwrite the existing tft.drawCircle(x, y, radius, color) method to draw triangles. But extremely unlikely.

Yes, I suppose that a disgruntled Apple ex-employee might try to change the style.

So libraries that inherit from Adafruit_GFX are easy to use by existing Arduino owners.
It makes life easier for the library author too.
But any library can invent any new class with any named methods that do not follow any logical pattern.

David.


RogerClark
Thu Jun 22, 2017 11:57 am
I missunderstood your post

I thought you meant that all libraries that has a println function (should always implement it the same way)
Not just Adafruit libs that inherit from the same base class


david.prentice
Thu Jun 22, 2017 12:57 pm
No problem!

Yes, I agree that println() is just a name. It does not necessarily mean inheritance of Print.h
But I maintain that most library authors would attempt to mimic the behaviour even if they implement without inheritance.

e.g. MarekB and Bodmer implement their own Graphics methods that behave identically to the Adafruit_GFX methods.

Somehow, println() is very likely to behave like Print.h
I suspect that print() is less likely to conform.

David.


danieleff
Thu Jun 22, 2017 2:59 pm
Whats wrong with
class GUI {
public:
GUI(Adafruit_GFX &gfx): gfx(gfx) {};
Adafruit_GFX &gfx;

void button() {
gfx.drawPixel(...);
}
};

//Use as:
Adafruit_ILI9341 tft(); // or other display
GUI(tft);


ChrisMicro
Fri Jun 23, 2017 5:18 am
I try this with your F746 TFT implementation but it seems not to be a derivative of the Adafruit_GFX.
So I would have to rewrite it to
class GuiPittixObject {
public:
GuiPittixObject( LTDC_F746_Discovery &gfx ): gfx(gfx)
{
};
private:
LTDC_F746_Discovery &gfx;
};

LTDC_F746_Discovery tft;
GuiPittixObject myObject(tft);


danieleff
Fri Jun 23, 2017 6:59 am
[ChrisMicro – Fri Jun 23, 2017 5:18 am] –
I try this with your F746 TFT implementation but it seems not to be a derivative of the Adafruit_GFX.

Yes it is.


zmemw16
Fri Jun 23, 2017 8:37 am
oh no it isn’t :)
sorry
stephen

ChrisMicro
Sat Jun 24, 2017 6:06 am
The goal of my efforts to make a GUI is the maximum reachable simplicity … Arduino like …
It has to be simple to use. All GUI APIs I saw are in my opinion to complicated.

So here is my proposal showed with an example placing to “virtual” LEDs on the screen and blink them:

Adafruit_ILI9341 * Display; // this pointer is needed from the GUI driver

GUI_Led Led1("up ");
GUI_Led Led2("down");

void setup()
{
tft.begin();
tft.fillScreen(LTDC_BLACK);
tft.setRotation(1);

Display = &tft;

Led1.setColor(COLOR_GREEN);
}

void loop(void)
{
Led1.on();
Led2.off();
delay(1000);
Led1.off();
Led2.on();
delay(1000);
}


ChrisMicro
Sun Jun 25, 2017 5:31 am
I just added a code example on GitHub for the BluePill and a ILI9341.
This is a very initial state which I plan to extend. Do you have suggestions for any improvements?

stevestrong
Sun Jun 25, 2017 8:40 am
[ChrisMicro – Sun Jun 25, 2017 5:31 am] –
I just added a code example on GitHub for the BluePill and a ILI9341.
This is a very initial state which I plan to extend. Do you have suggestions for any improvements?

No suggestion yet, but it looks nice so far, I am very interested in this project.


ChrisMicro
Sun Jun 25, 2017 9:36 am
No suggestion yet, but it looks nice so far, I am very interested in this project.

Thanks for your response :D

Meanwhile I came to the conclusion that it is best to have a “TFT_Adapter” class for every hardware configuration.

All hardware configuration ( like pin assignement ) has to be done in this Adapter for every hardware platform.
The rest of the software stays the same.

I have done the example for 3 different hardware platforms:

– Arduino Uno with ILI9341 parallel display shield
– BluePill with ILI9341 SPI
– STM32F746 Discovery

What do you think?


ChrisMicro
Mon Jun 26, 2017 6:49 am
Now it is converted to a library.
Now there are different adapters which have to be selected before including the Gui.

// choose your hardware:
#define TFT_Adapter_ILI9341_BluePill
//#define TFT_Adapter_SPFD5408_UNO
//#define TFT_Adapter_STM32F746_Discovery

#include "Gui.h"

GUI_Led Led1("up ");
GUI_Led Led2("down");

....


ChrisMicro
Thu Jun 29, 2017 6:26 am
Meanwhile I made some graphical elements

– button
– slider
– number display

Originally I wanted make the demos on small 2.4” ILI9341 and a BluePill but I got used to the convenience of the “big” 4” TFT of the F746 disco.

Here is a demo picture

Image

It looks a little bit basic compared to the GUIs which are around.

But take into account: My focus is on the simplicity of the programmers API.

What do you think?


danieleff
Thu Jun 29, 2017 7:33 am
You are limiting yourself waaay too much with those adapters. Just tell the user to call tft.begin(…) before gui.begin(). It is standard anyway.
And now your code works with on any arduino/teensy/eps/whatever, and every gui library that extends Adafruit GFX. Without needing to do anything.

Even if you do not do that, create an example for Arduino UNO, and make a post in an general arduino forum. You will get a TON more feedback there.

(You will need “adapters” for touch, not for the screen, because they do not have a common parent class I think. But if they have the same method (TSPoint getPoint()), then do it with one C++ template class, and not many different ones.)
(do not call setRotation(). Let the user decide that.)


ChrisMicro
Thu Jun 29, 2017 7:59 am
Thank your your detailed response :D

Even if you do not do that, create an example for Arduino UNO,

I bought an ILI9341 shield for the UNO. The display worked but the touch not. It seems to have a defect because only the ADC auf the Y-direction changes a little bit the X-axis not. So I gave it up …

Regarding the touch issue:
(You will need “adapters” for touch, not for the screen, because they do not have a common parent class I think. But if they have the same method (TSPoint getPoint()), then do it with one C++ template class, and not many different ones.)

That’s really somehow a problem. At the moment I solved it by reading the touch and writing the values to the gui.

This is the “interface”:

void loop(void)
{
// read touch pad and tell the gui
TSPoint p = ts.getPoint();
gui.setTouch(p.x, p.y, p.z);

value1 = slider1.getInt();
..


stevestrong
Thu Jun 29, 2017 8:36 am
Regarding the touch, some TFTs have the resistive touch pins on different display data pins. The best modality to find out on which display pins is mapped the touch is to measure effectively the resistance between different pins.

I can imagine a thin wrapper layer between the touch lib and user API which could unify the resistive with capacitive touch.


ChrisMicro
Fri Jun 30, 2017 4:46 pm
daniel wrote
You are limiting yourself waaay too much with those adapters.

I don’t know what the best way is.
For instance id you just take the Adafruit_ILI9341 examples:

tft.fillScreen(ILI9341_BLACK);


ChrisMicro
Fri Jun 30, 2017 4:54 pm
stevestrong wrote
I can imagine a thin wrapper layer between the touch lib and user API which could unify the resistive with capacitive touch.
Yes, good idea.
It is probably the best to seed the ILI9341 resistive touch API as standard and to make for all other touch screens a conversion layer.

Pito
Sun Jul 02, 2017 12:13 pm
Except GUI compatibility there is an issue with “low level” TFT interfaces drivers.
For example Steve’s nice 8bit stm lib uses 8bit approach across his library. So not easy usable for 16bit..

Q to experts: why we cannot create a “low level driver layer” for adafruit/utft/whatever lib, where the “DATA BUS” (8/16/SPI/I2C) will be accessible via 2 single functions/methods called for example:
uint8_t Write_TFT_Data_Bus(uint8_t mode, uint8_t parH, uint8_t parL);
uint16_t Read_TFT_Data_Bus(uint8_t mode);


david.prentice
Sun Jul 02, 2017 12:59 pm
Be realistic. No one is going to change from 8080-8 to 8080-16 at runtime. Likewise, you are not going to change from SPI to parallel at runtime.

There is nothing much that changes from a high level point of view. e.g. drawCircle(), fillScreen(), …
even readID() or writecommand() is the same at high level.

In other words, you choose your wiring and interface. Your decision is known at compile-time.
The binary that you have built will only run on a correctly wired TFT.

Adafruit did a brilliant job of providing Adafruit_GFX library. Arduino provided Stream and Print classes.
Authors of TFT libraries (or OLED, GLCD, …) simply provide the hardware methods.
Punters can port a program from Steve, Adafruit, Bodmer, Marek, … almost painlessly.

Yes, there might be small syntax differences between pushColors(), pushColor(), begin(), …
In an ideal world, they would be identical.

I have just considered the “popular” GFX style libraries that inherit (or mimic) classes.
An argument could be made for the UTFT approach. i.e. no inheritance, unintuitive methods.

Oh, most TFT libraries make the lowest level methods private. Some might be protected. Very few are public.
Note that you can modify/glue different behaviour or extra methods by extending an existing TFT class. And have access to protected variables and methods.

David.


ChrisMicro
Sun Jul 02, 2017 3:20 pm
I think “compatibility” in an Arduino context means that a software can easily ported from on to another platform.

The best is probably to port a demo to another platform like this tetris then you can see which problems may arise.


david.prentice
Mon Jul 03, 2017 8:01 am
Yes, I see your problem(s).

I inserted an ILI9341 SPI display on a 3.3V “Uno”.
I installed ILI9341_t3 with the Library Manager. It does not support the fonts.

So I removed the ILI9341_t3 library and built for a Teensy3.2.
The Teensy installs several popular libraries that have been tweaked for Freescale. This makes them incompatible with regular Arduinos.

In other words, the whole Arduino principle is violated.

Having said that, most ILI9341_t3 methods are fairly portable. But as soon as you start hacking, everything falls down.
In my personal opinion, it is fine for PJRC to add extra features that are specific to Freescale but they should maintain compatibility with the standard Arduinos.

Your particular example combines some specific bits of hardware. And you get a similar situation. i.e. the MapleCore variety for STM32 contains special hacked libraries.
I would be a lot happier if the STM32 support was merged into the main popular libraries. Then a punter could install “ILI9341_t3” with the Library Manager. And it would work with Arduino, STM32, Freescale, …

I am not particularly interested in a “Tetris” game. I do not possess any sound hardware.
But it should be fairly straightforward to port the TFT graphics code.

David.


Pito
Mon Jul 03, 2017 9:08 am
Be realistic. No one is going to change from 8080-8 to 8080-16 at runtime. Likewise, you are not going to change from SPI to parallel at runtime.
I do not think of a “runtime change”, of course.
But I think changing an interface (there will always be an effort needed with init the specific tft controller and mcu’s perihperals, sure) ie. from 9341-8bit to 9341-16bit or to 1289-16bit, or from 1289-16bit to 7735-SPI or to 9341-SPI shall be at the “hw interface bus level” much easier to do. Look at the way people do it today. And you need only two methods/functions..
Again, I do not comment on tft controller’s specific internal video features here.

david.prentice
Mon Jul 03, 2017 10:57 am
Look at my MCUFRIEND_kbv library. There is only one spot that is conditional for 16-bit or 8-bit interface. And that is the optimisation for fillRect() i.e. write 16-bit data bus once, wobble the /WR line many times.

Yes, the low level macros for the interface get messy when you have SPECIAL wiring.
But if you have a regular Shield and regular Arduino, the wiring is fixed.

I have my own ILI9341_kbv, ILI9163_kbv, HX8347D_kbv , … SPI classes. They use the same methods. You just change one include and one constructor to run the same sketch on different hardware.

And I can often port some “foreign TFT library” sketch with little more than a GLUE class.
For example, I compare my standard “graphictest_kbv” sketch by running it with other libraries e.g. ILI9341_t3

It is easier to “GLUE” to a class you are familiar with than to try and rewrite someone else’s sketch (or library).

David.


danieleff
Tue Jul 04, 2017 6:36 am
I once wanted to do a c++ template based one, where the protocol, the chip driver where just templates.
It was something like:
Display<PROTOCOL_ILI9341<ARDUINO_STANDARD_SPI_CS_DC>> tft1;
Display<PROTOCOL_WHATEVER<DRIVER_PARALLEL16_OPTIMIZED_FOR_STM32>> tft2;

ChrisMicro
Tue Jul 04, 2017 1:46 pm
Then I remembered https://xkcd.com/927/, and dropped the idea.
Yes, it is always a problem with standards. But despite of this yoke, standards do exist.

One type or standard is the standard designed from people sitting around a green table:
all fantasies are included and every one has problems implementing it over the next ten years.

Other standards evolve during time: Their sheer number will lead to declaring it at some point as standard.

From a point of Arduino-view I would say: close to a standard is Adafruit-GFX and its drawing functions and probably the best way to adapt written software is to write small classes which David calls “glue” and I call “wrapper”.
Efficiency might be a problem.


david.prentice
Tue Jul 04, 2017 2:46 pm
C++ Compilers are pretty clever. I would not worry too much about efficiency. It is seldom noticeable.

Templates and inheritance works slightly differently.

I have got to a stage where MCUFRIEND_kbv has so many initialisation sequences that it is significant on a Uno.
Which means that the “less common” controllers are conditionally supported. So you no longer have the situation where “anything” will plug and go.

Olikraus has a better system with u8glib. But you have to know exactly what hardware you have before you write the constructor.

Hey-ho. At least STM32 has more Flash to play around with.

I do think that it would be wise for Libraries to run on different platforms. i.e. make it painless for a Mega or Due program to work out of the box on STM32. Ok, you will always have a lowest common denominator but no one minds if an extra “bell or whistle” needs the bigger Flash or faster CPU. Just as long as the basics work.

David.


danieleff
Tue Jul 04, 2017 6:01 pm
I got to make your library work on my F411, but I had to #define STM32L476xx and ARDUINO_NUCLEO_L476RG in top of mcufriend_shield.h.

It will be a pain if every nucleo board needs to be spelled out one by one. (plus had to add one more WR_ACTIVE; to WRITE_DELAY to not have corrupt screen. what joy)


hyperion
Fri Dec 15, 2017 4:43 pm
hello!
why not use native Adafruit_GFX instead Adafruit_GFX_AS?
i try change in Adafruit_ILI9341_STM.h
#include <Adafruit_GFX_AS.h> to #include <Adafruit_GFX.h>
and everithing work fine with custom fonts.. maybe I do not know what? :)

mrburnette
Sun Dec 17, 2017 11:18 pm
I do think that it would be wise for Libraries to run on different platforms. i.e. make it painless for a Mega or Due program to work out of the box on STM32. Ok, you will always have a lowest common denominator but no one minds if an extra “bell or whistle” needs the bigger Flash or faster CPU. Just as long as the basics work.

Ever had to port or make a change to one of those multi-platform libs? I will summarize my feelings: P.I.T.A. Your worst nightmare.

This is an advanced forum, so unlike Arduino.cc where “libraries are the backbone of Arduino”; libraries here are more of a luxury … a few often needed libs were originally ported to assist the community, but simple libraries are best just written as a separate TAB in the ArduinoIDE.

If you want to excel at programming and extend your understanding of physical sensor interfacing, just write functions needed such as Init as a separate Tab … extend each Tab to include related functions. New sensor? New Tab. Easy to follow. Easy to reuse. Easy to evolve without breaking past usages. Easy to ZIP for backup or publishing.

Ray


Leave a Reply

Your email address will not be published. Required fields are marked *