Code

Get started coding 

Code
This video walks through the benchmark code, downloading the MIDI Library, the final piano code, and calibrating your piano.

Benchmark Code 

read_keys_raw_benchmark.ino

Now that the hardware is built, we want to run an sketch that will help us verify that the hardware is connected properly. read_keys_raw_benchmark.ino is a file that reads all of the keys and prints the raw key value in the serial monitor. If anything was incorrectly wired in the hardware setup, or something has become disconnected, This test will serve as the first software test of the hardware before we move onto the final code.

Download file Copy to clipboard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/* read_keys_raw_benchmark.ino
 *
 *  This is a benchmark test for the hardware of the piano kit,
 *  Uploading and running this code will help verify that the hardware
 *  portion is working as expected. It is always a good idea to test
 *  projects incrementally to ensure they work properly.
 *
 *  This benchmark will read all of the keys 0-6 attached to the mux0
 *  and print out the raw data to the serial monitor.
 */

//Mux0 Chip Enable 0
#define CE0   2
#define CE1   3
#define CE2   4

//Select bits for the Enabled Mux
#define S0    5
#define S1    6
#define S2    7

#define number_of_keys   7

int sensorValue[number_of_keys];

boolean readKeyFlag = false;
int ISR_count = 0;

//Variable used to determine next Select sequence
int r0 = 0;
int r1 = 0;
int r2 = 0;


void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);

  pinMode(S0, OUTPUT);  //S0
  pinMode(S1, OUTPUT);  //S1
  pinMode(S2, OUTPUT);  //S2

  pinMode(CE0 ,OUTPUT);   //CE0
  pinMode(CE1,OUTPUT);    //CE1
  pinMode(CE2,OUTPUT);    //CE2

  digitalWrite(CE0, LOW);  //On
  digitalWrite(CE1, HIGH); //Off
  digitalWrite(CE2, HIGH); //Off

  //This is what it looks like to set bits directly to the Arduino
  //Registers
  //The code here is to set up TIMER 2. This is not an expected learning
  //outcome
  //of the project, so don't be intimidated by this code.
  // TIMER 2 for interrupt frequency 1000 Hz(1ms):
  cli(); // stop interrupts
  TCCR2A = 0; // set entire TCCR2A register to 0
  TCCR2B = 0; // same for TCCR2B
  TCNT2  = 0; // initialize counter value to 0
  // set compare match register for 1000 Hz increments
  OCR2A = 249; // = 16000000 / (64 * 1000) - 1 (must be <256)
  // turn on CTC mode
  TCCR2B |= (1 << WGM21);
  // Set CS22, CS21 and CS20 bits for 64 prescaler
  TCCR2B |= (1 << CS22) | (0 << CS21) | (0 << CS20);
  // enable timer compare interrupt
  TIMSK2 |= (1 << OCIE2A);
  sei(); // allow interrupts
}

void loop() {
  // put your main code here, to run repeatedly:


  if(readKeyFlag == true)
  {
    readKeys();
    readKeyFlag = false;
  }

}

void readKeys()
{

  //Read Pins
  for(int i = 0; i < number_of_keys; i++)
  {
      delay(1);
      r0 = bitRead(i,0);
      r1 = bitRead(i,1);
      r2 = bitRead(i,2);

      digitalWrite(S0,r0);
      digitalWrite(S1,r1);
      digitalWrite(S2,r2);

      sensorValue[i] = analogRead(A0);

      Serial.print(sensorValue[i]);
      Serial.print("\t");
  }
  Serial.println(""); //newLine
}

//ISR, Interrupt Service Routine
ISR(TIMER2_COMPA_vect){
   //interrupt commands for TIMER 2 here
   if(ISR_count == 2)//5ms flag trigger
   {
     readKeyFlag = true;
     ISR_count = 0;
   }
   ISR_count++;
}

Code output 

Open up the Serial Monitor (Tools > Serial Monitor) to see the codes output. The expected output to the serial monitor will be 7 printouts of the number 1023 side by side, separated by some space. From left to right, each key should represent C-B. When you press down on a key, the number will change to represent the pressure measurement being collected. The expected output should look something like the below image, for reference the D key was pressed.

If the Benchmark code fails for any reason, head to the Troubleshooting section for some tips on common mistakes.

Piano Code 

Now that we know the hardware works correctly we're going to move onto the final code that will connect to our MIDI Software. This code is very similar to the benchmark code we just uploaded, so we're going to go through this new code step by step, breaking down the process of what's happening. Open up a new sketch and let's begin.

Downloading the MIDI Library 

If you don't already have the MIDI Library in your Arduino IDE, go to the menu bar and select Sketch > Include Libraries > Manage Library. The Library manager window will pop up and type MIDI into the search bar. Scroll down until you see the MIDI Library by Forty Seven Effects, the list is alphabetical so it is easy to find. Select the Library and a button will pop up in the lower right corner saying Install. Click on that button and the library will be downloaded into your IDE.

Libraries and variables 

Add the following code to the top of your program #include <MIDI.h> this is telling the Arduino IDE what other code your program will need to access. Below our library we are going to place our variables

Download file Copy to clipboard
1
2
3
4
5
6
7
8
9
10
11
#define LED 13

#define CE0 2 //Chip Enable 0
#define CE1 3
#define CE2 4

#define s0 5  //Select 0
#define s1 6
#define s2 7

#define number_of_keys 7

These are called Pound Define. This is a naming convention style I prefer for pins because they are constant (unchanging) values in the code. For example #define LED 13 means that any instance of LED in already has the value 13 assigned to it. This process is done at compile time, and doesn't take up memory on the Arduino like normal allocation. To compare int LED = 13; assigns an integer the value. The Arduino will reserve the space for an int which is 16 bits (2 bytes) on the Arduino. The benefit of a #define is that it saves space when you work with variables that do not change.

Breaking down #defines

Pin 13 is attached to the LED on the shield, that is why it is labeled LED.

CE0 is the pin attached to the Chip Enable for Mux0. The Chip Enable is similar to an on/off switch. If the Chip is Enabled, the Chip is on, Disabled is off. For this Multiplexer the Chip Enable is Active Low, Active Low means that, Low(0) is on, and High(1) is off. For other devices High(1) is on, and Low(0) is off.

CE1 and CE2 are the pins attached to the Chip Enable of Mux1 and Mux2 similar to CE0.

Multiplexers have Select bits, which determine which mux pin is connected to the output pin. Select bits are based off of binary, or base2. The table below outlines the Input that will be Selected and read. The seven keys we are setting up will scan from Input 0 - 6

S0 S1 S2 Input
0 0 0 0
1 0 0 1
0 1 0 2
1 1 0 3
0 0 1 4
1 0 1 5
0 1 1 6
1 1 1 7

Below our #defines we have a few more variables:

Download file Copy to clipboard
1
2
3
4
5
6
7
8
9
10
11
12
13
//Max is zero so the true max of the reading will be captured
int sensorMax[number_of_keys];
//True Min will be captured
int sensorMin[number_of_keys];
int sensorValue[number_of_keys];

int midi_Num[number_of_keys]= {60,62,64,65,67,69,71};
boolean readKeyFlag = false;
boolean isKeyPressed = false;
int ISR_count = 0;
int r0 = 0;
int r1 = 0;
int r2 = 0;

sensorMax and sensorMin and sensorValue are arrays that will hold the calibration values for each key, and come up later in the calibrate() function.

The Setup Function 

Following our variables is our setup() function, what ever code is place inside the setup function brackets { and } is only run once in the beginning of the program. So from Top to Bottom the code will run once. Copy in the setup() function and then we'll break it down.

Download file Copy to clipboard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

void setup()
{
  //Remember, this code only
  pinMode(LED, OUTPUT);  //LED

  pinMode(s0, OUTPUT);  //s0
  pinMode(s1, OUTPUT);  //s1
  pinMode(s2, OUTPUT);  //s2

  pinMode(CE0 ,OUTPUT);   //CE0
  pinMode(CE1,OUTPUT);    //CE1
  pinMode(CE2,OUTPUT);    //CE2

  digitalWrite(CE0, LOW);  //On
  digitalWrite(CE1, HIGH); //Off
  digitalWrite(CE2, HIGH); //Off

  calibrate_sensor();

  // TIMER 2 for interrupt frequency 1000 Hz(1ms):
  cli(); // stop interrupts
  TCCR2A = 0; // set entire TCCR2A register to 0
  TCCR2B = 0; // same for TCCR2B
  TCNT2  = 0; // initialize counter value to 0
  // set compare match register for 1000 Hz increments
  OCR2A = 249; // = 16000000 / (64 * 1000) - 1 (must be <256)
  // turn on CTC mode
  TCCR2B |= (1 << WGM21);
  // Set CS22, CS21 and CS20 bits for 64 prescaler
  TCCR2B |= (1 << CS22) | (0 << CS21) | (0 << CS20);
  // enable timer compare interrupt
  TIMSK2 |= (1 << OCIE2A);
  sei(); // allow interrupts

  MIDI.begin(4);          // Launch MIDI and listen to channel 4
}

The first thing in the setup function is seven pinMode() function calls. pinMode() configures a specific pin to behave as either a input or a output. These are setting the LED pin, the Chip Enable pins, and the Select Bit pins all to OUTPUT.

Following that is three digitalWrite() function calls, which writes HIGH or LOW to a digital pin. If the pin has been set with OUTPUT with pinMode(), it's voltage will be set to 5V(or 3.3V on 3.3V boards) for HIGH and 0V (ground) for LOW. In this case CE0, Chip Enable 0 for Mux0 is set to LOW because the multiplexer used is Active Low. The other two chip enables are set HIGH to disable them, because only one multiplexer should be enabled at a given time.

pinMode() and digitalWrite() are both Arduino supported functions.

The next function called in setup is calibrate_sensors(), which is a function made to calibrate the velostat keys. This happens only once in the setup function so you don't have to repeat the process.

The next block of code in the setup function is the Timer 2 Register setup. This allows Timer 2 to tick at a set interval, Understanding this section is not required for this kit. The Timing code is given, feel free to learn how it works on your own. We will skip over the explanation for this section.

Finally we begin the MIDI process using MIDI.begin(4); , this will keep the MIDI program listening for our output as we put it together using the outside software.

Main Loop 

The main loop function of this sketch is fairly short, consisting of Reading Keys and Sending MIDI Messages. Place this right after the setup() function:

Download file Copy to clipboard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void loop()
{
  if(readKeyFlag == true)
  {
    readKeys();
    readKeyFlag = false;
  }

  for(int i = 0; i < number_of_keys; i++)
  {
    if(sensorValue[i] > 10){
      MIDI.sendNoteOn(midi_Num[i], sensorValue[i],2);
    }else{
      MIDI.sendNoteOff(midi_Num[i], sensorValue[i],2);
    }
  }
}

The readkeys(); is only called when the readKeyFlag is true. readKeyFlag is set in the ISR (Interrupt Service Routine), which we will talk about later on in this tutorial.

The for loop in the main loop is where the actual MIDI Message is being generated. For each key, if the sensorValue is greater than > 10 then the MIDI Message MIDI.sendNoteOn(midi_Num[i], sensorValue[i],2); is sent.

The MIDI Library used in this project has a function, sendNoteOn(inNoteNumber, inVelocity, inChannel)

Parameters:

  • inNoteNumber Pitch value in the MIDI format (0 to 127).
  • inVelocity Note attack velocity (0 to 127). A NoteOn with 0 velocity is considered as a NoteOff.
  • inChannel The channel on which the message will be sent (1 to 16).

Later on the reason we set the LMMS Instrument to channel 2, is because the inChannel Parameter is 2 here.

Calibrate 

The calibrate_sensor() function is called in the void setup() function will be discussed here in detail. The Velostat material used to make the pressure sensor/keys for the piano kit is a variable resistor. The greater the pressure the less resistive the material is. Unfortunately there are small imperfections in the material causing some spots to react slightly differently to pressure that is applied. In other words, just because the Velostat sheet is one piece on material does not mean the far left side will react the exact same way as the far right side. This is why we need to calibrate the sensor.

This function follows after our main loop:

Download file Copy to clipboard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void calibrate_sensor()
{
  unsigned long adjusted_time;

  for(int i = 0; i < number_of_keys; i++)
  {
    sensorMax[i]   = 0;
    sensorMin[i]   = 1023;
    sensorValue[i] = 0;
    adjusted_time = millis();

    //Mux select
    r0 = bitRead(i,0);
    r1 = bitRead(i,1);
    r2 = bitRead(i,2);

    //Write the current select bits to the select pins
    digitalWrite(s0,r0);
    digitalWrite(s1,r1);
    digitalWrite(s2,r2);

    digitalWrite(LED, HIGH);
   // calibrate each key for five seconds
   while (millis() < adjusted_time + 5000)
   {
    delay(1);
     sensorValue[i] = analogRead(A0);

     // record the maximum sensor value
     if (sensorValue[i] > sensorMax[i])
     {
       sensorMax[i] = sensorValue[i];
     }

     // record the minimum sensor value
     if (sensorValue[i] < sensorMin[i])
     {
       sensorMin[i] = sensorValue[i];
     }
   }
   digitalWrite(LED,LOW);
   delay(1000);
  }
}/*End of calibrate_sensor()*/

The calibration function is comprised of one big for loop, that will loop through each key. Nested in the for loop is a while loop, that will continue to loop for 5 seconds reading the current key. In those 5 seconds the key will be repeatedly read, storing the Max and Min value read for that key.

The sensor reading will have a Max Value and Min Value, this will be stored in sensorMax[] and sensorMin[] for each key. The sensor value will range from 0 - 1023.

The adjusted_time is equal to the current millis() reading. This is used for timing purposes. This is a common method used for timing requirements that aren't strict. The code is still running all the way through, but timing is not exact.

There is a delay(1) , although the delay function is generally used sparingly, it is used here to make sure the analogRead() is not being called too fast with in the loop.

Once the delay is finished, the sensor value is read, sensorValue[i] = analogRead(A0); , Remember that i represents the key we are on, and the Mux0 is connected to A0. Additionally right before the while loop, the Select Bits are determined and set.

The two following if statements are used to record the Sensor Max and Sensor Min

Download file Copy to clipboard
1
2
3
4
5
// record the maximum sensor value
if (sensorValue[i] > sensorMax[i])
{
  sensorMax[i] = sensorValue[i];
}

If the sensor value is greater than > the sensor maximum, Then, the sensor maximum equals the sensor value.

This allows us to hold onto the largest value, and ignore new values that are smaller. The following if statement works exactly the same, but is used to capture the minimum value.

The while loop will continue to loop for approx. 5 seconds, reading and recording, Min and Max values on a single key. Then when the 5 seconds for that key is up, the LED will turn off for 1 second (1000ms), then the process is repeated for the next key.

Challenge

There was a section of code in the above section not mentioned. The three lines of code labeled //Mux Select use a function called bitRead(x,n) , I challenge you to research what bitRead(x,n) does, and try to figure out what r0 r1 and r2 are being used for in the calibration function.

readkeys() 

The readkeys() has some similar code seen in the calibrate_sensor() , mainly the code used to process and send the Select Bits to the Mux. The new code in this function is for normalizing the raw sensor values recorded into a range we can use. Place this after the calibrate_sensor() function:

Download file Copy to clipboard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void readKeys()
{
  //Read Pins
  for(int i = 0; i < number_of_keys; i++)
  {
      delay(1);
      r0 = bitRead(i,0);
      r1 = bitRead(i,1);
      r2 = bitRead(i,2);

      digitalWrite(s0,r0);
      digitalWrite(s1,r1);
      digitalWrite(s2,r2);

      sensorValue[i] = analogRead(A0);
      sensorValue[i] = map(sensorValue[i], sensorMin[i], sensorMax[i], 127, 0);
      // in case the sensor value is outside the range seen during
      //calibration
      sensorValue[i] = constrain(sensorValue[i], 0, 127);

    if(sensorValue[i] <= 10){
      sensorValue[i] = 0;
    }
  }
}

The first two code blocks should look familiar, the rest of the function is new. The sensor is read for that key, sensorValue[i] = analogRead(A0); , then the value is mapped using the sensorValue[i] , sensorMin[i] , and sensorMax[i] . The Min and Max being the same values captured in the calibrate_sensor() .

There is a small point that is not very easy to see here, the Velostat sensor gives us a reading from 0 - 1023. 1023 when No Pressure is applied, and 0 when Full Pressure. This is backwards to what we want for volume control, When we press the key really hard we want it to produce a louder note.


sensorValue[i] = map(sensorValue[i], sensorMin[i], sensorMax[i], 127, 0);

When mapping the raw values to a new range, we map the SensorMin[i] to 127, and the sensorMax[i] maps to 0. The maximum, and minimum inVelosity for the sendNoteOn MIDI function is listed in the parameters above, which happen to be 0 -127. This swap allows the harder press to produce a larger number, and in turn a louder note.

Now what happens if we read a value that is out of the range of the values gathered during calibration? This is why we constrain the value to the range of the mapping function. sensorValue[i] = constrain(sensorValue[i], 0, 127); , This will keep us within our 0 - 127 range.


if(sensorValue[i] <= 10){ sensorValue[i] = 0; }

The last if statement is used to drop everything less than or equal to <= 10 to 0. This is done to fix a problem where some keys, when not pressed would receive a reading that would persist around 1 or 2, causing that key to never turn off.

ISR 

The Interrupt Service Routine function follows the readKeys() function:

Download file Copy to clipboard
1
2
3
4
5
6
7
8
9
ISR(TIMER2_COMPA_vect){
   //interrupt commands for TIMER 2 here
   if(ISR_count == 2)
   {
     readKeyFlag = true;
     ISR_count = 0;
   }
   ISR_count++;
}

The ISR is triggered by Timer 2, which was set to 1ms, or 1000 Hz, in the setup() .

Calibrating the Piano 

Before running the code, there is a process for calibrating the pressure sensor that comes with this code. The calibration begins whenever you give power to the piano or press the reset button on the shield. The process starts with the small led will flash three times, then go out. It will then blink on and off seven times, one for each key. During which you have to press down a single key in order from C to B, switching keys when the light goes out.

Code output and results 

Once the code uploads the calibration process of the piano can begin, you can ignore it for now as we won't be getting output until we set up the MIDI software. In your serial monitor you will find a continuous string of backwards **?**s, xXs, and other symbols. This is the correct output even though it is very different than our benchmark code. If you calibrate the piano by pressing the button or reuploading the code, you'll still get a continuous line but with `` and ds inbetween. This is also fine. Now we can move onto the MIDI software that will make this piano play real music.

Full MIDI Code 

Download file Copy to clipboard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#include <MIDI.h>



/*
  killall ttymidi             End all connections
  ttymidi -s /dev/ttyACM1 &   Set up New one, ACM1 Not always the Case
  aconnect -i                 Print inputs, Arduino is Connected to This ttymidi
  aconnect -o                 Print Outputs, LMMS instrument is the one you want
  aconnect 129:0 128:2        Connect the input/output like This, numbers
                              subject to change
*/

#define LED 13

#define CE0 2 //Chip Enable 0
#define CE1 3
#define CE2 4

#define s0 5  //Select 0
#define s1 6
#define s2 7

#define number_of_keys 7

//Max is zero so the true max of the reading will be captured
int sensorMax[number_of_keys];
//True Min will be captured
int sensorMin[number_of_keys];
int sensorValue[number_of_keys];

int midi_Num[number_of_keys]= {60,62,64,65,67,69,71};
boolean readKeyFlag = false;
boolean isKeyPressed = false;
int ISR_count = 0;
int r0 = 0;
int r1 = 0;
int r2 = 0;



struct MySettings : public midi::DefaultSettings
{
   static const long BaudRate = 115200;
};
MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial, MIDI, MySettings);



void setup()
{
  //Remember, this code only
  pinMode(LED, OUTPUT);  //LED

  pinMode(s0, OUTPUT);  //s0
  pinMode(s1, OUTPUT);  //s1
  pinMode(s2, OUTPUT);  //s2

  pinMode(CE0 ,OUTPUT);   //CE0
  pinMode(CE1,OUTPUT);    //CE1
  pinMode(CE2,OUTPUT);    //CE2

  digitalWrite(CE0, LOW);  //On
  digitalWrite(CE1, HIGH); //Off
  digitalWrite(CE2, HIGH); //Off

  calibrate_sensor();

  // TIMER 2 for interrupt frequency 1000 Hz(1ms):
  cli(); // stop interrupts
  TCCR2A = 0; // set entire TCCR2A register to 0
  TCCR2B = 0; // same for TCCR2B
  TCNT2  = 0; // initialize counter value to 0
  // set compare match register for 1000 Hz increments
  OCR2A = 249; // = 16000000 / (64 * 1000) - 1 (must be <256)
  // turn on CTC mode
  TCCR2B |= (1 << WGM21);
  // Set CS22, CS21 and CS20 bits for 64 prescaler
  TCCR2B |= (1 << CS22) | (0 << CS21) | (0 << CS20);
  // enable timer compare interrupt
  TIMSK2 |= (1 << OCIE2A);
  sei(); // allow interrupts

  MIDI.begin(4);          // Launch MIDI and listen to channel 4
}


void loop()
{
  if(readKeyFlag == true)
  {
    readKeys();
    readKeyFlag = false;
  }

  for(int i = 0; i < number_of_keys; i++)
  {
    if(sensorValue[i] > 10){
      MIDI.sendNoteOn(midi_Num[i], sensorValue[i],2);
    }else{
      MIDI.sendNoteOff(midi_Num[i], sensorValue[i],2);
    }
  }
}




void calibrate_sensor()
{
  unsigned long adjusted_time;

  for(int i = 0; i < number_of_keys; i++)
  {
    sensorMax[i]   = 0;
    sensorMin[i]   = 1023;
    sensorValue[i] = 0;
    adjusted_time = millis();

    //Mux select
    r0 = bitRead(i,0);
    r1 = bitRead(i,1);
    r2 = bitRead(i,2);

    //Write the current select bits to the select pins
    digitalWrite(s0,r0);
    digitalWrite(s1,r1);
    digitalWrite(s2,r2);

    digitalWrite(LED, HIGH);
   // calibrate each key for five seconds
   while (millis() < adjusted_time + 5000)
   {
    delay(1);
     sensorValue[i] = analogRead(A0);

     // record the maximum sensor value
     if (sensorValue[i] > sensorMax[i])
     {
       sensorMax[i] = sensorValue[i];
     }

     // record the minimum sensor value
     if (sensorValue[i] < sensorMin[i])
     {
       sensorMin[i] = sensorValue[i];
     }
   }
   digitalWrite(LED,LOW);
   delay(1000);
  }
}/*End of calibrate_sensor()*/


void readKeys()
{
  //Read Pins
  for(int i = 0; i < number_of_keys; i++)
  {
      delay(1);
      r0 = bitRead(i,0);
      r1 = bitRead(i,1);
      r2 = bitRead(i,2);

      digitalWrite(s0,r0);
      digitalWrite(s1,r1);
      digitalWrite(s2,r2);

      sensorValue[i] = analogRead(A0);
      sensorValue[i] = map(sensorValue[i], sensorMin[i], sensorMax[i], 127, 0);
      // in case the sensor value is outside the range seen during calibration
      sensorValue[i] = constrain(sensorValue[i], 0, 127);

    if(sensorValue[i] <= 10){
      sensorValue[i] = 0;
    }
  }
}

ISR(TIMER2_COMPA_vect){
   //interrupt commands for TIMER 2 here
   if(ISR_count == 2)
   {
     readKeyFlag = true;
     ISR_count = 0;
   }
   ISR_count++;
}