Apologies - Multi-file project help

I am really keen to move from the Arduino IDE to PlatformIO but have experienced nothing but trouble when trying to migrate multi-file .ino projects across. Starting from scratch to try and learn the more robust C++ method I’m struggling to even move the simple example below across. I have read numerous threads and applied them to the point of only two errors but still it’s clearly not the right way to do things.

People have asked for a tutorial before and I certainly take the point that there are many references on this topic. I am conscious however that if I finally get the below working that it may not be the “best way” of doing things and I want to learn to be as efficient as possible.

I have not been lazy, I have tried many ways of splitting the OneWire functions out into their own file (protoType.h declarations, 1Wire.cpp containing the necessary variables and functions) but was wondering if some kind soul could take the single file example below and split to multi-file so I can analyse the “best-practice” approach before I continue down a path that meanders too far??

The below works fine as a single “main.cpp”

#include <Arduino.h>
#include <DallasTemperature.h>
#include "protoTypes.h"

//Variable definitions 
int numberOfSensors = 0;                          // 1 Wire number of Sensors
float tempProbeA = 0;                             // DS18B20 Temperature variable for probe 1 (Upper)
float tempProbeP = 0;                             // DS18B20 Temperature variable for probe 2 (Lower)
float tempProbeF = 0;                             // DS18B20 Temperature variable for probe 3 (Filter Pack)  
int tempProbeARounded = 0;                        // Probe 1 Float value as integer for Google Charts
int tempProbePRounded = 0;                        // Probe 2 Float value as integer for Google Charts
int tempProbeFRounded = 0;                        // Probe 3 Float value as integer for Google Charts

//Definitions
#define ONE_WIRE_BUS 04

//Instances
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

void setup() {

  Serial.begin(115200);
  sensors.begin();                    
  discoverOneWireDevices(); 
  readOneWireTemperatures();

}

void loop() {  

}

int discoverOneWireDevices(void) {
  byte i;
  byte addr[8];
  int count = 0;

  Serial.print("Looking for 1-Wire devices...\n\r");
  while(oneWire.search(addr)) {
    Serial.print("\n\rFound \'1-Wire\' device with address:\n\r");
    for( i = 0; i < 8; i++) {
      Serial.print("0x");
      if (addr[i] < 16) {
        Serial.print('0');
      }
      Serial.print(addr[i], HEX);
      if (i < 7) {
        Serial.print(", ");
      }
    }
    if ( OneWire::crc8( addr, 7) != addr[7]) {
       Serial.print("CRC is not valid!\n");
        return 0;
    }
    count ++;
 }
  Serial.print("\n\r\n\rThat's it.\r\n");  
  Serial.print("Number of Sensors ");
  numberOfSensors = count;
  Serial.println(count);
 return numberOfSensors;
}

void readOneWireTemperatures(){
  Serial.println("Requesting temperatures:");
  sensors.requestTemperatures();
  Serial.println("Done!");

  // Read each of our sensors and print the value
  for (int i = 0; i < numberOfSensors; i++) {                       // Request temperatures from the number of             sensor we discovered (See discoverOneWireDevices)
    Serial.print("Temperature for Device ");
    Serial.print( i );
    Serial.print(" is: ");
    Serial.println(sensors.getTempCByIndex(i) );                     // Why "byIndex"? You can have more than one IC on the same bus. 0 refers to the first IC on the wire
    if (i == 0) {
      tempProbeA = sensors.getTempCByIndex(i);                       // Hardcoded to allow max 3 OneWire     Temp sensors
      tempProbeARounded = tempProbeA;                                // Converts Float to Int for Charts mainly
      //dtostrf(tempProbeA,2,1,tempProbeAAr);
    }
    else if (i == 1) {
      tempProbeP = sensors.getTempCByIndex(i);
      tempProbePRounded = tempProbeP;                                // Converts Float to Int for Charts mainly
      }
    else if (i == 2) {
      tempProbeF = sensors.getTempCByIndex(i);
      tempProbeFRounded = tempProbeF;                                // Converts Float to Int for Charts mainly
      }
  }
return;
}

protoTypes.h

//Function Declarations
int discoverOneWireDevices();
void readOneWireTemperatures ();

Any help would be greatly appreciated to start me on the right path.

You didn’t mention how you want to split the code, i.e. what you want to keep together and what you want in separate files. Given that your temperature related code is the code accessing the OneWire and the DallasTemperature instance, I would tend to move everything into a separate file. But for the sake of the example, let’s assume that you have further code that wants to access the OneWire instance.

If something needs to be used by several files, it must have a declaration in a header file. So let’s introduce resources.h for shared resources such as oneWire:

#ifndef RESOURCES_H
#define RESOURCES_H

#include <Arduino.h>

#define ONE_WIRE_BUS 04

extern OneWire oneWire;


#endif

The relevant thing here is extern. It tells the compiler that this is a declaration and not a definition. The declaration tells the compiler that a OneWire instance called oneWire exists but does not create it. That’s important for header files so they can be included into multiple files. Definitions cannot as they would create several instance of the same object and result in an error (duplicate something…).

Instead of prototypes.h, let’s create a separate header file for each topic / .cpp file. So let’s call it temperature.h:

#ifndef TEMPERATURE_H
#define TEMPERATURE_H

#include <Arduino.h>

extern float tempProbeA;                             // DS18B20 Temperature variable for probe 1 (Upper)
extern float tempProbeP;                             // DS18B20 Temperature variable for probe 2 (Lower)
extern float tempProbeF;                             // DS18B20 Temperature variable for probe 3 (Filter Pack)  
extern int tempProbeARounded;                        // Probe 1 Float value as integer for Google Charts
extern int tempProbePRounded;                        // Probe 2 Float value as integer for Google Charts
extern int tempProbeFRounded;                        // Probe 3 Float value as integer for Google Charts

int discoverOneWireDevices();
void readOneWireTemperatures();

#endif

Now we can move the code related to the temperature sensor into a file called temperature.cpp:

#include <Arduino.h>
#include <DallasTemperature.h>
#include "resources.h"
#include "temperature.h"

int numberOfSensors = 0;                          // 1 Wire number of Sensors
float tempProbeA = 0;                             // DS18B20 Temperature variable for probe 1 (Upper)
float tempProbeP = 0;                             // DS18B20 Temperature variable for probe 2 (Lower)
float tempProbeF = 0;                             // DS18B20 Temperature variable for probe 3 (Filter Pack)  
int tempProbeARounded = 0;                        // Probe 1 Float value as integer for Google Charts
int tempProbePRounded = 0;                        // Probe 2 Float value as integer for Google Charts
int tempProbeFRounded = 0;                        // Probe 3 Float value as integer for Google Charts

DallasTemperature sensors(&oneWire);

int discoverOneWireDevices() {
    ...
}

void readOneWireTemperatures() {
    ...
}

Note that all temperature related variables (including sensors) have been moved to this file as well as they belong to the same topic. For most of them, a declaration has been added to temperature.h. So they can be accessed from other files.

Few things are left in main.cpp now:

#include <Arduino.h>
#include "resources.h"
#include "temperature.h"

OneWire oneWire(ONE_WIRE_BUS);

void setup() {
    Serial.begin(115200);
    discoverOneWireDevices(); 
    readOneWireTemperatures();
}

void loop() {
   // do something with tempProbeA, tempProbeP, tempProbeF...
}   

Note that sensors.begin() is missing. Add it to the start of discoverOneWireDevices().

2 Likes

Thank you so much for the thorough explanation, it’s hugely beneficial and I’ll apply the sage words in the knowledge I’m on the right path.

Is it reasonable therefore to assume if I split out a subset of functions (you refer to such as topics) i.e. temperature in this case but also maybe display for display related functions, that any further .cpp subdivision should have a corresponding .h (as you have suggested above I think?). Assuming a single .h could theoretically hold all the necessary information pan-topics I suspect it’s best practice to split out for ease of reading and quicker future coding ?

As an aside, where I was falling over was the OneWire oneWire and sensors.begin() elements which you have now clearly demonstrated the error of my ways :slight_smile:

Technically you can combine all header files into a single file. However, if you really want to organize your code by topic / subject / concern, you should go with pairs of .h and .cpp files. It’s also the way that scales up to bigger software projects.

2 Likes

For completeness and if anybody is following this, I neglected to add the #include “OneWire.h” in the original single file example. You should, if you are intending to use the above excellent example from Manuel, change resources.h to the below:

resource.h

#ifndef RESOURCES_H
#define RESOURCES_H

#include <Arduino.h>
#include "OneWire.h"

#define ONE_WIRE_BUS 04

extern OneWire oneWire;

#endif
1 Like

You guy Rocked my world.
This really helped out.
Thanks for this clear dialog and advise.
And Yes is work for me.

1 Like