// 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();
}
}