Thanks for your replies. Unfortunately I can't use a touch screen although that's a neat solution. I need physical buttons next to each LED.
I like the idea of using MCP23017 port expanders. I've never used the i2c bus before. Is it straight forward enough to set the address pins?
To set the address pins, you connect the pin to either ground or power. I found it simplest to use either a breadboard or a perma-proto board laid out like a breadboard (
https://www.adafruit.com/products/1608).
Adafruit has a library to access the pins:
https://github.com/adafruit/Adafruit-MCP23017-Arduino-Library
If you want to optimize things, there are two pins (INTA and INTB) that are set high when their is a change to either the A bank of 8 pins or B back of 8 pins changed (you could attach these to a pin on an interrupt so you know when a button was pressed or released). Also, the protocol gets things in 8 bit chunks, so rather than doing an individual read via the
digitalRead accessor function, you can use
readGPIO to read 8 bits at a time or
readGPIOAB to read all 16 bits. But if you are waiting for human input, it may be fast enough just doing it bit at a time.
Also, if you are using the neopixels just as on/off lights, and not really using the color capability, you could use 7 MCP23017's, and put 8 buttons and 8 LEDs on each MCP23017 (you can use the MCP23017 as
digitalRead or
digitalWrite replacements.
The other solutions won't really work as buttons may be pressed simultaneously which would stop the resistor ladder method from working properly and the other method uses too many pins
I've seen designs for a resistor ladder where you can detect simultaneous button presses.
Except for the Teensy 3.0, the 3.1/3.2/LC all have 2 i2c ports, so you could put 8 MCP23017's on one i2c port and 8 on the second. Going further, there are i2c multiplexers, such as onehorse's board:
https://www.tindie.com/products/onehorse/tca9548a-i2c-multiplexer/.
Note, there are distance constraints with i2c.