Need help using 74HC165 with midi keyboard

I'm trying to replicate this
http://www.openmusiclabs.com/learning/digital/input-scanning-matrix/shift-brigade/index.html

And have created the circuit below.

The grey "ribbon cable" goes to the keyboard scanning matrix. I'm unable to upload the scanning matrix diagram but it can be found here.
https://www.midiboutique.com/downloads

on the "Fatar 61 Key DF Schematic Diagram" link.

I'm trying to keep this simple to start with and am only activating one of the 8 columns, BR0.

This is my code.

//74HC165 pins
// PL pin 1
int load = 13;
// CE pin 15
int clockEnablePin = 10;
// Q7 pin 7
int dataIn = 11;
// CP pin 2
int clockIn = 12;

int BR0 = 0;
byte incoming;

void setup() {
  // 74HC165
  // setup pins output/input
  pinMode(load, OUTPUT);
  pinMode(clockEnablePin, OUTPUT);
  pinMode(clockIn, OUTPUT);
  pinMode(dataIn, INPUT);

  pinMode(BR0, OUTPUT);
  digitalWrite(BR0, HIGH);

  Serial.begin(31250);
}

void loop() {
  
  digitalWrite(BR0, LOW); //Switch BR ON
  
  //Read the switches using 74HC165
  digitalWrite(load, LOW);
  digitalWrite(load, HIGH);
  digitalWrite(clockIn, HIGH);
  digitalWrite(clockEnablePin, LOW);
  incoming = shiftIn(dataIn, clockIn, LSBFIRST);
  digitalWrite(clockEnablePin, HIGH);  
  Serial.println(incoming, BIN);
}

My problem is I'm not getting any response when I press a key. The "incoming" BYTE just stays at 11111111.


You need a 0.1uF ceramic bypass cap close to the Vdd pin of the '165.

I'm not saying this will fix the problem, I'm saying you should always have such caps for all chips. Without them, chips can behave strangely. So add one and leave it in place even if it doesn't fix the problem.

Look at the diodes in the schematic for the keyboard. They allow multiple keys to be pressed at the same time without causing additional unwanted "ghost" presses, which is what you need in a musical instrument. But there is a side effect to having those diodes. You have to connect higher voltage to the anodes and lower voltage to the cathodes for any current to flow.

It's hard to tell how you have connected everything from your diagram because the pins are not labelled, but have you connected things correctly for those diodes?

A schematic would be better. Fritzing can draw schematics too if you switch views.

That isn't what I would have expected. Your pull-down resistors should make the incoming byte stay at 00000000. Perhaps adding the bypass will fix it.

If not, check all your Dupont cables, they can be unreliable. I assume you are using Dupont cables because you are using a Leonardo with your breadboard, not the more sensible choice of a Pro Micro.

Compare that with the "Fatar 61 Key DR Schematic. I think the diodes in that would work with your circuit.

But no worries, either should work fine with Arduino, you just need to adjust the circuit and code to match.

Thank you for your suggestions! I'll dig into those today. The diodes do concern me but I set up a 74HC595 and got it working....hmmmm. What makes the pro micro a more sensible choice?

It's breadboard compatible. And proto-board/stripboard compatible.

Leonardo can be used with breadboard, but that means a rat's nest of fragile, unreliable dupont cables. If you make a wiring error, good luck finding it!

Pro Micro is designed to plug into a breadboard.

Hi Paul,

So I've managed to get half of my keyboard working with a 74HC595 and 74HC165. To get the whole thing working I'm cascading two 595's and two 165's together. With them cascaded together, I can get the lower half working, but not the upper half. With the lower half, I get both an MK and BK switch to close. With the upper half, I either get only one to close, or two, but they are the wrong two, such as BR4 and MK5. I have a feeling that is has something to do with how I have wired the HC165's together, or the code to control the 165's, (the "scanColumn" function I think is the most pertinent part) but I haven't really found any good examples of cascading 165's together. I've included a schematic (my first so it could be full of errors), code and a pic of the circuit that probably isn't helpful. Any thoughts would be helpful.

KeyBoard.pdf (276.2 KB)

#include <MIDIUSB.h>
#define GPIO2_PREFER_SPEED 1
//#include <DIO2.h>
#include <math.h>

//MIDI baud rate
#define SERIAL_RATE 31250 //31250

#define thresholdTime_max 2400
#define constrain_min 1
#define constrain_max 200
#define log_multiplier 25

//Exponential Velocity response curves data
int expCurve[127] = {1, 1, 2, 2, 3, 3, 4, 4, 5, 5,
6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 
14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27,
27, 28, 28, 29, 30, 30, 31, 32, 32, 33, 34, 35, 36, 37, 38, 40, 41, 43, 44, 46, 47, 49, 50, 52, 53, 55, 56, 58, 59, 
61, 62, 64, 65, 67, 68, 70, 71, 73, 74, 76, 77, 79, 80, 82, 83, 85, 86, 88, 89, 91, 92, 94, 95, 97, 98, 100, 
101, 103, 104, 106, 107, 109, 110, 112, 113, 115, 116, 118, 119, 121, 122, 124, 125, 127};

 //Lograthmic Velocity response curve data
 int logCurve[127] = {1,3,5,6,8,9,
 11,12,14,15,17,18,20,21,23,24,
 26,27,29,30,32,33,35,36,38,39,
 41,42,44,45,47,48,50,51,53,54,
 56,57,59,60,62,63,65,66,68,69,
 71,72,74,75,77,78,80,81,83,84,
 86,87,89,90,91,92,93,94,95,95,
 96,97,97,98,99,99,100,100,101,101,
 102,102,103,103,104,104,105,105,106,106,
 107,107,108,108,109,109,110,110,111,111,
 112,112,113,113,114,114,115,115,116,116,
 117,117,118,118,119,119,120,120,121,121,
 122,122,123,123,124,124,125,125,126,
 126,127};
 
 
 //S-Type Curve Velocity response curve data
 int sCurve[127] = {1,1,1,1,1,1,1,2,2,2,2,2,3,3,3,3,3,4,4,4,4,4,5,5,5,5,5,6,6,6,6,6,7,7,8,8,9,9,
 10,11,13,14,17,19,21,24,26,28,31,33,35,37,40,42,44,47,49,
 51,54,56,58,60,63,65,67,70,72,74,77,79,81,83,86,88,90,93,95,97,
 100,102,104,106,109,111,113,115,116,117,118,119,119,120,120,
 121,121,121,121,121,
 122,122,122,122,122,
 123,123,123,123,123,
 124,124,124,124,124,
 125,125,125,125,125,
 126,126,126,126,126,
 127,127,127};

 //N_Type Curve Velocity response curve data
int nCurve[127] = {10,11,12,14,15,17,18,20,21,23,24,26,27,28,29,
30,31,33,34,35,36,37,38,39,40,41,42,43,43,44,45,46,46,47,48,48,49,49,
50,50,51,51,51,52,52,53,53,53,54,54,54,55,55,55,56,56,56,57,57,57,57,58,58,59,59,59,
60,60,60,60,61,62,62,62,62,63,63,63,63,64,64,64,65,65,66,66,67,67,67,68,69,
70,71,73,74,76,78,80,81,83,85,87,90,92,93,95,97,99,101,102,104,105,107,108,
110,111,113,115,116,118,119,121,122,123,125,127,127};

// bitmasks for scanning columns //VECTOR
int bits[] =
{ 
  B00000001,  //A unique signal is sent through pin 1 of the bit shifter
  B00000010,  //A unique signal is sent through pin 2 of the bit shifter
  B00000100,  //So 7 pins are high, and 1 pin is low.  This is the pull up version...need to figure that out.
  B00001000,  //247
  B00010000,  //239
  B00100000,  //223
  B01000000,  //191
  B10000000   //127
};


int note;
int i;
byte aftertouch = 0xD0;
byte channel = 0;
byte firstNote = 36;
byte velocityOn;
byte velocityOff;
byte incoming_1, incoming_2;
uint16_t incomming;

int constrainTimeOn;
int constrainTimeOff;

double logTime1;
double logTime2;

int keyState[64]; //rows x columns
int col, row, blk;
unsigned long startTime1[64];
unsigned long startTime2[64];
unsigned long startTimeOn;
unsigned long startTimeOff;

unsigned long afterTouchStartThreshold = 1;
unsigned long startTimeBottom[64];
int afterTouchVal[64];
int afterTouchValPast[64];
int afterTouchValue;
int afterTouchValuePast = 0;
int maxVal = 0;
int testAnalogInput;

//74HC165 pins
int load = 13;// PL pin 1
int clockEnablePin = 10;// CE pin 15
int dataIn = 11;// Q7 pin 7
int clockIn = 12;// CP pin 2

// 74HC595 pins
const int dataPin = 7;
const int latchPin = 8;
const int clockPin = 9;

void setup() { // put your setup code here, to run once:

  // Shift Register setup 74HC595
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT);

  // 74HC165
  // setup pins output/input mode
  pinMode(load, OUTPUT);
  pinMode(clockEnablePin, OUTPUT);
  pinMode(clockIn, OUTPUT);
  pinMode(dataIn, INPUT);

  //Send out a signal through all 
  digitalWrite(latchPin, LOW);
  shiftOut(dataPin, clockPin, MSBFIRST, B00000000);
  shiftOut(dataPin, clockPin, MSBFIRST, B00000000);
  digitalWrite(latchPin, HIGH);
     
  for (i=0; i<64; i++){ // i is row x columns
    startTime1[i] = 0;
    startTime2[i] = 0;
    keyState[i] = 0;  
    startTimeBottom[i] = 0;
    afterTouchVal[i] = 0;
    afterTouchValPast[i] = 0;
  }

  Serial.begin(SERIAL_RATE);
  while (!Serial);
}

void loop() {
  // put your main code here, to run repeatedly:
  //delay(10000);
  scanAllKeys();
  //delay(10000);
}
// columns 0-1 are 0, 2-3 are 1, 4-5 are 2, 6-7 are 3, 7-8 are 4. need to round down the col/2 so use floor function
void scanAllKeys(){
  blk = 0;
  for (col = 0; col < 15; col+=2) {//increment by 2 through the MK columns{
    for (row = 0; row < 8; row++){
      scanKeys(keyState[row + (int)floor(col/2) + blk]);  //scan the col * row key based on its key state.
    } 
    blk = blk + 7;
  }
}

void scanColumn(int i)
{
  digitalWrite(latchPin, LOW); //595

  //if(i < 8) //go through first 8 columns assigned to the left most shift register
  {
    shiftOut(dataPin, clockPin, MSBFIRST, bits[i]); //right sr    All high, so no unique signal is sent   MSBFIRST is most significant bit first, which pins get which bite
    shiftOut(dataPin, clockPin, MSBFIRST, bits[i]); //left sr    Only a unique signal is sent through the pin with the 1
    //Serial.println(bits[col]);
  }
  //else
  //{
  //  shiftOut(dataPin, clockPin, MSBFIRST, bits[i-8]); //go through the remaining 3 columns assigned to the right most register
  //  shiftOut(dataPin, clockPin, MSBFIRST, B00000000); //left sr
  //}
  digitalWrite(latchPin, HIGH);  //Send a signal to that column

  digitalWrite(load, LOW); //165
  delayMicroseconds(5);
  digitalWrite(load, HIGH);
  delayMicroseconds(5);
  digitalWrite(clockIn, HIGH);
  digitalWrite(clockEnablePin, LOW);
  incoming_2 = shiftIn(dataIn, clockIn, LSBFIRST);
  incoming_1 = shiftIn(dataIn, clockIn, LSBFIRST);
  digitalWrite(clockEnablePin, HIGH);
  //Serial.println("incoming");
  incomming = (incoming_1<<8) | (incoming_2); 
  Serial.println(incomming, BIN);
}

void scanKeys(int var) {
  switch (var) {

    case 0:
      //digitalWrite(BR[col], LOW); //Switch BR ON
      scanColumn(row); //col + 1
      //Serial.println(digitalRead(ROW[row]));

    //if (digitalRead(ROW[row]) == LOW){ //BR key is pressed
    if (bitRead(incomming, col+1) == LOW){
      startTime1[row + col + blk] = millis();  //(int)floor(col/2)
      keyState[row + (int)floor(col/2) + blk] = 1;
    }
    //digitalWrite(BR[col], HIGH); //Switch BR OFF I don't think I need an equivalent with the Shift Register
      //Serial.println(incoming, BIN);
      //Serial.println(bitRead(incoming, row));
      //Serial.println(keyState[row + (int)floor(col/2) + blk]);
      //Serial.println("column");
      //Serial.println(col);
      //Serial.println("Row");
      //Serial.println(row);
    
     break;

     case 1:

     //if (digitalRead(ROW[row]) == HIGH){ //No key pressed
     if (bitRead(incomming, col) == HIGH){//should be incomming#######################
       keyState[row + (int)floor(col/2) + blk] = 2; 
     }
        //Serial.println(keyState[row + col + blk]);
        //Serial.println("column");
        //Serial.println(col);
        //Serial.println("Row");
        //Serial.println(row);
        //Serial.println("Digital Read");
        //Serial.println(digitalRead(ROW[row]));
    
     break;

     case 2:
      //digitalWrite(MK[col], LOW);  //Switch MK ON
      scanColumn(row);
      //if(digitalRead(ROW[row]) == LOW){ //MK key is pressed
      if (bitRead(incomming, col) == LOW){//should be incomming#######################
        startTimeOn = millis()-startTime1[row + col + blk];
        logTime1 = log_multiplier * log(startTimeOn);
        startTimeBottom[row + (int)floor(col/2) + blk] = millis();  //start timer for aftertouch Threshold
      if (startTimeOn > thresholdTime_max){
        keyState[row + (int)floor(col/2) + blk] = 0;
          //Serial.println(keyState[row + col + blk]);
          //Serial.println("column");
          //Serial.println(col);
          //Serial.println("Row");
          //Serial.println(row);
    
        //digitalWrite(MK[col], HIGH); //Switch MK OFF
        break;
      }
      constrainTimeOn = constrain(logTime1, constrain_min, constrain_max);
      velocityOn = map(constrainTimeOn, constrain_min, constrain_max, 127, 1);
      //Select Midi Velocity Curve
      velocityOn = expCurve[velocityOn];
      //velocityOn = logCurve[velocityOn];
      //velocityOn = sCurve[velocityOn];
      //velocityOn = nCurve[velocityOn];

      note = row + floor(col/2) + blk;
     // MIDI.sendNoteOn(firstNote + note, velocityOn, channel);
      noteOn(channel, firstNote + note, velocityOn);
      MidiUSB.flush();
      keyState[row + (int)floor(col/2) + blk] = 3;
     }
    //digitalWrite(MK[col], HIGH); //Switch MK off
    break;

    case 3:
    //digitalWrite(MK[col], LOW); //Switch MK ON
    scanColumn(row);
    //if (digitalRead(ROW[row]) == LOW){ //Key still at bottom
    if (bitRead(incomming, col) == LOW){//should be incomming#######################
      //afterTouch();
    }
    else{
      keyState[row + (int)floor(col/2) + blk] = 4; //no, key is erleased
    }
    //digitalWrite(MK[col], HIGH); //Switch MK OFF
    break;

    case 4:
    //digitalWrite(MK[col, LOW]) //Switch MK ON
    scanColumn(row);
    //if(digitalRead(ROW[row]) == HIGH){ //MK key is released
    if (bitRead(incomming, col) == HIGH){//should be incomming#######################
      startTime2[row + (int)floor(col/2) + blk] = millis();
      keyState[row + (int)floor(col/2) + blk] = 5;
    }
    //digitalWrite(MK[col], HIGH); // Switch MK OFF
      //Serial.println(keyState[row + col + blk]);
      //Serial.println("column");
      //Serial.println(col);
      //Serial.println("Row");
      //Serial.println(row);
      scanKeys(keyState[row + (int)floor(col/2) + blk]);
     
    break;

    case 5:
    //digitalWrite(BR[col], LOW): //Switch BR ON
    scanColumn(row);
    //if(digitalRead(ROW[row]) == HIGH) {//BR key reached top 
    if (bitRead(incomming, col + 1) == HIGH){//should be incomming#######################
      startTimeOff = millis();
      startTime2[row + (int)floor(col/2) + blk];
      logTime2 = log_multiplier * log(startTimeOff);
      constrainTimeOff = constrain(logTime2, constrain_min, constrain_max);
      velocityOff = map(constrainTimeOff, constrain_max, constrain_min, 1, 127);
      velocityOff = expCurve[velocityOff];
      //Select Velocity Curve
      velocityOff = logCurve[velocityOff];
      velocityOff = sCurve[velocityOff];
      velocityOff = nCurve[velocityOff];
      
      note = row + floor(col/2) + blk;
      //MIDI.sendNoteOff(firstNote + note, velocityOn, channel);
      noteOff(channel, firstNote + note, velocityOn);
      MidiUSB.flush();
      keyState[row + (int)floor(col/2) + blk] = 0;
    }
    //digitalWrite(BR[col], HIGH); //Switch BR OFF
    break;

  default:
    break;
  }
}

void afterTouch(){
  if((millis() - startTimeBottom[row + col + blk]) >= afterTouchStartThreshold){
    testAnalogInput = analogRead(A0) / 8; //1023/8 = values 0-127
    if ( testAnalogInput <= 125){
      testAnalogInput = 125 - testAnalogInput;
      afterTouchVal[row + col + blk] = testAnalogInput;

      for(i = 0; i < 64; i++){
        if(afterTouchVal[i] > maxVal){
          maxVal = afterTouchVal;
        } //maxVal now contains the largest Value
        afterTouchValue = maxVal;
      }
      if(afterTouchVal[row + col + blk] != afterTouchValPast[row + col + blk]){
        //Midi.sendAfterTouch(afterTouchValue, channel);
        channelPressure(channel, afterTouchValue);
        MidiUSB.flush();
        afterTouchValPast[row + col + blk] = afterTouchVal[row + col + blk];
        maxVal = 0;
      }
    }
  }
}

//http://midi.teragonaudio.com/tech/midispec.htm

void noteOn(byte channel, byte pitch, byte velocity) {
  midiEventPacket_t noteOn = {0x09, 0x90 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOn);
}

void noteOff(byte channel, byte pitch, byte velocity) {
  midiEventPacket_t noteOff = {0x08, 0x80 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOff);
}

void afterTouch(byte channel, byte pitch, byte aftertouch) {
  midiEventPacket_t afterTouch = {0x0D, 0xD0 | channel, pitch, aftertouch};
  MidiUSB.sendMIDI(afterTouch);
}

void channelPressure(byte channel, byte aftertouch){
  midiEventPacket_t channelPressure = {0x0D, 0xD0 | channel, aftertouch};
  MidiUSB.sendMIDI(channelPressure);
}
void controlChange(byte channel, byte control, byte value) {
  midiEventPacket_t event = {0x0B, 0xB0 | channel, control, value};
  MidiUSB.sendMIDI(event);
}

void pitchBend(byte channel, int value) {
  byte lowValue = value & 0x7F;
  byte highValue = value >> 7;
  midiEventPacket_t pitchBend = {0x0E, 0xE0 | channel, lowValue, highValue};
  MidiUSB.sendMIDI(pitchBend);
}

See post #3.

I took the time to post several long posts already while trying to help you. If you are not going to read them, I will stop wasting my time.

Some posts flagged, deleted along with replies. Please keep it civil.

@anon74337619 , this forum is a civil place for getting free help from volunteers, please keep it that way if you want to keep getting help. Comments that are basically of the playground "he started it" kind don't help.

Is post #10 considered civil? Because I read it to be mean spirited.

I think it represents frustration from someone trying to help you, someone helping for free in their spare time and feeling they are getting little meaningful response from you. I suggest that if you want to motivate people to continue to help you then take seriously the concern expressed and respond in a positive, understanding way.

The people here want to help people like you, they enjoy it (as do I), in return they expect meaningful engagement from the person they are trying to help, if they don't get that then they tend to stop helping. The forum guide, written by me, available at the top of every forum category, reflects what someone needs to do to get meaningful help.

2 Likes

I did not deserve that response from him, and I don't appreciate being blamed for bullying behavior when I called it out. So, I'll get out of your hair and delete my account, I'm sure you're tired of me, and I have no interest in being part of a forum where that kind of behavior is encouraged.

As you wish. I hope you find the help you need elsewhere.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.