Lab Exercise 4:
The purpose of this lab time is to give you practice with arrays. You'll also get more practice with functions and loops. The ultimate goal of the project is to create an electronic keyboard.
Tasks
Player Piano
You are welcome to leave your LCD connected. Though it is not a required part of this project, you may find it useful. If you do leave it connected, borrow a second breadboard for the second part of the lab.
- Hook up your speaker to one of the PWM output pins, such as pin 5. One end should be connected to ground, the other to the pin output.
- Make a new code file, put your name and lab 4 at the top and save it as player.ino.
- Include the following template, which defines a function called
playTune, as well as providing the main template.
int playTune( int notes[], int durations[], int unit, int N ) { } int main() { init(); return(0); }
- The playTune function takes in four arguments. We're going to define those arguments as an array of notes in the range [1,12], an array of durations, specified as the number of units to hold each note, an integer value unit that specifies the duration of a unit in ms, and the number of notes (and durations) N. The function should play the tune given the notes, durations, and unit definition.
- To keep track of the frequencies, create a float array of 12 frequencies. The base frequencies
are listed below, along with the frequencies for the 4th octave.
For the lab, use the 4th octave values.
Note Frequency (1) Frequency(4) C 16.35 261.63 C#/Db 17.32 277.18 D 18.35 293.66 D#/Eb 19.45 311.13 E 20.60 329.64 F 21.83 349.23 F#/Gb 23.12 369.99 G 24.50 388.00 (adjusted for speaker) G#/Ab 25.96 415.30 A 27.50 440.00 A#/Bb 29.14 466.16 B 30.87 493.88 - The function should loop N times. Inside the loop, if note[i] is equal to 0, then call noTone, else get the frequency corresponding to the note by using note[i] to index into your frequency array (remember to subtract 1 to get the proper index) and then passing the frequency to the tone function. The tone function takes two arguments: the pin, and the frequency to play. Then call delay for the appropriate amount of time (multiply the current duration by unit), then call noTone (which takes the pin as argument).
-
In your main function, define an int array that contains your
tune. Then define an int array that defines duration (needs
to be the same length as tune). Set up your speaker pin as an
OUTPUT using pinMode, call the playTune function with the
appropriate arguments, and then call noTone.
You may find it helpful to also declare a variable
const int N = #
where # is the number of elements in your tune array. Then use N to define the length of your tune and duration arrays and also pass N as the last argument to playTune. - Test your code.
Capacitive Sensor
-
Get a capacitive sensor (shown below). Place it on a
breadboard centered across the middle divide. The Adafruit documentation for the divide is here.
Connect your circuit using the following wiring diagram. The control pins are all on the side with fewer numbers of pins (the near side in the photo above). In the photo, pin 1 is on the right and marked with vin. Note that the sda (pin 5) and sdl (pin 6) wires will be right next to one another on the sensor board.
Cap Pin Connected to Purpose 1 (vin) 5V Power 3 (gnd) Ground Ground 5 (sda) Metro A4 i2c Data 6 (scl) Metro A5 i2c Clock - Download the Adafruit MPR121 library. In your sketchbook directory (look at the Arduino App options to find this), create a directory called libraries, if it does not already exist.. After unzipping the download, rename the directory Adafruit_MPR121 and put it into the libraries folder. You may need to restart the Arduino app after this.
- In the MPR121 directory should be a folder called MPR121test. Go ahead and open this file. Compile it and run it. If you touch the sensor, it should register your touch.
- In the file, you can comment out a return statement to get more information. Do this and look at the output. Connect a wire and alligator clip to input 11 and see how the signals change as you touch the sensor. Get a rough idea for the values when touched and when released.
-
Create a new Arduino file using the template below. Save
it as touched.ino. We're going to write our own version
of the test file. Use the code below as a template and
fill out the code.
#include <Wire.h> #include "Adafruit_MPR121.h" // create the MPR121 object Adafruit_MPR121 cap = Adafruit_MPR121(); // main program: detect whether a sensor is touched or not int main() { init(); // code: define an int variable lasttouched and assign it 0 // Setup section Serial.begin(9600); Serial.println("MPR121 Cap touch test"); // make sure we can talk with the capacitive sensor if( !cap.begin( 0x5A)) { Serial.println("MPR121 not found"); return(0); // exit } Serial.println("MPR121 found"); // code: use cap.setThresholds(low, high) function here // code: assign to lasttouched the result of calling cap.touched(); // loop for(;;) { // code: declare an int variable curtouched; // code: assign curtouched the result of calling cap.touched(); // code: begin a for loop that loops 12 times // code: if this sensor changed to touched // code: serial print the sensor number and that it was touched // code: if this sensor changed to released // code: serial print the sensor number and that it was released // code: assign to lasttouched the value of curtouched // code: delay by some number of ms (e.g 100) } return(0); }
How do I tell the state of a sensor? The function
cap.touched()
returns a single 16-bit integer. The term "16-bit integer" means that the computer uses 16 bits (each bit represents either a 0 or a 1) to represent integral numbers. However, we can also just consider each bit individually. With respect to the capacitive sensor, which has 12 sensor inputs, each of the first 12 bits of the int value returned by cap.touched() represents a different sensor. The rightmost bit (position 0) represents sensor pin 0, the bit in position 1 represents sensor pin 1, and so on.In order to test if a sensor is on, we need to know if the corresponding bit of the integer is a 0 or a 1. C has an operator
&
that executes a bit-wise AND function. The AND function is 1 if both inputs are 1 and otherwise 0. So to test if one bit of a 16-bit value is 1, we first create a test value that has a 1 in the position of interest and 0s elsewhere. Then we execute a bit-wise AND. If the result is 0 then the corresponding bit is 0 and sensor is not being touched. If the result is not zero, then the corresponding bit is 1 and the sensor is being touched.We can generate just such a test value by taking the value 1 (which is the bit string 0000000000000001) and then shifting the bit string left to the correct position. The bit shift operator in C is <<. So the expression (1 << 3) produces the bit string 0000000000001000.
The following is an example of how to test the bit in position 4 of an int. You can generalize this to a loop if you use your loop variable instead of the 3 as the number of positions to shift left.
int value = cap.touched(); int third_bit_test = value & (1 << 3 );
- Run your code and test it out, using a wire and an alligator clip. Then get some copper tape and make sure it still works.
When you are done with the lab exercises, you may start on the rest of the project.