Multiple HT16K33 Devices

Try connecting the pcf module to some of the other channels of the multiplexer.

Try another i2c device instead of the pcf module, connected directly to the Mega and connected to several of the multiplexer channels.

I can't see a problem with your wiring or code, so I'm wondering if there is a piece of faulty hardware here.

the sketch with the #define TCAADDR 0x70 doesn't fit to the serial output you posted in #55 and your picture.
Where does this 0x71 come from?

are you very confident that you provide us correct and consistent information?

1 Like

I am 100% confident on the information is accurate, and 100% confident that I'm an idiot.
For some reason I'd changed the ID of the TCA to 0x71, and not changed it in the code.
Suffice to say, it works great now - thanks everyone.

I guess that was because of the address clash with the ht16k33. You only needed to change 1 TCA Vs up to 8 HT33s.

So, in my code I define my PCF8574s like below:

PCF8574 ex1(0x20);
PCF8574 ex2(0x21);

and then use them as below:

  digitalWrite(ex1, 0, HIGH);

then later in the code

  digitalToggle(ex1, 0);

And that's a feed to a relay, that alternates depending on what I need it for.

How would I change that definition at the start, so it references 0x71 and i2c output 0x20 on output 1 of the TCA?

you could write a wrapper to create a pcf8574 object on a particular address (0x20) and on a particular TCA channel.

Then you could just use a digitalToogle(ex1) as ex1 knows it's PCA address and it's TCA channel.

on the other hand, if you name your objects like ex1, ex2,... we should talk about "naming things" and arrays ...

You said "wrapper".... and they're, in my head, people who make music that makes my head hurt.


  if (analogRead(sw1) > 1015 && analogRead(sw1) < 1024 )
  {
    buttonPushed1 = 1;
  }
  if (buttonPushed1) {
    angle1 = angle1 + angleStep1;
    if (angle1 <= 0 || angle1 >= 180) {
      angleStep1 = -angleStep1;
      buttonPushed1 = 0;
      if (angle1 <= 0) {
        lcd.setCursor(0, 0);
        lcd.print("Point 01 is set     ");
        leda[0].setRGB(16, 16, 8);
        ledb[0].setRGB(0, 0, 0);
        digitalToggle(ex1, 0);
      }
      else if (angle1 >= 180) {
        lcd.setCursor(0, 0);
        lcd.print("Point 01 is thrown ");
        leda[0].setRGB(0, 0, 0);
        ledb[0].setRGB(16, 16, 8);
        digitalToggle(ex1, 0);
      }
    }
    pwm0.setPWM(0, 0, angleToPulse(angle1) );
  }

That is my current code which receives the information from the switch and then does the 'deed'. In the fact that it changes a PWM servo, changed an LED on or off, and changes a relay. I know there are far easier ways to code this, but I understand it!! And I'd really like to be able to understand all that I code.

I have 36 switches and while I have enough space on my Mega, I'd rather use an i2c or other method if I can as it uses less wiring. I have gone to three inputs into a analogue input on the Mega, as I can get about ten switches on each, which have a resistor between each switch to give different levels, then read that into the mega to know which switch has been triggered. While it works really well and only uses 3 unused Analogue inputs, I have also found it a tad temperamental on occasion. About 1 in 20 switch throws, might throw another switch on the row. While I could ignore it, I'd rather do it properly.

So, this is why I've brought out the multiple i2c route.
I already have 5 PCF8574s in use and they control the relays. I need 6 more in order to have all switches controlled by the PCF8574s. You can't do that though due to running out of 0x's.
So, I am using the TCA9548 in order to extend that.

What I'd like to be able to do is go back to what I had before which was below:

  if (digitalRead(pushButton1) == LOW)
  {
    buttonPushed1 = 1;
  }
  if (buttonPushed1) {
    angle1 = angle1 + angleStep1;
    if (angle1 <= 0 || angle1 >= 180) {
      angleStep1 = -angleStep1;
      buttonPushed1 = 0;
      if (angle1 <= 0) {
        lcd.setCursor(0, 0);
        lcd.print("Point 01 is set     ");
        leda[0].setRGB(32, 16, 0);
        ledb[0].setRGB(0, 0, 0);
      }
      else if (angle1 >= 180) {
        lcd.setCursor(0, 0);
        lcd.print("Point 01 is thrown ");
        leda[0].setRGB(0, 0, 0);
        ledb[0].setRGB(32, 16, 0);
      }
    }
    pwm0.setPWM(0, 0, angleToPulse(angle1) );
  }

Which simply read a previously defined pushButton1 :

#define pushButton1 2

Except rather than having it #define pushButton1 as pin 2, have it say #define pushButton1 as 0x71(2) or whatever the code would be.
Does that make sense?

Is this likely to work?

#include <Wire.h>
#define addr1   0x20       // PCF8574 device 1 (I2C bus 1)
#define TCAADDR 0x71       // address of I2C switch module
#define PCFADDR 1          // address of PCF device on TCA
#define pushButton1    0   // address on PCF device

void setup() {

  // I2C init
  Wire.begin();
}

void loop() {

  if (digitalRead(TCAADDR, PCFADDR, addr1, pushButton1) == LOW)
  {
    serial.print("buttonpushed");
  }
 
}

Needless to say, to answer my own question, no it doesn't work.

How can I get this to work as simply as possible????

think outside the box.

WHAT should be the result:
? the pin state on a particular pin of a specific pcf, on a specific TCA, and a channel

What do you want to have as input
? the TCA address
? the TCA channel
? the PCF address
? the PCF pin

... ok, so for example a function with 4 input parameters and the return value of the readed PCF channel.
Don't forget to set the TCA channel also in your function!

"Didn't work"?

Did you mean, doesn't compile? Then you would/should read and/or post the error listing because it makes an attempt to tell you what is wrong. You will probably see a message about "too many parameters". Look up the documentation for digitalRead().

Please be precise and verbose in your problem reports. Not doing so wastes peoples time.

Apart from having too many parameters, the line in question attempts to bypass several layers of hardware in reading the digital input. So of course it will never work, for those reasons as well.

To make this clearer, consider the stripped down:

#define TCAADDR 0x71       // address of I2C switch module
...
  if (digitalRead(TCAADDR) == LOW)

This attempts to read a value from Arduino digital pin 113.

Additionally, your code lacks both state change detection and switch debouncing. Please investigate both, you will usually need them for a pushbutton.

The TCA address is 0x71
The TCA channel will be channel 1
The PCF address is 0x20
The PCF channel pin is 0.

The trouble is, I just don't really know where to begin.
I had a strong feeling that the digitalRead command wouldn't work due to too many parameters, but I wasn't wholly sure - as programming is really not something I can do easily.

Fundamentally, I would like to be able to 'digitally read' that pin to find out whether it is high or low, depending on whether the switch is triggered or not - it would be a momentary trigger and has it's own debounce built in because the servo that it operates takes about a second to move from position A to position B, which means that the debounce is 'there'.

I just don't really know where to start.
I can make the PCF work in terms of turning off or on, but I don't know how to read it, and I especially don't know how to read it via the TCA.

Sorry I appear daft.... the reason behind that is that I am a bit daft :smiley:

The documentation, and tutorials.

I had a strong feeling that the digitalRead command wouldn't work due to too many parameters, but I wasn't wholly sure

That is why the Arduino folks provided a reference page for commands like that. The libraries for the PCF and so on are the same, they come with documentation and example sketches.

The debounce remark, yes you can leave it out if your program is so sluggish that it inherently debounces the switch by exceeding the switch bounce period. But it might be a sign that your program is too slow to reliably execute other functions properly.

Lastly, the way to tackle complexity is with modularity. Construct sub-procedures that you can test (and understand) independently, then tie them together into something bigger. It isn't the size of your program that makes it challenging, it's that it is a single monolithic structure that demands an all or nothing test or understanding.

May I suggest:
Reduce complexity!

Leave avay the TCA for a moment.

Clarify, which library you want to use for the PCF8574.
Or do you feel confident enough to read a Pin from the PCF8574 just with I2C and some bit shifting?

So step one is:
Read the pin state of PCF8574 pin.
How does a working sketch look like?

So, I've removed the TCA from the equation and can, relatively simply, get the PCF working and reading the press of a switch, thus:

#include <pcf8574.h>
#include <wire.h>

PCF8574 ex1(0x20);

#define TCAADDR 0x71
#define PCFADDR 1
#define pushButton 0

void setup() {
  delay(200);
  wire.begin();
  Serial.begin(9600);
  pinMode(ex1, 0, INPUT_PULLUP);
}

void loop() {
  int state = digitalRead(ex1, 0);
  if (state == -1) Serial.println("PCF8574 not detected");
  if (state == LOW)
  {
    Serial.println("Switch triggered");
  }
  delay(30);
}

I'm sure it could be written better, but it works. I've started, as you'll see, that I've also started adding bits in for the TCA.... but I just don't know where to go. I've googled switches and wotnot, but it shocks me how incomprehensible it seems to be.
I just don't see how to get it to read via the TCA?!?!
Really sorry

just add the function to activate a specific channel.

something like that:

#include <pcf8574.h>
#include <wire.h>

PCF8574 ex1(0x20);

#define TCAADDR 0x71
#define PCFADDR 1
#define pushButton 0

void tcaselect(uint8_t i) {
  if (i > 7) return;
 
  Wire.beginTransmission(TCAADDR);
  Wire.write(1 << i);
  Wire.endTransmission();  
}


void setup() {
  delay(200);
  wire.begin();
  Serial.begin(9600);
  tcaselect(1);  // also in setup before you communicate with the PCF!
  pinMode(ex1, 0, INPUT_PULLUP);
}

void loop() {
  // select channel
  tcaselect(1);

  // check PCF
  int state = digitalRead(ex1, 0);
  if (state == -1) Serial.println("PCF8574 not detected");
  if (state == LOW)
  {
    Serial.println("Switch triggered");
  }
  delay(30);
}

would that work?

1 Like

Hiya,
Very interesting that.
I'm reading through it, making sure I understand it... which I think I mostly do.
I'll have a better read in the morning (falling asleep) but it works.
Thanks so much for the help.
You're absolutely a star.

next step would be a function to combine the TCA and PCF access.

untested:

#include <pcf8574.h>
#include <wire.h>

PCF8574 ex1(0x20);

#define TCAADDR 0x71
//#define PCFADDR 1
#define pushButton 0

void tcaselect(uint8_t i) {
  if (i > 7) return;

  Wire.beginTransmission(TCAADDR);
  Wire.write(1 << i);
  Wire.endTransmission();
}

// activate a specific TCA channel, choose a specific PCF instance, read one pin)
int read(int tcaChannel, PCF8574 &pcf, int pcfPin )
{
  tcaselect(tcaChannel);
  int state = digitalRead(pcf, pcfPin);
  if (state == -1) Serial.println("PCF8574 not detected");
  return state
}

void setup() {
  delay(200);
  wire.begin();
  Serial.begin(9600);
  tcaselect(1);
  pinMode(ex1, 0, INPUT_PULLUP);
}

void loop() {
  // select channel
  tcaselect(1);

  // check PCF
  int state = read(1, ex1, 0);
  if (state == LOW)
  {
    Serial.println("Switch triggered");
  }
  delay(30);
}

So, I tried the above code and it worked great.
So, I've popped it into my code... weird things happen though.

When I run the code, the switch triggers the move, but as soon as it goes over, it flips back.
I don't understand that at all. It did it before, and I can't remember why.
I have 35 other points that work fine.... I know this code is hardly perfect, but it works for me, but I'll show you two below:

  int state1 = read(1, sx1, 0);
  if (state1 == LOW)
  {
    buttonPushed1 = 1;
  }
  if (buttonPushed1) {
    angle1 = angle1 + angleStep1;
    if (angle1 <= 0 || angle1 >= 180) {
      angleStep1 = -angleStep1;
      buttonPushed1 = 0;
      if (angle1 <= 0) {
        lcd.setCursor(0, 0);
        lcd.print("Point 01 is set     ");
        Serial.println("Point 01 is set");
        leda[0].setRGB(16, 16, 8);
        ledb[0].setRGB(0, 0, 0);
        digitalToggle(ex1, 0);
      }
      else if (angle1 >= 180) {
        lcd.setCursor(0, 0);
        lcd.print("Point 01 is thrown ");
        Serial.println("Point 01 is back");
        leda[0].setRGB(0, 0, 0);
        ledb[0].setRGB(16, 16, 8);
        digitalToggle(ex1, 0);
      }
    }
    pwm0.setPWM(0, 0, angleToPulse(angle1) );
  }
  if (analogRead(sw1) > 510 && analogRead(sw1) < 520 )
  {
    buttonPushed2 = 1;
  }
  if (buttonPushed2) {
    angle2 = angle2 + angleStep2;
    if (angle2 <= 0 || angle2 >= 180) {
      angleStep2 = -angleStep2;
      buttonPushed2 = 0;
      if (angle2 <= 0) {
        lcd.setCursor(0, 0);
        lcd.print("Point 02 is set     ");
        leda[1].setRGB(32, 16, 32);
        ledb[1].setRGB(0, 0, 0);
        digitalToggle(ex1, 1);
      }
      else if (angle2 >= 180) {
        lcd.setCursor(0, 0);
        lcd.print("Point 02 is thrown ");
        leda[1].setRGB(0, 0, 0);
        ledb[1].setRGB(32, 16, 0);
        digitalToggle(ex1, 1);
      }
    }
    pwm0.setPWM(1, 0, angleToPulse2(angle2) );
  }

The bottom one is original, the upper one has the different method via the TCA and PCF, as the code in post 77. The only changes I made is to change it to state1 from state and sx1, as sx1=0x20 for the TCA. ex1 is already in use for another PCF used for the servos.

I feel the problem is in this section, but I can't see it. Wondered if it was debouncing, but I don't think so as I put in a small delay, and that did nothing. So, I'm a bit unsure.

So, I've tried both versions now. The version in post 75 and 77.
Still the same issue. Where it doesn't latch over.
All the other switches work, so it must be something to do with the way state1 is recorded, but I just don't see the problem. The code is exactly the same, but for the initial 'if' statement, which only returns low when the switch is triggered.

So.... it's a conflict.
I have this at the start during setting up:

//PCF8574 ex1(0x20);
PCF8574 ex2(0x21);
PCF8574 ex3(0x22);
PCF8574 ex4(0x23);
PCF8574 ex5(0x24);
// via TCA channel
PCF8574 sx1(0x20);
PCF8574 sx2(0x21);

So I already had a 0x20 and that is connected directly to the i2c line.
I // all references to ex1 and now the switch latches over. Obviously now I have the issue that the relay doesn't work.

That digitalToggle(ex,0); that I have in both if statements is causing the problem. When I // that out, the code works, although it doesn't flip my relay.

Is there a way around this?