The I2C protocol allows you to connect multiple I2C devices on a single bus. Koepel thought about it and came up with a creative application for the 30 LCDs. He succeeded in connecting them to the Mega and also created a sample code. Wokwi Arduino simulator is used in this project.
Arduino SImulation linkhttps://wokwi.com/arduino/projects/307973374851678786
Arduino simulation output// manyI2Cbuses.ino
//
// Clock with many I2C buses, sharing the SCL signal.
//
// Multiple I2C buses can be created with a software I2C bus.
//
// When this project is made with real hardware, there are some things that need attention:
// 1. The backlight needs current and the 5V pin of the Arduino board has not
// enough power for all of them. A separate power supply is required.
// 2. When 30 LCD displays have 10k pullup resistors, then the shared SCL
// has a combined pullup resistor of 333 Ohm.
// The SCL signal is not strong enough for that. A logic driver can be
// used to make it stronger. The SCL can be changed into a strong push/pull signal,
// because there are no I2C devices that require clock pulse stretching.
// An option can be to change the library and set the SCL pin always
// as a OUTPUT for a strong HIGH/LOW signal.
//
// Starting project:
// https://wokwi.com/arduino/projects/307841659182252608
//
// Evolved into (by sutaburosu)
// https://wokwi.com/arduino/projects/307849131382014528
//
// That evolved into (by arcostasi):
// https://wokwi.com/arduino/projects/307853926099583552
//
// Very nice big font:
// https://www.instructables.com/Custom-Large-Font-For-16x2-LCDs/
//
//
// 7-segment order:
// a
// f b
// g
// e c
// d
//
//
#include <SoftwareWire.h>
#include "LiquidCrystal_I2C.h"
// pattern for number 0...9 for 7-segment display
const byte pattern7seg[10] =
{
// gfedcba
0b00111111, // 0
0b00000110, // 1
0b01011011, // 2
0b01001111, // 3
0b01100110, // 4
0b01101101, // 5
0b01111101, // 6
0b00000111, // 7
0b01111111, // 8
0b01101111, // 9
};
// Table that connects the index of I2C bus to one of the 7 segments.
// There are 4 digits, each with 7 segments, order: abcdefg
// The two in the middle are 14 and 15 and are not in this table.
const int bus7seg[4][7] =
{
{ 0, 5, 6, 4, 3, 1, 2 },
{ 7, 12, 13, 11, 10, 8, 9 },
{ 16, 21, 22, 20, 19, 17, 18 },
{ 23, 28, 29, 27, 26, 24, 25 },
};
const int sclPin = 23; // common SCL pin for all I2C buses
const int sdaFirstPin = 24; // the first SDA pin
#define NUM_DISPLAYS 30
// 30 pointers to 30 objects for the software I2C buses
SoftwareWire * pWIRE[NUM_DISPLAYS];
// 30 pointers to 30 objects for each LCD display
LiquidCrystal_I2C * pLCD[NUM_DISPLAYS];
// Counting the seconds with a millis timer.
// The two bars in the middle blink.
int seconds; // 99 minutes will fit in a integer
unsigned long previousMillis;
const unsigned long interval = 1000UL;
const unsigned long twoMiddleDelay = 200UL;
bool twoMiddleTimer;
void setup()
{
// Create the objects for the I2C bus and for the displays.
// The display will hold a pointer to its own I2C bus inside its object.
for ( int i = 0; i < NUM_DISPLAYS; i++)
{
pWIRE[i] = new SoftwareWire( sdaFirstPin + i, sclPin);
pLCD[i] = new LiquidCrystal_I2C( pWIRE[i]);
}
for ( int i = 0; i < NUM_DISPLAYS; i++)
{
pLCD[i]->init();
pLCD[i]->backlight();
pLCD[i]->setCursor( 0, 0);
for (uint8_t j = 0; j < 16; j++) {
pLCD[i]->write(255);
}
pLCD[i]->setCursor( 0, 1);
for (uint8_t j = 0; j < 16; j++) {
pLCD[i]->write(255);
}
}
previousMillis = millis(); // the setup could take a long time, start at begin
}
void loop()
{
unsigned long currentMillis = millis();
if ( currentMillis - previousMillis >= interval)
{
previousMillis += interval; // special millis timer to stay in sync with time
// this part is untested yet...
int digit0 = (seconds / 600) % 10;
int digit1 = (seconds / 60) % 10;
int digit2 = (seconds / 10) % 6;
int digit3 = seconds % 10;
showDigit( 0, digit0);
showDigit( 1, digit1);
showDigit( 2, digit2);
showDigit( 3, digit3);
seconds++;
if ( seconds >= (100 * 60)) // 99 minutes + 59 seconds is the last valid one
seconds = 0;
// Turn on the two bars in the middle
twoMiddle( true);
// Start a millis-delay to turn the bars off
twoMiddleTimer = true;
}
// The millis-delay-timer uses the millis-timer of the clock.
// It is just another delay within the millis-timer of the clock.
if ( twoMiddleTimer) // millis-timer running ?
{
if ( currentMillis - previousMillis >= twoMiddleDelay)
{
twoMiddle( false); // turn off bars
twoMiddleTimer = false; // turn off own millis-timer
}
}
}
// The digit is from 0...3. The left one is 0
// The number is 0...9, the number that will be displayed
void showDigit( int digit, int number)
{
// Lay the number through the pattern conversion table on the right display
for ( int segment = 0; segment < 7; segment++)
{
int active = bitRead( pattern7seg[number], segment);
int bus = bus7seg[digit][segment];
if ( active == 1)
lcdOnOff( pLCD[bus], true);
else
lcdOnOff( pLCD[bus], false);
}
}
void twoMiddle( bool on)
{
if ( on)
{
lcdOnOff( pLCD[14], true);
lcdOnOff( pLCD[15], true);
}
else
{
lcdOnOff( pLCD[14], false);
lcdOnOff( pLCD[15], false);
}
}
// Turn segement on (true) or off (false)
// Displaying a active segment:
// a. by turning on and off the backlight.
// b. by changing the text from spaces to full blocks.
void lcdOnOff( LiquidCrystal_I2C * lcd, bool on)
{
if ( on)
{
lcd->backlight();
lcd->display();
}
else
{
lcd->noBacklight();
lcd->noDisplay();
}
}
You can meet Koepel and other creative souls here on Discord Wokwi Channel
Share your interesting projects and browse through several curious projects from fellow developers and makers on Facebook Wokwi Group!
Stay Safe!
Don't stop learning!
#wokwiMakes
Koepel

Comments
Please log in or sign up to comment.