Control-Surface
Control-Surface copied to clipboard
PCF8574 Digital I/O Port Expander (with toggle Buttons and KY-040 Digital Encoder)
Hi,
I have 6 toggle buttons and 6 KY-040 digital Encoder on the ports of 3 PCF8574 Port Expander. They are driven via i2C and have one Interrupt pin and 8 digital I/O Pins. I had already success via Wire to get a byte with the actual button states with the interrupt pin. (see example)
Is it possible to use this port expander with your library or maybe if not to use the byte that i get via wire to map it via your library to the toggle switches/encoders?
Thanks for your work!!
(example)
#define pcf1 0x20
#define pcf2 0x24
byte pcf1byte;
pinMode(INT_PIN, INPUT_PULLUP);
Wire.begin();
Wire.beginTransmission(pcf1); /
Wire.write(0xFF);
Wire.endTransmission();
Wire.beginTransmission(pcf2);
Wire.write(0xFF);
Wire.endTransmission();
[...]
if (digitalRead(INT_PIN) == LOW) {
Wire.requestFrom(0x20, 1); // request 1 bytes from PCF8574 at address 0x20 [output e.g: 11111010]
pcf1byte = Wire.read();
[...]
`
If you want to use it with Control Surface's NoteButton classes etc., you'll have to implement the ExtendedIOElement interface for the PCF8574. You have to inherit from the ExtendedIOElement class and override and implement all pure virtual member functions, see https://github.com/tttapa/Control-Surface/blob/master/src/AH/Hardware/ExtendedInputOutput/ExtendedIOElement.hpp. Just provide implementations for the pinMode, digitalRead, digitalWrite methods, as well as initialization in the begin method. You can have a look at the multiplexer and shift register implementations in the same folder for some inspiration. I'm not sure what your experience level is, so if you need more help, just let me know.
This approach will not work for rotary encoders. Polling them through a port expander will probably be too slow, and the PJRC Encoder library which is used internally by Control Surface doesn't support the ExtendedIOElement interface.
Control Surface does support rotary encoders through MCP23017 expanders, see MCP23017Encoders.hpp, but you cannot mix buttons and encoders on the same expander.
You could of course also just read the port expanders yourself, in your main loop. You can then call Control_Surface.sendNoteOn(...) directly instead of using the NoteButton class, for example.
Thanks for your time! Maybe you could give me more hints in the right direction how to implement he read and write functions the right way with your library that i mentionted above:
Wire.requestFrom(0x20, 1);
pcf1byte = Wire.read();
Do i have to modify the library and build it from source to implement my own IOElement or can i do the override in my code?
using namespace MIDI_Notes;
void setup() {
pinMode(4, INPUT_PULLUP);
Wire.begin();
Wire.beginTransmission(0x20); // transmit to PCF8574
Wire.write(0xFF);
Wire.endTransmission(); // stop transmitting
Wire.beginTransmission(0x24); // transmit to PCF8574
Wire.write(0xFF);
Wire.endTransmission();
Wire.beginTransmission(0x26);
Wire.write(0xFF);
Wire.endTransmission();
Control_Surface.begin();
}
void loop() {
if (digitalRead(4) == LOW) {
char bytes[3];
Wire.requestFrom(0x20, 1); // request 1 bytes from PCF8574 at address 0x20
bytes[0] = Wire.read(); // receive byte
Wire.requestFrom(0x24, 1);
bytes[1] = Wire.read();
Wire.requestFrom(0x26, 1);
bytes[2] = Wire.read();
char bits[24];
int i;
for (i=0; i<sizeof(bytes)*8; i++) {
bits[i] = ((1 << (i % 8)) & (bytes[i/8])) >> (i % 8);
}
compareBuffer(bits);
}
Control_Surface.loop();
}
void compareBuffer (char bits[24]){
for (int i = 0; i<24; ++i) {
if (bits[i] != bitbuffer[i]) {
if (bits[i] == LOW) Control_Surface.sendCC({i, CHANNEL_1}, LOW);
if (bits[i] == HIGH) Control_Surface.sendCC({i, CHANNEL_1}, HIGH);
bitbuffer[i] = bits[i];
}
}}
This works for now but i think i have to debounce sth.. to integrate it further as an ExtendedIO i don't know how to pass the bits in my array to other elements as pins that they could read button states from.
Sorry since I am not a native English speaker, it is not easy to communicate this correctly.
For the ExtendedIO integration, you could do something like this:
Header
class PCF8574 : public StaticSizeExtendedIOElement<8> {
public:
PCF8574(uint8_t address = 0x20, pin_t interruptPin = NO_PIN)
: address(address), interruptPin(interruptPin) {}
void pinModeBuffered(pin_t pin, PinMode_t mode) override;
void digitalWriteBuffered(pin_t pin, PinStatus_t status) override;
int digitalReadBuffered(pin_t pin) override;
analog_t analogReadBuffered(pin_t pin) override;
void analogWriteBuffered(pin_t, analog_t) override;
void begin() override;
void updateBufferedOutputs() override;
void updateBufferedInputs() override;
void updateBufferedPinModes();
private:
uint8_t address;
pin_t interruptPin;
bool pinModesDirty = true;
BitArray<8> bufferedPinModes;
bool pullupsDirty = true;
BitArray<8> bufferedPullups;
bool outputsDirty = true;
BitArray<8> bufferedOutputs;
uint8_t bufferedInputs = 0;
};
Implementation
oid PCF8574::pinModeBuffered(pin_t pin, PinMode_t mode) {
if (mode == INPUT) {
pinModesDirty |= bufferedPinModes.get(pin) == 1;
pullupsDirty |= bufferedPullups.get(pin) == 1;
bufferedPinModes.clear(pin);
bufferedPullups.clear(pin);
} else if (mode == OUTPUT) {
pinModesDirty |= bufferedPinModes.get(pin) == 0;
bufferedPinModes.set(pin);
} else if (mode == INPUT_PULLUP) {
pinModesDirty |= bufferedPinModes.get(pin) == 1;
pullupsDirty |= bufferedPullups.get(pin) == 0;
bufferedPinModes.clear(pin);
bufferedPullups.set(pin);
}
}
void PCF8574::digitalWriteBuffered(pin_t pin, PinStatus_t status) {
bool boolstate = status == HIGH;
outputsDirty |= bufferedOutputs.get(pin) != boolstate;
bufferedOutputs.set(pin, boolstate);
}
int PCF8574::digitalReadBuffered(pin_t pin) {
return bitRead(bufferedInputs, pin) ? HIGH : LOW;
}
analog_t PCF8574::analogReadBuffered(pin_t pin) {
return bitRead(bufferedInputs, pin) ? 1023 : 0;
}
void PCF8574::analogWriteBuffered(pin_t pin, analog_t value) {
digitalWriteBuffered(pin, value >= 0x80 ? HIGH : LOW);
}
void PCF8574::begin() {
if (interruptPin != NO_PIN)
ExtIO::pinMode(interruptPin, INPUT_PULLUP);
// I²C setup if necessary
}
void PCF8574::updateBufferedOutputs() {
updateBufferedPinModes();
if (!outputsDirty)
return;
uint8_t outputData = bufferedOutputs.getByte(0);
// Write this byte over I²C
outputsDirty = false;
}
void PCF8574::updateBufferedInputs() {
// Only update if a pin change interrupt happened
if (interruptPin != NO_PIN && ExtIO::digitalRead(interruptPin) == HIGH)
return;
bufferedInputs = ... // read from I²C
}
void PCF8574::updateBufferedPinModes() {
if (pinModesDirty) {
uint8_t outputData = bufferedPinModes.getByte(0);
// Write this byte over I²C
pinModesDirty = false;
}
if (pullupsDirty) {
uint8_t outputData = bufferedPullups.getByte(0);
// Write this byte over I²C
pullupsDirty = false;
}
}
After typing it out, I realized that the PCF8574 doesn't have any pull-up resistors, so you can ignore most code related to it.