I particularly like the idea of using these in a SPI chain, as SPI slaves, with buffer mode enabled (CTRLB.BUFWR set to 1). Each encoder would yield a 24-bit record, where the first byte is a 6-bit decisecond counter describing how long the button has been pressed, clamped to 63, starting at 1, with two MSB always 0b10. The next two bytes are the encoder state as a 16-bit unsigned integer. The slave chain works like a shift register, and the MCU can find out the number of encoders connected at run time from the first byte in each 24-bit record. Each encoder state is latched on a suitable transition of the /SS, noting that there must be a couple of milliseconds between that and the first clock pulse, so that all encoders have time to latch their state before the SPI clock starts cycling.
I have looked into "exotic" SPI bus architectures a few times for various things. The main draw back of custom bus signalling as you suggest, is the timing requirements. I needed microsecond timing, but once you try and add "grace time" for slaves to respond to the SS line and tight timing goes out the window.
Nothing exotic here! Well, actually, I was thinking of having the encoders latch their state at the rising edge of /SS.
In practice, you can either pulse /SS, in which case you're treating it as a latch strobe (in addition to slave select active low), or treat it as a normal slave select active low signal, in which case the slaves latch their state at the end of each read sequence. In neither case is there any kind of tight signaling: just a minimum /SS high time. With 16 or 20 MHz ATtiny202/204/402/404, I'd say 10µs minimum /SS high would be plenty (and lead to rock solid performance, since it corresponds to 160 or 200 cycles, several times the cycles the actual latch operation needs, even if accounting for concurrect encoder interrupts occurring – although I'd set it to higher priority than encoder interrupts anyway).
Sure, it would be nice if you had a separate latch strobe, but then you need more than 8 pins (VCC, GND, 3 for the encoders with a button, CLK, MOSI/DI, MISO/DO). ATtiny204 has 14 pins, is in stock, and at Mouser costs about 0.67€ apiece, so you could have a separate latch strobe and simultaneous support for I²C and chained SPI with those.
(I do have an idea how the minimum could be eliminated by "pre-buffering" the initial byte to the SPI outgoing data register whenever it changes, but I'd need to verify its timing in practice before I can claim it is possible to do.)
SPI bus chaining, I have seen it in some advanced SPI use cases with the MOSI leaves the master to the first slave, which at the same time, in it's slot sends the last buffer out it's while receiving the current on the MISO. Each in turn along the line do this. I can see this means you only need 1 SS line. It also adds latency.
That's why you want the latch strobe. That sets the exact moment when the encoder is read. True, there is latency, but let's consider for a moment. Even if you used a 400 kHz clock (similar to fast I²C), you can read 16 encoders once every millisecond (16×24×1000 = 384,000, leaving 40µs per millisecond for the latch strobe). Conversely, the latency is then less than a millisecond. USB HID devices like mice and keyboards also have at least a millisecond of hardware latency (due to USB HID protocol), so I'd say it is perfectly acceptable.
Even with naïve coding, I believe up to 1 MHz SPI clock should be easy to support, maybe higher. (I do believe the maximum SPI slave clock these ATtinys can support is half the main clock, so 8/10 MHz, but it needs to be limited it low enough so the encoder interrupts occurring during SPI transfers cause no glitches or errors to occur.)
So, no, latencies are not a problem. The 10µs minimum latch strobe duration can be annoying, considering it is 10
X clock cycles at
X MHz, but anything up to a millisecond should be perfectly acceptable, as it just adds to the latency (which is a fraction of a millisecond to a millisecond). Furthermore, repeated latching (without reading the data in between) is perfectly harmless.
If one considers the protocol, then one could just as easily support push buttons and analog potentiometers in the same bus, by e.g. using the two or three most significant bits of the 24-bit report as the report type identifier, with 000 or 111 indicating no device (achieved by a terminating resistor to ground or VCC in the MOSI/DI pin at the last device in the chain). For any kind of button-like device, I do like the idea of it reporting the duration it has been kept pressed (which is not automatically cleared when no longer pressed, only after the report has been successfully sent and it is no longer pressed).
Another option would be to have each encoder be an I²C master, with the MCU being the I²C slave, so that whenever the encoder state changes, it sends an I²C event to the MCU (slave!); inverted to the normal situation, essentially. One would need to assign each encoder a fixed identifier (included in the I²C data packet), basically their own 7-bit ID, repeated in the event report. Optimally, it would also encode the type of the encoder (maybe two bits?).
However, I haven't implemented such a many-master single-MCU-slave I²C bus myself, so I don't know if there are gotchas I haven't thought of that make it impractical. In particular, I don't know if the MCU-slave should respond to all messages on the I²C bus, with the address field indicating the
sender, except when it itself is the actual master (sending control commands to a particular encoder), or if there are issues with that.
I certainly don't like the idea of having to poll each device via I²C. It
feels inefficient, somehow, to me –– a purely emotive argument, though.