-
Notifications
You must be signed in to change notification settings - Fork 0
Managing Multiple Displays
- General Considerations
- Same I2C Bus with Different Addresses
- Multiple I2C Buses
- I2C Multiplexers (e.g., TCA9548A)
Since this is an object-oriented library, setting up multiple displays is as simple as initializing multiple display objects. There are some points to consider however:
- Unfortunately, the
SSD1306driver only supports two possible addresses (either0x3Cor0x3D). Meaning, you can have at most two independent displays per I2C bus. - If you want to show the same content on multiple displays you have two options:
- For displays with the same addresses, initializing a single display and connecting them on the same I2C bus is all you need.
- For displays with different addresses, initialize two displays for the two addresses using the same buffer array. You'll need to update both displays after drawing on one.
- If you want independent buffer contents for each display, define a separate buffer array for each one. However, if memory is limited, you have the option to share a single buffer across multiple displays. The
displayandgetter/setterfunctions will operate independently, even with a shared buffer. However, thedrawfunctions will affect all displays sharing the same buffer. To display different contents on sharing displays, you'll need to clear and redraw the entire buffer before every update.
To use multiple displays with different addresses on the same I2C bus, simply initialize the displays with the same i2c_write() implementation:
/* Your i2c_write() implementation... */
static struct ssd1306_display display1; /* 128x64 - I2C address 0x3C */
static struct ssd1306_display display2; /* 128x64 - I2C address 0x3D */
/* display1 initialization */
static uint8_t display1_array[SSD1306_ARRAY_SIZE_64];
ssd1306_init(&display1,
0x3C,
SSD1306_DISPLAY_TYPE_64,
display1_array,
i2c_write);
/* display2 initialization */
static uint8_t display2_array[SSD1306_ARRAY_SIZE_64];
ssd1306_init(&display2,
0x3D,
SSD1306_DISPLAY_TYPE_64,
display2_array,
i2c_write);To use multiple displays with different I2C buses, simply define separate i2c_write() functions for each I2C hardware:
/* Your i2c1_write() implementation for the I2C1 module */
/* Your i2c2_write() implementation for the I2C2 module */
static struct ssd1306_display display1; /* 128x64 - I2C address 0x3C */
static struct ssd1306_display display2; /* 128x64 - I2C address 0x3C */
/* display1 initialization (connected to I2C1) */
static uint8_t display1_array[SSD1306_ARRAY_SIZE_64];
ssd1306_init(&display1,
0x3C,
SSD1306_DISPLAY_TYPE_64,
display1_array,
i2c1_write);
/* display2 initialization (connected to I2C2) */
static uint8_t display2_array[SSD1306_ARRAY_SIZE_64];
ssd1306_init(&display2,
0x3C,
SSD1306_DISPLAY_TYPE_64,
display2_array,
i2c2_write);The simplest way to overcome the "maximum two independent displays per I2C bus" limit is by using an I2C multiplexer. An I2C multiplexer sits between the master and slave devices, routing communication from the source I2C bus to its multiple I2C channels.
Below is an example of managing four displays independently with a single I2C bus. The example uses an STM32 microcontroller with a popular TCA9548A I2C multiplexer breakout board.
The pin descriptions for TCA9548A are:
| Pin | Description |
|---|---|
| VCC | Supply voltage |
| GND | Ground |
| SDA | Serial data source |
| SCL | Serial clock source |
| RESET | Active-low reset input |
| Ax | Address inputs |
| SDx | Serial data channels [0-7] |
| SCx | Serial clock channels [0-7] |
The working principle of TCA9548A is straightforward. It is an I2C-controlled device with a single 8-bit register. Each bit in the register corresponds to the selected or deselected state of one of its eight multiplexed I2C channels. Selecting a channel directs all bidirectional serial communication from the source to that channel.
The diagrams from the datasheet illustrate the complete I2C transaction required to select and deselect channels:
Notes:
TCA9548Abreakout boards usually come with pull-up/pull-down resistors on some of the pins, but we'll connect all lines accordingly anyways for demonstration purposes- The STM32 micro has internal pull-ups for its SDA and SCL lines; hence no external resistors are required.
- All Ax pins of the
TCA9548Aare tied to ground; hence the resulting 7-bit address for it is0x70.Display-1andDisplay-2are connected to channel 7 of theTCA9548A, whileDisplay-3andDisplay-4are connected to channel 6.
/* Other includes... */
#include "ssd1306.h"
void i2c_write_c7(uint8_t *data, uint16_t length) {
/* Select TCA9548A channel 7 */
uint8_t channel = (1 << 7);
HAL_I2C_Master_Transmit(&hi2c1, (0x70 << 1), &channel, 1, 1000);
/* Send display commands (first byte is 8-bit write address) */
uint8_t address = *data;
HAL_I2C_Master_Transmit(&hi2c1, address, ++data, --length, 1000);
}
void i2c_write_c6(uint8_t *data, uint16_t length) {
/* Select TCA9548A channel 6 */
uint8_t channel = (1 << 6);
HAL_I2C_Master_Transmit(&hi2c1, (0x70 << 1), &channel, 1, 1000);
/* Send display commands (first byte is 8-bit write address) */
uint8_t address = *data;
HAL_I2C_Master_Transmit(&hi2c1, address, ++data, --length, 1000);
}
int main(void) {
/* System setup... */
/* Display-1 */
static struct ssd1306_display display1;
static uint8_t display1_array[SSD1306_ARRAY_SIZE_64];
ssd1306_init(&display1,
0x3C,
SSD1306_DISPLAY_TYPE_64,
display1_array,
i2c_write_c7);
/* Display-2 */
static struct ssd1306_display display2;
static uint8_t display2_array[SSD1306_ARRAY_SIZE_64];
ssd1306_init(&display2,
0x3D,
SSD1306_DISPLAY_TYPE_64,
display2_array,
i2c_write_c7);
/* Display-3 */
static struct ssd1306_display display3;
static uint8_t display3_array[SSD1306_ARRAY_SIZE_64];
ssd1306_init(&display3,
0x3C,
SSD1306_DISPLAY_TYPE_64,
display3_array,
i2c_write_c6);
/* Display-4 */
static struct ssd1306_display display4;
static uint8_t display4_array[SSD1306_ARRAY_SIZE_64];
ssd1306_init(&display4,
0x3D,
SSD1306_DISPLAY_TYPE_64,
display4_array,
i2c_write_c6);
/* Draw functions... */
}Note: The code works by selecting the appropriate
TCA9548Achannel for each display before sending the actual I2C packages. This can be accomplished by creating a separatei2c_write()function for each of the I2C channels.
i2c_write_c7()is for displays that connect to channel 7.i2c_write_c6()is for displays that connect to channel 6.