Skip to content

Instantly share code, notes, and snippets.

@UriShX
Created February 5, 2020 20:54
Show Gist options
  • Select an option

  • Save UriShX/ac12b4dfd76a2afa1785bcdb08027061 to your computer and use it in GitHub Desktop.

Select an option

Save UriShX/ac12b4dfd76a2afa1785bcdb08027061 to your computer and use it in GitHub Desktop.

Revisions

  1. UriShX created this gist Feb 5, 2020.
    213 changes: 213 additions & 0 deletions AB4MC_64_Button_Matrix.ino
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,213 @@
    /*
    * 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
    }