Sending unique information to 4 OLED displays with I2C?

Hi! I wasn't sure if I should put this in the "displays" section or even the "programming questions" section, as it's kind of a hybrid question, but here goes.

I've done a few small projects in Arduino, but I'm still new to a lot of it (as well as electronics in general), so forgive the noobishness. I am working on a project where I want to connect four OLED dispays to my Arduino UNO, and send different text/data to each one. The displays I currently have are cheap ones from China that a lot of people seem to get (Amazon.com ), but they only have four pins (VCC, GND, SCL, and SDA) as opposed to 6- or 8-pin displays that I've seen elsewhere. Because there are only four pins, I am led to believe that I2C is the best/only way to communicate with them via the UNO.

I did a test with two of them (using the u8glib, by the way - not sure if that's recommended/how that's different from Arduino's 'Wire' library), and am able to display the same text on each. Which is a start, but I don't know how I would send different messages to different displays via the u8glib. I dug around a lot more, eventually finding the "I2C Scanner" on Arduino's site. Running the scanner, I get the result "I2C device found at address 0x3C !" when I have either OR both displays connected, and "No displays found" if I disconnect both of them. Huh? This tells me that both displays are given the same address, which would mean that I couldn't differentiate between them in my code. If this is the case, is there something I can do to change the address of one of the displays?

As an aside, on the back of the displays there is a box which says "Address select: 0x7A | 0x7B" (see picture). Might this be useful for the Arduino detecting different addresses for the two (and soon to be four) displays, or is it nothing I should concern myself with?

Finally, I have read in many places that a 4.7k 'pull-up' resistor is necessary for I2C devices. This is where my lack of electronics hardware starts to shine. The displays are obviously displaying stuff fine without them, but would that possibly be a reason for not having more than one address show up from the I2C Scanner?

Whew, that was a lot of questions in one! I feel like it's a simple enough request (send data from an Arduino to four OLED displays independently), but actually figuring it out has been plaguing me. If I can provide any additional pictures or information to help clear anything up, let me know. Any help would be greatly appreciated. Thank you!

What's written on the back of the board is obviously incorrect, but you should be able to get 2 addresses by moving the jumper to the other side. You will get 0x3C and either 0x3D or 0x3B. To get more than two displays on the bus, you could use an I2C multiplexer.

See I²C Logic Multiplexers/Switches | NXP Semiconductors

A Google search on "multiple i2c devices with the same address" might also lead you to some solutions without the multiplexer but rather directly toggling the control lines to select between which of the two devices at the same address are being addressed.

Hi there Pete,

I'm trying to do exactly the same thing (but with 2 displays).

I have exactly the same displays as well. I'm about to change the jumper which I'm sure will address it to 0x3D

What I don't know is how to address the respective LCD's within the code.

IE, how would you

print.oled1 ('Hello World')
print.oled2('Hello World')

Which is what I think you are asking? Assuming the addressing is sorted that is.

If anyone has the answer to that I'll swap the pins over and report back on the outcome.

I'm interfacing my arduino with a raspberry PI, so my other option is to plug the 2nd display into the Pi and drive it from there, but I'd rather not.

Thanks for the responses so far! I will look more into an I2C multiplexer, but I'm not sure that I understand how I could get more than two unique addresses out of it, when I would need a total of four. I've checked some of the forums and have seen mixed opinions on this.

And I'm with pazu, I have no idea how I would actually send the different data to the displays software-side. Is u8glib capable of this, or possibly Wire or something else (if I need to be asking this in a different forum, I can do that)? Also, will pull-up resistors be necessary for all the screens? It seems like they work right now, so I don't know why everybody makes a big deal about them, but I'm also incredibly ignorant when it comes to electronics hardware.

There's also the option of getting different screens, like the 6- or 8- pin ones that have a "CS" pin (I assume that would be for an SPI transfer protocol, which I don't know much about yet). If my current screens will be too difficult to make work, would those other screens be a better option?

Again, thanks for any help.

pazu:
l. I'm about to change the jumper which I'm sure will address it to 0x3D

What I don't know is how to address the respective LCD's within the code.

IE, how would you

print.oled1 ('Hello World')
print.oled2('Hello World')

You could try what I just tried and see if it actually works. I just changed a code that works by making the changes as shown below. It compiles, but it's not tested.....

Here's part of my original, working code for 1 display:

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DS1302.h>

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

void setup()   {                
  // by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)
  // init done

  // Show image buffer on the display hardware.
  // Since the buffer is intialized with an Adafruit splashscreen
  // internally, this will display the splashscreen.
  display.display();
  delay(2000);

  // Clear the buffer.
  display.clearDisplay();
}


void loop() {
  // text display tests
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.println("Jim says: Yo, world!");

}

And this code compiles for 2 (display. and display1.) but is not tested

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DS1302.h>

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
#define OLED1_RESET 5  // would need to be not 4 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Adafruit_SSD1306 display1(OLED_RESET);   //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<, new

void setup()   {                
  // by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)
  display1.begin(SSD1306_SWITCHCAPVCC, 0x3D);   // new <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  // init done

  // Show image buffer on the display hardware.
  // Since the buffer is intialized with an Adafruit splashscreen
  // internally, this will display the splashscreen.
  display.display();
  display1.display();  //new<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<,
  delay(2000);

  // Clear the buffer.
  display.clearDisplay();
  display1.clearDisplay();  // new<<<<<<<<<<<<<<<<<<<<<<
}


void loop() {
  // text display on one panel
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.println("Jim says: Yo, world!");
  
    // text display on the other
  display1.clearDisplay();
  display1.setTextSize(1);
  display1.setTextColor(WHITE);
  display1.setCursor(0,0);
  display1.println("Jim says: Yo, world!");

}

Maybe worth a try, assuming the hardware way of setting the address. Use the i2cscanner to make sure the 2nd panel is seen.

I had posted a similar question in the Display's section and had a good response there that I am going to test tonight, my post is here: Multiple OLED SSD1306 Displays using 2IC - Displays - Arduino Forum

But I got directed to this post here by Olikraus who I think helps maintain u8glib:
http://forum.arduino.cc/index.php?topic=219419.30

The excerpt from there shows this as a solution using the u8glib library:

olikraus:
SSD1306 I2C address:

In the utility folder you will find the file "u8g_com_arduino_ssd_i2c.c". In line 57 you will see:

#define I2C_SLA		(0x3c*2)

Change this to 0x03d*2 (or 0x07a). You could also change this macro a global variable:

uint8_t I2C_SLA = 0x3c*2;

Then add to your .ino file:
extern uint8_t I2C_SLA;

Now you can change between the displays by assigning the address to this global variable.

 I2C_SLA = 0x078;   // first display

I2C_SLA = 0x07a;   // second display




ok, not tested, but i think it should work.

Oliver

The following posts after this bit indicate that it works fine.

I'm wondering if this will work with the Adafruit library as well (declaring a global variable that is).

I'll switch the jumper tonight and test the above code (and try with adafruit library) as well as the suggestion by JimboZA and report back on the outcome.

Pete-st, this will solve the issue if the display's have different addresses you can reference, if you are going to use 4, perhaps a multiplexer combined with this example may get it work (at least this provides a way to display based on address, so in theory you could expand to 4 displays).

Cheers,
Matt

Just providing some quick feedback, I swapped the jumper for one of the displays and as expected it switched the address correctly. Using 2IC Scanner I am getting 0x3C and 0x3D so that works :slight_smile:

I've tested JimboZA's suggestion and it works to some extent, when sending commands to display 1 its fine, when I send something to display 2, I get Display 1's AND display 2 appearing on both screens overlapping, which is weird.

I'm about to run up u8glib now.

My code is as follows:

Initial defines:

//Define Type of LCD and objects
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
#define OLED_RESET 5
Adafruit_SSD1306 display2(OLED_RESET);

Code within void setup()

  // by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (using 128x64 oLED)
  display1.begin(SSD1306_SWITCHCAPVCC, 0x3D); // initialize with the I2C addr 0x3D (using 128x64 oLED)
  // init done

Sample code is:

  display.clearDisplay();
  display1.clearDisplay();
  display.setCursor(0,0);
  display1.setCursor(0,0);
  display.setTextSize(1);
  display1.setTextSize(1);
  display.setTextColor(WHITE);
  display1.setTextColor(WHITE);
  display.println("System Boot Check");
  display.println("Display 1");
  display1.println("System Boot Check");
  display1.println("Display 2");
  display.display();
  display1.display();

Past that code, the original boot sequence continues just on display 1, so in the image attached you can see when I tried to identify Display 1 and 2, it doubled up on both screens? Am I sending the commands in correctly? Seems odd to get that result?

Ok, so I have tested the u8glib library, and I can address them individually which is fine., the problem I am having now is that I can't address them at the same time within the runtime. I feel this is similar to the adafruit lib so maybe I'm using the libs wrong?

For example, I'm just using the HelloWorld example as a test, so if I declare/change the variable at the start of the loop like this:

void loop(void) {
   I2C_SLA = 0x3c*2;   // second display
//     I2C_SLA = 0x3d*2;   // first display
  // picture loop
  u8g.firstPage();  
  do {
    draw();
    } while( u8g.nextPage() );
  
  
  // rebuild the picture after some delay
  delay(500);
}

This will display 'Hello World' on one display, if I then comment out display 2 and uncomment display 1, when I compile and run, 'Hello World' will display on the other display fine.

However if I try and display content on both displays at the same time, it seems to ignore the 2nd display and load everything into the first display. I have tried a few different ways, which include:

Attempt 1:

void loop(void) {
//   I2C_SLA = 0x3c*2;   // second display
//   I2C_SLA = 0x3d*2;   // first display
  // picture loop
  u8g.firstPage();  
  do {
    draw();
    draw2();
    } while( u8g.nextPage() );
  
  
  // rebuild the picture after some delay
  delay(500);
}



void draw(void) {
  I2C_SLA = 0x3c*2;   // second display
  // graphic commands to redraw the complete screen should be placed here  
  u8g.setFont(u8g_font_unifont);
  //u8g.setFont(u8g_font_osb21);
  u8g.drawStr( 0, 22, "DISPLAY1");
}

void draw2(void) {
  I2C_SLA = 0x3d*2;   // first display
  // graphic commands to redraw the complete screen should be placed here  
  u8g.setFont(u8g_font_unifont);
  //u8g.setFont(u8g_font_osb21);
  u8g.drawStr( 0, 22, "DISPLAY2");
}

Attempt 2:

void loop(void) {
I2C_SLA = 0x3c*2;   // second display
//   I2C_SLA = 0x3d*2;   // first display
  // picture loop
  u8g.firstPage();  
  do {
    draw();
    } while( u8g.nextPage() );

//   I2C_SLA = 0x3c*2;   // second display
I2C_SLA = 0x3d*2;   // first display
  // picture loop
  u8g.firstPage();  
  do {
    draw2();
    } while( u8g.nextPage() );  
  
  // rebuild the picture after some delay
  delay(500);
}


void draw(void) {
  // graphic commands to redraw the complete screen should be placed here  
  u8g.setFont(u8g_font_unifont);
  //u8g.setFont(u8g_font_osb21);
  u8g.drawStr( 0, 22, "DISPLAY1");
}

void draw2(void) {
  // graphic commands to redraw the complete screen should be placed here  
  u8g.setFont(u8g_font_unifont);
  //u8g.setFont(u8g_font_osb21);
  u8g.drawStr( 0, 22, "DISPLAY2");
}

I've posted the above in my other post so hopefully Oliver see's it and can provide some advice to get it going.

Hey pazu and jimboZA, thanks for responding! Unfortunately, I am at work and away from my displays at the moment, but I'll have a chance to look at them later tonight. After reading through the code that pazu/Olikraus posted, I think I understand the 2-display setup (other than the overlapping issue that you seem to be getting on u8glib).

If I were to get a multiplexer, however, I don't know what that would do for me. How would I get two more unique addresses other than x78 and x7a?

Pete-st:
Hey pazu and jimboZA, thanks for responding! Unfortunately, I am at work and away from my displays at the moment, but I'll have a chance to look at them later tonight. After reading through the code that pazu/Olikraus posted, I think I understand the 2-display setup (other than the overlapping issue that you seem to be getting on u8glib).

If I were to get a multiplexer, however, I don't know what that would do for me. How would I get two more unique addresses other than x78 and x7a?

I was feeling especially bored at work, so I did a lot of research on multiplexers. I'm very interested in this one - http://www.dsscircuits.com/index.php/i2c-multiplexer - as it seems to be able to handle 4 I2C devices. But I have also seen people linking (namely here: http://forum.arduino.cc/index.php/topic,110115.0.html ) to this multiplexer - SparkFun Analog/Digital MUX Breakout - CD74HC4067 - BOB-09056 - SparkFun Electronics - which I'm a little confused about as it mentions using the digital pins on the Arduino, rather than the two analogue pins.

Am I at least beating down the right path? Thanks!

With the mux chips, you use the same address for all devices, and use 2 digital pins to select which device you are sending to before you actually send.
So for example:

for (x=0; x<2; x=x+1){
  for (y=0; y<2; y=y+1){
    digitalWrite(A0pin, x); // write to mux address line
    digitalWrite (A1pin, y); // write to mux address line
// Sends to device #00, 01, 10, then 11
// your I2C code for a screen here
   } 
}

CrossRoads:
With the mux chips, you use the same address for all devices, and use 2 digital pins to select which device you are sending to before you actually send.
So for example:

for (x=0; x<2; x=x+1){

for (y=0; y<2; y=y+1){
    digitalWrite(A0pin, x); // write to mux address line
    digitalWrite (A1pin, y); // write to mux address line
// Sends to device #00, 01, 10, then 11
// your I2C code for a screen here
   }
}

Thanks! That definitely makes sense in concept, but I'll have to wait until I actually get the multiplexer to see how it looks hardware and software side. I went ahead and ordered this one - http://www.dsscircuits.com/index.php/i2c-multiplexer - so I'll post back here when it comes.

Fixed!!!

I've converted my code over to u8glib and got it working.

I posted in the original forum post and got some sample code there I could reverse engineer with slight modifications. My complete code is

Updated ug8_com_arduino_ssd_i2c.c file:

//#define I2C_SLA		(0x7A)
uint8_t I2C_SLA = 0x7A;

Sample Sketch (almost identical to the sample post here: Problem with SSD1306 LCD and U8glib - Displays - Arduino Forum other than the 3rd line.

#include "U8glib.h"

U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE);
extern uint8_t I2C_SLA;

boolean redraw_display_one = false;
boolean redraw_display_two = false;

void setup()
{
  I2C_SLA = 0x078;
  u8g.firstPage();
  do
  {
    draw_message_one();
  }
  while(u8g.nextPage());

  delay(10);

  I2C_SLA = 0x07A; 
  u8g.firstPage();
  do
  {
    draw_message_two();
  }
  while(u8g.nextPage());
}

void loop()
{
  if (redraw_display_one)
  {
    I2C_SLA = 0x078;

    u8g.firstPage();
    do
    {
      draw_message_two();
    }
    while(u8g.nextPage());

    redraw_display_one = false;
  }

  if (redraw_display_two)
  {
    I2C_SLA = 0x07A;
    u8g.firstPage(); 
    do
    {
      draw_message_one();
    }
    while(u8g.nextPage());
    redraw_display_two = false;
  }
  
  unsigned long time = millis();
  
  if (time > 5000 && time < 5033)
  {
    redraw_display_one = true;
    redraw_display_two = true;
  }
}

void draw_message_one()
{
  u8g.setFont(u8g_font_fub20);
  u8g.drawStr(12, 28, "Hello");
  u8g.drawStr(12, 58, "World");
}

void draw_message_two()
{
  u8g.setFont(u8g_font_fub20);
  u8g.drawStr(12, 28, "Goodbye");
  u8g.drawStr(12, 58, "World");
}

Works perfectly now!

Glad to hear you got yours working, pazu! Hopefully I'll have similar success once I get my hands on a multiplexer.

Although CrossRoads, rereading your response I realized that I'm confused about something. What did you mean by "digital pins"? Will I have to plug 2 additional wires from the digital pins on the Arduino and into the mux? I get the concept of counting to 4 in binary with two "pins", but I guess I don't follow how I would actually use your code/set up my hardware.

I get the concept of counting to 4 in binary with two "pins"

Clearly you don't :slight_smile: since you can only count to 3 with 2 pins. 4 values, yes, counting 00, but only up to 3..... (00, 01, 10, 11)

JimboZA:

I get the concept of counting to 4 in binary with two "pins"

Clearly you don't :slight_smile: since you can only count to 3 with 2 pins. 4 values, yes, counting 00, but only up to 3..... (00, 01, 10, 11)

Haha, counting to 4 discrete values. My bad :slight_smile:

The multiplexer came!

...and after some initial testing, I'm a little stuck. I plugged everything into the mux as seemed most logical. And when I ran the I2C scanner, it gave me address 0x70 (the address of the mux, different from 0x3C, the address of the screens). Hooray!

However, I don't seems to be able to get any farther than that. When the screens' SDA and SCL lines are plugged into the multiplexer, it doesn't show up on the I2C scanner, nor does the original "hello, world" example work on it. When I plug the SDA and SCL lines directly from my Arduino to the screen, it works. So I know that it has to be a problem with the mux, somehow. See the picture for how I wired it. That's my least strong suit, so I'm guessing something is wrong there.

GND, VDD, SDA, and SCL on the multiplexer are wired straight to the Arduino's GND, 5V, A4, and A5 pins, respectively (just as the screens were). And then on the other side of the mux, SCL0 and SDA0 go to one of the screen's SCL and SDA pins. I also split the wires going from the Arduino's 5V and GND pins to connect with the screen's GND and VCC pins, so that both the screen and mux have power.

Is this part of the process correct? Or are there digital pins that I need to plug in somewhere as well that I don't know about. Also, I'm not sure what the additional pins on the mux (A0, A1, A2, A3, INT) are for, or if I need to concern myself with them. I expected to get both the address of the mux and the address of the screen from the I2C scanner, but that obviously isn't the case.

I'd appreciate any help. Thanks!

EDIT: I forgot to add the picture, so that should be attached now. I haven't soldered the connections yet because I don't know if they're right, but they're connecting just fine at the moment. If any other pictures would help, just let me know and I can certainly post them.

A0 A1 A2 are the address pins which select which of the 4 displays the mux is talking to. You will need to pull them high or low with Arduino digital output pins. See Crossroads and JimboZA's earlier posts.

cattledog:
A0 A1 A2 are the address pins which select which of the 4 displays the mux is talking to. You will need to pull them high or low with Arduino digital output pins. See Crossroads and JimboZA's earlier posts.

Hi cattledog,

Thanks, that should have been obvious. I kept all the other wiring the same, but added two wires going from the UNO's digital pins 12 and 13 to A0 and A1 on the mux. When I ran the I2C scanner, I got 0x73 instead of 0x70 for the mux address (as well as either 0x71 and 0x72 when I accidentally disconnected one of the new wires). So that means I have 4 unique addressed for the mux due to the two digital pins. Progress!

However, the address of the screen (I only have one of the four displays connected right now for testing purposes) still doesn't show up on the I2C scanner. I added the loop the CrossRoads provided into the loop() section of the Hello World example, but nothing was output to the screen. So now the new code looks like this:

void setup(void) {
  pinMode(A0pin, OUTPUT);
  pinMode(A1pin, OUTPUT);

  //u8glib setup stuff here...
}
void loop(void) {
  for (int x=0; x<2; x=x+1){
    for (int y=0; y<2; y=y+1){
      digitalWrite(A0pin, x); // A0pin is set to 12 above
      digitalWrite(A1pin, y); // A1pin is set to 13 above
      // your I2C code for a screen here...
      
      // picture loop
      u8g.firstPage();  
      do {
        draw();
      } while( u8g.nextPage() );
      // rebuild the picture after some delay
      delay(2000);
    } 
  }
}

I also played with putting in pull-up resistors on the screen. I only have 10k resistors laying around at the moment, instead of the recommended 4.7k resistors, so that may be an issue as well.

Any ideas? Again, sorry for being so new. If there's any additional information that would be helpful if I provided, I can certainly do so.

Hi Pete-st

Is the mux the one you linked to in your reply #11?

If so, it works slightly differently from how your code expects.

The A0 etc pins select the I2C of the mux itself. But to select which display the mux connects at a given time, you need to send a control byte to the mux.

There is some sample code on the website you linked to. Have a look at the mux() function defined in that. You will need to use that to select each display just before you write to it.

Also, if you only have one mux, you can hardwire the A0 etc pins low and remove that part of your code.

Regards

Ray