Having to create two identical instances when using I2C hub

I got my RC522 I2C card reader working by connecting it directly to the I2C port (on my M5Stack Fire) but wanted it to work on my I2C hub. I've cobbled together a sketch (below) to do that and it is working fine, but only if I create an instance of MFRC522 before the setup() and then again after setting up the RFID. I understand why the second instance has to be created/updated after the hub has been set up, but was wondering whether there is a better way of doing this. Perhaps using a pointer.

#include <Wire.h>
#include "MFRC522_I2C.h"
#include "ClosedCube_TCA9548A.h"
#include <M5Stack.h>
#define PaHub_I2C_ADDRESS  0x70
ClosedCube::Wired::TCA9548A tca9548a;

// 0x28 is RFID i2c address on port2.
MFRC522 mfrc522(0x28);   // Create MFRC522 instance.

void setup() {
  //RFID setup
  uint8_t returnCode = 0;
  uint8_t channel = 2;
  tca9548a.address(PaHub_I2C_ADDRESS);
  returnCode = tca9548a.selectChannel(channel);
  Wire.beginTransmission(channel);
  // 0x28 is RFID i2c address on port2.
  MFRC522 mfrc522(0x28);   // Create MFRC522 instance.
  returnCode = Wire.endTransmission();

  M5.begin();
  M5.Power.begin();
  M5.Lcd.fillScreen( BLACK );
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.setTextColor(YELLOW);
  M5.Lcd.setTextSize(2);

  M5.Lcd.fillScreen( BLACK );
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.println("M5StackFire MFRC522");
  Serial.begin(115200);           // Initialize serial communications with the PC
  Wire.begin();                   // Initialize I2C
  //Serial.println(F("Hello from Steve"));

  mfrc522.PCD_Init();             // Init MFRC522
  ShowReaderDetails();            // Show details of PCD - MFRC522 Card Reader details
  Serial.println(F("Scan PICC to see UID, type, and data blocks..."));
  M5.Lcd.println("Scan PICC to see UID, type, and data blocks...");
}

void loop() {

  // Look for new cards, and select one if present
  if ( ! mfrc522.PICC_IsNewCardPresent() || ! mfrc522.PICC_ReadCardSerial() ) {
    delay(50);
    return;
  }
  // Now a card is selected. The UID and SAK is in mfrc522.uid.

  // Dump UID
  Serial.print(F("Card UID:"));
  M5.Lcd.println(" ");

  for (byte i = 0; i < mfrc522.uid.size; i++) {
    Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ");
    Serial.print(mfrc522.uid.uidByte[i], HEX);
    M5.Lcd.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ");
    M5.Lcd.print(mfrc522.uid.uidByte[i], HEX);
  }
  Serial.println();

}

void ShowReaderDetails() {
  // Get the MFRC522 software version
  byte v = mfrc522.PCD_ReadRegister(mfrc522.VersionReg);
  Serial.print(F("MFRC522 Software Version: 0x"));
  Serial.print(v, HEX);
  if (v == 0x91)
    Serial.print(F(" = v1.0"));
  else if (v == 0x92)
    Serial.print(F(" = v2.0"));
  else
    Serial.print(F(" (unknown)"));
  Serial.println("");
  // When 0x00 or 0xFF is returned, communication probably failed
  if ((v == 0x00) || (v == 0xFF)) {
    Serial.println(F("WARNING: Communication failure, is the MFRC522 properly connected?"));
  }
}

In setup() you create an additional local instance that is destroyed on exit of setup(). Remove that creation from setup() and try again.

If you definitely want 2 instances then declare both globally, before setup(), and give them unique names.

If I remove the creation from setup() the sketch doesn't work, it hangs. I think it is probably necessary to give the RC522 I2C address AFTER the I2C hub has been set up, otherwise only the hub is directly connected to the I2C bus and not the RS522.

If I remove the global creation the sketch complains that "mfrc522 was not declared in this scope" originating in the function ShowReaderDetails().

BTW, I don't "want" two instances, I just cannot get the sketch to work without creating two instances. I don't think this can be good programming and that is why I am asking for a better solution.

Of course you should initialize everything in the right order. If you step in before the ship arrives... :wink:
Fix all that and try again.

The M5Stack Fire uses a ESP32 : https://shop.m5stack.com/products/m5stack-fire-iot-development-kit-psram-v2-6.

ClosedCube TCA9548A I2C multiplexer library is very simple : https://github.com/closedcube/ClosedCube_TCA9548A_Arduino

The RC522 is a 3.3V chip. The module could be this one at Amazon or this one at Reichelt or similar.

The M5Stack library are additions for the device : https://github.com/m5stack/M5Stack

The MFRC522_I2C library could be this one : https://github.com/kkloesener/MFRC522_I2C


The TCA9548 is a I2C multiplexer, not a "hub". Could you do me a favor and call it a "mux" or "multiplexer" or "i2c multiplexer" ?

The constructor of the MFRC522_I2C only initializes the variables, not the hardware. That is how it is supposed to be. In the examples is also a reset pin in the second parameter. You don't need the reset ?

Creating the 'tca9548a' and 'mfrc22' object before setup() is good.

You should do a Wire.begin() in setup() before the I2C bus is used.
The tca9548a.address() (initialize variable) and tca9548a.selectChannel() (set hardware channel) don't do that for you.
The MFRC522 library has a PCD_Init() function to start the hardware. I suggest you do that after the I2C mux is set.

Could you use the Serial Monitor and remove the M5Stack, I don't know if the M5Stack library has influence on the I2C bus. Try to put everything in the right order.

[ADDED] By the way, the Wire.beginTransmission() does not start something on the I2C bus and the Wire.endTransmission() may not be used to stop or end something on the I2C bus.

From Reference:

Ends a transmission to a slave device that was begun by beginTransmission() and transmits the bytes that were queued by write().

I have carefully chosen my word (at least this time :wink: ), so I think that I'm right and the reference is confusing.
The Wire.endTransmission() does not end something that is going on the hardware I2C bus. It is the full I2C session with START, reading ACK, sending data, and STOP.

What exactly do you feel confusing?

beginTransmission and endTransmission are almost self-explanatory to the coder. The library reference documents the usage of the subroutines, not the protocol on the bus. Hardware abstraction is the most important part of a software development system.

I don't think so. The constructor in that library requires 2 parameters. The third parameter has a default, but the second does not. So I think because @steveinaustria 's code compiles with a single parameter for the constructor, he must be using a different library to that one or another one I found, or at least a different version. Maybe the constructor in that library/version does more than simply create the object and store the parameter values. Maybe it initialises the hardware also, which would explain what @steveinaustria is saying about setting up the i2c multiplexer before creating the mfrc522 object. If so, that's a poor way to make a library work, and switching to a different library/version would solve the problem.