Using PCF8574 I2C extender devices in a C++ class

0

I am trying to use ESP32 with PCF8574, using VScode and platformIO. Basically it is working well - I can upload code after compilation and control the leds.

While defining the I2C extender (PCF8574), the physical I2C address is required. In this case it is 0x38, and it is working well from hardware point of view, as long as I use it directly from the setup() routine. The definition looks like this: PCF8574 i2c_ctrl1(0x38); // Option A When placed within an "init()" routine (see the code below, as "Option A"), the init() works well, but of course the "i2c_ctrl" variable is not recognized outside the init().

I was sure that option B (defining the i2c_ctrl variable in the global part of the class will work, yet in this case the compilation failed because of the "0x38" value: "error: expected identifier before numeric constant"

What am I doing wrong?

Here is the code:

#include "PCF8574.h"

class I2C_devices{

public:
    PCF8574 i2c_ctrl(0x38);  // **Option B**

void I2C_init() {
    //PCF8574 i2c_ctrl(0x38); // **Option A**
    i2c_ctrl.pinMode(P3,OUTPUT); // LED1
    i2c_ctrl.pinMode(P4,OUTPUT); // LED2
    i2c_ctrl.pinMode(P5,OUTPUT); // LED3
    i2c_ctrl.pinMode(P6,OUTPUT); // LED4
    i2c_ctrl.pinMode(P7,OUTPUT); // LED5
    i2c_ctrl.begin();
} // of I2C_init

void Led1_On() {
    i2c_ctrl.digitalWrite(P3,LOW);
} // of Led1_On()

void Led2_On() {
    i2c_ctrl.digitalWrite(P4,LOW);
} // of Led2_On()

which one is this ?

did you try having the instance variable initialized as part of the constructor's initialization list ?

The include is:

  • PCF8574 GPIO Port Expand
  • AUTHOR: Renzo Mischianti
  • VERSION: 2.3.7

From the platformIO access.

When declared and used within the i2c_init routine it was working well.

The problem is when defined (exactly the same) after the "public" of the class.

try something like this where you initialise the instance in the constructor

(typed here so not sure)

#include "PCF8574.h"

class Extension {
  private:
    PCF8574 pcf8574;
  public:
    Extension(const byte addr = 0x38) : pcf8574(addr) {}
    void begin() {
      pcf8574.pinMode(P3, OUTPUT); // LED1
      pcf8574.pinMode(P4, OUTPUT); // LED2
      pcf8574.pinMode(P5, OUTPUT); // LED3
      pcf8574.pinMode(P6, OUTPUT); // LED4
      pcf8574.pinMode(P7, OUTPUT); // LED5
      pcf8574.begin();
    }
    void led1On() {pcf8574.digitalWrite(P3, LOW);}
    void led2On() {pcf8574.digitalWrite(P4, LOW);}
};

Extension extender; // will use default address from the constructor 0x38

void setup() {
  extender.begin();
  extender.led1On();
  extender.led2On(); 
}

void loop() {}

Failing on extension: "error: ISO C++ forbids declaration of 'Extension' with no type [-fpermissive]"

You're doing something wrong. Post your complete updated code.

Try with

    Extension(const uint8_t addr = 0x38) : pcf8574(addr) {}

(you might need to include Arduino.h too if using platformIO)

Same failure: error: ISO C++ forbids declaration of 'Extension' with no type [-fpermissive]
Extension(const uint8_t addr = 0x38) : pcf8574(addr) {}

Arduino.h was added, yet no chnage.

image

Your improperly posted code is incomplete.

Couldn't you just try my full code.. you did not copy my code... I renamed the class from I2C_devices to Extension...

➜ the constructor has the same name as the class and not returned type.

You are right, I usually try it manually to understand the changes, I missed that part. My bad. Now it is passing compilation - without the actual write to hardware.
I will try that soon.

It is working !
I highly appreciate the good support and help.

I am not much of a programmer, yet I write a lot of simple code with classes for controllers application (as a hobby, not a pro). Yet I never face that solution.

Could you kindly explain this line: "Extension(const byte addr = 0x38) : pcf8574(addr) {}"

I'm assuming you are familiar with classes, member variables (instance variables) and member functions (methods).

A special member function in a class is the constructor; This function gets called automatically by the compiler when an object of that class is created. Its main purpose is to initialize the object's member variables or perform any setup required for the object to function properly.

In the Extension class above, the constructor is:

Extension(const byte addr = 0x38) : pcf8574(addr) {}

When an object of the Extension class is created, this constructor is called and in the given example, the Extension class has a constructor that takes one parameter, addr, with a default value of 0x38. When an object of the Extension class is created, this constructor is invoked.

if I do

Extension extender;

then extender is the instance and the constructor got called with addr using the default value 0x38

If I had done

Extension extender(0x40);

then extender is the instance and the constructor got called with addr using the value 0x40
➜ the default value lets you instantiate the object without passing any parameter but you can tailor it if needed.

The constructor uses an initialization list, which is an efficient way to initialize member variables in C++.

An initialization list is written after the constructor's parameter list and is preceded by a colon, followed by a comma-separated list of member initializations. In the provided example, the initialization list
: pcf8574(addr)
initializes the member variable pcf8574 with the value of addr.

Because pcf8574 is of type PCF8574, It specifies that the pcf8574 member should be initialized using its own constructor, with addr as the argument. This means pcf8574 is directly constructed with the value 0x38 (or whatever value addr has).

Using an initialization list is beneficial because it is often more efficient than assigning values to member variables inside the constructor's body. This is especially true for objects of user-defined types, such as PCF8574 in this example, where calling the constructor directly with the required parameters avoids the overhead of default construction followed by an assignment.


Note that in your case the Extension class could really be just a subclass of PCF8574 since it's a specialisation of that class. You could have written (using the same concept as initialisation list but here note the special syntax to call the parent class constructor : PCF8574(addr)

#include "PCF8574.h"

class Extension : public PCF8574 {
  public:
    Extension(const byte addr = 0x38) : PCF8574(addr) {}
    
    void begin() {
      pinMode(P3, OUTPUT); // LED1
      pinMode(P4, OUTPUT); // LED2
      pinMode(P5, OUTPUT); // LED3
      pinMode(P6, OUTPUT); // LED4
      pinMode(P7, OUTPUT); // LED5
    }

    void led1On() { digitalWrite(P3, LOW); }
    void led2On() { digitalWrite(P4, LOW); }
};

// Create an instance of Extension with the default address 0x38
Extension extender;

void setup() {
  extender.begin();
  extender.led1On();
  extender.led2On(); 
}

void loop() {}

1 Like