PIMPL idiom with PlatformIO

Hi guys,

I have a problem and I would ask you for help.

I have the following scenario:
I built a small software for Mega2560 compatible board.
Now, I want to switch to a Due compatible board.
In order to use the DMA of the Due I have to switch also the display library. The display controller is not the same.

I though using the PIMPL idiom would be good to “hide” the actual hardware dependent implementation.
I created first the wrapper UI.h
#ifndef UI_H
#define UI_H

#include <SPI.h>

#define TFT_RST 10
#define TFT_DC 7
#define TFT_CS 9

class UI
{
    public:
      UI();
      ~UI();

      void begin();
      void reset();
      void drawSth();

    private:
      struct Impl;
      Impl* _impl;
};

#endif

Then, I have the UI.cpp file:
#include “UI.h”

#if Is_Atmel
    #include "UI_ILI9341.cpp"
#else
    #include "UI_ILI9325.cpp"
#endif

At this point, I want to change the implementation based on a build_flag defined in the environments of the platformio.ini

And last but not least the real implementation for the hardware:
#ifndef UI_ILI9341_H
#define UI_ILI9341_H

#include "UI.h"
#include "ILI9341_due_config.h"
#include "ILI9341_due.h"
#include "allFonts.h"
#include "allIcons.h"

struct UI::Impl
{
    ILI9341_due tft = ILI9341_due(TFT_CS, TFT_DC, TFT_RST);

    void begin()
    {
        tft.begin();
        tft.setRotation(iliRotation90);

        reset();
    }

    void reset()
    {
        tft.fillScreen(ILI9341_WHITE);
    }

     void drawSth()
     {
          tft.fillRectangle(0, 0, 20, 20, ILI9341_BLACK);
      }
}

UI::UI()
{
    _impl = new Impl();
}

UI::~UI()
{
    delete _impl;
}

void UI::begin()
{
    _impl->begin();
}

void UI::reset()
{
    _impl->reset();
}

void UI::drawSth()
{
     _impl->drawSth();
 }

#endif

In addition, there is of course an implementation for the other display and the Atmega2560.
But for now, the implementation is completely empty. The public methods are available, but empty.

When I am trying to compile the whole thing for the Atmega2560 I am getting the following error:
In file included from C:\Users\phoef.platformio\packages\framework-arduinoavr\cores\arduino/Arduino.h:28:0,
from C:\Users\phoef.platformio\packages\framework-arduinoavr\libraries_cores_\arduino\SPI\src/SPI.h:17,
from lib\UI\UI.h:4,
from lib\UI\UI_ILI9341.cpp:4:
lib\Fonts/ArialRounded34.h:48:33: error: variable ‘ArialRounded34’ must be const in order to be put into read-only section by means of 'attribute((progmem))'
static uint8_t ArialRounded34[] PROGMEM = {
^
lib\Fonts/Arial29.h:48:26: error: variable ‘Arial29’ must be const in order to be put into read-only section by means of 'attribute((progmem))'
static uint8_t Arial29[] PROGMEM = {
^
*** [.pioenvs\megaatmega2560\libf9d\UI\UI_ILI9341.o] Error 1

I was hoping, that the LDF would ignore the not included file.
I guess it is not, because I am including the cpp file?

Any ideas how to achieve a hardware independent interface based on environments?

Thanks

How do you define Is_Atmel in the platformio.ini? You can also use #error "Something" to trigger a compile-time error if some define was not set the way you wanted. Thus you can check if you get to your #include "UI_ILI9341.cpp" line.

Here is my platformio.ini.
I think it’s nothing special. I define the Is_Atmel as build_flag.

[platformio]
env_default = due

[env:due]
platform = atmelsam
board = due
framework = arduino
build_flags = 
    -D Is_Atmel=1
monitor_baud = 115200

[env:megaatmega2560]
platform = atmelavr
board = megaatmega2560
framework = arduino
build_flags = 
    -D Is_Atmel=0
monitor_baud = 115200

I think that the definition of the build flags are working.
I’ve added #error "something" to the UI.h and this is not thrown, when compiling for the Atmega2560

#include "UI.h"

#if Is_Atmel
    #error "Something"
    #include "UI_ILI9341.cpp"
#else
    #include "UI_ILI9325.cpp"
#endif

But still the error from my initial post is thrown.
From my point of view, the LDF is passing the wrong files to the compiler. But I do not understand why this is happen.

Any ideas how to get different implementation based on environments/hardware

Has nobody an idea how to solve that?
Pleaaaasee …

You need src_filter. So, you can exclude source files from building depending on the environment type.

Hallo Ivan,

thanks for your reply, it sounds that this is the way to go, but I still have problems.

[env:due]
platform = atmelsam
board = due
framework = arduino
build_flags = 
    -D Is_Atmel=1
    -D I2CDEV_IMPLEMENTATION=I2CDEV_ARDUINO_WIRE
lib_deps =
    Adafruit FRAM I2C
monitor_baud = 115200
src_filter = +<*> -<.git/> -<svn/> -<example/> -<examples/> -<test/> -<tests/> -<../lib/UI_ILI9325/>

I’ve added the src_filter to the platoformio.ini.
I’ve moved the real Implementation for ILI9341 and ILI9325 below lib/UI_ILI9341 respectively lib/ILI9325 and excluded this folder for the other environment.
But then I am getting strange SPI.h is missing errors.

Is it a problem, that these classes are in the library folder? Should I put them below the src folder of the project?

src_filter starts from src_dir.

If you need that for a library, see Redirecting...

again, thanks for your help.

I think we’re on the right track, but I still have problems.
Is it possible to exclude single files not directories?

[env:megaatmega2560]
platform = atmelavr
board = megaatmega2560
framework = arduino
build_flags = 
    -D Is_Atmel=0
    -D I2CDEV_IMPLEMENTATION=I2CDEV_BUILTIN_FASTWIRE
monitor_baud = 115200
srcFilter = +<*> -<.git/> -<svn/> -<example/> -<examples/> -<test/> -<tests/> -<ILI9341_due/> -<Fonts/> -<Icons/> -<UI/UI_ILI9341.cpp>

Here is the error message:

In file included from C:\Users\phoef\.platformio\packages\framework-arduinoavr\cores\arduino/Arduino.h:28:0,
from C:\Users\phoef\.platformio\packages\framework-arduinoavr\libraries\__cores__\arduino\SPI\src/SPI.h:17,
from lib\UI\UI.h:4,
from lib\UI\UI_ILI9341.cpp:4:
lib\Fonts/ArialRounded34.h:48:33: error: variable 'ArialRounded34' must be const in order to be put into read-only section by means
of '__attribute__((progmem))'
static uint8_t ArialRounded34[] PROGMEM = {
^
lib\Fonts/Arial29.h:48:26: error: variable 'Arial29' must be const in order to be put into read-only section by means of '__attribute__((progmem))'
static uint8_t Arial29[] PROGMEM = {
^
lib\Icons/warning.h:9:40: error: size of array 'warning' is too large
const uint16_t warning[16714] PROGMEM={
^
lib\Icons/black.h:9:33: error: size of array 'black' is too large
const uint16_t black[47680] PROGMEM={
^
lib\UI\UI_ILI9341.cpp: In member function 'void UI::Impl::drawSth()':
lib\UI\UI_ILI9341.cpp:125:31: error: 'warning' was not declared in this scope
tft.drawImage(warning, tft.width() / 2 - warningWidth / 2,
^
lib\UI\UI_ILI9341.cpp: In member function 'void UI::Impl::drawSth2()':
lib\UI\UI_ILI9341.cpp:318:23: error: 'black' was not declared in this scope
tft.drawImage(black, 0, tft.height() / 2 - blackHeight / 2, blackWidth, blackHeight);
^
*** [.pioenvs\megaatmega2560\libf9d\UI\UI_ILI9341.o] Error 1

When moving the hardware specific part of the UI classes in an separate folder, I’ll get some strange errors, that the SPI.h library could not be found …

Any ideas?
Thanks for your help!

Sure, just use srcFilter = +<*> -<src/single.file>

as you can see in my last post, I‘ve excluded the UI file for other display, but according to the error message it is still considered during compilation?

-<UI/UI_ILI9341.cpp>

from lib\UI\UI_ILI9341.cpp:4:

Could you provide a simple project to reproduce this issue? Also, please check a case of letters.

Sure, please find attached a sample project.
https://1drv.ms/u/s!Apt1qXSUQKkxxFk1MGUZ3sKKLeQw

When compiling for due everything is working fine.
But when I am trying to compile for the mega2560, then an error occurred as platformio seems to include the Fonts folder into the compilation process.

Thanks for your help.

You have a Library Dependency Graph will details. Also, check in verbose mode pio run -v.

See DueDisplayTest/lib/UI/UI_ILI9341.cpp which depends on DueDisplayTest/lib/Fonts/allFonts.h

Please read Library Dependency Finder (LDF) — PlatformIO latest documentation and learn how to exclude some library from a build process.

Hallo Ivan,

thanks for you reply.
I am sorry, but I still didn’t get it.

I set the LDF mode to chain+. As far as I understood the documentation, I thought the LDF would follow my #if Is_Atmel pre-processor statements and NOT include the UI_ILI9341 files. Then, I would have expected that all the ILI9341 related files would not be considered by the LDF?

But I still getting an error …

The other way around, when compiling for the due and the chain+ option is set, I am also getting an error, that the ILI9341_due_config.h could not be found …

I am getting more and more confused.
Could you please help again :-)?

Now I understand what do you mean. You have a few files in a library and want to exclude someone from a build process. src_filter in platformio.ini works ONLY FOR PROJECT SOURCE FILES.

If you need dynamically exclude files from build process, you need extraScript for library. There is a good example on that page how to do that.

Hallo Ivan,

thanks for your help, I might have found a solution.
According to your last post, I understand, that excluding the source files is not possible, when they are libraries (only with a extraScript).

But I do not neceserraly have the need to put the UI in the lib folder.
So, I moved all the files in the src_dir/UI folder. Then I enabled the src_filter again. Excluding both UI_ILI**** files.
In addition I’ve set the LDF mode to chain+, as otherwise the config.h will not properly loaded for the due.

I’ve uploaded the project again: https://1drv.ms/u/s!Apt1qXSUQKkxxFk1MGUZ3sKKLeQw
Could you please have a look if this is a robust and valid solution?

Thanks,
caldi

I checked it. Looks good!

Happy coding with PlatformIO! :blush:

Thank you so much :slight_smile: