I had some curiosity and interest with MIDI devices for some years and this small project came up after my Yamaha Portasound PSS-190 had a couple of burnt traces and the synthesizer IC was gone.
This particular instrument isn’t really high end, so there were no velocity sensing (velocity matters, try pressing a key on a piano slower and faster). And the keys were wired to form a matrix – thus known as matrix keyboards. This means each wont connect directly to the microprocessor/ synthesizer IC but the wiring arrangement is like a matrix/ table.

This means you only need 8 wires to access 16 keys. In case of the Yamaha keyboard I tried to fix, it had 7 + 6 wires and served 30+ keys. However the drawback is, you cannot all the keys at the same time (real time – I’m talking microseconds), the reason is that these needs “scanning”, enabling one row and read the values and so on. But this can be implemented to be fast enough for a human. For example, the standard PC keyboard is almost always a matrix keyboard with the internals scanning for key presses at least hundred times per second.
Back to the topic, since I couldn’t source the original synth IC, I decided to build a MIDI synth and install inside the keyboard! I had a STM32F4 Discovery board with me, so i went for the “Goom” (http://www.quinapalus.com/goom.html) ported to MidiBox (http://ucapps.de). Midibox is a platform to build various types of MIDI instruments. Will discuss about this in a later post.
With the synth running, what I needed was to get MIDI signals upon key presses of the keyboard. Therefore I did some googling, found out that MIDI is pretty much serial communication at 31250 baud rate. So to test, I used Arduino Mega.
// Pin Definitions // Rows are connected to const byte Mask = 255; double oldtime; uint8_t keyToMidiMap[32]; boolean keyPressed[50]; int command = 0x90; int noteVelocity = 60; //#define DEBUG // use prepared bit vectors instead of shifting bit left everytime byte bits[] = { B00000001, B00000010, B00000100, B00001000, B00010000, B00100000, B01000000, B10000000 }; byte colVals[] = { 255,255, 255, 255, 255, 255, 255, 255 }; byte bits1[] = { B11111110, B11111101, B11111011, B11110111, B11101111, B11011111, B10111111, B01111111 }; void scanColumn(int value) { PORTA=value; } void setup() { DDRA=B11111111;//output DDRC=B00000000;//input // Enable the pullups PORTC = PORTC | Mask; for(int i=0; i<50;i++){ keyPressed[i]=false; } Serial2.begin(31250); Serial.begin(115200); delay(500); //noteOn(176,124,0); for (int note = 0x1E; note < 0x5A; note ++) { //Note on channel 1 (0x90), some note value (note), middle velocity (0x45): noteOn(0x90, note, 0x45); delay(100); //Note on channel 1 (0x90), some note value (note), silent velocity (0x00): noteOn(0x90, note, 0x00); delay(100); } delay(100); // noteOn(176,124,0); } void loop() { for (int col = 0; col < 7; col++) { // shift scan matrix to following column scanColumn(bits1[col]); //enable all except one. delayMicroseconds(3); byte rowVal1 = PINC & Mask; byte rowVal= ~rowVal1;//inverted rowVal => key press = 1 if(colVals[col] == rowVal1){ continue; } else{ colVals[col] = rowVal1; } for (int row = 0; row < 6; row++) { if(col==0 &&row>0){ break; } int index =row + ((int)col *6) ; int note= index + 48; byte k =(bits[row] & rowVal); if(k>0 && keyPressed[index]==false){ //and op. on each bit of rowval and determine note press. keyPressed[index]=true; noteOn(command,note,noteVelocity); } if(k==0 && keyPressed[index]==true){ keyPressed[index]=false; noteOn(command,note,0); } } } } void noteOn(int cmd, int pitch, int velocity) { Serial2.write(cmd); Serial2.write(pitch); Serial2.write(velocity); /** * DEBUG stuff */ /* Serial.print("Note: "); Serial.print(pitch,DEC); Serial.print(" Velocity :"); Serial.print(velocity,DEC); Serial.println(); */ }
First I setup the basics, enable internal pullups, then set the output port (PORT A) to a given arrangement – one pin turned OFF, others turned ON. The reason to do this than other way around is due to the usage of pullups instead of pull down resistors.
Then I read the input at PORT C. now this is where the rows are connected, so if a key is pressed, the corresponding pin would go LOW. For ease of processing I inverted this reading and I also keep track of “change of state” which means the code will proceed if an only if the previous state was changed.
Then depending whether it was a press down or releasing a key, the appropriate MIDI command is sent. – 0x91 means channel 1, note ON. Pitch is mapped as “48” = C3. (refer https://newt.phys.unsw.edu.au/jw/notes.html for detailed mapping information).
With the code tested, all that remains is to wire it up to the keyboard and test!