Library and schematics are at https://github.com/maxgerhardt/rotary-encoder-over-mcp23017.
To read a rotary encoder (i.e., detect state changes of the knob), there are two possible implementations:
- poll the state of the two output pins and check for changes
- set up an interrupt which will be called when an output pin changes, then check for changes
Using interrupts is more efficient than always polling the pins, because an action is only executed when an actual change occurs.
Per rotary encoder you would need two pins on your microcontroller. However, we can use GPIO expanders over e.g. I2C or SPI, such as
Using a port expander over I2C has the advantage being a bus that only uses 2 pins. This means that there can be multiple (up to 127) devices on the I2C bus, and each I2C device can then e.g. provide 16 GPIO pins.
Using an ATTiny85 per encoder and possibly joining them via an I2C bus is also possible, however more expensive and more work.
I've chosen a MCP23017 here because it is interrupt-capable, has 16-bit and I had it lying around. A PCF8574 would have also worked just fine, since it has the same capabilites.
Tests were made using an MCP23017 and a KY-040.

The written library uses a modified Adafruit MCP23017 library and the Rotary library. The Rotary library also handles debouncing by using a state-machine based approach for decoding. The library is a simple header file which defines a RotaryEncOverMCP
C++ object, with which a rotary encoder connected to an MCP23017 device can be easily described and managed. It has an example for the polling method and the interrupt method.
For 1 encoder, one can write down schematic like: (interrupt based)


Since one MCP23017 has 16 ports, we can connect up to 8 rotary encoders to one such device.

However the MCP23017 also has three address pins with which its I2C address can be selected. This means we can have 2^3 = 8 such device on the same I2C bus (i.e., connected to the same SDA and SCL lines).
Thus, with one I2C bus, which uses 2 pins, we can have a maximum of 8xMCP23017 and 64 rotary encoders (assuming no interrupts are used, which would need 1 pin for any number of rotary encoders). At the cost of 2 more pins on the microcontroller, we can create another software bit-banged I2C bus, each of which adds again a maximum of 64 rotary encoders.
Assuming D2 to D13 and A0 to A5 are free, on these 18 pins we can have 9 I2C buses with a total of 72 MCP23017 devices hosting a grand total of 576 rotary encoders. Whatever one might need that for.
I've tested this library with one rotary encoder and a MCP23017, but the code is structured in a way that adding more encoders is just adding an entry in an array.


rotary encoders need two pins
... not all ... what kind do you have?