Jump to content
Paul Christopher

Generic board file for ILI9341 and the Arduino IDE based on Adadruit's generic library supporting different boards

Recommended Posts

Hi, I'm trying to get started with the µGFX lib, and my first project is to write a new board file for the Arduino IDE and ILI9341 displays based on the example given in µgfx/boards/base/ArduinoTinyScreen.

The basic idea of my project is to use parts of Adafruit's library "Adafruit_ILI9341", since it supports a wide range of boards (Arduino Mega, TEENSYDUINO, ESP8266, ESP32, Feather STM32, ...): https://github.com/adafruit/Adafruit_ILI9341.

See board file attached: I have already stripped down the library and isolated the necessary parts to keep it small. I have already integrated the parts in the interface necessary for the ILI9341 driver. When init_board is called, I simply create a new instance of the Adafruit class and store it to g->board = new Adafruit_ILI9341(GDISP_PIN_CS, GDISP_PIN_DC, GDISP_PIN_MOSI, GDISP_PIN_CLK, rst, miso) so that I can work with it. By doing this, it should also work with multiple displays, I guess. The other functions such as "acquire_bus" are just wrapper functions

static GFXINLINE void acquire_bus(GDisplay *g) {
    g->board.startWrite();
}

Alas, there is an unsolved problem which keeps me from debugging the application and testing it with different boards. It is a problem related to C and C++ intermingeling: The compiler throws the error "unknown type name 'class'  class Adafruit_ILI9341". I have already tried different things (extern "C"...) but is doesn't work out for me.

Is there any experienced programmer who can give me hint, so that I can finish my work and donate the board file to the project?

By the way: https://wiki.ugfx.io/index.php/Teensy seems to be outdated?  I think the way to go is like shown in µgfx/boards/base/ArduinoTinyScreen?

Maybe we can rename the wiki page in future to "Arduino IDE supported boards" and use the new generic board file based on the Adafruit library as an example project (with wiring examples for Arduino Mega and ESP32)?

My next project would then be to test µGFX Studio and show an integration in Arduino projects as well (as a replacement for the Nextion displays). Espcially the ESP32 seems suitable for that since it has two cores: one core could do the display output stuff, the other core the sensor stuff.

Thanks and cheers!

board_ILI9341.cpp

board_ILI9341.h

ugfx_test.ino

Share this post


Link to post
Share on other sites

Hello & Welcome to the µGFX community!

 

On 12/05/2018 at 21:41, Paul Christopher said:

When init_board is called, I simply create a new instance of the Adafruit class and store it to g->board = new Adafruit_ILI9341(GDISP_PIN_CS, GDISP_PIN_DC, GDISP_PIN_MOSI, GDISP_PIN_CLK, rst, miso) so that I can work with it.

You should use the private area of the GDisplay structure instead (g->priv) for these sorts of things. g->board is used to identify the board if using multiple displays at once.

 

On 12/05/2018 at 21:41, Paul Christopher said:

Alas, there is an unsolved problem which keeps me from debugging the application and testing it with different boards. It is a problem related to C and C++ intermingeling: The compiler throws the error "unknown type name 'class'  class Adafruit_ILI9341". I have already tried different things (extern "C"...) but is doesn't work out for me.

You need to write an intermediate wrapper. You need to call pure C functions from the driver file (which is C code). Those "pure C functions" can be implemented using C++ however. I'd recommend you to have a look at /drivers/gdisp/QImage to get an idea. We did the same there to interface Qt's QImage class in C++.

 

On 12/05/2018 at 21:41, Paul Christopher said:

By the way: https://wiki.ugfx.io/index.php/Teensy seems to be outdated?  I think the way to go is like shown in µgfx/boards/base/ArduinoTinyScreen?

We're very happy if you'd like to participate and update existing or create new documentation. An Arduino specific page would be very welcomed.

 

I hope this helps.

 

 

Share this post


Link to post
Share on other sites

Thanks, Joel! Your hints helped me to isolate the problem, and the code compiles now. I will test the new board file this weekend when I have more time.

By the way: How to deal with the initialization of multiple displays of the same kind in the board file?

To be more generic (so that the user does not need to change the board file itself when using my library), I use #defines in main.c / the main .ino file to pass the pin-setting to the board file, i.e. init_board picks those #defines up and communicates with the board via the mentioned pins.

But what happens if I want to use two displays of the same kind?

Is init_board called twice and does GDisplay *g contain some kind of counter/index like 0, 1 I can use?

If so, I could create some kind of global configuration object and use the passed index in init_board to fetch the right settings from it and store them to g->board:

uint8_t GDISP_PIN_CONFIG a[2][4] = {  
   {0, 1, 2, 3} ,   // MISO, CS, CLK, CD display 0
   {4, 5, 6, 7} 	// MISO, CS, CLK, CD display 1
};

Or do I need to keep track in init_board, how many times it has been called with a static var/counter?

All in all: What is the suggested way to pass the pin settings to the board file from main.c when using two displays of the same kind?

Or have I misunderstood the whole concept of drivers and board files in a way?

Share this post


Link to post
Share on other sites
On 15/05/2018 at 19:23, Paul Christopher said:

By the way: How to deal with the initialization of multiple displays of the same kind in the board file?

That what's the board field in the GDisplay struct is there for. See https://wiki.ugfx.io/index.php/Multiple_displays#Board_files

So what you propose to do is fine, just use the existing g->board integer.

Share this post


Link to post
Share on other sites

The init is called seperately for each display. There is a integer field in the GDisplay structure that reports the display number on the controller and another field that reports the display number in the whole system. See some of the example template board files for various controllers, you will see a switch statement in the init function that demonstrates how 2 displays on the one controller type can be used.

Share this post


Link to post
Share on other sites

Thank you Joel and inmarket for the hints! I have implemented the multiple display support now in the board file.

Attached you can find a working version of a more generic board file for the Arduino IDE based on the Adafruit_ILI9341 library which supports severals boards.

I have successfully tested the board file with "Arduino Uno" and "Arduino Mega 2560". I doesn't work with the ESP32 so far (and presumably ESP8266) -- although the Adafruit library supports them. Reason: No implementation for setjmp.h in the Arduino environment, see https://community.ugfx.io/topic/921-errors-into-compile-ili9341-driver-for-arduino/?tab=comments#comment-6704

In contrast to the ESP8266, the ESP32 (with 2 cores) is however based on FreeRTOS. I tried to enable FreeRTOS for the ESP32, but with no success so far.

All in all, I ran in this well-known issue with the "duplicate const" when compiling, see https://community.ugfx.io/topic/778-ads7843-board-file-for-arduino/?tab=comments#comment-5870

Having changed "gdriver.h", the board file compiled just fine.

You can find a detailed explanation how to setup the Arduino project in readme.txt in the attached zip. The example is based on ugfx/boards/base/ArduinoTinyScreen.

ArduinoILI9341.zip

Share this post


Link to post
Share on other sites

The newbie made a major mistake since he didn't know the difference between "software SPI" and "hardware SPI" and instantiated the Adafruit_ILI9341 class with software SPI  by default. I changed this now, see updated zip file attached.

I'm just wondering about the performance: Drawing a blue solid rectangle all over the screen takes the original Adafruit GFX library 270ms, µGFX with my Adafruit based board file 512ms.

Am I doing something wrong? Is there a way to speed this up? The original Adafruit library seems to have special functions for drawing a rectangle (with no equivalent in µGFX -- sorry for the silly newbie questions?).

void Adafruit_ILI9341::fillRect(int16_t x, int16_t y, int16_t w, int16_t h,
        uint16_t color) {
    startWrite();
    writeFillRect(x,y,w,h,color);
    endWrite();
}

void Adafruit_ILI9341::writeFillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color){
    if((x >= _width) || (y >= _height)) return;
    int16_t x2 = x + w - 1, y2 = y + h - 1;
    if((x2 < 0) || (y2 < 0)) return;

    // Clip left/top
    if(x < 0) {
        x = 0;
        w = x2 + 1;
    }
    if(y < 0) {
        y = 0;
        h = y2 + 1;
    }

    // Clip right/bottom
    if(x2 >= _width)  w = _width  - x;
    if(y2 >= _height) h = _height - y;

    int32_t len = (int32_t)w * h;
    setAddrWindow(x, y, w, h);
    writeColor(color, len);
}

void Adafruit_ILI9341::setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
    uint32_t xa = ((uint32_t)x << 16) | (x+w-1);
    uint32_t ya = ((uint32_t)y << 16) | (y+h-1);
    writeCommand(ILI9341_CASET); // Column addr set
    SPI_WRITE32(xa);
    writeCommand(ILI9341_PASET); // Row addr set
    SPI_WRITE32(ya);
    writeCommand(ILI9341_RAMWR); // write to RAM
}

void Adafruit_ILI9341::writePixel(uint16_t color){
    SPI_WRITE16(color);
}

For drawing the rectangle, I use gdispFillArea(10, 10, width-10, height - 10, Blue).

In Adafruit GFX, I use tft.fillRect(10, 10, width-10, height-10, ILI9341_BLUE); The latter seems to be much faster. But I didn't know how to implement the setAddrWindow stuff in my board file.

ArduinoILI9341.zip

Share this post


Link to post
Share on other sites

Thank you for sharing your progress & files with everybody - much appreciated!

 

On 20/05/2018 at 22:50, Paul Christopher said:

I'm just wondering about the performance: Drawing a blue solid rectangle all over the screen takes the original Adafruit GFX library 270ms, µGFX with my Adafruit based board file 512ms.

µGFX can definitely do a lot better than that.

 

On 20/05/2018 at 22:50, Paul Christopher said:

Am I doing something wrong? Is there a way to speed this up? The original Adafruit library seems to have special functions for drawing a rectangle (with no equivalent in µGFX -- sorry for the silly newbie questions?).

µGFX does provide those kind of features as well (in a much bigger scope even). We basically call those "hardware acceleration". There are a number of functions that a GDISP driver can implement to use hardware acceleration. For rectangles that would be the `fill area` function. You can have a look at existing drivers. As you'll see it's simply a matter of turning the hardware support for a feature on in the drivers configuration file and then implementing the corresponding function.
Have a look at the ST7735 driver for an example. You can see that GDISP_HARDWARE_FILLS is enabled in the drivers config file (gdisp_lld_conf.h) and that the drivers implementation file (gdisp_lld_ST7735.c) is implementing the corresponding hardware filling function named gdisp_lld_fill_area().
The actual implementation of such a function varies a lot. Some display controller have a simple command which takes X, Y, width, height and color while other drivers rely on using DMA and similar techniques to speed things up.

Share this post


Link to post
Share on other sites

Thank you Joel for the helpful explanations!

I have now digged more into the code and learned a lot. With GDISP_HARDWARE_STREAM_WRITE, the µGFX driver already implements the approach I pointed out above and which is used in the Adafruit library. And I also had a look into the datasheet: The ILI9341 chip seems not to support GDISP_HARDWARE_FILLS (but for example GDISP_HARDWARE_SCROLL).

My conclusion is, that the bad performance is caused by a) the need to write a wrapper around the Adafruit class, b) no way to speed up the run time by inlining the wrapper functions and c) some remaining inefficencies in the library itself (e.g. like testing on each write whether software or hardware SPI is used, although I only use hardware SPI). But the more changes I make to the library, the more difficult it gets to keep the board file in sync with the ongoing development process on https://github.com/adafruit/Adafruit_ILI9341 .

So the great flexibility of the board file has a price: Performance. But for beginners like me, I think the generic board file is a nice starter for µGFX in Arduino since it works out of the box with different boards. To put it short: There is no need to write a board file.

All in all: If you want to have performance, a) you won't program using the Arduino IDE, b) you will write a MCU specific board file avoiding all the extra calls / evaluations done by the library.

By the way: I had some trouble making the library work with the ESP32, see

and

Attached you can find the updated version of the board file, tested so far on Arduino Uno, Mega 2560, ESP32.

I'm going to test it also with ESP8266. Maybe I will also try to write an ESP32 specific board file to boost performance (avoiding wrappers, unnecessary calls and evaluations).

Generally speaking, I think I have learned two things so far:

1) You need computing power and fast SPI to have good graphical output. Arduino Uno/Mega are far too slow for that (and have too little memory) . ESP32 is better but maybe even too slow?

2) Low-level functions such as spi.write are time critical. It is not a good idea to mess around here with much flexibility (like I tried with my generic board file)

 

ArduinoILI9341.zip

Share this post


Link to post
Share on other sites

If the adafruit library supports a rectangular fill then add that as a gdisp acceleration. It will significantly boost performance. It can still never match the underlying adafruit library as it has its own overheads and it uses the adafruit library, but it will improve the gdisp performance significantly.

By the way, the layering of gdisp over another API is what the Win32, Linux-X, SDL, and OSX display drivers do too. While it has a performance impact over the native library it is a quick and easy way to get started with uGFX and provides great portability. A specific driver can be added later eg linux-Framebuffer.

Share this post


Link to post
Share on other sites

Thank you, inmarket and Joel! I have now implemented the fillrect function used in the Adafruit library as GDISP_HARDWARE_FILLS, and indeed, it is much faster now. All in all, I'm quite happy with the performance: Clearing the screen with a certain color takes 33ms on the ESP32 using SPI.

I had some trouble getting µGFX work with the ESP8266. I have found a solution now by adding a custom setjmp/longjmp. Filling the screen just with a certain color works, but I doubt that for this the ugfx scheduler / ugfx multi-threading solution is used?

What would be a good example in the code base to test whether multithreading / scheduling really works with my patch?

Is this the time for me now to dive into GWIN? Or for what is the ugfx scheduler actually used?

Share this post


Link to post
Share on other sites
12 hours ago, Paul Christopher said:

Thank you, inmarket and Joel! I have now implemented the fillrect function used in the Adafruit library as GDISP_HARDWARE_FILLS, and indeed, it is much faster now. All in all, I'm quite happy with the performance: Clearing the screen with a certain color takes 33ms on the ESP32 using SPI.

Nice, glad to hear that you got everything working! :)
We'd appreciate it if you could create a ready-to-run example for the downloads page and maybe some documentation for the wiki.

 

12 hours ago, Paul Christopher said:

What would be a good example in the code base to test whether multithreading / scheduling really works with my patch?

Have a look at /demos/gos.

 

12 hours ago, Paul Christopher said:

Is this the time for me now to dive into GWIN? Or for what is the ugfx scheduler actually used?

The scheduler is also used for things like GTIMER which in turn is used by GINPUT and GWIN. As mentioned above, /demos/gos should help.

Share this post


Link to post
Share on other sites

Thanks Joel, the demo "demos\modules\gos\threads" works fine on my ESP32 which uses FreeRTOS. Using "Bare Metal" and my own setjmp replacement, it crashes on the ESP8266. The example does not work on Arduino Uno and Mega with "Bare Metal" either. So I am thinking about a replacement/enhancement for the threading in gos_arduino.c (without the setjmp solution in gos_x_threads). There are quite some generic Arduino threading libraries out there, but I haven't found one, that supports yield() within the threads. The all built on the principle that there is a central scheduler that regularily calls the threads (which are non-blocking and could for example be marked as 'paused').

Do you know some code that implements a cooperative scheduler supporting yield() without using setjmp and longjmp? I.e. a scheduler that simply relies on the completion of function calls? Or is this a mission impossible and I would be caught in infinte loops and race conditions? If the solution is not that efficient, I wouldn't care since real programmers who care about efficiency would not use the Arduino IDE. But I have simply no idea how to design a scheduler supporting yield without setjmp. By reinventing the wheel, I also might overlook some established principles. Any idea where to find some basic patterns for this cooperative scheduler?

Share this post


Link to post
Share on other sites

There are two ways to implement multi-tasking..

1. Write an assembly language routine for each CPU variant, or

2. cheat by using setjmp and longjmp.

Method 1 is used internally by FreeRTOS and any other multi-tasking operating system. The operating system writer has done all the hard work for you. Similarly for our BareMetal (RAW32) port we have written assembly routines for the Cortex Arm 0 to 7 series of processors.

Method 2 works because setjmp and longjmp are supposedly a standard part of the C library and therefore this method should work everywhere and on every CPU. The problem here is that sometimes the C library is missing these routines, sometimes it is buggy (setjmp and longjmp are usually not well tested by the library writers), and sometimes a C library from an not-fully-compatible CPU is incorrectly used. Stack checking code that is inserted by some compilers can also cause problems although this can usually be turned off with a compiler flag.

All of this is to provide general multi-tasking, either pre-emptive or cooperative. These other libraries you are talking about are NOT multi-tasking libraries but rather task execution frameworks (as evidenced by their lack of a yield function). They are not the same and uGFX requires true multi-tasking. We are looking at removing that requirement in future (with suitable restrictions in functionality) but that is definitely well after v3.0 as it requires very large internal changes and considerations.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×