// manyI2Cbuses.ino
//
// Version 1, 29 August 2021, by Koepel
//
// Project: A clock is shown that counts the minutes and seconds.
// With 30 software I2C buses plus 1 hardware I2C bus.
// Each segment of the 7-segment number is a LCD display.
// Each LCD display has its own I2C bus, but they do share the SCL.
//
// This Wokwi project:
// https://wokwi.com/arduino/projects/307886580731740737?dark=1
// (the ?dark=1 is added for a dark theme, to make the number more visible)
//
//
// 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
//
// With help from urish:
// https://wokwi.com/arduino/projects/307973374851678786
//
// And help from mattam:
// https://wokwi.com/arduino/projects/307982556017459778?dark=1
//
//
// 7-segment order:
// a
// f b
// g
// e c
// d
//
#include <SoftwareWire.h>
#include "LiquidCrystal_I2C_Soft.h" // changed for using software I2C
#include "StatusDisplay.h" // an extra OLED display for the status
// Pattern for the numbers 0...9 for 7-segment display
const byte pattern7seg[10] =
{
// gfedcba the segments, 'a' is upper in bit 0, 'b' is upper-right in bit 1
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 the displays 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 index7seg[4][7] =
{
// a, b, c, d, e, f, g the segments
{ 0, 5, 6, 4, 3, 1, 2 }, // the indexes of the display for the segments
{ 7, 12, 13, 11, 10, 8, 9 },
{ 16, 21, 22, 20, 19, 17, 18 },
{ 23, 28, 29, 27, 26, 24, 25 },
};
#define MIDDLE_BAR_1 14 // the bar in the middle, upper one
#define MIDDLE_BAR_2 15 // the bar in the middle, lower one
const int sclPin = 23; // common SCL pin for all software 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_Soft * pLCD[NUM_DISPLAYS];
// Counting the seconds with a millis timer.
// The two bars in the middle blink within that millis timer
int seconds; // 99 minutes will fit in a integer
unsigned long previousMillis;
const unsigned long interval = 1000UL;
const unsigned long middleBarDelay = 300UL;
bool middleBarTimer;
void setup()
{
StatusDisplayInit();
// 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.
// These objects are created, but never released from memory.
for( int i=0; i<NUM_DISPLAYS; i++)
{
pWIRE[i] = new SoftwareWire( sdaFirstPin + i, sclPin);
pLCD[i] = new LiquidCrystal_I2C_Soft( pWIRE[i]);
}
for( int i=0; i<NUM_DISPLAYS; i++)
{
pLCD[i]->init();
pLCD[i]->backlight();
// fill all the rows with full blocks (character 255 is full block)
for( int row=0; row<2; row++)
{
pLCD[i]->setCursor( 0, row);
for( int column=0; column<16; column++)
{
pLCD[i]->write( 255);
}
}
}
// Show initial value of zero
showDigit( 0, 0);
showDigit( 1, 0);
showDigit( 2, 0);
showDigit( 3, 0);
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
seconds++;
if( seconds >= (100 * 60)) // 99 minutes + 59 seconds is the last valid one
seconds = 0;
// these four lines are not fully tested 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);
// Turn on the two bars in the middle
lcdOnOff( MIDDLE_BAR_1, true);
lcdOnOff( MIDDLE_BAR_2, true);
// Start a millis-delay to turn the bars off
middleBarTimer = 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( middleBarTimer) // millis-timer-delay running ?
{
if( currentMillis - previousMillis >= middleBarDelay)
{
lcdOnOff( MIDDLE_BAR_1, false);
lcdOnOff( MIDDLE_BAR_2, false);
middleBarTimer = false; // turn off own millis-timer-delay
}
}
StatusDisplayUpdate( seconds); // update the extra OLED display
}
// The digit is from 0...3. The left one is 0
// The number is 0...9, it is the number that will be displayed
void showDigit( int digit, int number)
{
// Lay the number through the pattern conversion table on the display
// A single bit indicates if a segment should be on or off.
// The index of the LCD display for that segment is in a table.
for( int segment=0; segment<7; segment++)
{
int bit = bitRead( pattern7seg[number], segment); // bit is on or off ?
bool active = (bit == 1) ? true : false; // turn bit into bool
int index = index7seg[digit][segment]; // the index of the display
lcdOnOff( index, active);
}
}
// Turn segement on (true) or off (false)
// The segment of the 7-segment is a LCD display.
// The first parameter is the index of the display.
//
// There are a number of posibilities:
// a. by turning on and off the backlight.
// b. by changing the text from spaces to full blocks.
// c. by turning on and off the display
// d. any combination of a, b and c.
void lcdOnOff( int i, bool on)
{
if( on)
{
pLCD[i]->backlight();
pLCD[i]->display();
}
else
{
pLCD[i]->noBacklight();
pLCD[i]->noDisplay();
}
}