// 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();
}
}
mega:SCL
mega:SDA
mega:AREF
mega:GND.1
mega:13
mega:12
mega:11
mega:10
mega:9
mega:8
mega:7
mega:6
mega:5
mega:4
mega:3
mega:2
mega:1
mega:0
mega:14
mega:15
mega:16
mega:17
mega:18
mega:19
mega:20
mega:21
mega:5V.1
mega:5V.2
mega:22
mega:23
mega:24
mega:25
mega:26
mega:27
mega:28
mega:29
mega:30
mega:31
mega:32
mega:33
mega:34
mega:35
mega:36
mega:37
mega:38
mega:39
mega:40
mega:41
mega:42
mega:43
mega:44
mega:45
mega:46
mega:47
mega:48
mega:49
mega:50
mega:51
mega:52
mega:53
mega:GND.4
mega:GND.5
mega:IOREF
mega:RESET
mega:3.3V
mega:5V
mega:GND.2
mega:GND.3
mega:VIN
mega:A0
mega:A1
mega:A2
mega:A3
mega:A4
mega:A5
mega:A6
mega:A7
mega:A8
mega:A9
mega:A10
mega:A11
mega:A12
mega:A13
mega:A14
mega:A15
lcd1f:GND
lcd1f:VCC
lcd1f:SDA
lcd1f:SCL
lcd1b:GND
lcd1b:VCC
lcd1b:SDA
lcd1b:SCL
lcd1e:GND
lcd1e:VCC
lcd1e:SDA
lcd1e:SCL
lcd1c:GND
lcd1c:VCC
lcd1c:SDA
lcd1c:SCL
lcd1a:GND
lcd1a:VCC
lcd1a:SDA
lcd1a:SCL
lcd1g:GND
lcd1g:VCC
lcd1g:SDA
lcd1g:SCL
lcd1d:GND
lcd1d:VCC
lcd1d:SDA
lcd1d:SCL
lcd2f:GND
lcd2f:VCC
lcd2f:SDA
lcd2f:SCL
lcd2b:GND
lcd2b:VCC
lcd2b:SDA
lcd2b:SCL
lcd2e:GND
lcd2e:VCC
lcd2e:SDA
lcd2e:SCL
lcd2c:GND
lcd2c:VCC
lcd2c:SDA
lcd2c:SCL
lcd2a:GND
lcd2a:VCC
lcd2a:SDA
lcd2a:SCL
lcd2g:GND
lcd2g:VCC
lcd2g:SDA
lcd2g:SCL
lcd2d:GND
lcd2d:VCC
lcd2d:SDA
lcd2d:SCL
lcd5a:GND
lcd5a:VCC
lcd5a:SDA
lcd5a:SCL
lcd5b:GND
lcd5b:VCC
lcd5b:SDA
lcd5b:SCL
lcd3f:GND
lcd3f:VCC
lcd3f:SDA
lcd3f:SCL
lcd3b:GND
lcd3b:VCC
lcd3b:SDA
lcd3b:SCL
lcd3e:GND
lcd3e:VCC
lcd3e:SDA
lcd3e:SCL
lcd3c:GND
lcd3c:VCC
lcd3c:SDA
lcd3c:SCL
lcd3a:GND
lcd3a:VCC
lcd3a:SDA
lcd3a:SCL
lcd3g:GND
lcd3g:VCC
lcd3g:SDA
lcd3g:SCL
lcd3d:GND
lcd3d:VCC
lcd3d:SDA
lcd3d:SCL
lcd4f:GND
lcd4f:VCC
lcd4f:SDA
lcd4f:SCL
lcd4b:GND
lcd4b:VCC
lcd4b:SDA
lcd4b:SCL
lcd4e:GND
lcd4e:VCC
lcd4e:SDA
lcd4e:SCL
lcd4c:GND
lcd4c:VCC
lcd4c:SDA
lcd4c:SCL
lcd4a:GND
lcd4a:VCC
lcd4a:SDA
lcd4a:SCL
lcd4g:GND
lcd4g:VCC
lcd4g:SDA
lcd4g:SCL
lcd4d:GND
lcd4d:VCC
lcd4d:SDA
lcd4d:SCL