TCA9548A MUX with TLV493D Hall sensors using ProMini 3.3v

My project is to connect 48 TLV493D magnetic field sensors through a TCA9548A MUX. I'm using the 3.3V version of the ProMini, as the sensors require 3.3V. The TLV sensors have 8 possible addresses, and there are 8 channels on the MUX, so I actually could use as many as 64 sensors.

As a first step, I set the MUX to address 0x70 and used just channel 5 to control only 8 sensors. I'm not reading any data yet. That worked great - a scan of the I2C bus recognizes the MUX and the 8 sensors.

 Final setup for Sensor 5 =========

I2C scanner. Scanning ...
Found address: 11 (0xB)
Found address: 15 (0xF)
Found address: 27 (0x1B)
Found address: 31 (0x1F)
Found address: 74 (0x4A)
Found address: 78 (0x4E)
Found address: 90 (0x5A)
Found address: 94 (0x5E)
Found address: 112 (0x70)
Done.  Found 9 device(s).

Here is the code:

  #include <Wire.h>
  #define baud 115200
  #define TCA_add0  0x70

// TLV493 registers
byte byte0 = 0;  // Bx value MSB's
byte byte1 = 0;  // By value MSB's
byte byte2 = 0;  // Bz value MSB's
byte byte3 = 0;  // Selected channel, frame counter, temp MSB's
byte byte4 = 0;  // By, Bx LSB's
byte byte5 = 0;  // Bz LSB's, PD flag
byte byte6 = 0;  // Temp LSB's
byte byte7 = 0;  // Factory settings
byte byte8 = 0;  // Factory settings
byte byte9 = 0;  // Factory settings 
byte MOD1;
byte b8;
byte MOD2;

//set addresses
const int def_addr_lo = 31; // boot-up address with SDA low
const int def_addr_hi = 94;  // boot-up address with SDA high
byte addr;   
byte add1 = 11; 
byte add2 = 74; 
byte add3 = 78; 
byte add4 = 90; 
byte add5 = 94; 
byte add6 = 15; 
byte add7 = 27; 
byte add8 = 31; 

const int config_reg = 0x00;   // initialization value
const byte sens1Pwr = 2;   // sensor 1 power
const byte sens2Pwr = 3;   // sensor 2 power
const byte sens3Pwr = 4;   // sensor 3 power
const byte sens4Pwr = 5;   // sensor 4 power
const byte sens5Pwr = 6;   // sensor 5 power
const byte sens6Pwr = 7;   // sensor 6 power
const byte sens7Pwr = 8;   // sensor 7 power
const byte sens8Pwr = 9;   // sensor 8 power

byte sensor;  // sensor # on current channel (1-8)
int sensor_pin;  // pin # of sensor (sensor # + 1)
const byte addrPin = 10;  // used to hold SDA/ADDR to 0 on power up to use lower I2C addresses
const byte del = 100;
unsigned int i = 0;

// ###########
//  Functions
// ###########

void I2Cscan()   // scans for I2C device addresses
{
  Serial.println ();
  Serial.println ("I2C scanner. Scanning ...");
  byte count = 0;
  Wire.begin();
  for (byte j = 1; j < 127; j++)  
  {
     Wire.beginTransmission (j);
     if (Wire.endTransmission (j) == 0) 
     {
       Serial.print ("Found address: ");
       Serial.print (j, DEC);
       Serial.print (" (0x");
       Serial.print (j, HEX);
       Serial.println (")");
       count++;
     }
  } 
  Serial.print ("Done.  Found ");
  Serial.print (count, DEC);
  Serial.println (" device(s).");
}
// ======= 

void tcaselect(byte TCA_addr, int chan) //set MUX address and channel
{
  if (chan > 7) return;
  Wire.beginTransmission(TCA_addr);
  Wire.write(1 << chan);
  Wire.endTransmission();
}

// =======  

void init_sensor_lo(uint8_t sensor, byte mod1_val) // Routine to initialize sensors to addresses 11, 15, 27, or 31
{ 
  sensor_pin=sensor+1;

  Serial.print("Setup sensor ");
  Serial.println(sensor);
  Serial.print("............\n");
  pinMode(sensor_pin, OUTPUT);   // 
  digitalWrite(sensor_pin, LOW); // turn sensor off
  pinMode(addrPin, OUTPUT); // )
  digitalWrite(addrPin, LOW);  // turn SDA/ADDR off
  delay(del);
 
  digitalWrite(sensor_pin, HIGH);   // turn sensor on while SDA is low
  delay(del);   
  pinMode(addrPin, INPUT); 
  delay(del); 
  I2Cscan();
   
  Wire.beginTransmission(def_addr_lo); 
  Wire.write(0); 
  Wire.endTransmission();
  delay(del); 

  readReadRegs(def_addr_lo, true);  // read  all 10 registers
  MOD1 = (byte7 & B00011000) | mod1_val;  // set appropriate bits
  b8 = byte8;
  MOD2 = (byte9 & B00011111) | B11000000;  // set appropriate bits
  configTLV(def_addr_lo, MOD1, b8, MOD2);
  readReadRegs(add1, true);  // read register settings

  I2Cscan();
  Serial.print("Setup Complete for Sensor ");
  Serial.println(sensor);
  Serial.print("............\n");
}
// =======

void init_sensor_hi(uint8_t sensor, byte mod1_val) // Routine to initialize sensors to addresses 74, 78, 90, and 94
{ 
  sensor_pin=sensor+1;

  Serial.print("Setup sensor ");
  Serial.println(sensor);
  Serial.print("............\n");
  pinMode(sensor_pin, OUTPUT);   
  digitalWrite(sensor_pin, LOW); // turn sensor off
  pinMode(addrPin, OUTPUT); 
  digitalWrite(addrPin, HIGH);  // set SDA/ADDR High
  delay(del);   
 digitalWrite(sensor_pin, HIGH);   // turn sensor on while SDA is high
 delay(del);   
  pinMode(addrPin, INPUT);  
 delay(del); 
  I2Cscan();
  
  Wire.beginTransmission(def_addr_hi); 
  Wire.write(0); // Sensor initialization
  Wire.endTransmission(); 
  delay(del); 

  readReadRegs(def_addr_hi, true);  // read  all 10 registers
  MOD1 = (byte7 & B00011000) | mod1_val;  // set appropriate bits
  b8 = byte8;
  MOD2 = (byte9 & B00011111) | B11000000;  

  configTLV(def_addr_hi, MOD1, b8, MOD2);
  readReadRegs(add2, true);  // read register settings
  
  Serial.print("Setup Complete for Sensor ");
  Serial.println(sensor);
  Serial.print("............\n");
}  
// =======
void readReadRegs(byte addr, bool ten)  // read register settings, if argument = true, get all 10 else 7
{
  byte noOfBytes;
  ten ? noOfBytes = 10 : noOfBytes = 7; //if (ten == true) read 10 bytes else 7
  Wire.requestFrom(addr, noOfBytes); // read correct number of bytes
  if (noOfBytes <= Wire.available()) // if all bytes were received
  {
   byte0  = Wire.read();   // Bx value MSBs
   byte1  = Wire.read();   // By value MSBs
   byte2  = Wire.read();   // Bz value MSBs
   byte3  = Wire.read();   // TEMP MSbits 7:4; FRM 3:2; CH 1:0
   byte4  = Wire.read();   // Bx LSbits 7:4; By LSBits 3:0
   byte5  = Wire.read();   // RSV 7; TestMode 6; PFF 5; PowerDown 4;  Bz LSbits 3:0
   byte6  = Wire.read();   // Temp LSB
   if (ten == true) 
     {
       byte7  = Wire.read(); // Factory settings
       byte8  = Wire.read(); // Factory settings
       byte9  = Wire.read(); // Factory settings
     }
  } 
}
// =======
void configTLV(byte addr, byte MOD1, byte b8, byte MOD2)   
{
  Wire.beginTransmission(addr);  
  Wire.write(byte(0x00)); // config byte 0
  Wire.write(byte(MOD1)); // config byte 1
  Wire.write(byte(b8));   // config byte 2
  Wire.write(byte(MOD2)); // config byte 3
  Wire.endTransmission(); 
  delay(500);
}
// ###########
// Setup
// ###########
void setup() 
{
  Wire.begin(); 
  Serial.begin(baud);
  while (!Serial) {}

  tcaselect(TCA_add0,5);
  Serial.print("\n Initialize all sensors on Channel 5 =========\n");
  I2Cscan();
  init_sensor_lo(1,B01100111);  // sensor 1 = address 11
  init_sensor_hi(2,B01100111);  // sensor 2 = address 74
  init_sensor_hi(3,B01000111);  // sensor 3 = address 78
  init_sensor_hi(4,B00100111);  // sensor 4 = address 90
  init_sensor_hi(5,B00000111);  // sensor 5 = address 94
  init_sensor_lo(6,B01000111);  // sensor 6 = address 15
  init_sensor_lo(7,B00100111);  // sensor 7 = address 27
  init_sensor_lo(8,B00000111);  // sensor 8 = address 31
  Serial.print("\n Final setup for Sensor 5 =========\n");
  I2Cscan();
  
[b]
//  tcaselect(TCA_add0,7);
//  Serial.print("\n Initialize all sensors on Channel 7 =========\n");
//  I2Cscan();
//  init_sensor_lo(1,B01100111);  
//  init_sensor_hi(2,B01100111); 
//  init_sensor_hi(3,B01000111);  
//  init_sensor_hi(4,B00100111);  
//  init_sensor_hi(5,B00000111);  
//  init_sensor_lo(6,B01000111); 
//  init_sensor_lo(7,B00100111);  
//  init_sensor_lo(8,B00000111);  
//  Serial.print("\n Final setup for Sensor 7 =========\n");
//  I2Cscan();
[/b]

  Serial.print("\n\n Re-check all sensors on Channel 5 =========\n");
  tcaselect(TCA_add0,5);
  I2Cscan();
  Serial.print("\n =========\n");
}

// ###########
//  Main Loop
// ###########
void loop() 
{}

The problem comes when I try to connect an additional channel of the MUX. If you uncomment the 13 bolded lines in the Setup section of the code, it will initialize and confirm the settings for channel 5, then do the same for Channel 7. However, it then re-checks the settings for Channel 5, and shows that its settings have been lost. Can anyone see what I'm doing wrong? Thanks.

 Re-check all sensors on Channel 5 =========

I2C scanner. Scanning ...
Found address: 94 (0x5E)
Found address: 112 (0x70)
Done.  Found 2 device(s).

TLV493D addressing.jpg

Register Map.pdf (886 KB)

tca9548a-1-to-8-i2c-multiplexer.pdf (629 KB)

Can you post a schematic please? Just needs to show a couple of sensors on each of a couple of the mux channels, plus the mux and Arduino itself, details of power supply etc.

I have attached a schematic for the full project, which uses 196 sensors and 4 MUXes. The parts for the two channels which are being used in my current test are highlighted in yellow. I have also included a schematic of the details for the 8 sensors attached to each channel. Thanks.

Schematic for using 196 sensors.pdf (95.6 KB)

Scematic for each channel.pdf (63.9 KB)

Ok, here's my guess as to what the problem is. The addresses of the sensors have to be set at startup, and they have to be started up one at a time so that each can be given its own individual address while the remaining, un-initialised sensors are still powered down. This is all described around page 22 in the data sheet. The circuit there demonstrates using an MCU I/o pin to power each sensor. Actually the first sensor is permanently powered and doesn't require power from an I/o pin, but it could be powered that way. So that's one I/o pin per sensor. You have 16 sensors (and eventually 196) but you only use 8 I/o pins to power them. The first group of 8 sensors can be initialised ok. When you initialise the next set of 8 sensors, you are de-powering the first set of 8 sensors (because they share the same 8 I/o pins) causing them to loose their configurations.

So you need to use 16 I/o pins to initialise 16 sensors, which might be possible, and eventually 196 pins to initialise 196 sensors, which obviously a pro mini doesn't have.

Also, using 8 I/o pins to power 8 sensors each would overload the I/o pin's current limits, and the overall current limit of the Pro Mini.

So if I'm right, a re-think of the addressing strategy is needed. I can think of a couple of ideas to get you started.

You could add 8 mcp3008 I/o extenders, one for each group of 8 sensors/mux channel. Each chip's outputs could power the 8 sensors on that channel.

You could forget the mux and use 8 pro-mini. Each Pro mini can connect to 8 sensors using is i2c bus and power them with 8 I/o pins. But you then have to figure out how to get the 8 pro-mini to communicate and coordinate between themselves. Perhaps one Pro-mini could be the "master" and it could request the other 7 to send their data through the SPI bus by treating them like a bunch of chained shift registers.

Thank you PaulRB for your detailed response. I am thinking I would switch to using the Arduino Due, which has two I2C busses and 54 I/O ports and runs on 3.3 volts. With 2 Dues I believe i would be able to handle all 196 sensors. What do you think? Arduino has discontinued producing the Due but they are still readily available.

jdfalk:
With 2 Dues I believe i would be able to handle all 196 sensors.

I don't understand. How would 2x Due with 2x i2c bus enable you to connect more than 8x2x2 = 32 sensors?

From my understanding, I need 1 digital I/O pin per sensor. Since the Due has 54, I could run that many sensors on the first I2C bus. Then another 54 on the second, independent I2C bus. If I can set up a second Due as a slave and the first one as master, than I should be good. Of course, there is always a non-zero chance that I'm wrong . . .

Arduino Due.pdf (264 KB)

Are you still planning to use the mux chips with the Due? That might work. But the wiring would be a mess.

I would prefer the mcp2308 solution over that. At least the wiring would be a little simpler.

I've just had an idea. Maybe you can do this with only 8 pins...

You just need to do things the other way around from what your code above is doing. Once a power pin is high, it must stay high from then on, otherwise some sensors will lose their configuration. Start by turning one pin HIGH. Then loop through the mux channels and set the address of all sensors powered by that pin. Then turn the second pin high, and again loop through the mux channels and set addresses of the sensors powered by the second pin. And so on. Put another way, instead of looping through the mux channels, and within that loop, looping through the 8 Arduino pins, turn those loops inside out, i.e. loop through the 8 Arduino pins, then within that loop, loop through the mux channels. That way, you never need to put an Arduino pin back to LOW, and no sensors loose their configuration.

I'm still a little concerned about how many sensors a single Arduino pin can power. A single Atmega pin can source 40mA absolute max, 30mA is a safer long-term limit. Also the Atmega328 has an overall current limit which may be only 200mA. So you may need to use some PNP transistors or something.

Great idea - I'll try that. I think I'll be Ok on the power - the TLV493D chips draw only 80uA in low power mode, so even with 196 of them I'll only be at 15.4 mA. And, I could operate in ultra low power mode which draws on 10 uA per chip ( but which requires operating at a slower speed).

At the instant that each chip is making a reading, the current jumps to 3.7 mA for 270 uS, but the readings are only done one at a time.