Separating logic into multiple files

Hi,
I would like to know what's wrong with (this) approach of including file in another .ino sketch just for separating parts of the program.
I have main file:

#include <mqtt.h>

void setup() {
  Serial.begin(115200);

  pinMode(16, OUTPUT);
  digitalWrite(16, LOW);   

  // delay(42);    
  startWifi();
  delay(42);    
  reconnMqtt();
}

unsigned long next_fire = 0;
void loop() {
  mqttLoop();
}

and the included one mqtt.h:

#ifndef Mqtt_h
#define Mqtt_h

#include <PubSubClient.h>
#include "WiFi.h"
#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager 

bool on = false;
void callback(char* topic, byte* payload, unsigned int length);

WiFiClient wifi;
IPAddress server(192, 168, 0, 107);
PubSubClient client(server, 1883, callback, wifi);

bool startWifi() {
  WiFiManager wm;

  bool res = wm.autoConnect("AutoConnectAP", "password");  // password protected ap

  if (!res) {
    Serial.println("Failed to connect wifi");
  } else {
    Serial.println("wifi connected...yeey :)");
  }

  return res;
}

void reconnMqtt() {
  client.setBufferSize(2048);

  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP32C3")) {
      Serial.println("connected");
      client.subscribe("ping");
      client.subscribe("cam");
      client.subscribe("switch");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  String message = ""; 
  String domain = String(topic);

  for (int i = 0; i < length; i++) {
    message += (char)payload[i];
    // Serial.print((char)payload[i]);
  }

  Serial.println(message);
  // shoot();

  if(domain == "ping") {
    client.publish("pong", message.c_str());
  }

  if (domain == "cam") {
    if (message == "shoot") { 
      Serial.println("should be shooting");
    }
  }

  if (domain == "switch") {
    on = !on;
    Serial.print("will turn: ");
    Serial.println(on);

    digitalWrite(16, on);   
  }
}

void mqttLoop() {
  client.loop();
}


#endif

this ends with this stacktrace and I googled and found answers such that it's caused by (wrong?) memory allocation for a variable (I suppose it would be WiFiClient in my case).

it crashes as follows:

16:00:39.304 -> Attempting MQTT connection...Guru Meditation Error: Core  1 panic'ed (LoadProhibited). Exception was unhandled.
16:00:39.336 -> 
16:00:39.336 -> Core  1 register dump:
16:00:39.336 -> PC      : 0x40091691  PS      : 0x00060d30  A0      : 0x800919e4  A1      : 0x3ffb1fc0  
16:00:39.336 -> A2      : 0xffffffff  A3      : 0x00060d23  A4      : 0x00060d20  A5      : 0x3f40c004  
16:00:39.336 -> A6      : 0x000000fd  A7      : 0xb33fffff  A8      : 0x00000001  A9      : 0x00000004  
16:00:39.336 -> A10     : 0x00060d23  A11     : 0x000000a6  A12     : 0x00000bb8  A13     : 0x3f40c100  
16:00:39.369 -> A14     : 0x00000077  A15     : 0x6b00a8c0  SAR     : 0x0000001d  EXCCAUSE: 0x0000001c  
16:00:39.369 -> EXCVADDR: 0x0000000f  LBEG    : 0x4008a794  LEND    : 0x4008a7aa  LCOUNT  : 0xffffffff  
16:00:39.369 -> 
16:00:39.369 -> 
16:00:39.369 -> Backtrace: 0x4009168e:0x3ffb1fc0 0x400919e1:0x3ffb1fe0 0x40091405:0x3ffb2000 0x4009153c:0x3ffb2020 0x4008374a:0x3ffb2040 0x4008397a:0x3ffb2070 0x400f631f:0x3ffb2090 0x400d5d5a:0x3ffb2100 0x400d547b:0x3ffb2160 0x400d3665:0x3ffb21a0 0x400d37f6:0x3ffb2210 0x400d2e2a:0x3ffb2240 0x400d2ec9:0x3ffb2260 0x400e9423:0x3ffb2290
16:00:39.401 -> 

when I put it all in a single file it works, so I'm doing something wrong and would like to know about it more, please.

I'm using Esp32-Cam board with AI-Thinker ESP32-CAM board selected in Arduino Studio 2.3.2

Thanks!

Where is your mqtt.h located? Using < and > for the include means that it picks something from the default directories and not from the sketch directory. If mqtt.h is located in the sketch directory, you need to use #include "mqtt.h".

Usually when you start separating, you use .h (function prototypes, extern variables) and .cpp files (the actual code, variables).

You can try to rename your mqtt.h to mqtt.ino. That will basically result in one big file that will be compiled. There are disadvantages to this approach so splitting into .cpp and .h files is preferred.

No experience with your board or anything else that you use so can't be of much more help.

This may or may not help you, but I'll throw it in anyway.

In the Version 1.8.x IDE, you can also have multiple tabs, which are separate .ino files in your sketch directory. For example in my present project, I have the following tabs:
[ServoPixelNodeV0_1_0][inputs][serialin][buttonin][IRin][processing][serialout][outputs]

The first tab is the name of the folder as well. #includes(both my libraries, and more commonly available ones), data definitions, and setup() and loop() are in the first tab, and loop() just calls a series of functions in the other tabs. Those more or less follow the IPO model, though debugging usually involves #define-conditional serial output in more than one tab.
It's a relatively simple approach, but it works for me. Others no doubt have more elegant processes.

thanks, that is some useful information for me!

anyway, I tried to make changes you suggested - to rename mqtt.h -> mqtt.ino and "WiFi.h" -> <WiFi.h>, and then that second .ino whouldn't be included in the first one right? I suppose so, because when I had it, it was complaining about double definition.

anyway, I got back to one (where it's all together) that was working ang I found out, I was not connecting mqtt broker and when I called it, it fails the same way as the one with separated routines. the sketch is this, very simple:

#include <PubSubClient.h>
#include <WiFi.h>
#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager 

#define FLASH_GPIO_NUM 4

bool startIncuWifi();
void reconnMqtt();
void callback(char* topic, byte* payload, unsigned int length);

bool on = false;

WiFiClient wifi;
IPAddress server(192, 168, 0, 107);
PubSubClient client(server, 1883, callback, wifi);

void shooting() {
  Serial.println("shoooting");
}

void setup() {
  Serial.begin(115200);

  pinMode(FLASH_GPIO_NUM, OUTPUT);

  pinMode(16, OUTPUT);
  digitalWrite(16, LOW);   

  digitalWrite(4, HIGH);  // turn the LED on (HIGH is the voltage level)
  delay(420);                      // wait for a second
  digitalWrite(4, LOW);   // turn the LED off by making the voltage LOW
  delay(420);    

  // delay(42);    
  startIncuWifi();
  delay(42);    
  reconnMqtt();
  // delay(42);    
}

unsigned long next_fire = 0;
void loop() {
  client.loop();
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  String message = ""; 
  String domain = String(topic);

  for (int i = 0; i < length; i++) {
    message += (char)payload[i];
    // Serial.print((char)payload[i]);
  }
  Serial.println(message);

  if(domain == "ping") {
    client.publish("pong", message.c_str());
  }

  if (domain == "cam") {
    if (message == "shoot") { 
      Serial.println("should be shooting");
    }
  }

  if (domain == "switch") {
    on = !on;
    Serial.print("will turn: ");
    Serial.println(on);

    digitalWrite(16, on);   
  }
}

void reconnMqtt() {
  client.setBufferSize(2048);

  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP32C3")) {
      Serial.println("connected");
      client.subscribe("ping");
      client.subscribe("cam");
      client.subscribe("switch");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

bool startIncuWifi() {
  WiFiManager wm;

  bool res = wm.autoConnect("AutoConnectAP", "password");  // password protected ap

  if (!res) {
    Serial.println("Failed to connect wifi");
  } else {
    Serial.println("wifi connected...yeey :)");
  }

  return res;
}

is there any reason why it should end up such a nasty core dump message?

17:51:56.924 -> Attempting MQTT connection...Guru Meditation Error: Core  1 panic'ed (LoadProhibited). Exception was unhandled.
17:51:56.924 -> 
17:51:56.924 -> Core  1 register dump:
17:51:56.956 -> PC      : 0x40091691  PS      : 0x00060d30  A0      : 0x800919e4  A1      : 0x3ffb1fc0  
17:51:56.956 -> A2      : 0xffffffff  A3      : 0x00060d23  A4      : 0x00060d20  A5      : 0x3f40c004  
17:51:56.956 -> A6      : 0x000000fd  A7      : 0xb33fffff  A8      : 0x00000001  A9      : 0x00000004  
17:51:56.956 -> A10     : 0x00060d23  A11     : 0x000000a6  A12     : 0x00000bb8  A13     : 0x3f40c100  
17:51:56.988 -> A14     : 0x00000077  A15     : 0x6b00a8c0  SAR     : 0x0000001d  EXCCAUSE: 0x0000001c  
17:51:56.988 -> EXCVADDR: 0x0000000f  LBEG    : 0x4008a794  LEND    : 0x4008a7aa  LCOUNT  : 0xffffffff  
17:51:56.988 -> 
17:51:56.988 -> 
17:51:56.988 -> Backtrace: 0x4009168e:0x3ffb1fc0 0x400919e1:0x3ffb1fe0 0x40091405:0x3ffb2000 0x4009153c:0x3ffb2020 0x4008374a:0x3ffb2040 0x4008397a:0x3ffb2070 0x400f6337:0x3ffb2090 0x400d5d72:0x3ffb2100 0x400d5493:0x3ffb2160 0x400d367d:0x3ffb21a0 0x400d380e:0x3ffb2210 0x400d2dba:0x3ffb2240 0x400d2eeb:0x3ffb2260 0x400e943b:0x3ffb2290
17:51:57.020 -> 

i think your fighting multiple problems.

the first is establishing a MQTT connection. the other is how to organize code into multiple files. and you've already written additional code to use an mqtt broker

i suggest you go back to a tutorial and get it to work on your network and with your broker.

it's not clear where your broker is. is it your laptop?

once you get the tutorial up, then add code

connecting to broker is sorted out long time ago. I really just isolated this to find why it all-of-the-sudden started to fail.
and I know it now:

  pinMode(16, OUTPUT);
  digitalWrite(16, LOW);   

this is what I added today (to move on with the project) and that's causing that core dump when connecting MQTT. ESP32-Cam has something on those pins I guess, I just can not use them as digital out. which is really pity because I already soldered everything on that position on the PCB. according to pinout schema, there is something called U2RXD . I would like that pin to be digital out. is there any chance to do it, please?
I don't need SD card for this project and I will need one analog input and I2C.

then as far as creating separate files ...

code typically belongs in .ino or .cpp files and references to that code is in a .h file which may be included in more than one other file.

the problem with what you did by including code in your ,h file is that it would be duplicated if not result in multi symbols error when that .h is included in more than one file.

your .h file should only have declarations for function that are used in the other file that include the .h or use symbols needed in the file with the code

bool startWifi();
void callback(char* topic, byte* payload, unsigned int length) {
void reconnMqtt();
void mqttLoop();

you would have 3 files: your .ino, a .cpp file which was your mqtt.h and a new .mqtt.hj with the declarations above.

hope you get the idea of what belongs in the .h

The exception occurs because something in your code is reading from an invalid memory address. This is most often the result of attempting to read outside the bounds of an array, or de-referencing an null pointer, or a pointer that does not point to a valid memory address. Using the ESP exception decoder should tell you WHERE in the code that invalid access is occuring.

As for using multiple files, that may be a red herring. I would pursue the actual exception first, and see if that solves the problem.

If using multiple .ino files, beware that the .ino files are included in alphabetical order, which could lead you to one or more variables or functions not being defined before they are referenced. I've always found that approach frustrating.

There is an excellent write-up on Nick Gammon's website on the many different way you can organize Arduino applications, up to and including doing way with the whole setup/loop paradigm, and using your own main function. It is well worth tracking down that tutorial, and reading it.

understood; thanks!
also I'll use pinout.h just to define GPIO in/out pins across the application.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.