Photon, TCA9548A, multiple i2c SSD1306's (the cheap ones)

Sorry if this is the wrong spot, seemed like it could fit into several places.

Where to begin… This started on a nodeMCU and moved to a photon mostly because I’ve had a lot of trouble with reliability and consistency with the esp8266 platform and not to knock Adafruit (I love their stuff), but the ‘help’ I got there was a bit of a mix between dangling the carrot in front of the rabbit, and I’m doing something weird… go away. It didn’t help that I was getting frustrated and it turned into a rant (which I’ll try not to do here).

Anyhoot…

Details details… Particle Photon with an Adafruit TCA9548A and 3 (it was 4 till I cracked the screen on one) of the cheap-o i2c SSD1306 128x64 oled screens.

What works - a single screen. I was able to get 2 screen going on the esp8266 when I set their addresses (0x3C and 0x3D), but that gets me 2 screens - thus the muxer. I’m assuming the same will work with the photon, but the problem of more than 2 screens is still present, so I skipped from 1 screen straight to the muxer (is that even the right name?)

I can verify that each screen works (one of them is set to 0x3D, the others 0x3C).

I’ve beat my head against library imports as I’d been trying to bring in @rickkas7 's TCA9548-RK library (even created my own repo for it) but it was always complaining that I didn’t own the library - why that matters, I’m not sure since it was going to my private stuffs. In the end I gave up and just added the .h and .cpp files straight to the project.

I can scan the i2c bus and see both the TCA and the screens using the following (I actually was able to import @rickkas7 's library, but I had to change EVERY reference to -RK to -JJ to get it to stop complaining about ownership - otherwise it’s identical, at least as of today: TCA9548A-RK ):

#include <TCA9548A-JJ.h>
TCA9548A mux(Wire, 0);
byte error, address;
int nDevices;
  
void setup() 
{
    Serial.begin(115200);
    mux.begin();
    mux.setChannel(0);
    delay(2000);
    for(int i = 0; i < 8; i++)
    {
        mux.setChannel(i);
        Serial.print("Channel: ");
        Serial.println(i);
        nDevices = 0;
        for(address = 1; address < 127; address++ ) 
        {
            Wire.beginTransmission(address);
            error = Wire.endTransmission();
            if (error == 0)
            {
                Serial.print("I2C device found at address 0x");
                if (address<16) 
                    Serial.print("0");
                Serial.print(address,HEX);
                Serial.println("  !");
                nDevices++;
            }
            else if (error==4) 
            {
                Serial.print("Unknown error at address 0x");
                if (address<16) 
                    Serial.print("0");
                Serial.println(address,HEX);
            }    
        }
        if (nDevices == 0)
        {
            Serial.println("No I2C devices found\n");
        }
        else
        {
            Serial.println("done\n");
        }        
    }
}

void loop() 
{
}

A little borrowed code from somewhere… anyway, I get this (which seems correct - truncated everything after ch 2):

Channel: 0
I2C device found at address 0x3C  !
I2C device found at address 0x70  !
done

Channel: 1
I2C device found at address 0x3D  !
I2C device found at address 0x70  !
done

Channel: 2
I2C device found at address 0x3C  !
I2C device found at address 0x70  !
done

I suspect I got the order wrong in some spot which is why 0x70 keeps showing up as its the address of the TCA board, but I didn’t bother fixing it as it told me what I needed to know - stuff’s there. On Adafruit’s site, they have some instructions that differ a bit from RK’s, but when I ran it it would put the Photon into SOS mode - with 5 flashes which I believe is essentially user error aka PEBKAC… couldn’t figure it out. I took the code from RK’s libs and some examples elsewhere (and have tried a variety of combos (this is already getting long so I’ll skip those bits unless they come up). Here’s the code I’ve simplified just to get something on the screen (which still doesn’t work):

#include <Adafruit_GFX.h>
#include "Adafruit_SSD1306.h"
#include "TCA9548A-RK.h"

TCA9548A mux(Wire, 0);

#define OLED_RESET D4
Adafruit_SSD1306 display0(OLED_RESET);
Adafruit_SSD1306 display1(OLED_RESET);
Adafruit_SSD1306 display2(OLED_RESET);

void setup()   
{                
    Serial.begin(115200);
    delay(2000);
    
    Serial.println("Begin Mux");
	mux.begin();
	delay(1000);
    Serial.println("mux begun");
    
    Serial.println("Set Disp0");
	mux.setChannel(0);
    display0.begin(0x70);//3C?
    display0.display();
    delay(1500);
    
    Serial.println("Set Disp1");
	mux.setChannel(1);
    display1.begin(0x70);//3D?
    display1.display();
    delay(1500);
    
    Serial.println("Set Disp2");
	mux.setChannel(2);
    display2.begin(0x70);//3C?
    display2.display();
    
    delay(2000);
    Serial.println("Displays set, doing stuff...");
    doTheStuff();
    Serial.println("Stuff done");
}

void doTheStuff()
{
    Serial.println("Set Ch0, do stuff");
    mux.setChannel(0);
    display0.clearDisplay();
    display0.setTextSize(2);
    display0.setTextColor(BLACK, WHITE);
    display0.setCursor(0,30);
    display0.clearDisplay();
    display0.println("Hello OLED");
    display0.display();
    
    Serial.println("Set Ch1, do stuff");
    mux.setChannel(1);
    display1.clearDisplay();
    display1.setTextSize(2);
    display1.setTextColor(BLACK, WHITE);
    display1.setCursor(0,30);
    display1.clearDisplay();
    display1.println("Hello OLED");
    display1.display();
    
    Serial.println("Set Ch2, do stuff");
    mux.setChannel(2);
    display2.clearDisplay();
    display2.setTextSize(2);
    display2.setTextColor(BLACK, WHITE);
    display2.setCursor(0,30);
    display2.clearDisplay();
    display2.println("Hello OLED");
    display2.display();
    Serial.println("Done with the stuff");
    delay(2000);
}

void loop() 
{
    doTheStuff();  
}

I don’t like using delays, but for the sake of testing I figured it’d be fine. I’m not sure if I’ve either misunderstood the syntax, or the addressing or what - I feel like I should have to specify the i2c address of the displays per channel as well, but the examples indicate otherwise (though I tried it anyway with no luck).

Regarding the Adafruit libs at the top, I added the SSD1306 lib to the project directly like I did with the TCA lib (i.e. in the tabs), but not the GFX lib - I added that the “normal” way. The reason being that I had tried the Adafruit_mfGFX lib first as it would appear to have more usage, but didn’t seem to change anything useful.

Anyway, I’m stuck. Sorry that was a lot, happy to provide more details if I missed something.

@variable, consider the multiplexer as a set of switches that connect a number of devices with unique addresses on a single I2C bus to the Photon’s I2C bus. The mux supports 8 channels or individual I2C buses that can be individually connected to the Photon. This is great when you have multiple I2C devices, all with very few or a single I2C address. The mux allows you to connect one at a time, preventing addressing conflicts.

On your configuration, the I2C scanner correctly shows 0x70 as being the address of the mux which will always appear, otherwise you would not be able to control it! The SSD1306s are showing up as the 0x3C and 0x3D in your scans.

In your code, you are using 0x70 as the address of your OLEDs instead of 0x3C or 0x3D (depending on the channel). The TCA9548A-RK library uses address 0x70 for managing the mux so it should not appear in your code.

I suggest that you change the display.begin() addresses to the correct ones and try again. :wink:

1 Like

I actually tried that first - thats why I’ve got the comments to the side: //3C? or //3D? It behaved the same. I actually pulled that bit out of some of his sample code on an i2c tutorial: TCA9548A-led

His sample - though it could just be he’s doing something generic for demonstration purposes - shows the TCA’s address:

mux.begin();

mux.setChannel(0);
sevenSeg.begin(0x70);
	
mux.setChannel(1);
alphaNum.begin(0x70);

Unfortunately it’s just a snippet of the code and his link to the full thing just links to the repo - couldn’t find the project he was referring to.

When I plug in either the address of the display or the TCA, the result is the same - nothing on screen. I also left out this part (sorry) that comes from one of the ssd1306 examples as I wasn’t really sure what to do with it or if it was needed as is:

display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

Mostly I’m referring to the SWITCHAPVCC part. Leaving it out doesn’t seem to do anything, nor does it complain about it, though in the header and cpp file it would seem to be looking for a value there - but most of thats above my head…

@variable, I suggest that you put the mux aside and test a single display directly with the Photon. I suggest using the example app from the web IDE for testing. Once that works, the next step will be with the mux.

The Adafruit_SSD1306 library has a constructor for both with and without the argument so that's why it doesn't complain.

Ok, well, I had a feeling I was probably missing it :stuck_out_tongue:

Still stuck on the rest though…

Start small, end big. Let me know when you have the single display setup WITHOUT the mux. You may need pull-up resistors on the I2C lines.

I was able to get one working without the mux - after not being able to get the mux working I went back to do just that, start small (and make sure I hadn’t managed to break the glass near the poorly located mounting holes - why there’s 3 and not 4 displays :wink: Good thing they’re cheap…)

I feel like whatever the issue is lies with the libs for the displays not knowing what to do with what the muxer is giving them. I’m also wondering if the SWITCHAPVCC is relevant to the problem or not.

As for pull up resistors, I didn’t need them - though historically I’ve used them, I just forgot and got lucky - or do you mean I should add pull ups to the muxer? None of the examples I’ve seen have them (I think it has them on board), though none of the examples I’ve seen used oled displays… though I don’t see why that would matter…

@variable, if a single non-muxed display is working, you should test each display. Try with and without the SWITCHAPVCC argument though I suspect it is not relevant for your displays.

The next step will be to connect a single display through the mux, then two. Have you test two display with different addresses connected directly to the Photon? If not, you should.

Well… pays to listen I guess! So it looks like it doesn’t like the screen with the 0x3D address. I did as suggested (didn’t need the SWITCHAPVCC btw), and was able to get the two 0x3C displays working - I might also add that I changed the OLED_RESET in ‘Adafruit_SSD1306 display(OLED_RESET)’ to ‘-1’ (read that somewhere - don’t know why it popped in my head), but it occurred to me that I actually had the reset pin on the TCA hooked up to D4 (which was an experiment as well… that I forgot about…).

I digress… So, I can change the 0x3D display back but crazy small 0 ohm resistors aside, I feel like thats not really addressing the problem (see what I did there :wink: ) Worth mentioning - the displays actually say on the back 0x78 (default) and 0x7A, but these don’t work (as noted in the comments on Amazon), plus 0x3D does work on its own…

I also tried moving it to channel 3 (and display3) thinking maybe it didn’t like me labeling something 0 (long shot), but no dice. It’s progress though!

Just re-read some of the info on Adafruit’s site and there’s no pullups on that board… may give that a go.

@peekay123 Tried some pullups - 4.7k and it didn’t help. Though, I imagine if it was going to need them, it would have needed them for the other 2 screens - If there’s an issue there, its probably unrelated. I could try some different values, but to be honest, I’ve never entirely grasped the relationship between the need for a pull-up resistor and the resistance of said resistor (a topic for another time).

Since the one it has issue with is a different address, I could technically add it to the same bus the muxer is on, but that would still be a workaround in my mind - what if I had a variety of devices, 2 with the same address, 2 with a different address from the first 2, and 2 more with unique addresses? It would be nice and tidy (at least from a wiring perspective) to have them all addressable from the TCA - I think this is a valid use case…?

Came across this post on Adafruit’s site: I2c Multiplexer with OLED Displays

Which basically says to override the call to begin();

void begin(uint8_t switchvcc = SSD1306_SWITCHCAPVCC, uint8_t i2caddr = SSD1306_I2C_ADDRESS, bool reset=true);

I can’t tell though if that’s what needs to be overridden or if thats what you need to override with… either way, cobbling together what I think they meant by comparing the Adafruit_SSD1306 libs for particle and the stuff straight from Adafruit, I didn’t see what the difference really was - the changes I made didn’t do anything either so I’m guessing I misunderstood - or more likely, I’m already doing it by calling display.begin(0x3D); which isn’t working…

<<>sigh>

Ok - found this line (was trying to ignore it, but for giggles):

#define SSD1306_I2C_ADDRESS 0x3C // 011110+SA0+RW - 0x3C or 0x3D

In the Adafruit_SSD1306 header… changed it to 3D and the display came to life. I suspect though the others will not - haven’t tried yet. Need to go home and relax :slight_smile:

@variable, one thing at a time. The OLED reset and the TCA reset are not related. I suggest keeping the TCA reset line.

The 0x78 an 0x7A addresses are “unshifted” addresses. By shifting them right by one bit, you get the actual device address - 0x78 becomes 0x3C and 0x7A becomes 0x3D :slight_smile:

I suggest resetting the one display back to 0x3C and testing it standalone. I’m not clear it worked standalone at 0x3D and it’s better to keep things the same IMO. Once it is working, you can move back to the TCA.

BTW, I2C is an open-collector protocol. This means a processor’s I2C GPIO pin will pull it LOW but let it float otherwise. The pull-up resistors are need to pull the I2C lines to a voltage common to both the processor and the I2C device. This could be to 3.3v or 5v in the case of the Photon since the pins are 5v tolerant. The trick is not to have a) multiple pull-ups and b) one common pull-up voltage.

The TCA is a dumb switch with 8 I2C buses you can individually connect to the Photon. As long as two devices don’t have the same address on any one of those buses, then everything is fine. The TCA is there to isolate those same address devices on different channels.

I’m not sure why you are having problems but while studying Engineering I learned than when all else fails, go back to first principles. Start over and test each display thoroughly then add the TCA and test ONE screen moving it to different ports between tests. Test EACH screen this way. Then, do two screens on two different ports. Finally, try three. If there are issues, they are possibly related to the libraries… maybe.

Oh, and that override thing is pure, unadulterated BS. BTW, can you post an image of your setup?

This line is a bit troubling in connection with that

when omitting paramters the default values are used, but you can only omit parameters from the back to front.

Providing 0x3D as only parameter will render the resulting call as

display.begin(0x3D, 0x3C, true);
// or for the original
// display.begin(0x3D, 0x3C);

The parameter you intended to be the address will be inserted as switchvcc parameter and the actual i2caddr parameter will still get its default value SSD1306_I2C_ADDRESS = 0x3C

BTW, in this library I can not find an overload for Adafruit_SSD1306::begin() that would take the address as only parameter and the constructor(s) don't take an I2C address at all

@variable, @ScruffR, seems I’m partially asleep at the wheel. I was focusing on the constructor and not the begin() function. For the IDE version of the Adafruit_SSD1306 library, the single begin() function takes two parameters - switchvcc and i2caddr. The correct call should be:

`display.begin(SSD1306_SWITCHCAPVCC, 0x3C);`

:wink:

1 Like

@peekay123 @ScruffR So if I understand correctly, it really wants all 3(?) values - switchvcc, address, and reset, where switchvcc, address and reset are technically already defined meaning I could really just do display.begin() assuming I didn’t care what the default values were, but if I want to change any of them, they all need to be specified or it’ll assume you’re trying to define the first item in the list (assuming less than all 3 are being overridden)?

So, if I’m passing the address to switchvcc - and pardon my lack of understanding here (sadly I stopped being a sponge for this kind of thing in high school) - aren’t I A: passing a hex value to a variable looking to be a uint8_t and B: passing an invalid value (assuming A doesn’t matter) to switchvcc? To which end, I would think I’d get a compiler error of some sort, unless 0x3C and 0x3D are valid values for switchvcc…?

@ScruffR I’m not sure I understand what you mean by an ‘overload’ - override?

If I get the gist of things, what I really should have been doing is display.begin(SSD_SWITCHAPVCC, 0x3D,true); or sans the ‘true’ as I think (if I read correctly) the ‘non-particle’ version has a third value. So at least the first two parameters… which I’m pretty sure I tried early on without success - my suspicion being that because i2caddr is already defined in the header, me specifying a value for it isn’t overriding anything (which is why I’m assuming display.begin() would work) - which would support what you mentioned about it not taking the address as a parameter (assuming I read that right).

I’ve definitely gotten two different i2c addresses working before - though it wasn’t on a particle device and I wasn’t using a muxer/multiplexer. I noticed in the header a line about …SETMULTIPLEX but couldn’t figure out how to call it/set it (it was also passed my bedtime)…

I ended up moving the jumper on the oled set to 0x3D back to 0x3C and that worked, though by that point it was a bit anticlimactic as I wanted to figure out how to get it working with different addresses too (which seems like it should work…?) While I guess I can move on, I’ve got a feeling this is going to come up again down the road…

Small victory… at least I have a better understanding, which ain’t nothing, Having the right solution would have satisfied that part of my brain that’s keeping my head tilted to one side :stuck_out_tongue: Where’s the 'brain dripping out of my ears emoji? :wink:

Thanks for your help guys, I probably would have scrapped the project and tried using SPI instead (not that there’s anything wrong with SPI - I just know very little about it other than it takes more wires). If the answer to this presents itself, I’d love to know - unless you already answered it and it just went straight over my head (totally possible - more brain drippage)

:slight_smile:

Nope. As I pointed out, the version on the web IDE is not the same as the one referred to by @ScruffR. The reset pin is specified in the the constructor while switchvcc and address are specified in the begin() function. The switchvcc parameter is one of two values, as defined in Adafruit_SSD1306.h:

#define SSD1306_EXTERNALVCC 0x1
#define SSD1306_SWITCHCAPVCC 0x2

The value seems to define the power source for the OLED. You can try either value but I suspect the SSD1306_SWITCHCAPVCC value is the one you want. The address parameter is the one you set for the given display unit (0x3C or 0x3D).

One thing to note is that you seem to have a single RESET line for all displays. This won't work since this will reset the first display when you call begin() for the second display and so on. Either specify -1 for the RESET pin in the constructor or specify a unique pin for each display.

Knowing this correct format, I suggest you go back to my previous post as to how to proceed with a step-by-step approach and report back your findings. To recap:

// Constructor for each display (here showing display 0)
Adafruit_SSD1306 display0(OLED_RESET0);  // Unique pin or -1 is no pin

// Initialization call for each display (here showing display 0)
// This is called after the correct TAC channel has been selected
display0.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // you could also try SSD1306_EXTERNALVCC

There is a version on Web IDE (Adafruit_1306_RK by @rickkas7 maybe? Linking a GitHub repo would be good) that featues a three-parameter begin() , but usage-count of that is0 too https://build.particle.io/libs/Adafruit_SSD1306_RK/1.1.2/tab/Adafruit_SSD1306.h

But the version I refered to is also on Web IDE and takes only two parameters for begin()

1 Like

I guess there is a misconception about a hex value being a different datatype than uint8_t.
hex values are numeric values but are not of any "datatype" as such.
Variables are of a particular type and numeric datatypes can hold numeric values of the range defined by the datatype.
But 0x3C is just as valid for a uint8_t as decimal 60 or even '<' (ASCII character with index 60 / 0x3C).

So the compiler nor the controler would not know wheter 0x3C is a valid value for the first parameter, so they just trust you as the programmer to know what you are doing :wink:

1 Like