Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Two OLED Displays 128x64 #138

Closed
geojohn5 opened this issue Aug 10, 2017 · 35 comments
Closed

Two OLED Displays 128x64 #138

geojohn5 opened this issue Aug 10, 2017 · 35 comments
Labels

Comments

@geojohn5
Copy link

Hi!

I cant get two displays to work. Basically write to one, and then the other. As soon as the 2nd one gets initialized the first one no longer works.

SSD1306 display1(0x3d, D3, D5);
SSD1306 display2(0x3c, D1, D2);

display1.init();
display1.flipScreenVertically();
display1.setFont(ArialMT_Plain_10);
display2.init();
display2.flipScreenVertically();
display2.setFont(ArialMT_Plain_10);
display1.clear();
display1.setTextAlignment(TEXT_ALIGN_CENTER);
display1.setFont(ArialMT_Plain_10);
display1.drawString(64, 5, "test");
display1.display();
display2.clear();
display2.setTextAlignment(TEXT_ALIGN_CENTER);
display2.setFont(ArialMT_Plain_10);
display2.drawString(64, 5, "test");
display2.display();

@tfreudling
Copy link

Same problem here :(
But it works, if you init the display again.
Actually it seems, that everything is going to the last initialized display, regardless which one you address...
Not sure, if this re-init all the time will get me some side effects?!?

Thomas

#include "SSD1306.h"
SSD1306  display1(0x3c, D3, D5);
SSD1306  display2(0x3c, D6, D7);
void setup() {
display1.init();
display1.flipScreenVertically();
display1.setFont(ArialMT_Plain_10);
display1.clear();
display1.drawString(0, 0, "first test on display 1");
display1.display();
delay(1000);
display2.init();
display2.flipScreenVertically();
display2.setFont(ArialMT_Plain_10);
display2.clear();
display2.drawString(0, 20, "first test on display 2");
display2.display();
delay(1000);
display1.init();  // have to init it again? Otherwise everything will go to display 2 from now on???
display1.flipScreenVertically();
display1.setFont(ArialMT_Plain_10);
display1.clear();
display1.drawString(0, 40, "second test on display 1"); // this will appear on display2 again! (unless you re-init display 1!
display1.display();
}
void loop() {}

@geojohn5
Copy link
Author

Yeah, the problem is when you do that in a loop it makes the display flash, and eventually it stops working altogether until you reset it.

@tfreudling
Copy link

True! :(
But I found another workaround ;-) Use the brzo_i2c for the second display...

Not pretty, but it seems to work...

#include "SSD1306.h"
#include "SSD1306Brzo.h"
SSD1306  display1(0x3c, D3, D5);
SSD1306Brzo  display2(0x3c, D6, D7);

@geojohn5
Copy link
Author

Nice work! yes, ugly but it works :)

@RestiveX
Copy link

RestiveX commented Dec 5, 2017

Connect the displays to one I2C bus (identical pins). But before that, set them different addresses.
SSD1306 display1(0x3c, D3, D4);
SSD1306 display2(0x3d, D3, D4);

@tofrnr
Copy link

tofrnr commented Feb 19, 2018

agree, same to me, both OLEDs work fine on the same i2c bus (by arbitrary i2c GPIOs):

#include "SSD1306.h"
// Initialize the OLED displays
SSD1306 display1(0x3c, D2, D1);
SSD1306 display2(0x3d, D2, D1);

void setup() {
display1.init();
display2.init();

// snip
}

as usual, at 1 single bus one always needs different i2c bus addresses for either device, instead one may use i2c port splitters, e.g. PCA9548A chip from NXP

IMO solved, issue can be closed.

@dpharris
Copy link

You init'd the same display twice:
display1.init();
display1.init();
I assume that was a typo?

@tofrnr
Copy link

tofrnr commented Feb 19, 2018

sure, a typo, sorry!
just corrected it

@Tannoo
Copy link

Tannoo commented Mar 9, 2018

I can't change the addresses on my displays, so I am using different bus pins. (2 SH1106 - 128x64)

When displaying for each display, I still have to init each one before use.

Works great, but after 18 inits, the ESP errors on (3,6). No amount of ESP.wdtFeed();'s or yield();'s or delay(x);'s seem to fix this.

I'm going to try the Brzo trick for the second display.

Is there something I'm just not getting?

@Tannoo
Copy link

Tannoo commented Mar 10, 2018

The Brzo thing would not work for me at all.

I found @TD-er's PR (#171) and tried that.

My ailments are gone!! All's good now.

@tofrnr
Copy link

tofrnr commented Mar 11, 2018

so which is actually the way how it works now, for identical dev addresses, by explicite source code?

@Tannoo
Copy link

Tannoo commented Mar 11, 2018

This is my simple test with ESP8266, OTA, Neopixels, and two identical 1106's.
The count1 and count2, are just to keep track of each lcd.init() that happens.

#include "SH1106.h"

// Initialize the OLED displays using Wire library
SH1106 display1(0x3c, 4, 5);
SH1106 display2(0x3c, 13, 12);

// Setting up some timers
unsigned long timer1, timer2;
#define INTERVAL1 6000
#define INTERVAL2 1000

uint8_t ledState = LOW;
uint8_t r, g, b;
#define OFF 0, 0, 0

void setup() {
  Serial.begin(115200);

  timer1 = timer2 = millis();
}

void loop() {
  if ((millis() - timer1) > INTERVAL1) {
    timer1 += INTERVAL1;

    // Initialize the first display -- If we don't do this, all the info will be displayed on the last initialized display.
    displayOne();

    // Set font
    display1.setFont(ArialMT_Plain_24);
    // Set alignment
    display1.setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
    // Set what to display and where
    display1.drawString(DISPLAY_WIDTH/2, DISPLAY_HEIGHT/2, "Rainbow");
   // Go put that stuff on the screen
    display1.display();

    timer2 = millis();
  }
  if ((millis() - timer2) > INTERVAL2) {
    timer2 += INTERVAL2;
    if (ledState == LOW) ledState = HIGH;
    else ledState = LOW;

    if (ledState == LOW) {
      // Generate a new RGB color
      r = random(127);
      g = random(127);
      b = random(127);

      // Initialize the second display -- If we don't do this, all of the info will be displayed on the last initialized display.
      displayTwo();

      // Set font
      display2.setFont(ArialMT_Plain_16);
      // Set alignment
      display2.setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
      // Set what to display and where
      display2.drawString(DISPLAY_WIDTH/2, 22, "Timer 1: " + String(timer1 / 1000));
      display2.drawString(DISPLAY_WIDTH/2, 42, "Timer 2: " + String(timer2 / 1000));
      // Set font
      display2.setFont(ArialMT_Plain_10);
      // Set alignment
      display2.setTextAlignment(TEXT_ALIGN_CENTER);
      // Set what to display and where
      display2.drawString(DISPLAY_WIDTH/2, 53, String(count2) + " inits!");
      // Go put that stuff on the screen
      display2.display();
    }
    else {
      // Initialize the first display -- If we don't do this, all the info will be displayed on the last initialized display.
      displayOne();

      display1.setFont(ArialMT_Plain_16);
      display1.setTextAlignment(TEXT_ALIGN_CENTER);
      display1.drawString(DISPLAY_WIDTH/2, 0, "LED: " + String(LEDnum));
      display1.drawLine(40, 18, 90, 18);
      display1.setFont(ArialMT_Plain_10);
      display1.setTextAlignment(TEXT_ALIGN_LEFT);
      if (r >= 100) display1.drawString(44, 24, "*");
      if (g >= 100) display1.drawString(44, 35, "*");
      if (b >= 100) display1.drawString(44, 46, "*");
      display1.drawString(50, 24, "R: " + String(r));
      display1.drawString(50, 35, "G: " + String(g));
      display1.drawString(50, 46, "B: " + String(b));
      display1.display();
    }
  }
}

void displayOne() {
  display1.init();
  // These have to be set after `init()`
  display1.flipScreenVertically();
  // This only needs to be set if other than 255
  display1.setContrast(255);
}

void displayTwo() {
  display2.init();
  // This has to be set after `init()`
  display2.flipScreenVertically();
  // This only needs to be set if other than 255
  display2.setContrast(255);
}

@tofrnr
Copy link

tofrnr commented Mar 15, 2018

@ Tannoo:
for demonstrating the principles of codeing and commanding 2 OLEDs by identical i2c addr, your code is actually way too oversized -
could you please provide a downstripped version, just to show how to initialiaze the 2 OLEDs and then how to use alternating print/draw commands?

@Tannoo
Copy link

Tannoo commented Mar 15, 2018

Sure, I'll just edit the previsous post.

@squix78
Copy link
Collaborator

squix78 commented Mar 15, 2018

FYI: I’m adding this feature to the library. You should be able to transparently use many displays in parallel. It works on my local branch

@Tannoo
Copy link

Tannoo commented Mar 15, 2018

Are we going to be able to display to a specific one without sending an init() first?

@squix78
Copy link
Collaborator

squix78 commented Mar 15, 2018

I want to integrate this into the library. Now the question is how to do this best. First I thought I could just instantiate the TwoWire object in the display driver, but the sda and scl pins are static and shared between all instances.
Now the brute force solution would be to always call Wire.begin(..) when we write to the I2C bus. A slightly better solution would be if we could detect if the default_sda_pin and default_scl_pin in the TwoWire class have changed but I cannot access them. Next best thing would be to keep a static member in the driver to see if another driver instance has changed the default pins. But this only works we the I2C bus is only used by our display drivers, otherwise we wouldn't see a change.
I also have to admit that I'm not really that great in C++, so any suggestion is welcome...

@Tannoo
Copy link

Tannoo commented Mar 15, 2018

Is there a way to elimate the default pins unless none are define in any instance?

@Tannoo
Copy link

Tannoo commented Mar 15, 2018

I say that because SH1106 display1(0x3c, 4, 5); doesn't assume or imply anything as default.

SH1106 display1(0x3c); -- sould take on default pins of 4 and 5 (on ESP).

@TD-er
Copy link
Contributor

TD-er commented Mar 15, 2018

What about a Wire object, which is initialized with the proper pins and hand that to the constructor of the Display class (or those classes inheriting it)?
Then the Display class will keep a pointer to the Wire object. and of course the I2C address of the display.
Personally I don't like static objects, since they may lead to various bugs and keep resources allocated.
There are use cases, for them but storing SDA/SCL pins should not be stored in the display object, but in some object related to the I2C communications object, which can be shared.
This object can be as simple as a struct with 2 members (uint8_t) and maybe some value indicating its used library if needed.

@squix78
Copy link
Collaborator

squix78 commented Mar 16, 2018

Hi guys. I don't like static variables either, but the problems come from the fact that Arduino/ESP8266 wire library uses two static variables to store the pins which makes it a lot harder for us to keep two or more separate instances of the Wire/I2C driver. Have a look here: https://github.com/esp8266/Arduino/blob/master/libraries/Wire/Wire.cpp#L55

So the 100 Mio dollar question is how to we get around this nasty fact? The brute force solution is to always call Wire.begin(...) whenever we communicate over I2C. But that probably will be very inefficient

@igrr: Do you have a suggestion? Do you know why those static variables were introduced in the first place? I think the ESP32 code base doesn't have them anymore...

@TD-er
Copy link
Contributor

TD-er commented Mar 16, 2018

Can't both display objects share the same I2C bus?

@igrr
Copy link
Contributor

igrr commented Mar 16, 2018

@squix78 The reason these were introduced is for the sketch to allow setting pin numbers of I2C bus, even if a 3rd party library is not aware of the way to set pins.

For example, a library written for normal Arduino boards calls Wire.begin(); in its Library.begin() method. In order to use non-standard pins, sketch first initializes Wire library with specific pins (Wire.begin(2,4);) and then calls Library.begin();. This way, when library calls Wire.begin(); (without pin arguments) it ends up using the pins set in the sketch.

@tofrnr
Copy link

tofrnr commented Mar 16, 2018

having even more i2c devices attached to the esp8266, such as ADS1115 or PCF8574, they have to be initialized by static Wire pins.
So when using alternating Wire.pins() it will be indispensible to reset the Wire.pins and restart Wire() before addressing either devices.
So IMO the best way would be to do it that way:

SH1106 display1(0x3c);  // instanciate just by i2c-addr   
SH1106 display2(0x3c);

loop() { 
  display1.init(D1,D2);  // new OLED init,  includes Wire.pins(D1,D2) and Wire.begin();
  // display1 print cmds

  display2.init(D3,D4);  // new OLED init, includes Wire.pins(D3,D4) and Wire.begin();
  // display2 print cmds
 
  // reset Wire pins for arbitrary i2c devices  
  Wire.pins(D1,D2); 
  Wire.begin();
  // code for arbitrary i2c devices

}

@squix78
Copy link
Collaborator

squix78 commented Mar 17, 2018

Here is my suggestion (and currently available on the unreleased master branch). By setting setI2cAutoInit(true) the driver always calls Wire.begin(..) before writing to the I2C bus. This only properly works if we are only using display drivers, not if we are using other I2C devices:

display.setI2cAutoInit(true);
display2.setI2cAutoInit(true);

There is a new demo for this called SSD1306TwoScreenDemo.

What do you guys think?

@TD-er
Copy link
Contributor

TD-er commented Mar 17, 2018

Why not just use 2 screens on the same I2C bus, just with a different address? That's what this bus is for, right?
So why all this effort to use different I2C busses?
If it's a bandwidth issue, I guess there may be other solutions like using a separate "datasender" which can de shared among instances of these Display objects, which is aware of the protocol and may perform data transfers out-of-order when needed.

@tofrnr
Copy link

tofrnr commented Mar 17, 2018

@TD-er :
the topic is about having just OLEDS with identical i2c dev addr

@squix78 :
errm - no, sorry, IMO that is puzzling and anything but intuitive.
tbh, all the d-> things look very weird.

I think the best way would be to make it like I showed in my example above, that is easy and handsome both for beginners with single display and those who use 2 of them

SH1106 display1(0x3c); // instantiate just by i2c-addr

and then
display1.init(pCL, pDA);
both sets the actual Wire.pins and (re)starts Wire accordingly.

But for backward-compatiility you may create such an overloaded function additionally to
display1.init();
without parameters to pass to and which is supposed to work with the default (or the manually chosen) pin settings.

@Tannoo
Copy link

Tannoo commented Mar 17, 2018

I would love to change the address and avoid all the trouble.

Any insight?
20180317_045722

@TD-er
Copy link
Contributor

TD-er commented Mar 17, 2018

@Tannoo the "1" and "2" positions near the connector have some SMD resistors connected either to Vdd or GND.
So changing the 1 from Vdd to GND position should change the I2C address.
I have to look into the datasheet to see to which address it is changed, but you can also do some I2C scan to find it.
Make sure not to place them on both side (GND and Vdd) for the same number, since these "resistors" are very likely just 0 Ohm and adding both to the same "1" or "2" will short circuit it and probably let the magic smoke escape. (the magic smoke which makes electronics work)

@dagnall53
Copy link

I have just run into an issue with two 128*64 displays connected to a common bus. They are addressed 60 and 61, and I use SSD1306 display1(0x3c, OLED_SDA, OLED_SCL); SSD1306 display2(0x3d, OLED_SDA, OLED_SCL);
I initiate them and then am calling both at approx 1 second intervals.
About 10 seconds (constant) after the init, Display 2 goes dark. Display 1 (added as part of debugging to see what was happening) kept working perfectly.

What I noted was that at an earlier point in testing, Display2 was "exposed" to random signals on the I2C, so I have a suspicion that some internal registers were set to a timeout? Is this feasible?
I tried adding the display2.setI2cAutoInit(true); to the init subroutine but it did not cure the Display 2 blanking, a simple but inelegant solution was:
if (millis()>=Disp2ReInit){ display2.init();display2.flipScreenVertically(); Disp2ReInit=millis()+3000;} else{display2.clear();}

HOWEVER, powering off the board for 60 secs seemed to "cure" the timeout issue. (making the re-initialization unnecessary)

So the question remains.. Is there a "I2C Timeout" register somewhere in the SSD, and if so, can we have a function to use (or reset!) it??

@TD-er
Copy link
Contributor

TD-er commented Mar 3, 2019

What happens if you swap both displays?
Just to be sure there isn't an issue with the data lines.
You could also try to add pull-up resistors to both SDA and SCL lines to 3V3. (4k7 - 10k)

@dagnall53
Copy link

Just to give background:
My two displays are addressed 60 and 61, so were both connected to the same SDA/SCL.

I was developing code that works with both addresses, but only had my display2 (61) connected at first.
I was trying some different SDA/SCL porting, and at some points, used SDA/SCL that were doing "other" things, so the display did not start, but may easily have seen random input.
When I had a SDA/SCL pair that worked, I realized that the display was switching off after about 6 seconds, despite being called every second. (I had a countdown displaying on the screen and there was nothing else happening in the code). To help solve this I added the "first" display "display1 (60)" to the same SDA/SCL lines, and saw that it was staying ON when display 2 went off. Remember D1 was not connected when I think some random stuff "got at" D2.
I tried a lot of re-programming using suggestions from this thread, but all the time the board was connected to the PC com port for programming, so was constantly powered up. The only thing that kept D2 on was to do a constant re-init. Eventually I did the "sensible thing" and powered it all off for a minute, and then found the timeout issue had disappeared. (I also removed the display.init every second).
I switched it on again this morning and its again all working fine, both displays - no timing out.

So my only conclusion was that there might be a software timeout in the driver chip to turn off the display (save power??) a set time after initialization. This seemed a sensible forum to ask if such a thing exists.. If it does not, then its at least a good place to note that random input to the displays can potentially result in some very strange, but repeatable, behavior that is not corrected until a power off.

Cheers

@stale
Copy link

stale bot commented Aug 31, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Aug 31, 2019
@stale stale bot closed this as completed Sep 14, 2019
@khvalera
Copy link

khvalera commented Sep 14, 2019

I would love to change the address and avoid all the trouble.

Any insight?
20180317_045722

I also have a problem with this display, I can’t change the address. I tried to change the position of jumpers 1 and 2 of Vdd GND, but this did not help. Does anyone know how to change the address of this display?

The display address cannot be changed without alteration. The 15th contact is pulled up to G, and to change the address you need to apply 3.3 Volts to it.

@TechGraphix
Copy link

TechGraphix commented Aug 12, 2020

This display has a fixed address of 3C
SH1106_1
You have to lift the display from the board with a wedge.
SH1106_2
Give it time to get loose, don't apply force, be gentle...
SH1106_2B
The double sided foamtape remains on the board.
SH1106_3
Fold back the ribboncable. Do'n't make the fold too sharp, just enough to see the traces.
SH1106_4
Locate pin 15.. With the gap on the right side, it is the upper one of the group of 3 traces.
SH1106_5
Use a small sharp knife to cut the trace. Take care not to puncture the ribbon or damage the rest of the PCB. clean it with a brush,
SH1106_6
And fold it back in place. Solder a small thin wire to pin 15 and one of the Vdd connections. In this case i took the closest zere ohm resistor
SH1106_7
And then you're done.. just change the address to 3D in the software.

At last a drawing that makes it more clear..
SH1106_DRAWING

Kees

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests