Instantly share code, notes, and snippets.
Created
February 5, 2020 20:54
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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