Arduino PCF8574/8575 MIDI Controller

This project uses a PCF8574 or PCF8575 I2C digital IO expander with an Arduino as a MIDI controller.

Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

These are the key Arduino tutorials for the main concepts used in this project:

If you are new to Arduino, see the Getting Started pages.

Parts list

The Circuit

Either a PCF8574 or PCF8575 module can be used. PCF8574 is a 8-bit module with 8 digital IO ports; the PCF8575 is a 16-bit module with 16 digital IO ports. To start with I’m assuming the use of the PCF8574. I’ll come back to the PCF8575 in a bit.

Several PCF8574 modules can be used by specifying different I2C addresses for each module using the three yellow jumpers on the module.

Here I’ve configured three modules for addresses 0x22, 0x21, 0x20 respectively:

There are 9 headers for IO connections. The left-most pin is for an interrupt connection back to the microcontroller. I’ve not used that here. The right-most 8 pins are for IO connections 7 down to 0 respectively.

To use the pins as inputs, just treat them as you would digital inputs in INPUT_PULLUP mode – i.e. they will read HIGH until pulled to GND via a button or other means of making a connection.

The connection to the Arduino uses 5V and GND, plus SDA and SCL either via the dedicated pins on the “digital” side or via A4 and A5 as required.

Finally a MIDI module is connected to the Arduino TX pin – in my case I’m using one of my Arduino MIDI Proto Shields.

A note on I2C pull-up resistors…

The I2C bus requires pull-up resistors to ensure signal integrity. The Arduino will enable internal pull-ups for the I2C bus but these are generally considered too weak for effective communications. Some sources appear to suggest between 4K7 and 10K, but the internal pull-ups are between 30K and 50K (see discussion here and a lot more detail here), so people will usually add external pullups.

Each of these I2C modules includes a 1K pull-up resistor (according to the schematic), so adding more of these modules in a chain will add additional resistors in parallel. For four modules, this means the resultant pull-up resistor value will be approx 250Ω which isn’t ideal…

I am not an electronics person, but here are some views of the clock signals for one, four, and eight modules. The key impact appears to be the time for pulling the signal low.

But in general it doesn’t look too bad, but notice how the level for “low” is creeping up… However, when it comes to the data signal…

We can see that there is increasing ambiguity about where “low” should be for the signal.

From experimenting, I seem to be able to access 7 modules, but not 8. But by removing the pull-up resistors (the SMT resistors near the I2C connector) on some of the boards, I find all eight modules can be recognised successfully.

There is one other consideration though – the Arduino has a peak current sink capacity for an IO pin of 40mA with 20mA being a more acceptable value.

With a 1K pull-up the sink current will be 5.0/1000 = 5mA.

However with four 1K resistors in parallel, the sink current will be 5.0/250 = 20mA

Actually, it will be very slightly higher, as the combined resistance including the internal Arduino pull-ups will be something like 245-248 Ω.

With all eight 1K resistors in parallel, the sink current will hit 5.0/125 = 40mA which is definitely pushing things.

Warning: other microcontrollers may have a limit that is a lot less than 20mA!

In the end I removed the pull-ups on three modules and now all 8 can be detected fine.

The photos below show the pullup resistors as received (left) and when removed (right). Note, I’ve left them connected to one pad in case I want to restore them at some point in the future.

The PCF8575 16-bit Module

The PCF8575 is effectively the same as te PCF8574 in use, but the available breakout boards are slightly different. The key differences are the additional 8 IO pins (naturally), but also the address is configured using solder jumpers. There are a number of designs of board, but the one I’m using is shown below.

The change the default I2C address, the A0-A1-A2 jumpers on the bottom of the board can be set to GND or VDD. With no solder jumpers it appears to work ok with the default address of 0x20.

There is an additional jumper which appears to connect VDD directly to the VCC pin. I think this bypasses the onboard regulator which presumably is necessary for 3V3 operation. But this also seems to link the input and output of the regulator together – is that ok!?

The Code

Reading the modules is fairly straight forward. For the 8-bit version there is a single register at the provided address which when read will give the values of the digital inputs and when written will set the pins as if they were digital outputs. For the 16-bit version there are two registers at that address.

There are a number of libraries that exist for accessing the PCF8574/8575, but in reality the device requires very little managing, so it is fairly easy to use the Arduino’s Wire library directly. Here is the code for the PCF8574:

#include <Wire.h>

#define PCF8574_ADDR  0x20
#define PCF8574_CLOCK 100000

void pcf8574setup() {
  Wire.begin();
  Wire.setClock(PCF8574_CLOCK);
}

uint8_t pcf8574read() {
  if (Wire.requestFrom(PCF8574_ADDR, 1) != 0) {
    return (uint8_t)Wire.read();
  } else {
    return -1;
  }
}

The code for the PCF8575 is largely the same, just reading two bytes from the device at a time.

#include <Wire.h>

#define PCF8575_ADDR  0x20
#define PCF8575_CLOCK 100000

void pcf8575setup() {
  Wire.begin();
  Wire.setClock(PCF8575_CLOCK);
}

uint16_t pcf8575read() {
  if (Wire.requestFrom(PCF8575_ADDR, 2) != 0) {
    uint16_t retval = (uint16_t)Wire.read();
    return retval + (((uint16_t)Wire.read())<<8);
  } else {
    return -1;
  }
}

My example code allows a number of devices to be linked together and mapped onto either MIDI Control Change, Program Change or Note messages. There is a structure at the top of the file with one entry per IO pin:

#define PCF857x_DEVICES 3
#define PCF857x_BITS    8

#define NUM_BTNS (PCF857x_DEVICES*PCF857x_BITS)
#define MB_CC 0
#define MB_PC 1
#define MB_N  2
int midiBtns[NUM_BTNS][2] = {
// T, Value T=0 (CC), T=1 (PC), T=2 (NoteOn/Off)
MB_PC, 0,
MB_PC, 1,
MB_PC, 2,
MB_PC, 3,
MB_PC, 4,
MB_PC, 5,
MB_PC, 6,
MB_PC, 7,
MB_N,  60,
MB_N,  61,
MB_N,  62,
MB_N,  63,
MB_N,  64,
MB_N,  65,
MB_N,  66,
MB_N,  67,
MB_N,  68,
MB_N,  69,
MB_N,  70,
MB_N,  71,
MB_N,  72,
MB_CC, 80,  // Generic On/Off
MB_CC, 81,  // Generic On/Off
MB_CC, 82,  // Generic On/Off
};

The first value on each line specifies the type of message, and the second value gives the MIDI value to use as appropriate. The operation is as follows:

  • For MIDI CC messages: button press = send MIDI CC message configured in the table, with value 127; button release = send MIDI CC message with value 0.
  • For MIDI PC messages: button press = send MIDI PC message with configured value.
  • For MIDI Note messages: button press = send NoteOn with configured value and velocity 127; button release = send NoteOff with velocity 0.

The listed table allocates the first 8 IO lines (so a PCF8574 expander at 0x20) to Program Change messages selecting voices 1 to 8 (in MIDI Program Change messages are 0-indexed); then the next 13 IO lines (so expander 0x21 and some of 0x22) to MIDI notes 60 through 72; finally the last 3 lines (expander 0x22) are set as general purpose MIDI controllers for CC 80, 81 and 82.

In the photo I’ve used a “stylus touch” module to trigger the eight Program Change messages (only the “white notes” are connected up) and a single button keyboard for the 13 notes. I’m not using the remaining three CC buttons.

Find it on GitHub here.

Closing Thoughts

I wasn’t sure what kind of performance I’d get from having to scan the I2C bus, but with three expanders I’ve not noticed any issues.

A key benefit of this approach is that the buttons are all independently scanned with no matrix required and no ghosting of keypresses possible. If the sound source supports it, then full polyphony is possible!

Whilst I’ve shown that I can recognise eight of these modules, I’ve not coded up a MIDI application to use them all.

Another benefit of course is that almost all of the Arduino’s IO pins (only D0/D1 are used for MIDI and A4/A5 for I2C) are available for doing other things too.

Kevin

2 thoughts on “Arduino PCF8574/8575 MIDI Controller

  1. Buenas. alguien por aca habla español, deseo encontrar a alguien que me ayude con un proyecto.
    pago por sus conocimientos ,los quiero aplicar a una arpa, deseo instalarle un sistema de semitonos midi.

    Like

    1. Sorry, no. I’m not sure I can help. There are lots of synthdiy communities around though, so I’m sure you can find one that speaks Spanish. Good luck!

      Like

Leave a comment