How to monitor state of 50 Digital Inputs

Hello. Good day.

I have a specific project need and I spent the past couple of days searching on forum, blogs, etc and I'm still not clear if that's feasible to do with Arduino or not.

I need to monitor the state of ~50 digital inputs and trigger an action when any of the pin changes either from 0 to 1 or from 1 to 0, pretty much the same as pin state interrupt works, only difference is that I need to "listen" to ~50 pins, instead of just a couple. I must be able to identify what pin triggered the action and what was the change (0->1 or 1->0).

My initial idea is to use a MEGA 2560 and have a group of 50 "if" statements on the main loop comparing the previous state or each pin (stored in individual variables) with the current one and triggering the corresponding action if something changed.

I'd like to hear from the experts here what would be a more elegant or smarter/faster way to accomplishing this.

I'm eager to receive your feedback.

Thank you.

Mega maybe - and then scan all the pins, keeping track of the state.

For all those pins make sure they’re on as few PORTs as possible. Then you can read a PIN register (8 bits for 8 pins), compare that to the previous state of that register, and see if any of those 8 pins changed status. If any change, XOR the two, and you see which pin changed.

Something like this:

byte previousPIND;

void loop() {
  byte currentPIND = PIND;  // Read the state of the pins of the D port.
  if (currentPIND != previousPIND) { // Something has changed here.
    byte changedPin = previousPIND ^ currentPIND;
    if (changedPin | (1 << 0)) {} // PD0 changed.
    if (changedPin | (1 << 1)) {} // PD1 changed.
    if (changedPin | (1 << 2)) {} // PD2 changed.
    // etc.
  }
}

Sounds like an XY problem.

This could be an XY problem so you need to give more information about why you want to do this. You also need to give more information about what will be attached to the inputs, how freqently inputs will change state and the minimum length of time inputs might stay in a given state.

If there are switches attached to the inputs and they change freqently for short durations then you will have to think hard about how you will deal with contact bounce in order to have an elegant solution.

wvmarle:
Mega maybe - and then scan all the pins, keeping track of the state.

For all those pins make sure they’re on as few PORTs as possible. Then you can read a PIN register (8 bits for 8 pins), compare that to the previous state of that register, and see if any of those 8 pins changed status. If any change, XOR the two, and you see which pin changed.

Something like this:

byte previousPIND;

void loop() {
  byte currentPIND = PIND;  // Read the state of the pins of the D port.
  if (currentPIND != previousPIND) { // Something has changed here.
    byte changedPin = previousPIND ^ currentPIND;
    if (changedPin | (1 << 0)) {} // PD0 changed.
    if (changedPin | (1 << 1)) {} // PD1 changed.
    if (changedPin | (1 << 2)) {} // PD2 changed.
    // etc.
  }
}

I too have been pondering solutions to manage a large number of digital inputs, without having to scan (ie., read every time through loop()) each pin or port. I haven’t found one I like yet.

Which is faster

if (changedPin | (1 << 0)) {} // PD0 changed.

or

if (bitRead (changed pin(0))) {} // PD0 changed

bitRead() is a macro, see this thread.

I have the feeling that my solution will be optimised just a bit better: the bit shift is a constant, so doesn't need to be performed all the time. The macro performs it on the provided variable, so this can't be done at compile time. That's one extra instruction for the macro.

Then I do an OR, the macro an AND, I suppose both are equivalent in processing time. Should be a single instruction. So one instruction for me, two for the macro, making the macro code twice as big. But maybe the compiler is smarter than that and can optimise both to a single instruction.

The real test would be to write code that does one or the other, compile it, and see what happens. Though I've seen code go up/down by 100 bytes or so over a change that didn't actually change the functionality of a sketch in any way. That was the optimiser that suddenly found (or lost) a good optimisation.

Perhaps a few 16-bit port expanders would work. The MCP23017 (I2C) or MCP23S17 (SPI) is under $2 on a breakout board from eBay. The SPI model can be read at 10 MHz (8 MHz is Arduino UNO max) where the I2C tops out at 400 KHz on the UNO. The I2C model has three address pins so you can put eight of them (128 inputs) on the I2C bus.

Four expanders (64 inputs) should be plenty for your purpose.

johnwasser:
Perhaps a few 16-bit port expanders would work. The MCP23017 (I2C) or MCP23S17 (SPI) is under $2 on a breakout board from eBay. The SPI model can be read at 10 MHz (8 MHz is Arduino UNO max) where the I2C tops out at 400 KHz on the UNO. The I2C model has three address pins so you can put eight of them (128 inputs) on the I2C bus.

Four expanders (64 inputs) should be plenty for your purpose.

The OP is using a MEGA and I assume many of the input are direct wired. In my particular case many of the inputs are via I2C PCF8575 I/O boards. Still leaves the 'scanning' aspect as the 8 bit input word for the 8 bit input port (PORTD from above) is essentially the same thing.

adwsystems:
I too have been pondering solutions to manage a large number of digital inputs, without having to scan (ie., read every time through loop()) each pin or port. I haven’t found one I like yet.

Well, with a large number of inputs - such as a keyboard (but you would always scan a keyboard as a matrix anyway), you most certainly scan them by the width of the port at a time, because the actual comparison operators in code are generally byte/ word wide, so that becomes the fastest way. When you find something has changed, you go to the bit level, identify the first changed bit, process that, alter the mask to confirm it as changed, and run the test again to see if another has changed.

It is pointed out that PCF8574 or such chips are good as you can just poll the pin change interrupt pins …

adwsystems:
Which is faster

if (changedPin | (1 << 0)) {} // PD0 changed.

or

if (bitRead (changed pin(0))) {} // PD0 changed

Darned if I know - I don’t even understand that! :astonished:

adwsystems:
In my particular case many of the inputs are via I2C PCF8575 I/O boards.

Which also makes more sense than using a Mega 2560 in any case as it simplifies the wiring loom by modularising it. You group the inputs by 8s and have fewer (I2C) lines running from group to group.

Limited length of I2C bus you say? Is it any better running masses of wire from a Mega 2560 the same distance?

Paul__B:
Which also makes more sense than using a Mega 2560 in any case as it simplifies the wiring loom by modularising it. You group the inputs by 8s and have fewer (I2C) lines running from group to group.

Only if the source of the inputs is grouped somewhere away from the Arduino, or that you can otherwise group 8 or 16 of them together on a single port expander. You somehow, somewhere have 50 pairs of wires going to the individual buttons. Well, maybe a few less depending on the physical layout of course, but in the end every button has two connections.

I’m working on a project that has some 20 sensors total plus 8 pumps to be controlled - going to be mostly cables of three strands each for the sensors, and only at the PCB, right next to the MCP23017s and the ESP8266, they all come together. The sensors are just too far apart to bundle them elsewhere. Also the pumps connect directly on the PCB. Going to be a big bundle of wires, glad I don’t have to do this for 50 inputs!

Monitoring the interrupt lines is a good idea indeed, much faster than requesting the current port states over I2C.

wvmarle:
Monitoring the interrupt lines is a good idea indeed, much faster than requesting the current port states over I2C.

You can do the same thing with '328 PortX.