Dual I2C Displays [Arduino]

posted in: Tutorials 0

History

When I worked on the Arduino Blaster project, I mentioned that the two displays could have been connected to the same I2C pins saving valuable I/O pins as well as memory, by not having to load another driver. To demonstrate this, I created a tutorial showing how to do this, initially using the displays from the aforementioned project.

The first thing I needed to do was to set up a simple circuit with just the Arduino Nano and the two displays, both connected to the Arduino Nano hardware I2C pins (A4 / A5).

Using More Than One Display

Both of the displays used in the Arduino Blaster project are SSD1306-based. This means that, by default they have the same address. The 128×32 display does not have the ability to change the I2C address, however the 128×64 display does have a single address selection on the back of the PCB.

This is in the form of a 4.7K SMD resistor that connects two of three pads to select one of two addresses. You’ll want to desolder the resistor from the 1st and 2nd pad and move it to the 2nd and 3rd pad. While the silk-screen shows addresses of 0x78 and 0x7A, due to the way I2C addressing works including the read / write bit, the actual address you will use in the code is 0x3C and 0x3D. See the resources link for more information on I2C addressing.

NOTE: The actual method of changing the address can vary for different modules. Some cannot be changed at all. The addresses of connected I2C devices can be found using the I2C Scanner code found in the Arduino IDE under File / Examples / Wire / i2c_scanner

Changing the Jumper (Warning)

I want to warn you about using a hot-air tool to do this. I often use my hot-air tool to remove (or even reflow) SMD parts. The OLED is usually secured to the controller PCB via double-sided sticky tape.

Unfortunately, the heat damaged the OLED, blowing out several pixels in the upper-right corner of the display, which is opposite that resistor on the back of the display. Sadly, there’s no way to repair this unit, however I did order more displays and will use a soldering iron on these and future displays.

The first photo shows not only the displays linked above, but also ELEGOO 6PCS 0.96 Inch OLED Display Screen Module with Dupont Wire and UCTRONICS 0.96 Inch OLED Module 128×64 Yellow Blue SSD1306 Driver I2C. The photo on the right shows MakerFocus 5pcs 0.96″ OLED Display Module I2C SSD1315 Driver 128 x 64. Not all of these displays can have their address changed, but these are the ones I am experimenting with, since they are relatively inexpensive.

Connections

The connections on most of these displays are the same:

  1. GND – Power / Signal Ground
  2. VCC – +5VDC
  3. SCL – Serial Clock (I2C)
  4. SDA – Serial Data (I2C)

These pins (both displays) connect to the same signals on the Arduino Module. On the Arduino Uno / Nano, the I2C pins are:

  • Arduino GND = Display GND
  • Arduino 5V = Display VCC
  • Arduino A4 = Display SDA
  • Arduino A5 = Display SCL

Adafruit Library Example

The first demo shown at the beginning of this article uses the Adafruit Library for the SSD1306. Since one of the displays was 128×32, I had initially set the resolution set that way. When I switched to 128×64, I found the demo no longer ran, even with two 128×64 displays. Switching to 128×32 for both gives the above results.

What I learned is that the Adafruit library uses a large chunk of RAM for the display buffer (1K, I believe). When you’re running two 128×32 displays there is just enough memory for things to work. When you increase the resolution to 128×64, you run out of RAM. As you can see in the photo above, the screen looks rasterized, but handles the display nicely, treating it as a 128×32 display (using every other row). Here’s the code:

#include <Wire.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)

Adafruit_SSD1306 display1(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Adafruit_SSD1306 display2(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setup() {  
  display1.begin(SSD1306_SWITCHCAPVCC, 0x3C); // Default OLED address, usually
  display2.begin(SSD1306_SWITCHCAPVCC, 0x3D); // Second OLED address, via onboard jumper
  
  
  display1.clearDisplay();
  display1.setTextSize(1); 
  display1.setTextColor(WHITE);
  display1.setCursor(0,0);
  display1.print("--=<[ Display A ]>=--");
  display1.setCursor(0,8);
  display1.print("Hello, World!");
  display1.setCursor(0,16);
  display1.print("128x64 pixels");
  display1.setCursor(0,24);
  display1.print("Arduino Example");
  display1.display();
    
  display2.clearDisplay();
  display2.setTextSize(1); 
  display2.setTextColor(WHITE);
  display2.setCursor(0,0);
  display2.print("--=<[ Display B ]>=--");
  display2.setCursor(0,8);
  display2.print("The Quick Brown Fox");
  display2.setCursor(0,16);
  display2.print("Jumped Over The");
  display2.setCursor(0,24);
  display2.print("Lazy Dog");
  display2.display();
}

void loop() {
}

U8G2 Library Example

In this example I’m using the u8g2 library to save RAM. Both displays are configured as 128×64 with the left display is using a 7-point font and the right display is using a 15-point font. The code used is:

#include <Arduino.h>
#include <U8g2lib.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif


U8G2_SSD1306_128X64_NONAME_F_HW_I2C display1(U8G2_R0, 5,4,U8X8_PIN_NONE);
U8G2_SSD1306_128X64_NONAME_F_HW_I2C display2(U8G2_R0, 5,4,U8X8_PIN_NONE);


void setup(void) {
  display1.setI2CAddress(0x78);
  display1.begin();
  display1.setFont(u8g2_font_squeezed_b7_tr); // choose a suitable font
  display1.clearDisplay();
  display2.setI2CAddress(0x7A);
  display2.begin();
  display2.setFont(u8g2_font_lubR14_tr); // choose a suitable font
  display2.clearDisplay();
}

void loop(void) {
  display1.clearDisplay();
  display1.clearBuffer();          // clear the internal memory
  display1.drawStr(0,8, "--==<([ DISPLAY A ])>==--");
  display1.drawStr(0,24, "Hello, World!");
  display1.drawStr(0,32, "128x64 pixels");
  display1.drawStr(0,40, "The Quick Brown Fox");
  display1.drawStr(0,48, "Jumped Over The");
  display1.drawStr(0,56, "Lazy Dog!");
  display1.sendBuffer();          // transfer internal memory to the display

  
  display2.clearDisplay();
  display2.clearBuffer();          // clear the internal memory
  display2.drawStr(0,16, "DISPLAY B");
  display2.drawStr(0,32, "Hello,");
  display2.drawStr(0,48, "World!");
  display2.drawStr(0,64, "Arduino Uno");
  display2.sendBuffer();          // transfer internal memory to the display
  delay(1000);  
}

This code runs the display routine inside the loop, unlike the previous version. This is because the full demo was meant to change some variables and display them every second, however I ripped out that code to keep the demo simple and short and just display static text. If you have any questions on the hardware, software or operation of the code, please follow the link below to discuss this on the forums.

Resources

Understanding the I2C Bus – External PDF Link

Discuss this tutorial on Savage///Chats


Dual I2C Displays by Chris Savage is licensed under CC BY 4.0

PLEASE FEEL FREE TO LEAVE YOUR COMMENTS, QUESTIONS, SUGGESTIONS OR FEEDBACK ON THIS POST.

Leave a Reply