Running functions on separate pages

Hi, although I came to programming and micro controllers a little late in life I have been using Arduino boards for several years now, initially with the Arduino IDE and lately with Atmel Studio but just recently discovered PlatformIO and wish to learn a little more about C++.
I have a tendency on larger programs to place all Functions on a separate page and have had no problems, until now, I have read several articles about doing this in C++ and it seemed quite straight forward until I tried it, I can place simple functions on a page called “functions.cpp” and create a header file and it works fine, but more complex functions are causing me problems, so I have a few questions below.

  1. Should the functions on a separate page be able to use libraries included on main.cpp. Mine don’t seem to be able to.
  2. I cannot seem to change global variables defined on main.cpp from these separate functions.
  3. Does pin definitions and pinMode statements in “setup” apply to functions on a separate page.

You need to read something about C/C++ programming languages. Your 1. and 2. points relate to the same topic called declarations. In order to be able to use library or global variable contained in other source files, you need to declare them in current source file.

Usually this is done by including a header file which contains necessary declarations. In case of library, such header files are shipped with the rest of library source files. In case of source files written by you, you should write corresponding header files too.

Example links:
https://en.cppreference.com/w/c/language/declarations

And yes, pin definitions and pinMode statements in setup() will apply to functions in separate source file IF they are called after setup().

1 Like

Hi, thanks for your help, I am reading C++ tutorials online but I am still having trouble on this issue as follows

A device on my front gate sends a signal back when the gate is opening or closing via a rf24L01 transceiver module, and works fine when all on main.cpp. (I’m just using this program to practise with)

I have 3 files, main.cpp, functions.cpp and functions.h

One simple function to measure battery voltage works OK on functions.cpp but only with Arduino.h is included and forward declared in functions.h which is included at the top of main.cpp

Function “Sending” which sends the data back via the rf24L01 transceiver, works Ok on main.cpp, but when moved to functions.cpp and forward declared in functions.h it does not compile and complains that anything to do with the RF24Network.h library is not declared in this scope and global variables are not in scope, I cannot work out why!

If I use a very simple program and declare a global variable at near the top of main.cpp and try to use those variables in a function on functions.cpp which is declared in functions.h they are “not declared in this scope” and “undefined”

If you can post the code and the error message we can help you.

1 Like

Hi, Many thanks for your help, this is a cut down version of a program running on a device on my front gate which has been working for about 5 years but I am using a copy of it to practice, it works fine with the 3 functions all on main.cpp but not when separated, the libraries and variables on main.cpp are out of scope.

//----------main.cpp-----------------

#include <Arduino.h>
#include "functions.h"
#include <RF24Network.h> 		//Network library
#include <RF24.h>        		//Radio library
#include <SPI.h>         
#include <OneWire.h>
#include <DallasTemperature.h> 		//Needed for Temperature sensor
#include "Wire.h"
//************Variables etc***********************
OneWire oneWire(4);           		//Pin number for DS18B20 temperature sensor
DallasTemperature sensors(&oneWire);
RF24 radio(9, 10);            		// nRF24L01(+) radio attached
RF24Network network(radio);
int Ledpin = 8;
int Gate, Gstate;
const uint16_t Base = 00;
const uint16_t GateTX = 03;
float GtempC, Volts;
struct payload_t
{ //Universal payload structure used on several devices
     float Flt_1;
     float Flt_2;
     int Integ_1;
     int Integ_2;
 };
payload_t payload;

void setup()
{
     SPI.begin();
     Serial.begin(9600);
     sensors.setResolution(10);      		//For DS18B20  Sensor
     radio.begin();                  		//Start radio
     radio.setPALevel(RF24_PA_HIGH); 		//Set power level high on radio
     network.begin(90, GateTX);      		//Channel and Address of this node
     radio.startListening();
     radio.setDataRate(RF24_250KBPS); 		//Set data rate
     radio.setRetries(15, 15);
     Wire.begin();
     pinMode(Ledpin, OUTPUT);
     pinMode(2, INPUT_PULLUP);
     pinMode(3, INPUT_PULLUP);
} //End of Setup
void loop()
{
     network.update();
     if (digitalRead(2) == LOW) //Gate is opening
     {
          Gstate = 10; //10 = gate opening
          Volts = Voltage();
          GtempC = Temperature();
          payload = {GtempC, Volts, Gstate, 0 }; //Set the payload data followed with dummy data
          Sending();
          //Double flash indicates opening
          digitalWrite(Ledpin, HIGH);
          delay(150);
          digitalWrite(Ledpin, LOW);
          delay(150);
          digitalWrite(Ledpin, HIGH);
          delay(150);
          digitalWrite(Ledpin, LOW);
     }
         delay(3000);
     if (digitalRead(3) == LOW)
     {
          Gstate = 5; //5 = gate closing
          Volts = Voltage();
          GtempC = Temperature();
          payload = {GtempC, Volts, Gstate, 0}; //Set the payload data 
          Sending();
          //Single flash indicates closing
          digitalWrite(Ledpin, HIGH);
          delay(150);
          digitalWrite(Ledpin, LOW);
     }
} //End of Loop

//-----------------functions.cpp------------------------------

#include <Arduino.h>
float Voltage()
{
     float Volts;
     int sensorValue = analogRead(A0);      		// read the input on analog pin A0:
     Volts = sensorValue * (3.39 / 1023.0); 		// Convert the analog reading
     Volts = Volts * 2.0;
     return Volts;
}
 float Temperature()
 {    
     sensors.requestTemperatures();                    	//Fetch temperatures
     float tempC = sensors.getTempCByIndex(0);        	//And load variable with correct data
     delay(100);
     return tempC;
 }
  void Sending()      //Send data back
{
     delay(100);
     RF24NetworkHeader header(Base); //Set address to go to
     radio.stopListening();
     network.write(header, &payload, sizeof(payload)); 	// Transmit the data
     radio.startListening();
     radio.read(&payload, sizeof(payload)); 		// Listen for acknowledgment from the receiver
     delay(100);
     network.update();
}

//--------------functions.h-----------------------

float Voltage();
void Sending();
float Temperature();

//---------ERRORS------
in functions.cpp

'sensors' was not declared in this scope
'RF24NetworkHeader' was not declared in this scope
'radio' was not declared in this scope
'network' was not declared in this scope
'header' was not declared in this scope
'payload' was not declared in this scope

You are accessing variables in your functions.cpp file which you have only declared and defined in the main.cpp file or not at all.

e.g.

  • the sensors object is declared and defined in main.cpp and functions.cpp knows nothing about it
  • the RF24NetworkHeader structure is unknown in functions.cpp because you did not include its definitions via #include <RF24Network.h>
  • the RF24 radio object is unknown to functions.cpp for the same reason as the first bullet point
  • same for network
  • the payload_t type and payload object are only known within main.cpp

So, fixing the code would be:

  • move
struct payload_t
{ //Universal payload structure used on several devices
     float Flt_1;
     float Flt_2;
     int Integ_1;
     int Integ_2;
 };

from main.cpp into a header file (e.g. functions.h) and include it everywhere where it’s needed (i.e. in main.cpp and functions.cpp)

  • declare all shared global variables within functions.h using the extern keyword along with the needed type includes
#ifndef FUNCTIONS_H
#define FUNCTIONS_H

#include <DallasTemperature.h>
#include <RF24Network.h>
#include <RF24.h> 

struct payload_t
{    //Universal payload structure used on several devices
     float Flt_1;
     float Flt_2;
     int Integ_1;
     int Integ_2;
 };

extern DallasTemperature sensors;
extern RF24 radio;
extern RF24Network network;
extern payload_t payload;

float Voltage();
void Sending();
float Temperature();

#endif
  • add include <functions.h> in your functions.cpp file
1 Like

Thanks you so much Max for all your help, it does not seem as simplistic as I had read, and am now beginning to understand the complexities of it, so for me to clarify I take it that the header.h file has to follow the following format:-

#ifndef HEADER_H
#define HEADER_H

Include any libraries needed in separate files
Declare variables on main.cpp with the extern keyword
Declare any variables needed in separate files
And forward declare all functions in other files

#endif

Oh! And include the header.h file in all files where it is needed not just main.cpp

I’m sure I will come across more complications as I learn but this will get me on the right track, my program now compiles Ok.

Once again thank you very much for your help, much appreciated.

1 Like

Forgot to say these are the “include guards” and prevent the double-inclusion of the same file. See here. But yes, you’ve got the important things right. Everytime you call into a function or access a variable it must be known at that point to the compiler. If it is not, then you must declare the existance of this function or variable somewhere (best in a header file) and the variable / function should be defined / implemented in one place only. That’s really all there is to it.

2 Likes

Thanks once again Max you have made things very clear to me, I do know about “include guards” but I am a little unsure about the rules of the “if_not_defined” part, does the program step through each item in the block and either declare it or not depending if it has been declared before, or does it lump all the statements together. If it looks at each one in turn then it would be difficult to declare it twice from the header file, hope I am being clear?

The preprocessor will basically do a text search-and-replace. If you were to e.g. include the file twice, it would produce code such as

#ifndef INCLUDE_GUARD
#define INCLUDE_GUARD

//FUNCTIONS
#endif

#ifndef INCLUDE_GUARD
#define INCLUDE_GUARD

//FUNCTIONS
#endif

It will start parsing top-to-bottom, and the first time it’s included the macro INCLUDE_GUARD will not be defined, to it will define it and declare all the function. The next time it hits the #ifndef INCLUDEGUARD the condition is not met because the the macro INCLUDE_GUARD is indeed defined, so skips the entire block up to the #endif and does not re-declare the functions. Without include guards and a double #include you would receive warnings such as: redefinition of function...

See here and here

2 Likes

Nb : Declaring structure instances in header files is wrong… Only type definitions, constants and prototypes /macros there. An instance of a structure would be declared in a cpp file and the relevant header would declare it as an extern.

1 Like

I think it’s a very useful example, i sumup it:

— main.cpp —

#include <Arduino.h>
#include <OneWire.h>
#include <SPI.h>
#include "Wire.h"
#include "functions.h"

//************Variables etc***********************
OneWire oneWire(4);  //Pin number for DS18B20 temperature sensor
DallasTemperature sensors(&oneWire);
RF24 radio(9, 10);  // nRF24L01(+) radio attached
RF24Network network(radio);
int Ledpin = 8;
int Gate, Gstate;
const uint16_t Base = 00;
const uint16_t GateTX = 03;
float GtempC, Volts;
payload_t payload;


//
void setup() {
    SPI.begin();
    Serial.begin(9600);
    sensors.setResolution(10);       //For DS18B20  Sensor
    radio.begin();                   //Start radio
    radio.setPALevel(RF24_PA_HIGH);  //Set power level high on radio
    network.begin(90, GateTX);       //Channel and Address of this node
    radio.startListening();
    radio.setDataRate(RF24_250KBPS);  //Set data rate
    radio.setRetries(15, 15);
    Wire.begin();
    pinMode(Ledpin, OUTPUT);
    pinMode(2, INPUT_PULLUP);
    pinMode(3, INPUT_PULLUP);
}  //End of Setup

//
void loop() {
    network.update();
    if (digitalRead(2) == LOW)  //Gate is opening
    {
        Gstate = 10;  //10 = gate opening
        Volts = Voltage();
        GtempC = Temperature();
        payload = {GtempC, Volts, Gstate, 0};  //Set the payload data followed with dummy data
        Sending(Base);
        //Double flash indicates opening
        digitalWrite(Ledpin, HIGH);
        delay(150);
        digitalWrite(Ledpin, LOW);
        delay(150);
        digitalWrite(Ledpin, HIGH);
        delay(150);
        digitalWrite(Ledpin, LOW);
    }
    delay(3000);
    if (digitalRead(3) == LOW) {
        Gstate = 5;  //5 = gate closing
        Volts = Voltage();
        GtempC = Temperature();
        payload = {GtempC, Volts, Gstate, 0};  //Set the payload data
        Sending(Base);
        //Single flash indicates closing
        digitalWrite(Ledpin, HIGH);
        delay(150);
        digitalWrite(Ledpin, LOW);
    }
}  //End of Loop

— functions.h —

#ifndef FUNCTIONS_H

#define FUNCTIONS_H

#include <DallasTemperature.h>
#include <RF24.h>
#include <RF24Network.h>

struct payload_t { //Universal payload structure used on several devices
   float Flt_1;
   float Flt_2;
   int Integ_1;
   int Integ_2;
};

extern DallasTemperature sensors;
extern RF24 radio;
extern RF24Network network;
extern payload_t payload;
float Voltage();
void Sending(const uint16_t Base);
float Temperature();

#endif

— functions.cpp —

#include <functions.h>

float Voltage() {
    float Volts;
    int sensorValue = analogRead(A0);       // read the input on analog pin A0:
    Volts = sensorValue * (3.39 / 1023.0);  // Convert the analog reading
    Volts = Volts * 2.0;
    return Volts;
}

float Temperature() {
    sensors.requestTemperatures();             //Fetch temperatures
    float tempC = sensors.getTempCByIndex(0);  //And load variable with correct data
    delay(100);
    return tempC;
}

void Sending(const uint16_t Base)  //Send data back
{
    delay(100);
    RF24NetworkHeader header(Base);  //Set address to go to
    radio.stopListening();
    network.write(header, &payload, sizeof(payload));  // Transmit the data
    radio.startListening();
    radio.read(&payload, sizeof(payload));  // Listen for acknowledgment from the receiver
    delay(100);
    network.update();
}

— platformio.ini —

[env:uno]
platform = atmelavr
board = uno
framework = arduino

lib_deps =
OneWire
DallasTemperature
RF24
RF24Network

1 Like

Thanks for your comment Dave, I am now using this as a sort of template for similar future code.

2 Likes

I’m in your condition, sometime i need to combine more functions.

I added the platformio.ini file, it’s necessary for the library.