// 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