Hierachical header structure

Hi,

I have an bigger project with my arduino with an e-Paper display and and a keyboard an something like that and would like to get more structure and cleanness into the project. Therefore I would like to outsource some methods into header and cpp-files like:

main.ino

#include <master.h>

void setup()
{
// ...
}

void loop()
{
loop_actions();
}

master.h

#ifndef MASTER_H
#define MASTER_H

#include "keyboard.h"
#include "display.h"

#define MAX_MESSAGE_LENGTH 200

void loop_actions(void);

#endif

master.cpp

#include "master.h"

void loop_actions(void) { 
  // Commands done periodically in main-loop
  }

keyboard.h

#ifndef KEYBOARD_H
#define KEYBOARD_H

#include <BBQ10Keyboard.h>

#define KB_INTERRUPT_PIN 13 // Interrupt pin
// SDA = 21
// SCL = 22

char* read_keys (void);

#endif

keyboard.cpp

#include "keyboard.h"

char* read_keys (void) { 
  // read from keyboard
}

display.h
display.cpp
Similar to keyboard

Is that possible? I tried it with the code-pieces I posted, but IDE says there are multiple calls for every externel/library include.

Could you please help me

Thanks in advance
sut3

post a minimal zip with all the files necessary to demonstrate the behavior you see

How about just using tabs ?

You can have tabs with .ino or .h or .cpp or .hpp etc.

If you use .ino the IDE will (likely) merge everything into a large file before attempting to compile, so it might work - but is far from standard C++

Just change #include <master.h> to #include "master.h" and at least I've successfully compiled with the file structure you posted.

EDIT:
If you want to use Arduino standard functions etc., you need to add #include <Arduino.h> to each header file.

1 Like

In main.ino I had to change #include <master.h> to #include "master.h" so the tab would be used.

I had to comment out #include <BBQ10Keyboard.h> and #include "display.h" because you didn't include those files.

There was a warning in keyboard.cpp because char * read_keys (void) didn't return a value.

After those changes it compiled without error.

1 Like

Thank you very much (also @johnwasser ) - I will try it tomorrow and will report my sucess or not :wink:

sut3

So I tried it today an get

This is the new/real code:

sketch\src.ino.cpp.o:(.bss.display+0x0): multiple definition of `display'
sketch\master.cpp.o:(.bss.display+0x0): first defined here
collect2.exe: error: ld returned 1 exit status
exit status 1
Error compiling for board WiFi LoRa 32(V2).

I changed display.h to displayown.h, because I thought meybe there is a multiple use of the name...

src.ino

#include "master.h"

BBQ10Keyboard keyboard;
volatile bool dataReady = false;
char EPD_preview_buffer[MAX_MESSAGE_LENGTH];
int EPD_preview_buffer_pos = 0;
int EPD_preview_cursor_pos = 0;

char fifo[FIFO_SIZE];
int fifo_in_pos = 0;
int fifo_out_pos = 0;
int fifo_elements = 0;

int rowUpperEdge[] = {10, 26, 42, 58, 74, 90, 106, 122}; // Cursorposition for text with 1px
int rowLowerEdge[] = {11, 27, 43, 59, 75, 91, 107, 123}; // Cursorposition for text with 1px
int maxColChars = 21;
int mailboxGUILine = 0;
int initialized = 0;

String messagesLines[MAX_MESSAGES];
int messagesLinesCount = 7;

void setup()
{
  Serial.begin(115200);
  Serial.println();
  Serial.println("setup");
  delay(100);

  display.init(115200);

  display.firstPage();
  do
  { 
    display.fillScreen(GxEPD_WHITE); // set the background to white (fill the buffer with value for white)
    display.fillRect(0, 79, maxColChars*11 + 7, 1, GxEPD_BLACK); // Trennstrich horizontal
    display.fillRect(maxColChars*11 + 7, 0, 1, display.height(), GxEPD_BLACK); // Trennstrich vertikal
  }
  while (display.nextPage());
  

  delay(50);
  Serial.println("setup display end");
}

void loop()
{
// ...
}

master.h

#ifndef MASTER_H
#define MASTER_H

#define MAX_MESSAGE_LENGTH 200
#define FIFO_SIZE 20

#include <Arduino.h>
#include "keyboard.h"
#include "displayown.h"

void printFIFO (char fifo[]);
void printArray (char a[], int s);

#endif

master.cpp

#include "master.h"

void printFIFO (char fifo[]) {
  for (int i = 0; i < FIFO_SIZE; i++) {
    Serial.printf("%c, ", fifo[i]);  
  }  
  Serial.println("");
}

void printArray (char a[], int s) {
  for (int i = 0; i < s; i++) {
    Serial.printf("%c, ", a[i]);  
  }  
  Serial.println("");
}

displayown.h

// Code from ZinggJM/GxEPD2^1.3.6
#ifndef DISPLAYOWN_H
#define DISPLAYOWN_H

#include <Arduino.h>
#include <GxEPD2_BW.h>
#include <Fonts/FreeMonoBold9pt7b.h>
#include "GxEPD2_display_selection_new_style.h"

#include "bitmaps/Bitmaps128x296.h" // 2.9"  b/w

#define ENABLE_GxEPD2_GFX 0
#define ENABLE_GxEPD2_GFX 0
#define MAX_MESSAGES 10
#define MAILBOXGUI_LINES 5

#define MAX_EPD_PREVIEW_LINES 3
#define MAX_EPD_PREVIEW_ROWS 21

#define FIFO_SIZE 20
#define MAX_MESSAGE_LENGTH 200

#endif

keyboard.h

#ifndef KEYBOARD_H
#define KEYBOARD_H

#include <Arduino.h>
#include <BBQ10Keyboard.h>

#define KB_INTERRUPT_PIN 13 // Interrupt pin
// SDA = 21
// SCL = 22

#endif

I have no idea why this is not working...

It would be helpful if you actually complied with this request from Reply #2. Nobody can test-compile your code with all those #include statements when they don't have the actual files you're trying to #include. In other words, post an MRE!!!

It looks like one of the files included by 'displayown.h' defines the global variable 'display'. My guess is "GxEPD2_display_selection_new_style.h" (which you did not provide). Since 'displayown.h' is included by 'master.h' and 'master.h' is included in both 'src.ino' and 'master.cpp' the global variable 'display' is defined twice.

Maybe you can change the global definition in the .h file into an extern reference and move the definition into master.cpp. That way you only have one definition of the variable but can use it anywhere 'master.h' is included.

src.zip (2,4 MB)

Here is my structure :slight_smile:

[platformio]
default_envs = heltec_wifi_lora_32_V2

[env]
lib_deps = 
	adafruit/Adafruit GFX Library @ ^1.10.10
	adafruit/Adafruit BusIO @ ^1.8.2
	heltecautomation/ESP32_LoRaWAN @ ^2.1.1
	arturo182/BBQ10Keyboard @ ^1.1.0
	zinggjm/GxEPD2 @ ^1.3.6
upload_speed = 115200
monitor_speed = 115200

[env:heltec_wifi_lora_32_V2]
platform = espressif32
board = heltec_wifi_lora_3

Yes. That is the problem. If I include every own library in main.ino everything works fine.

WHat do you mean? I don'T exectly understand how to change the global definition in the .h file into an extern reference.

Thank you
sut3

Is there any reason you're unwilling to post an MRE as requested?

Change the definition:
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=5*/ 2, /*DC=*/ 12, /*RST=*/ 17, /*BUSY=*/ 39)); // Heltec LORA
to something like this declaration:
extern GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display; // Heltec LORA
and put the declaration:
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=5*/ 2, /*DC=*/ 12, /*RST=*/ 17, /*BUSY=*/ 39)); // Heltec LORA
into master.cpp.

Maybe you could use '#ifdef' to put both in the .h file:

#ifdef DEFINE_display_HERE
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=5*/ 2, /*DC=*/ 12, /*RST=*/ 17, /*BUSY=*/ 39)); // Heltec LORA
#else
extern GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display; // Heltec LORA
#endif

Then, in master.cpp put:
#define DEFINE_display_HERE
just above:
#include "GxEPD2_display_selection_new_style.h