Minimizing pins using I2C question

I found this sketch by Robojax, his Lesson 67.

/*
 Lesson 67: Arduino code for 4x3 keypad with 8 channel relay
 * Library taken : https://playground.arduino.cc/Code/Keypad
 * This is the Arduino code for 4x3 keypad with 8 channel relay
  Watch instruction video for this code: https://youtu.be/dkESWpdDBYk
 *   
 * written by Ahmad Shamshiri for Robojax.com 
 * on Feb 04, 2019 at 18:01 in Ajax, Ontario, Canada
 * This video is part of Arduino Step by Step Course which starts 
 *  here: https://youtu.be/-6qSrDUA5a8
 *  
 *  
 *  If you found this tutorial helpful, please support me so I can continue creating 
 *  content like this. You can support me on Patreon http://robojax.com/L/?id=63
 *  
 *  or make donation using PayPal http://robojax.com/L/?id=64
 *  
 *   This code is "AS IS" without warranty or liability. Free to be used as long 
 *  as you keep this note intact.* 
 * This code has been download from Robojax.com
 * This program is free software: you can redistribute it and/or modify
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 * 
 * @file HelloKeypad.pde
|| @version 1.0
|| @author Alexander Brevig
|| @contact alexanderbrevig@gmail.com
||
|| @description
|| | Demonstrates the simplest use of the matrix Keypad library.
|| #
 *
 * My modifications 7/30/23
 * 1. mod to 4x4 keypad
 * 
*/


////////////////////////////////////////////////////////////////
// start of old keypad settings 
#include <Keypad.h>
// new keypad settings
//#include <I2CKeyPad.h> //  https://github.com/RobTillaart/I2CKeyPad

const byte ROWS = 4; //four rows
const byte COLS = 4; //4 columns
char keys[ROWS][COLS] = {
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'*','0','#','D'}
};

byte rowPins[ROWS] = {2, 3, 4, 5}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {6, 7, 8, 9}; //connect to the column pinouts of the keypad
Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
// end of old keypad settings
/////////////////////////////////////////////////////////////////////////





//////////////////////////////////////////////////////////////////////
// start of relay settings
const int relayPin[]={10,11,12, 13, 14, 15, 16, 17};// output pins where 8 relays will be connected
String relayNames[] ={"CH1","CH2","CH3","CH4","CH5","CH6","CH7","CH8"};
// Just put name for 8 relays

// do not change lines bellow
int pushed[] ={0,0,0,0, 0,0,0,0};// status of each buttons
int relayStatus[] ={HIGH,HIGH,HIGH,HIGH, HIGH,HIGH,HIGH,HIGH};// initial status of relay

// end of relay settings
/////////////////////////////////////////////////////////////////////




















/////////////////////////////////////////////////////////////////
void setup()  //old setup
{
  // Robojax 4x4 keypad with 8ch Relay test
  Serial.begin(115200);  
  for(int i=0; i<8; i++)// for the keypad-pressed numbers 1-8 only
  {
    pinMode(relayPin[i], OUTPUT); 
  // set relay pins as OUTPUT 10,11,12, 13, 14, 15, 16, 17 
    digitalWrite(relayPin[i], HIGH);
    // initially, all 8 (i) relay status to be OFF
    // like this line Wire.write(0xFF);  

  }
  
  Serial.println("My mod of Robojax 8 channel relay keypad");
  
}// end old setup
/////////////////////////////////////////////////////////////////  







/////////////////////////////////////////////////////////////////
void loop()
{
   // Robojax 4x4 keypad with 8ch Relay test
  int val;
  int knum;
  char key = keypad.getKey();
  
    // just print the pressed key
  if(key && key !='*' && key !='#' && key !='0' && key !='9' )
  // verifies that pressed key is only 1-8
  {
    knum = (int)key-49;// convert char to integer (one less)
    if(knum>=0 && knum<8){
     Serial.println(knum);
         if(relayStatus[knum] == LOW){
        
            pushed[knum] = 1-pushed[knum];
            delay(50);
          }// if   
         controlRelay(knum);// turn relay ON or OFF
    }
  }else{
    val = LOW;
  }
  
  if(knum>=0 && knum<8){
        relayStatus[knum] = val;
  }

delay(50);

}
// loop end
/////////////////////////////////////////////////////////////////////




////////////////////////////////////////////////////////////////////
// void controlRelay
/*
 * 
 * @brief Turns the relay ON or OFF 
 * @param relayPin is integer pin of relay
 * @return no return value
 * Written by Ahmad Shamshiri for Robojax.com 
 */

 void controlRelay(int number)
 {

     if(pushed[number] == 1)
     {
      digitalWrite(relayPin[number], LOW);// Turn ON relay
      Serial.print(relayNames[number]);
      Serial.println(" ON");
     }else{
      digitalWrite(relayPin[number], HIGH); // turn OFF
      Serial.print(relayNames[number]);
      Serial.println(" OFF");
     }
 // Robojax 4x3 keypad with 8ch Relay test
}//controlRelay end
//////////////////////////////////////////////////////

It does everything I need it to do. How would I go about using an I2C Keypad, an I2C 8-channel relay module to print, not on the serial monitor but to specific areas of a 20x4 I2C LCD.
here's a quick and dirty schematic.

start small. Get a i2c LCD display and get it working. Do the same for an i2c relay module and keypad module.

After you have each one working individually, then start putting them all together to achieve your final goal.

Robotax missed initialising this local variable. That can lead to some seriously nasty and difficult to find errors.

You may just be lucky. Please

  int knum = -1;

which will never match a key press, and will not risk undefined behaviour for using an uninitiated variable.

As for the LCD, it's just code. I note that Robojax has some material covering LCDs. It's conceptually very simple: you say where on the screen you want to be, then say what you want printed there.

As you drew it, all I2C devices live on the same bus, the only thing to worry about is that they have unique addresses and you have the right amount of pull up resistors installed.

HTH

a7

Thank you for the advice. I'm still trying to get the breakdown of what he is doing. About ten years ago I was able to do some Arduino IDE programming and since then, not so much. I have more patience now and I'm trying to understand how Robojax takes a keypad entry and turn on a specific relay and that relay stays on until it is tapped again. I'm trying to use Rob Tillaarts PCF8574 V. 0.3.8, and he's not worked out the pinMode like Robojax did. I'm also using I2CKeyPad for my 4x4 keypad. The not same one Robojax uses, but working out to a 4x4 keypad.
When he goes all through the integer testing that's what throws me. With I2CKeyPad, I used his KeyMap example and it works well writing and I can place items at specific location on a 20x4 LiquidCrystal_I2C LCD.

The code reads a key and makes sure it is in the range '1' to '8'
The code then converts this ASCII char into a number by substracitng 49. A more obvious solution would be to use

knum = key - '1';

since the ASCII code for '1' is 49 (or 0x31). This is now the index into the array of pins connected to the relay (0..7). The relayStatus array keeps track of the current state of the relays, so if it is currently on, it turns it off and vice-versa

I did start smaller and arrived at the point where I could only label and energize/de-energized one at a time. I stepped back even farther than that and , while searching for libraries that had keywords that matched some of Robojax's programming, I came up with this code below, which threw an error "too many arguments to function 'void controlRelay()' .
I'm try to use Rob Tillaart's I2CKeyPad library to get the char ch = keyPad.getChar(); to work the I2C LCD display while using the int of key = keyPad.getLastKey(); for the working Robojax's knum variable to toggle the relayStatus[] ={0,0,0,0,0,0,0,0}, as each I2CKeyPad key is pressed.

#include "Arduino.h"
#include <Wire.h>
#include <PCF8574.h>  // https://www.mischianti.org/2019/01/02/pcf8574-i2c-digital-i-o-expander-fast-easy-usage/
#include <I2CKeyPad.h>  // Rob Tillaart https://github.com/RobTillaart/I2CKeyPad
#include <LiquidCrystal_I2C.h>

// setting up display features of 20x4 at address 0x3F
LiquidCrystal_I2C lcd(0x3F, 20, 4);

// setting up keypad to keymap at 0x20
const uint8_t KEYPAD_ADDRESS = 0x20;
I2CKeyPad keyPad(KEYPAD_ADDRESS);
char keymap[19] = "123A456B789C*0#DNF";  // N = NoKey, F = Fail


// Set i2c address of I2C 8-ch relay module at 0x21
unsigned char addr = 0x21;
PCF8574 pcf8574(addr);  // Reno Mischianti's PCF8574 library supports pinMode



// start of relay settings
const int relayPin[] = {P0, P1, P2, P3, P4, P5, P6, P7};
// modified to the PCF8574 pin bunbers where 8 relays will be connected

String relayNames[] = {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"};
// Just put name for 8 relays

// do not change lines bellow
int pushed[] = {0, 0, 0, 0, 0, 0, 0, 0}; // status of each buttons
int relayStatus[] = {HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH}; // initial status of relay
// end of relay settings

void controlRelay();


void setup()
{
  lcd.init();
  lcd.backlight();
  Serial.begin(115200);

  pcf8574.pinMode(P0, INPUT_PULLUP);
  pcf8574.pinMode(P1, INPUT_PULLUP);
  pcf8574.pinMode(P2, INPUT_PULLUP);
  pcf8574.pinMode(P3, INPUT_PULLUP);
  pcf8574.pinMode(P4, INPUT_PULLUP);
  pcf8574.pinMode(P5, INPUT_PULLUP);
  pcf8574.pinMode(P6, INPUT_PULLUP);
  pcf8574.pinMode(P7, INPUT_PULLUP);



  Wire.begin();
  Wire.setClock(400000);
  Serial.print("Init pcf8574...");
  // this verifies the I2C_Relay_8-ch_module is up and running
  if (pcf8574.begin()) {
    lcd.setCursor(0, 0);
    lcd.print("OK");
  } else {
    lcd.setCursor(5, 0);
    lcd.print("KO");
  }



  for (int i = 0; i < 8; i++)
  {
    pinMode(relayPin[i], OUTPUT); // set relay pins as output at relayPin array
    digitalWrite(relayPin[i], HIGH);// initial relay status to be OFF
    //this takes the place of the I2C set all 1 at addr
    //Wire.begin();
    //Wire.setClock(400000);
    //Wire.beginTransmission(addr);   // only from wire,
    //Wire.write(0xFF);         // Turns off all relays
    //Wire.endTransmission();
  }



  if (keyPad.begin() == false)
  {
    lcd.setCursor(0, 0);
    // lcd.autoScroll();
    lcd.print("\nERROR: cannot communicate to keypad. Please reboot.\n");
    while (1);
  }

  keyPad.loadKeyMap(keymap);  //
}


void i2c_relay(unsigned char addr, unsigned char value)
{
  Wire.beginTransmission(addr);
  Wire.write(value);         //
  Wire.endTransmission();
}




////////////////////////////////////////////////////////////////////////////
void loop()
{
  int val;
  int knum;
  int key;
  char ch;


  if (keyPad.isPressed())
  {
    char ch = keyPad.getChar();     // note we want the translated char
    int key = keyPad.getLastKey();

    // just print the pressed key
    if (key && key != '*' && key != '#' && key != '0' && key != '9' && key != 'A' && key != 'B' && key != 'C' && key != 'D')

    { // makes sure that the the "key" is from 4x3 keypad and ignores these keystrokes

      knum = (int)key - 49; // convert char to integer (one less)
      if (knum >= 0 && knum < 8) // verifies that knum is between 0-<8
      {
        if (relayStatus[knum] == LOW)
        { //changes relayStatus variable at knum position
          pushed[knum] = 1 - pushed[knum];
          delay(50);
        }

        controlRelay(knum);// turn relay ON or OFF
      }
    } else {
      val = LOW;
    }

    if (knum >= 0 && knum < 8) {
      relayStatus[knum] = val;  // sets relayStatus at knum to val
    }


    // Light up key pressed on LCD
    switch (ch)
    {
      case '1':
        lcd.setCursor(0, 0);
        lcd.print(ch);

        break;

      case '2':
        lcd.setCursor(3, 0);
        lcd.print(ch);

        break;

      case '3':
        lcd.setCursor(6, 0);
        lcd.print(ch);

        break;

      case '4':
        lcd.setCursor(0, 1);
        lcd.print(ch);

        break;

      case '5':
        lcd.setCursor(3, 1);
        lcd.print(ch);

        break;

      case '6':
        lcd.setCursor(6, 1);
        lcd.print(ch);

        break;

      case '7':
        lcd.setCursor(0, 2);
        lcd.print(ch);

        break;

      case '8':
        lcd.setCursor(3, 2);
        lcd.print(ch);

        break;

      case 'B':
        lcd.setCursor(8, 2);
        lcd.print(ch);

        break;

      case 'D':
        lcd.setCursor(8, 3);
        lcd.print(ch);
        lcd.print(" clear LCD");
        delay(1000);

        lcd.clear();    //lcd.clear();
        break;
    }
  }


  /*

     @brief Turns the relay ON or OFF
     @param relayPin is integer pin of relay
     @return no return value
     Written by Ahmad Shamshiri for Robojax.com
  */

  void controlRelay(int number)
  // turns knum into a number so it can compare with "pushed" string
  // variable as eith 0 or 1
  {
    if (pushed[number] == 1)
    {
      pcf8574.digitalWrite(relayPin[number], LOW);// Turns ON relay
    } else
    {
      digitalWrite(relayPin[number], HIGH); // Turns OFF relay
    }
    // Robojax 4x3 keypad with 8ch Relay test
  }//controlRelay en

  // -- END OF FILE --

Then why are you trying to redo it?

That's a declaration of a function you are planning to put elsewhere in you code.

It promises that controlRelay takes no arguments, and returns nothing.

This is your function

  void controlRelay(int number)
  // turns knum into a number so it can compare with "pushed" string
  // variable as eith 0 or 1
  {
    if (pushed[number] == 1) ... 

// blah blah ya dad ya dad
  }

It says that controlRelay takes one argument and returns noting.

You've basically disagreed with yourself, and the compiler will variously not like that at all.

HTH

a7

Robojax's code does everything I need it to, except cutting down on the pin numbers, i.e., put everything on the MEGA2560's I2C bus. I need other pins for a whole lot more things to add into a menu-like program to run a piece of machinery that I an trying to reverse engineer.