Skip to content

Instantly share code, notes, and snippets.

@UriShX
Created February 5, 2020 20:54
Show Gist options
  • Save UriShX/ac12b4dfd76a2afa1785bcdb08027061 to your computer and use it in GitHub Desktop.
Save UriShX/ac12b4dfd76a2afa1785bcdb08027061 to your computer and use it in GitHub Desktop.
64 button matrix connected to Arduino through MCP23x17, based on Microchip app note AN1081
/*
* Depends on https://github.com/sumotoy/gpio_expander library for operating MCP23x17 port expander
* Depends on https://github.com/MajicDesigns/MD_CirQueue/blob/master/src/MD_CirQueue.h for circular queue operation.
* Algorithm based on Microchip app note AN1081, checked & working.
*
* Written by Uri Shani, 2018. Part of AB4MC project, see: https://hackaday.io/project/109296-arduino-blocks-for-midi-controllers
*/
#include <Arduino.h>
#include <mcp23s17.h>
#include <MD_CirQueue.h>
#include <MIDI.h>
#include <SPI.h>
#define MCP_CSPIN 4
#define MCP_ADRS_1 B0100010//0x22
//const byte MCP_ADRS_1 = B0100010;
#define QUEUE_SIZE 24
//Following depend of the processor you are using!!!!
#define INTused 0
#define INTpin 2
// array of buttons mapped similarly to Mackie control (based on http://www.jjlee.com/qlab/Mackie%20Control%20MIDI%20Map.pdf)
const byte buttonToNote[8][8] = {
{0, 8, 16, 24, 40, 42, 44, 46}, // Ch.1 (Rec/rdy, solo, mute, select), I/O, pan, EQ, fader bank left
{1, 9, 17, 25, 41, 43, 45, 47}, // Ch.2 (Rec/rdy, solo, mute, select), sends, plug-ins, dyn, fader bank right
{2, 10, 18, 26, 48, 49, 50, 51}, // Ch.3 (Rec/rdy, solo, mute, select), ch. left, ch. right, flip, edit
{3, 11, 19, 27, 91, 92, 93, 94}, // Ch.4 (Rec/rdy, solo, mute, select), rewind, ff, stop, play
{4, 12, 20, 28, 95, 96, 97, 99}, // Ch.5 (Rec/rdy, solo, mute, select), rec, cursor up, cursor down, zoom
{5, 13, 21, 29, 100, 101, 75, 82}, // Ch.6 (Rec/rdy, solo, mute, select), cursor left, cursor right, rec/rdy, marker
{6, 14, 22, 30, 83, 84, 85, 86}, // Ch.7 (Rec/rdy, solo, mute, select), mixer, < frame, > frame, loop
{7, 15, 23, 31, 87, 88, 89, 90} // Ch.8 (Rec/rdy, solo, mute, select), PI, PO, home, end
};
volatile bool keyPressed;
volatile bool handledPress = false;
volatile bool intPinState = 0xFF;
volatile bool functionDone = false;
byte column = 0x00;
byte buttonPress[8] = {0, 0 ,0, 0, 0, 0, 0, 0};
byte DAWpressed[8] = {0, 0 ,0, 0, 0, 0, 0, 0};
const byte midiChannel = MIDI_CHANNEL_OMNI; // MIDI channel can be 1 thru 16, or MIDI_CHANNEL_OMNI
unsigned int mcpState = 0xFF00;//0b1111111100000000;// bank A is rows, bank B is columns in app note AN1081
mcp23s17 mcp1(MCP_CSPIN,MCP_ADRS_1);
//MIDI_CREATE_DEFAULT_INSTANCE();
// Define a queue that's 24 signed 8 bit values.
MD_CirQueue Q(QUEUE_SIZE, sizeof(int8_t));
void setup()
{
Serial.begin(115200);
Serial.println("start");
Q.begin();// begin circular buffer
mcp1.begin();
//IOCON = BANK MIRROR SEQOP DISSLW HAEN ODR INTPOL -NC-
// (0=16bit) (0=separate interrupt) (0=not sequential) (0=for i2c) (1=use addr. in SPI) (0=no open drain int) (0=int polarity LOW)
mcp1.gpioRegisterWriteByte(mcp1.IOCON, 0b00001000);
mcp1.gpioRegisterWriteByte(mcp1.IPOL, 0x00);// make sure polarity of bank A is set to reflect the same state as the pin (used to be:invert polarity of bank A (GPIO register bit reflects the opposite logic state of the input pin))
mcp1.gpioRegisterWriteByte(mcp1.IPOL + 1, 0x00);// make sure polarity of bank B is set to reflect the same state as the pin
mcp1.gpioPort(mcpState);// set GPIO, bank A as HIGH, bank B as LOW
mcp1.gpioPinMode(mcpState);// Set IODIR, bank A as input, bank B as output
mcp1.gpioRegisterWriteByte(mcp1.INTCON, 0x00);// set interrupt for bank A as pin value is compared against the previous pin value
mcp1.gpioRegisterWriteByte(mcp1.INTCON + 1, 0x00);// set interrupt for bank A as pin value is compared against the previous pin value
mcp1.gpioRegisterWriteByte(mcp1.DEFVAL, 0xFF);// set interrupt compare for bank A as HIGH
mcp1.gpioRegisterWriteByte(mcp1.DEFVAL + 1, 0x00);// set interrupt compare for bank B as LOW
mcp1.gpioRegisterWriteByte(mcp1.GPPU, 0x00);// make sure pull-up resistor for switch of bank A is LOW
mcp1.gpioRegisterWriteByte(mcp1.GPPU + 1, 0xFF);// make sure pull-up resistor for bank B is HIGH
mcp1.gpioRegisterWriteByte(mcp1.GPINTEN, 0xFF);// enable all interrupt for bank A
mcp1.gpioRegisterWriteByte(mcp1.GPINTEN + 1, 0x00);// disable all interrupt for bank B
mcp1.gpioRegisterReadByte(mcp1.INTCAP);// read from interrupt capture ports to clear them for bank A
//now prepare interrupt pin on processor
pinMode(INTpin, INPUT_PULLUP);
digitalWrite(INTpin, HIGH);
keyPressed = false;
attachInterrupt(digitalPinToInterrupt(INTpin), keypress, FALLING);
Serial.println("mcp1 setup OK");
// MIDI.begin(midiChannel);
}
void loop ()
{
if (keyPressed == true) {
Serial.println("interrupt");
handleKeypress();
keyPressed = false;
}
pressedKeyToMatrix();
// delay(50);
for (byte i = 0; i < 8; i++) { //need to make number of iterations based on size
if (buttonPress[i] != 0) {
uint8_t pressed;
Q.pop((uint8_t *) & pressed);
for (byte j = 0; j < 8; j++) {
if (bitRead(buttonPress[i], j)) { //find bit of pressed button, then check if need to send note on or off
byte sendVar = buttonToNote[i][j];
if (!bitRead(DAWpressed[i], j)) {
// MIDI.sendNoteOn(sendVar, 127, 1); //send midi note on of the note value respective to the button pressed
Serial.print("note on: ");
} else {
// MIDI.sendNoteOff(sendVar, 0, 1); //send midi note off of the note value respective to the button pressed
Serial.print("note off: ");
}
Serial.println(sendVar);
bitWrite(DAWpressed[i], j, ~bitRead(DAWpressed[i], j));
bitWrite(buttonPress[i], j, 0);
}
}
}
}
} // end of loop
void keypress()
{
boolean intPinNow = digitalRead(INTpin);
Serial.print("interrupt pin state:\t"); Serial.println(intPinNow);
keyPressed = true;
}
void pressedKeyToMatrix()
{
if (!Q.isEmpty())
{
uint8_t row;
uint8_t column;
// uint8_t pressed;
Q.pop((uint8_t *) & row);
Q.pop((uint8_t *) & column);
// Q.pop((uint8_t *) & pressed);
buttonPress[column] = row;
}
}
void handleKeypress()
{
cli();
uint8_t bankA = 0;
uint8_t bankB = 0;
uint8_t keyState = 0;
boolean currStateA;
boolean currStateB;
if (bankA = ~mcp1.gpioRegisterReadByte(mcp1.INTCAP)){
// int intfRegVal = mcp1.gpioRegisterReadByte(mcp1.INTF);
// Serial.print("INTF register value:\t"); Serial.println(intfRegVal);
// read from interrupt capture ports to clear them for bank A
Serial.print("bank A value:\t"); Serial.println(bankA, BIN); // debug
mcp1.gpioRegisterWriteByte(mcp1.GPINTEN, 0x00);// disable all interrupt for bank A
mcpState ^= 0xFFFF;// reverese state of all gpio pins
mcp1.gpioPort(mcpState);
mcp1.gpioPinMode(mcpState);// Set banks A and B to reversed state
bankB = ~mcp1.gpioRegisterReadByte(mcp1.GPIO + 1);// read from interrupt capture ports to clear them for bank B
Serial.print("bank B value:\t"); Serial.println(bankB, BIN); // debug
for (byte buttonRecieve = 0; buttonRecieve < 8; buttonRecieve++)
{
// this key down?
if (bankA & (1 << buttonRecieve))
{
Serial.print ("Row ");
Serial.print (buttonRecieve + 1, DEC);
Serial.print (" ");
delay(1);
} // end of if this bit changed
}
for (byte buttonRecieve2 = 0; buttonRecieve2 < 8; buttonRecieve2++)
{
// this key down?
if (bankB & (1 << buttonRecieve2))
{
bitWrite(column, buttonRecieve2, (currStateA ? 1:0));
Serial.print ("Column ");
Serial.print (buttonRecieve2 + 1, DEC);
Serial.println (currStateB ? " now pressed":" now depressed");
delay(1);
} // end of if this bit changed
}
// Q.push((uint8_t *) & bankA);// place data from bank A into stack
// Q.push((uint8_t *) & bankB);// place data from bank B into stack
// Q.push((uint8_t *) & keyState);// place the on/off status of bank A into stack
mcpState ^= 0xFFFF;// reverese state of all gpio pins
mcp1.gpioPort(mcpState);// set GPIO, bank A as HIGH, bank B as LOW
mcp1.gpioPinMode(mcpState);// Set IODIR, bank A as input, bank B as output
// mcp1.gpioRegisterWriteByte(mcp1.INTCON, 0xFF);// set interrupt for bank A as pin value is compared against the previous pin value
// mcp1.gpioRegisterWriteByte(mcp1.INTCON + 1, 0x00);// set interrupt for bank A as pin value is compared against the previous pin value
// mcp1.gpioRegisterWriteByte(mcp1.DEFVAL, 0xFF);// set interrupt compare for bank A as HIGH
// mcp1.gpioRegisterWriteByte(mcp1.DEFVAL + 1, 0x00);// set interrupt compare for bank B as LOW
// mcp1.gpioRegisterWriteByte(mcp1.GPPU, 0x00);// make sure pull-up resistor for switch of bank A is LOW
// mcp1.gpioRegisterWriteByte(mcp1.GPPU + 1, 0xFF);// make sure pull-up resistor for bank B is HIGH
mcp1.gpioRegisterWriteByte(mcp1.GPINTEN, 0xFF);// enable all interrupt for bank A
// mcp1.gpioRegisterWriteByte(mcp1.GPINTEN + 1, 0x00);// disable all interrupt for bank B
mcp1.gpioRegisterReadByte(mcp1.INTCAP);// read from interrupt capture ports to clear them for bank A
}
functionDone = true;
delay (5); // de-bounce before we re-enable interrupts
sei();
// mcp1.gpioRegisterWriteByte(mcp1.GPINTEN, 0xFF);// enable all interrupt for bank A
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment