CS 153: Lab 4

Title image Fall 2018

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.

  1. 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.
  2. Make a new code file, put your name and lab 4 at the top and save it as player.ino.
  3. 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);
    }	
  4. 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.
  5. 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.

    NoteFrequency (1)Frequency(4)
    C16.35261.63
    C#/Db17.32277.18
    D18.35293.66
    D#/Eb19.45311.13
    E20.60329.64
    F21.83349.23
    F#/Gb23.12369.99
    G24.50388.00 (adjusted for speaker)
    G#/Ab25.96415.30
    A27.50440.00
    A#/Bb29.14466.16
    B30.87493.88

  6. 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).
  7. 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.

  8. Test your code.

Capacitive Sensor

  1. 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 PinConnected toPurpose
    1 (vin)5VPower
    3 (gnd)GroundGround
    5 (sda)Metro A4i2c Data
    6 (scl)Metro A5i2c Clock
  2. 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.
  3. 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.
  4. 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.
  5. 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 );	
  6. 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.