Help with port expander usage

Hi, I'm working on a gaming device that consists of two screens with 31 buttons, 1 rotary switch (3 positions) and two potentiometers each one. Essentially this: https://i0.wp.com/totalcontrols.eu/wp-content/uploads/2022/06/Total_Controls_1657webb.jpg?fit=1920%2C1009&ssl=1

My idea is to use only one Pro Micro board to control both, and for that, I wanted to use a PCF8575 for each screen in the following way: use 12 of the pins for a 6x6 matrix, and 3 of the rest of the inputs for the rotary switch.

I already have another working device that uses a PCF8575 with an 8x8 matrix. The simplified code for that other device (removing the parts that are not relevant) is:

#include "Joystick.h"
#include "Wire.h"

#define address 0x20

byte buttons[8][8] = {
  { 0, 1, 2, 3, 4, 5, 6, 7 },
  { 8, 9, 10, 11, 12, 13, 14, 15 },
  { 16, 17, 18, 19, 20, 21, 22, 23 },
  { 24, 25, 26, 27, 28, 29, 30, 31 },
  { 32, 33, 34, 35, 36, 37, 38, 39 },
  { 40, 41, 42, 43, 44, 45, 46, 47 }
  { 48, 49, 50, 51, 52, 53, 54, 55 },
  { 56, 57, 58, 59, 60, 61, 62, 63 },
};

bool buttonvalues[8][8] = {
  { 1, 1, 1, 1, 1, 1, 1, 1 },
  { 1, 1, 1, 1, 1, 1, 1, 1 },
  { 1, 1, 1, 1, 1, 1, 1, 1 },
  { 1, 1, 1, 1, 1, 1, 1, 1 },
  { 1, 1, 1, 1, 1, 1, 1, 1 },
  { 1, 1, 1, 1, 1, 1, 1, 1 },
  { 1, 1, 1, 1, 1, 1, 1, 1 },
  { 1, 1, 1, 1, 1, 1, 1, 1 },
};

const bool initAutoSendState = true;

Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_JOYSTICK, 64, 0, false, false, true, true, true, true, false, false, false, false, false);

void setup() {
  Wire.begin();
  Wire.beginTransmission(address);
  Wire.write(0xFF);
  Wire.write(0xFF);
  Wire.endTransmission();
  Joystick.begin();
}

void loop() {
  byte read_value1;
  byte read_value2;
  byte write_value = 1;
  for (int n = 0; n < 8; n++) {
    byte temp = ~write_value;
    Wire.beginTransmission(address);
    Wire.write(temp);
    Wire.write(0xFF);
    Wire.endTransmission();
    if (Wire.requestFrom(address, 2) == 2) {
      read_value1 = (Wire.read());
      read_value2 = (Wire.read());
    }
    for (int i = 0; i < 8; i++) {

      if (bitRead(read_value2, i) != buttonvalues[i][n]) {
        Joystick.setButton(buttons[i][n], !bitRead(read_value2, i));
        buttonvalues[i][n] = bitRead(read_value2, i);
      }
    }
    write_value <<= 1;
  }
}

So essentially as you can see, is what a standard keypad does, setting rows (or cols, I can't remember which one was wich) as output highs by writing them 0xFF, and scanning row by row, which one is pulled down. With that in mind, I check the button ID in array buttons, and assign it a value in array buttonvalues.

Now for my project, I want to do the same, but I only need a 6x6 matrix, and I want to be able to use the other 4 pins (I only need 3), the above code won't work, because I'm writing all the pins to high at some point, and that may give a wrong reading of the other two bits.

So my question is, how can I modify the above code to use the leftover pins? If I could write bit by bit, instead of having to write a full byte, I think it would be easier, but I don't think that's the case.
Also, can you write a byte in binary instead of HEX? so wire.write(111111111) instead of wire.write(0xFF). Because if that's possible, then I could read the last two bits and just write whatever they read.

And since people in this forum is very fond of schematics, and although I don't think is really needed for this question, here it is:

This is not to "entertain" us. We are "fond of schematics" because

  • often, the problem isn't software at all, and a well-drawn schematic tells us that
  • often, the schematic content shows us things that are
    • different from what we are told in the textual problem statement, or
    • are different from what the software is clearly doing.
  • often, a schematic reveals much about the capability of the poster
  • and finally, a schematic tells us much about what the software should require. So, independent of your posting, we can 'see' what should go on.

Hence, a schematic often demonstrates whether the original post's content is self-consistent, and well thought out. In the case of your original post here, the schematic appears to be consistent with the problem statement. Well done, we have confidence now that we can actually look at the software, without chasing a chimera. Off to look at your code, to see what can be done.

2 Likes

Thank you camsysca. I'm also working in parallel to see if I can figure it out by myself, and the used pins may change, as I think using P00 to P05 instead of P02 to P07 may be beneficial for the code.
In any case, the pin usage is up to modification if needed for the code to work, the device is still in the test phase, obviously.

First observation. You have 31 buttons to read. They could be a 4x8 matrix, that lets you scan from 0..7, but only check 4 bits in each. Then, read the other 4 pins separately.
Or, scan both row and column from 0...5, and check 2 of each side separately. I'd go with the 4x8, as long as I was sure I wasn't going to add more than one more button tomorrow...

Does that help?

They won't be left over until you stop using them.

Looks like a few 8s could be 6s in the part that is a matrix, then use the means of answering the question is a switch closed on an ad-hoc manner for the three or four you could now use.

Looks like you can just digitalRead() for you rotary switch. No, wait, you could grab them out of the same place the 6x6 is scanner is getting its bits.

HTH

a7

Unfortunately that part is not possible to change, the matrix needs to be a 6x6 matrix because I've already designed and manufactured a custom PCB for the device.

Ok, I think I might have found something that works, or it's close to the solution. So first thing, I'm using pins P07, P16 and P17 as the "leftover" pins in the expander. That way, the bits for that are at the end of the byte. Then I just need to read the byte before running the matrix loop and change the write and read values depending on the rotary switch position. This is the test code (I'm sorry, I'm aware it can be a bit confusing):

#include "Wire.h"
#define address 0x20

void setup() {
  Serial.begin(9600);
  Wire.begin();
}

void loop() {
  byte read_value1; //pins P00 to P07. Only the last bit is used for the rotary switch. 
  byte read_value2; //pins P10 to P17. P16 and 17 are also used for the rotary switch.
                    //This is the byte that stays fixed in the matrix scan, and only varies if a button
                    //is pressed. If the rotary switch is in the P07 position, this will be 255 or 11111111.
                    //if rotary position is in P16, it will be 191 or 10111111. If P17 it will be 127 or 01111111
  byte write_value = 1; //This is the value of the column that we will be turning on and off for 
                        //scanning. If P07 is not selected, it will rotate the bit (column) that is low every time
  byte temp2 = 255;
  if (Wire.requestFrom(address, 2) == 2) { //I first read the board to know where the rotary switch is
    read_value1 = (Wire.read());
    read_value2 = (Wire.read());
    if (read_value1 == 127) { //this value will be 127 if the rotary is in P07, because it pulls down.
      write_value = 128; //write_value is now 10000001
    } else if (read_value2 == 127) { //this value will be 127 if the rotary is in P17.
      temp2 = 127;
    } else if (read_value2 == 191) { //this value will be 191 if the rotary is in P16
      temp2 = 191;
    }
  }
  for (int n = 0; n < 6; n++) { 
    byte temp = ~write_value; // we need to reverse this value because outputs are high by default
    Wire.beginTransmission(address);
    Wire.write(temp); // this temp value is the one that simulates the output value in the scan so turns on an off each column at a time
    Wire.write(temp2); // this temp value is fixed and depends on the position of the rotary switch
    Wire.endTransmission();
    if (Wire.requestFrom(address, 2) == 2) {
      read_value1 = (Wire.read());
      read_value2 = (Wire.read());
    }
    for (int i = 0; i < 6; i++) {
      Serial.print(bitRead(read_value2, i)); // we read each bit of the row. Here's where we would add the proper code when a button is pressed.
    }
    write_value <<= 1;
  }
}

Sounds good, but you don't need to know anything but which 6x6 is closed when you scan, and get the rotary switch.

Then decide how to interpret any 6x6 button pressed based on the rotary switch.

In many cases, separating the inputs gleaned from what they eventually mean is a way to divide and conquer.

The general idea is seen in the IPO model. Here it would mean you could fully write, test and perfect the all switch reading, and in another section entirely do anything about it.

See

IPO Model

and here's a tip o' the hat to @paulpaulson, relentless advocate.

a7

@Assamita OK, I thought I would show you what I meant with a few deft changes to what I thought I saw through the tiny windo in transit, and all I can say (lazy!) is that to trick the scanner by changing how it works based off the rotary switch is a nightmare.

Just plain scan the matrix to get any presses, then read the rotary switch and use logic to do whatever that press coupled with that rotary position means to the code using them as input.

Divide and rule. Sleep better. :expressionless:

a7

1 Like

You're right!! I was overthinking/overcomplicating it by treating the rotary switch as an output when it's obviously an input. It doesn't matter what do I do with the state of those pins when I'm scanning the matrix, the state will be correct when I read those pins for the switch as they are two different steps.

It would be an issue if those pins were outputs.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.