This project uses a PCF8574 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
- Arduino Uno
- One or mode PCF8574 I2C IO expander modules (see photos)
- Arduino MIDI OUT circuit: see Arduino MIDI Interfaces
- Breadboard and jumper wires
The Circuit
Additional PCF8574 modules can be added by specifying a different I2C address 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.
The Code
Reading the modules is fairly straight forward - there is a single register at the provided address which when read will give the values of the 8 digital inputs and when written will set the pins as if they were digital outputs.
There are a number of libraries that exist for accessing the PCF8574, but in reality the device requires very little managing, so it is fairly easy to use the Arduino's Wire library directly as follows:
#include <Wire.h> #define PCF8574_ADDR 0x20 #define PCF8574_CLOCK 100000 void pcf8574setup() { Wire.begin(); Wire.setClock(PCF8574_CLOCK); } uint8_t pcf8574read(int device) { if (Wire.requestFrom(PCF8574_ADDR+device, 1) != 0) { return (uint8_t)Wire.read(); } 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 NUM_BTNS (PCF8574_DEVICES*8) #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 expander 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!
In princple up to eight of these modules can be used on the same I2C bus. There is also a PCF8575 that apparently has 16 IO channels, so that will be worth a look too.
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
No comments:
Post a Comment