Touch-Free Laser Piano

Caroline Chu (cc2295), Ashley He (ach238), Mira Kim (mk864)

Table of Contents

Introduction

High Level Design

Rationale and Sources of Project Idea

Background Math

Logical Structure

Hardware/Software Trade-offs

Software/Hardware Design

Software

ISR (DAC)

Timer Thread

Photoresistor Thread (DAC)

Stepper Motor Thread

Main

Hardware

Piano (overall hood design)

Stepper Motor

Photoresistors

Results

Conclusions

Appendices

Appendix A

Appendix B: Source Code

Appendix C: Schematics

Appendix D: Bill of Materials

Appendix E: Work Distribution

Appendix F: References

Introduction

Using the PIC32, we developed a touch-free, laser piano through the integration of a laser, mirror, stepper motor, and several light sensors. The user can play the piano by waving his or her hand across a streak of seven laser beams where each streak corresponds to a key on a piano. The laser piano is designed by attaching a mirror to a stepper motor and a laser beam is pointed at the rotating mirror. By manipulating the speed and direction of which the motor steps, we successfully produced seven laser streaks. The entire design is encompassed within a frame where the laser streaks are shot upwards toward their corresponding light sensor. If the light sensor is covered up and it senses no light, the piano key that corresponds to that light sensor will be produced. There might have been another way to approach the problem, such as having a light sensor on the motor itself instead of on the frame. However, we believed this was the optimal solution, given our limited resources and our knowledge about the microcontroller.

High Level Design        

Rationale and Sources of Project Idea

        The rationale behind this project was that we were interested in developing an interdisciplinary product that combined our love for both technology and music. As we brainstormed ideas, the idea of creating a portable and low-cost piano surfaced. Pianos are one of the most commonly played instruments. However, they usually are on the more expensive side and are not very portable, even with electronic keyboards. Our touch-free piano would solve both these problems. It can be built with parts under $80 dollars and it can fit easily onto one’s desk.

        Creating a portable piano was an interesting idea. However, our main source of inspiration of creating a piano that used lasers to display notes, came from our childhood. In elementary school, it was very common for the whole school to visit science museums. One of the more memorable science displays was the laser harp. It was fun, interactive, and easy to play for all ages. We wanted to apply the same concept to our musical instrument, but instead of making a harp, we wanted to make a more common instrument--the piano.         

Background Math

        The main math behind this project was related to the interaction between the laser, mirror, and light sensor. When one shines a laser beam onto a mirror, there will be an incident angle and a reflected angle. Both these angles are relative to the normal of the mirror. In an ideal scenario when there is no loss of energy, the incident angle will equal the reflected angle. We used this assumption to make our calculations. Below is a diagram of the incident and reflected angle.

Constraints

Degree Range of the Mirror

Minimum Degree of the Mirror

 (not inclusive)

Maximum Degree of the Mirror

(not inclusive)

Since it is known that the angle of the mirror range is 0° to 90°, we divided the key degrees accordingly. We assigned the middle key to = 45°. We then added 7.5° to each key above the middle key and subtracted 7.5° to each key below. 7.5° is added and subtracted relative to the previous key’s degree. This is how we got the numbers below. The degree below represents the degree between the horizon and the normal.

Key corresponding to sensor

F

G

A

B

C

D

E

Degree relative to horizon

67.5°

60°

52.5°

45°

37.5°

30°

22.5°

To find the angle of the mirror, we used the equation below.

The last calculation we needed to make was to determine the distance of each light sensor from the mirror. There were 2 parameters we had, the degree relative to the horizon and the height of the frame (30.48cm). By using the trigonometric function, tan, we were able to calculate the distance needed. The given example below is for the key D.

Degree needed:

        30 + 30 = 60°

        tan(60°) =

        x = 17.6 cm

Therefore, the key D needs to be placed 17.6 cm to the right of the mirror. This calculation is performed for all the keys.         

Standards and Copyrights

This project does not follow any of the IEEE standards. There are no copyrights associated with this system.

Hardware/Software Trade-offs

Our stepper motor rotated slower than expected in between steps, possibly because of interrupt service routine delaying the output signal to the stepper motor, so we had to put a software filter on the photoresistor readings to make sure the piano does not make sounds when none of the photoresistors are covered. Because the stepper motor was slow, there was too long of a time gap between when the stepper motor points at a photoresistor and when it points a beam at the next one. This made the system act like some of the photoresistors are covered when they are not. To fix this, we stored previous readings from each photoresistor into a buffer and OR-ed the previous seven readings with the current reading. Since each of the readbits are 1 when not covered (there is light pointing at the photoresistor), by OR-ing these bits, we are making it favor the “not covering” input over “covering.” For one photoresistor on the very end (the “E” note), we had it OR the previous nine readings and current reading since it was more prone to mistakenly registering as covered, since it is furthest away from the stepper motor and it has the longest time interval in between the laser pointing the photoresistor. This successfully solved the issue of piano generating sound when it shouldn’t, although this sacrifices response time when photoresistors are covered. Because the readings are OR-ed, when any of the readings are 1, the piano did not make any sound. Therefore, it takes more time for piano to start making sound once covered than it could with the photoresistor’s response time. This was a trade off for the functionality of the system.

Software/Hardware Design

Software

ISR (DAC)

The ISR is responsible for SPI communication for DAC. Because we are using the port expander and the port expander also needs SPI communication, it is important to wait for the port expander to finish its SPI transaction before writing DAC data. The DAC data is calculated by shifting the accumulated phase from phase increment from the photoresistor thread to the right 24 bits and then looking up this data in the sine table. 2048 is added to this data we want the range to be 0 to 4096. Although we do not to read the SPI for functionality, we need to read SPI because every time you write to SPI, you also need to read, and not reading here can confuse the next port expander transaction.

Timer Thread

        The timer thread updates every second to display the number of seconds since the program started on the TFT display.

Photoresistor Thread (DAC &  Port Expander)

        Whenever port expander is being used, it is important to put it in an SPI critical section to make sure it doesn’t get interrupted in the middle by the ISR. The port expander is initialized and input pins are set up in the critical section, and then the thread enters a while loop. In the while loop, each one of the input pins from the port expander are read in an SPI critical section, and stored in variables. Then, each input goes through a filter as mentioned earlier in the hardware / software trade off section. Next, the variable ‘playingnote’ counts how many notes are being played by counting how many photoresisters are covered. If only one is being covered, the DAC frequency is set to play one note on the scale that corresponds to its key. If two of them are covered, the DAC frequency is set to output dual tone of corresponding keys. If there are more than two, the DAC outputs 0. The phase of the DAC data was incremented by . 25000 was selected since we want to cover all ranges up to the highest note 1477Hz*16 (the highest frequency at a sample rate of 16 per cycle); 232 accounts one phase of the sine wave. This increment variable is used in the ISR to calculate the DAC data.

Stepper Motor Thread

        The stepper motor thread was used to control how many steps and to what degree the stepper motor would move at. Since we used a 6-wire stepper motor driven in unipolar mode, each time we set a bit to high and reset it back to zero, we could step through one step of 7.5 degrees. In unipolar mode, energizing the wires or ports in one order produces a rotation in either the clockwise or counterclockwise direction, and energizing the ports in the reverse order rotates the motor in the opposite direction. As depicted in the diagram in Figure 3.1, we wired the I/O ports RB3, RB7, RB8, and RB13 to the coils driving the stepper motor. To rotate the stepper motor one step in the clockwise direction, we first set RB3 high, yield the thread for 10 milliseconds, and set the bit low again. We then yield for another 20 milliseconds to allow the mirror to hold that position longer, to ensure there was a clear beam being projected onto the photoresistors. This process of setting and resetting the bit was repeated for RB7, RB8, and RB13 for two cycles for a total of seven steps in the clockwise direction. After that, the stepper motor would enter another loop where it would step seven times in the opposite (counterclockwise) direction. The stepper motor continuously stepped in a clockwise/ counterclockwise pattern as described above during the entire program execution.

Main

        In the main, timer is first set up at the rate of 1600 for 25ksamples/sec. Chip select and MOSI pins are configured for the DAC, as well as SPI channel set up. The sine lookup table is set up, and output pins for the stepper motor are set up. The TFT screen setup is done for printing time any useful information for debugging and the threads are set up and run in a while loop.

Hardware

Piano (frame/ laser hood design)

        The piano needed a frame to hold everything in place, as alignment of the laser, mirror on the stepper motor, and the photoresistors are very important. To stay in the budget, we used foam board and styrofoam to build the outer frame of the piano. The length of the hood was decided by running the stepper motor and marking the shortest length with 7 different steps at an optimal height of 30.48cm. The stepper motor is screwed onto a piece of wood block, which is hot glued onto another piece of wood block where we taped the laser on. This whole chunk of wood was then taped onto the foam board. While this provided enough stability for our system, the system was still fragile to outer shock, so it would have been nice to get a more sturdy material or way of stabilizing our system to the foam board. Seven photoresistors were taped onto the hood, which has additional foam core covers pinned on each side to limit the amount of ambient light that reaches the photoresistors. Without these additions, the photoresistors did sometimes respond to other ambient light. Overall, we made the frame as sturdy as possible while trying to stay in the budget, which proved to be enough for stable functionality when there is no outside interference.

Stepper Motor

Figure 3.1: Stepper Motor and Surrounding Circuitry


        To setup and control the 6-wire stepper motor, we first determined which wires corresponded to which coil that would drive the motor. We used a breadboard and plugged all six wires in an arbitrary order. We then used a multimeter to measure the resistance between the wires. If there was no reading on the multimeter, that indicated that the two wires being tested were not part of the same coil. For wires that were part of the same coil (three wires to a coil), the “center tap to coil end resistance of that set is half of the coil end to end resistance.” What we eventually deduced from that were that the red and black wires were the “center taps” of our stepper motor, and that the two coils were composed of the following combinations of wires: Coil 1 - Black, Grey, White; Coil 2 - Red, Purple, Brown. Next, we had to figure out which order to drive the wires to step the motor in the clockwise and counterclockwise direction. To do so, we attached the positive lead of a 5V power source to the center tap wires and the ground to the rightmost coil end wire on the breadboard (labeled as “4”). We observed the motor movement as we touched a wire connected to ground to the other three wires on the breadboard. If there was counterclockwise step, we labeled the wire “1.” If there was no step, we labeled the wire “2,” and if there was a clockwise step, we named the wire “3.” From there, we rearranged the wires so that it followed the order 1, 2, 3, 4. The colors that we found that corresponded to each of the numbers were as follows: White - 1, Brown - 2, Grey - 3, Purple - 4. Driving the wires in order from 4, 3, 2, 1, the stepper motor rotates in the clockwise direction. Driving them in the opposite order allowed us to rotate the motor in the counterclockwise direction. After that, we determined which I/O ports were available to use on the development board (RB3, RB7, RB8, and RB13). We connected the wires corresponding to 1, 2, 3, 4 to the ULN2003 IC driver (improved control for stepper motor) and then to RB3, RB7, RB8, and RB13 in that order. In the software, we can then control which wire is driven and at what interval.

Photoresistors

Figure 3.2: Photoresistors and Surrounding Circuitry


To make sure that the photoresistor response time is short enough for our system, we measured the photoresistor response time by reading the voltage from a voltage divider with a photoresistor to see how fast it takes for the photoresistor to change voltage levels. As seen in Figure 3.3, the response time of the photoresistor was fairly quick. It takes 54ms for the photoresistor voltage level to rise from 250 mV to 650mV. It takes 30ms for the voltage to fall back to 250mV. This data showed us that the photoresistors can respond fast enough for good response time of the overall system.

The photoresistors were set up as a resistor divider with 4.7kΩ resistors with 3.3V and GND from the PIC32 as shown in the schematics diagram, Figure 3.2. These are then connected to an input pin on the PIC32. When the photoresistor receives light, it reads 1 and when it doesn’t, it reads 0, as the photoresistor ranges from 120 - 1.5kΩ and the threshold for PIC32 to register as 0 or 1 is 1.8V.

Figure 3.3: The change of voltage level with a photoresistor resistor divider is shown.

Audio Socket

Figure 3.4: Audio Socket and Surrounding Circuitry


        An audio socket is connected to DACA pin on the PIC32 through a low pass RC filter for noise filtering and audio output. A pair of lab speakers were plugged into this socket for audio. The schematics for the audio socket setup is shown in Figure 3.4.

Results

Video Demo:



The system worked pretty well. The piano stays quiet when none of the photoresistors are covered, and when one is covered, it produces the sound that corresponds to its key after a little bit of delay. This delay is created from the software filtering mentioned above. The system sometimes acted a bit inconsistently and produced sounds when it wasn’t supposed to, as the photoresistors are very sensitive to change. Even hitting the hood of the piano a little can easily make the photoresistors unaligned from the beam, and make it act wrong. We didn’t fix the laser’s position on the wooden block in case we needed adjustments, but this created some instability. The best way to improve this would be to use a more stable material for the entire piano. Also, we noticed that the piano tends to act more inconsistently when it hasn’t been long since it was powered. Letting it sit for a while makes it more stable. Except for these stability issues, the piano worked well as expected.

Conclusions

        Overall, we were pleased with the outcome of our project. The laser beam cutoff detection and sound generation worked well, and resulted in a very playable touch-free laser piano. The process of building this piano was extremely rewarding as it allowed us to apply our ECE knowledge gained from our undergraduate experience to create something that was fun, interactive, and related to our interest in music.

        In this project, we learned the importance of being adaptable and how critical having properly functioning hardware is during the early stages of development. Originally, we planned on using MIDI for sound generation and only one photoresistor to track whether or not a laser beam was cut off and when. We found that the additional hardware and software involved in using MIDI sound generation would complicate our system greatly, so we decided to use DAC sound generation, which was something we were more familiar with. As for using only one photoresistor, after setting up the stepper motor and seeing the behavior of the laser beams, we discovered that it would be difficult to track which of the seven beams was being cut off with just the one photoresistor. We also found that having the laser beam unenclosed in the lab space was going to be a safety issue, and therefore needed to build a frame that would encase the beam and additional photoresistors to more easily track the keys played. In the end, the frame we built around our system made it more robust and easier to calibrate the beam detection and photoresistor placement.

        Our beam detection for our system was not the most reliable, as there was a very narrow laser beam (about one millimeter) being reflected across the seven different photoresistors, and thus had a very small margin for error. Calibrating the system involved having to mount the laser and stepper motor combination to the frame, and then adjusting each individual photoresistor to coincide with the spots the beams shined the brightest on the top of the frame. Photoresistors that were placed closer to the mirror that reflected the laser beam had more consistent behavior when they were covered compared to the photoresistors closer to the edges of the frame where the beam did not shine as frequently or as consistently as towards the middle. Another problem we ran into was how precisely the photoresistors had to be covered to play a note; if there was any residual light bouncing off the frame from the laser, the note would not produce a sound or produce a sound at the wrong time. To combat some of the residual light interference created even when a beam was covered by our hands, we made a cup-like device to cover the photoresistor on three sides. This worked well, but still required precise movements from a user so not to interfere with beams shining on different photoresistors. This is something that can be improved in future iterations of the project by widening the laser beam (using a laser beam expander or a sheet of plastic) and decreasing the light interference seen by the photoresistors (could design a cover for each individual photoresistor that blocks ambient light).

Another small issue that arose was the speed at which the motor was rotating. We found that it was a bit slow in changing directions and stepping through the whole range, which resulted in some timing inconsistencies in creating the beams, despite setting the frequency to the correct value. This might be because we chose to drive the motor at 5V instead of 12V. For the future, it would be important to see if that change in power heavily impacted our motor’s function.

Standards

Our laser piano complies with aviation safety rules by always running the system indoors with an external hood/ frame, and never pointing the projected laser beams through windows.

Intellectual Property Considerations

Our laser piano is loosely based off of the laser harp linked in our references section (laser harp was patented by Bernard Szajner in 1981). However, our design differs substantially in how the sounds are generated and how we deal with tracking which notes are played. During the design process, we decided to use DAC sound generation rather than MIDI standard due to our previous experience and to improve our system’s portability. We also added a frame for our system to contain our laser beam. We consider our use of a frame an improvement as we can more easily and reliably control the beam and photoresistor placement for a more accurate read of what notes are played. Another major difference in our project is that we allow for multiple notes (up to two) to be played simultaneously, and we chose to not play any sounds for any number of notes exceeding two.

All of our source code in lab5.c was written by members of our group. Libraries that we used such as the protothreads and port expander libraries were used with permission of the instructor, Bruce Land. Our code is open-source and can be published in any magazine or journal. No design was reverse-engineered to create this project. We had no patent or trademark issues. No non-disclosure agreements were signed. There are no patent opportunities for this project.

Ethical Considerations

Since we were using a relatively high power laser in combination with the unpredictable nature of a rotating mirror, we had to take constant precautions in building and testing our system. We never left the laser on while we weren’t actively testing or monitoring the system and we tested the mirror/motor movement without shining the laser on it whenever possible. Additionally, we used a black bag as a shield around the laser beams to prevent the beams from shining across the room, and ultimately designed a frame for our system to contain any beams generated through the mirror’s reflection. We did most of our testing in the back corner to also minimized any interaction with other groups and we also operated the laser far from eye level, which is compliant with the requirements of the IEEE Code of Ethics with regards to holding “paramount the safety, health, and welfare of the public.” Finally, by making our code, schematics, and design process open source and available to the public, we hope to encourage understanding of technology, its capabilities, and societal implications as per point five of the IEEE Code of Ethics.

Appendices

Appendix A:

The group approves this report for inclusion on the course website. The group approves the video for inclusion on the course youtube channel.

Appendix B: Source Code

/*
* File:        lab5.c
* Author:      Caroline Chu, Ashley He, Mira Kim
* For use with Sean Carroll's Big Board
* http://people.ece.cornell.edu/land/courses/ece4760/PIC32/target_board.html
* Target PIC:  PIC32MX250F128B
*/


////////////////////////////////////
// clock AND protoThreads configure!
// You MUST check this file!
#include "config_1_2_3.h"
// threading library
#include "pt_cornell_1_2_3.h"
// yup, the expander
#include "port_expander_brl4.h"

////////////////////////////////////
// graphics libraries
// SPI channel 1 connections to TFT
#include "tft_master.h"
#include "tft_gfx.h"
// need for rand function
#include <stdlib.h>
// need for sin function
#include <math.h>
#include <string.h>
////////////////////////////////////

// lock out timer 2 interrupt during spi comm to port expander
// This is necessary if you use the SPI2 channel in an ISR.
// The ISR below runs the DAC using SPI2
#define start_spi2_critical_section INTEnable(INT_T2, 0)
#define end_spi2_critical_section INTEnable(INT_T2, 1)

// string buffer
char buffer[60];

////////////////////////////////////
// DAC ISR
// A-channel, 1x, active
#define DAC_config_chan_A 0b0011000000000000
// B-channel, 1x, active
#define DAC_config_chan_B 0b1011000000000000
// DDS constant
#define two32 4294967296.0 // 2^32
#define Fs 25000.0         // Nyquist: higher than 1477*16 (hiehest frequency)(at least 8 samples)

//== Timer 2 interrupt handler ===========================================
volatile SpiChannel spiChn = SPI_CHANNEL2; // the SPI channel to use
volatile int spiClkDiv = 2;                // 10 MHz max speed for port expander!!
// the DDS units:
#define sine_table_size 256
volatile int sin_table[sine_table_size];

// ==================================================================
// initialize output signals
static float Fout = 400.0;
static float Fout2 = 500.0;
static float Fout1 = 600.0;

//== Timer 2 interrupt handler ===========================================
// actual scaled DAC
volatile int DAC_data;
// the DDS units:
volatile unsigned int phase_accum_main, phase_accum_main2, phase_incr_main, phase_incr_main2;
volatile unsigned int phase_accum_test, phase_incr_test;
volatile int playingnote = 0;

// ==================================================================

////////////////////////////////////
// pullup/down macros for keypad
// PORT B
#define EnablePullDownB(bits) \
 CNPUBCLR = bits;            \
 CNPDBSET = bits;
#define DisablePullDownB(bits) CNPDBCLR = bits;
#define EnablePullUpB(bits) \
 CNPDBCLR = bits;          \
 CNPUBSET = bits;
#define DisablePullUpB(bits) CNPUBCLR = bits;
//PORT A
#define EnablePullDownA(bits) \
 CNPUACLR = bits;            \
 CNPDASET = bits;
#define DisablePullDownA(bits) CNPDACLR = bits;
#define EnablePullUpA(bits) \
 CNPDACLR = bits;          \
 CNPUASET = bits;
#define DisablePullUpA(bits) CNPUACLR = bits;
////////////////////////////////////


void __ISR(_TIMER_2_VECTOR, ipl2) Timer2Handler(void)
{
 
int junk;

 mT2ClearIntFlag();
 
// main DDS phase and sine table lookup
 phase_accum_main += phase_incr_main;
 DAC_data = sin_table[phase_accum_main >>
24];

 
// === Channel A =============
 
// wait for possible port expander transactions to complete
 
while (TxBufFullSPI2());
 
// reset spi mode to avoid conflict with port expander
 SPI_Mode16();
 
// CS low to start transaction
 mPORTBClearBits(BIT_4);
// start transaction
 
// write to spi2
 WriteSPI2(DAC_config_chan_A | ((DAC_data +
2048) & 0xfff));
 
while (SPI2STATbits.SPIBUSY); // wait for end of transaction
                       
// CS high
 mPORTBSetBits(BIT_4);
// end transaction
 
// need to read SPI channel to avoid confusing port expander
 junk = ReadSPI2();
 
//
}

// === print a line on TFT =====================================================
// print a line on the TFT
// string buffer
char buffer[60];

void printLine2(int line_number, char *print_buffer, short text_color, short back_color)
{

 
int v_pos;
 v_pos = line_number *
20;
 
// erase the pixels
 tft_fillRoundRect(
0, v_pos, 239, 16, 1, back_color); // x,y,w,h,radius,color
 tft_setTextColor(text_color);
 tft_setCursor(
0, v_pos);
 tft_setTextSize(
2);
 tft_writeString(print_buffer);
}

// === thread structures ============================================
// thread control structs
// note that UART input and output are threads
static struct pt pt_timer, pt_key, pt_serial;
// The following threads are necessary for UART control
static struct pt pt_input, pt_output, pt_DMA_output;

// system 1 second interval tick
int sys_time_seconds;

// === Timer Thread =================================================
// update a 1 second tick counter
static PT_THREAD(protothread_timer(struct pt *pt))
{
 PT_BEGIN(pt);

 
while (1)
 {
   
// yield time 1 second
   PT_YIELD_TIME_msec(
1000);
   sys_time_seconds++;
   
// draw sys_time
   
sprintf(buffer, "Time=%d", sys_time_seconds);
   printLine2(
0, buffer, ILI9340_BLACK, ILI9340_YELLOW);
 }
// END WHILE(1)
 PT_END(pt);
}
// timer thread

// ============================== Read Photoresistor Thread =============================================

static PT_THREAD(protothread_key(struct pt *pt))
{
 PT_BEGIN(pt);

 
static int playback;
 
static int bit0;
 
static int bit1;
 
static int bit2;
 
static int bit3;
 
static int bit4;
 
static int bit5;
 
static int bit6;
 
static int prevbit0 = 0;
 
static int prevbit1 = 0;
 
static int prevbit2 = 0;
 
static int prevbit3 = 0;
 
static int prevbit4 = 0;
 
static int prevbit5 = 0;
 
static int prevbit6 = 0;

 
static int prev2bit0 = 0;
 
static int prev2bit1 = 0;
 
static int prev2bit2 = 0;
 
static int prev2bit3 = 0;
 
static int prev2bit4 = 0;
 
static int prev2bit5 = 0;
 
static int prev2bit6 = 0;

 
static int prev3bit0 = 0;
 
static int prev3bit1 = 0;
 
static int prev3bit2 = 0;
 
static int prev3bit3 = 0;
 
static int prev3bit4 = 0;
 
static int prev3bit5 = 0;
 
static int prev3bit6 = 0;

 
static int prev4bit0 = 0;
 
static int prev4bit1 = 0;
 
static int prev4bit2 = 0;
 
static int prev4bit3 = 0;
 
static int prev4bit4 = 0;
 
static int prev4bit5 = 0;
 
static int prev4bit6 = 0;

 
static int prev5bit0 = 0;
 
static int prev5bit1 = 0;
 
static int prev5bit2 = 0;
 
static int prev5bit3 = 0;
 
static int prev5bit4 = 0;
 
static int prev5bit5 = 0;
 
static int prev5bit6 = 0;

 
static int prev6bit0 = 0;
 
static int prev6bit1 = 0;
 
static int prev6bit2 = 0;
 
static int prev6bit3 = 0;
 
static int prev6bit4 = 0;
 
static int prev6bit5 = 0;
 
static int prev6bit6 = 0;

 
static int prev7bit0 = 0;
 
static int prev7bit1 = 0;
 
static int prev7bit2 = 0;
 
static int prev7bit3 = 0;
 
static int prev7bit4 = 0;
 
static int prev7bit5 = 0;
 
static int prev7bit6 = 0;

 
static int prev8bit0 = 0;
 
static int prev8bit1 = 0;
 
static int prev8bit2 = 0;
 
static int prev8bit3 = 0;
 
static int prev8bit4 = 0;
 
static int prev8bit5 = 0;
 
static int prev8bit6 = 0;

 
static int prev9bit6 = 0;
 
static int prev10bit6 = 0;

 
static int readbit0 = 0;
 
static int readbit1 = 0;
 
static int readbit2 = 0;
 
static int readbit3 = 0;
 
static int readbit4 = 0;
 
static int readbit5 = 0;
 
static int readbit6 = 0;

 
// port expander
 start_spi2_critical_section;
 initPE();
 
// key 0
 mPortZSetPinsIn(BIT_0);
 mPortZEnablePullUp(BIT_0);
 
// key 1
 mPortZSetPinsIn(BIT_1);
 mPortZEnablePullUp(BIT_1);
 
// key 2
 mPortZSetPinsIn(BIT_2);
 mPortZEnablePullUp(BIT_2);
 
// key 3
 mPortZSetPinsIn(BIT_3);
 mPortZEnablePullUp(BIT_3);
 
// key 4
 mPortZSetPinsIn(BIT_4);
 mPortZEnablePullUp(BIT_4);
 
// key 5
 mPortZSetPinsIn(BIT_5);
 mPortZEnablePullUp(BIT_5);
 
// key 6
 mPortZSetPinsIn(BIT_6);
 mPortZEnablePullUp(BIT_6);
 
// without pullup, threshold is approx vdd/2, but with pullup
 
// need external voltage divider

 end_spi2_critical_section;

 
// ====== SET UP INPUTS AND PULL UPS ================================
 
while (1)
 {

   
// yield time
   PT_YIELD_TIME_msec(
30);

   
// turn on cs
   start_spi2_critical_section;
   readbit0 = readBits(GPIOZ, BIT_0);
   readbit1 = readBits(GPIOZ, BIT_1);
   readbit2 = readBits(GPIOZ, BIT_2);
   readbit3 = readBits(GPIOZ, BIT_3);
   readbit4 = readBits(GPIOZ, BIT_4);
   readbit5 = readBits(GPIOZ, BIT_5);
   readbit6 = readBits(GPIOZ, BIT_6);

   
if (readbit0 || prevbit0 || prev2bit0 || prev3bit0 || prev4bit0 || prev5bit0 || prev6bit0 || prev7bit0 || prev8bit0)
     bit0 =
0;
   
else
     bit0 =
1;
   
if (readbit1 || prevbit1 || prev2bit1 || prev3bit1 || prev4bit1 || prev5bit1 || prev6bit1 || prev7bit1 || prev8bit1)
     bit1 =
0;
   
else
     bit1 =
1;
   
if (readbit2 || prevbit2 || prev2bit2 || prev3bit2 || prev4bit2 || prev5bit2 || prev6bit2 || prev7bit2 || prev8bit2)
     bit2 =
0;
   
else
     bit2 =
1;
   
if (readbit3 || prevbit3 || prev2bit3 || prev3bit3 || prev4bit3 || prev5bit3 || prev6bit3 || prev7bit3 || prev8bit3)
     bit3 =
0;
   
else
     bit3 =
1;
   
if (readbit4 || prevbit4 || prev2bit4 || prev3bit4 || prev4bit4 || prev5bit4 || prev6bit4 || prev7bit4 || prev8bit4)
     bit4 =
0;
   
else
     bit4 =
1;
   
if (readbit5 || prevbit5 || prev2bit5 || prev3bit5 || prev4bit5 || prev5bit5 || prev6bit5 || prev7bit5 || prev8bit5)
     bit5 =
0;
   
else
     bit5 =
1;
   
if (readbit6 || prevbit6 || prev2bit6 || prev3bit6 || prev4bit6 || prev5bit6 || prev6bit6 || prev7bit6 || prev8bit6 || prev9bit6 || prev10bit6)
     bit6 =
0;
   
else
     bit6 =
1;

   prev10bit6 = prev9bit6;
   prev9bit6 = prev8bit6;

   prev8bit0 = prev7bit0;
   prev8bit1 = prev7bit1;
   prev8bit2 = prev7bit2;
   prev8bit3 = prev7bit3;
   prev8bit4 = prev7bit4;
   prev8bit5 = prev7bit5;
   prev8bit6 = prev7bit6;

   prev7bit0 = prev6bit0;
   prev7bit1 = prev6bit1;
   prev7bit2 = prev6bit2;
   prev7bit3 = prev6bit3;
   prev7bit4 = prev6bit4;
   prev7bit5 = prev6bit5;
   prev7bit6 = prev6bit6;

   prev6bit0 = prev5bit0;
   prev6bit1 = prev5bit1;
   prev6bit2 = prev5bit2;
   prev6bit3 = prev5bit3;
   prev6bit4 = prev5bit4;
   prev6bit5 = prev5bit5;
   prev6bit6 = prev5bit6;

   prev5bit0 = prev4bit0;
   prev5bit1 = prev4bit1;
   prev5bit2 = prev4bit2;
   prev5bit3 = prev4bit3;
   prev5bit4 = prev4bit4;
   prev5bit5 = prev4bit5;
   prev5bit6 = prev4bit6;

   prev4bit0 = prev3bit0;
   prev4bit1 = prev3bit1;
   prev4bit2 = prev3bit2;
   prev4bit3 = prev3bit3;
   prev4bit4 = prev3bit4;
   prev4bit5 = prev3bit5;
   prev4bit6 = prev3bit6;

   prev3bit0 = prev2bit0;
   prev3bit1 = prev2bit1;
   prev3bit2 = prev2bit2;
   prev3bit3 = prev2bit3;
   prev3bit4 = prev2bit4;
   prev3bit5 = prev2bit5;
   prev3bit6 = prev2bit6;

   prev2bit0 = prevbit0;
   prev2bit1 = prevbit1;
   prev2bit2 = prevbit2;
   prev2bit3 = prevbit3;
   prev2bit4 = prevbit4;
   prev2bit5 = prevbit5;
   prev2bit6 = prevbit6;

   prevbit0 = readbit0;
   prevbit1 = readbit1;
   prevbit2 = readbit2;
   prevbit3 = readbit3;
   prevbit4 = readbit4;
   prevbit5 = readbit5;
   prevbit6 = readbit6;

   end_spi2_critical_section;

   playingnote = bit0 + bit1 + bit2 + bit3 + bit4 + bit5 + bit6;

   
//one note playing
   
if (playingnote == 1)
   {
     
if (bit0 == 1)
     {
// F note
       Fout =
698;
       Fout2 =
0;
     }
     
if (bit1 == 1)
     {
// G note
       Fout =
784;
       Fout2 =
0;
     }
     
if (bit2 == 1)
     {
// A note
       Fout =
880;
       Fout2 =
0;
     }
     
if (bit3 == 1)
     {
//B
       Fout =
988;
       Fout2 =
0;
     }
     
if (bit4 == 1)
     {
//C
       Fout =
1047;
       Fout2 =
0;
     }
     
if (bit5 == 1)
     {
//D
       Fout =
1175;
       Fout2 =
0;
     }
     
if (bit6 == 1)
     {
//E
       Fout =
1319;
       Fout2 =
0;
     }
   }
   
else if (playingnote == 2)
   {
     
if (bit0 == 1)
     {
       Fout =
698;
       
if (bit1 == 1)
         Fout2 =
784;
       
if (bit2 == 1)
         Fout2 =
880;
       
if (bit3 == 1)
         Fout2 =
988;
       
if (bit4 == 1)
         Fout2 =
1047;
       
if (bit5 == 1)
         Fout2 =
1175;
       
if (bit6 == 1)
         Fout2 =
1319;
     }
     
if (bit1 == 1)
     {
       Fout =
784;
       
if (bit0 == 1)
         Fout2 =
698;
       
if (bit2 == 1)
         Fout2 =
880;
       
if (bit3 == 1)
         Fout2 =
988;
       
if (bit4 == 1)
         Fout2 =
1047;
       
if (bit5 == 1)
         Fout2 =
1175;
       
if (bit6 == 1)
         Fout2 =
1319;
     }
     
if (bit2 == 1)
     {
       Fout =
880;
       
if (bit0 == 1)
         Fout2 =
698;
       
if (bit1 == 1)
         Fout2 =
784;
       
if (bit3 == 1)
         Fout2 =
988;
       
if (bit4 == 1)
         Fout2 =
1047;
       
if (bit5 == 1)
         Fout2 =
1175;
       
if (bit6 == 1)
         Fout2 =
1319;
     }
     
if (bit3 == 1)
     {
       Fout =
988;
       
if (bit0 == 1)
         Fout2 =
698;
       
if (bit1 == 1)
         Fout2 =
784;
       
if (bit2 == 1)
         Fout2 =
880;
       
if (bit4 == 1)
         Fout2 =
1047;
       
if (bit5 == 1)
         Fout2 =
1175;
       
if (bit6 == 1)
         Fout2 =
1319;
     }
     
if (bit4 == 1)
     {
       Fout =
1047;
       
if (bit0 == 1)
         Fout2 =
698;
       
if (bit1 == 1)
         Fout2 =
784;
       
if (bit2 == 1)
         Fout2 =
880;
       
if (bit3 == 1)
         Fout2 =
988;
       
if (bit5 == 1)
         Fout2 =
1175;
       
if (bit6 == 1)
         Fout2 =
1319;
     }
     
if (bit5 == 1)
     {
       Fout =
1175;
       
if (bit0 == 1)
         Fout2 =
698;
       
if (bit1 == 1)
         Fout2 =
784;
       
if (bit2 == 1)
         Fout2 =
880;
       
if (bit3 == 1)
         Fout2 =
988;
       
if (bit4 == 1)
         Fout2 =
1047;
       
if (bit6 == 1)
         Fout2 =
1319;
     }
     
if (bit6 == 1)
     {
       Fout =
1319;
       
if (bit0 == 1)
         Fout2 =
698;
       
if (bit1 == 1)
         Fout2 =
784;
       
if (bit2 == 1)
         Fout2 =
880;
       
if (bit3 == 1)
         Fout2 =
988;
       
if (bit4 == 1)
         Fout2 =
1047;
       
if (bit5 == 1)
         Fout2 =
1175;
     }
   }
   
else
   {
     Fout =
0;
     Fout2 =
0;
   }

   phase_incr_main = (
int)((Fout) * (float)two32 / Fs);
   phase_incr_main2 = (
int)((Fout2) * (float)two32 / Fs);

 }
// END WHILE(1)

 PT_END(pt);
}
// =============== END PHOTORESISTOR THREAD ============================

volatile int i = 0;

//============================================ Stepper Motor thread =================================================

static PT_THREAD(protothread_serial(struct pt *pt))
{
 PT_BEGIN(pt);

 
while (1)
 {

   
// half step 7 different times
   
// each bit set and reset is one half step
   
while (i < 2)
   {
     
// rotate CW one half step
     mPORTBSetBits(BIT_3);
     PT_YIELD_TIME_msec(
10);
     mPORTBClearBits(BIT_3);
     PT_YIELD_TIME_msec(
20);
     
// rotate CW one half step
     mPORTBSetBits(BIT_7);
     PT_YIELD_TIME_msec(
10);
     mPORTBClearBits(BIT_7);
     PT_YIELD_TIME_msec(
20);
     
// rotate CW one half step
     mPORTBSetBits(BIT_8);
     PT_YIELD_TIME_msec(
10);
     mPORTBClearBits(BIT_8);
     PT_YIELD_TIME_msec(
20);
     
// rotate CW one half step
     mPORTBSetBits(BIT_13);
     PT_YIELD_TIME_msec(
10);
     mPORTBClearBits(BIT_13);
     PT_YIELD_TIME_msec(
20);
     i++;
   }

   
// rotate CCW
   
while (i < 4)
   {
     
// rotate CCW one half step
     mPORTBSetBits(BIT_13);
     PT_YIELD_TIME_msec(
10);
     mPORTBClearBits(BIT_13);
     PT_YIELD_TIME_msec(
20);
     
// rotate CCW one half step
     mPORTBSetBits(BIT_8);
     PT_YIELD_TIME_msec(
10);
     mPORTBClearBits(BIT_8);
     PT_YIELD_TIME_msec(
20);
     
// rotate CCW one half step
     mPORTBSetBits(BIT_7);
     PT_YIELD_TIME_msec(
10);
     mPORTBClearBits(BIT_7);
     PT_YIELD_TIME_msec(
20);
     
// rotate CCW one half step
     mPORTBSetBits(BIT_3);
     PT_YIELD_TIME_msec(
10);
     mPORTBClearBits(BIT_3);
     PT_YIELD_TIME_msec(
20);
     i++;
   }

   i =
0;
   
// never exit while
 }
// END WHILE(1)
 PT_END(pt);
}
//============================================ END Stepper Motor thread =================================================

// === Main  ======================================================
void main(void)
{
 
//SYSTEMConfigPerformance(PBCLK);

 ANSELA =
0;
 ANSELB =
0;

 
// set up DAC on big board
 
// timer interrupt //////////////////////////
 
// Set up timer2 on,  interrupts, internal clock, prescalar 1, toggle rate
 
// at 30 MHz PB clock 60 counts is two microsec
 
// 400 is 100 ksamples/sec
 
// 2000 is 20 ksamp/sec
 OpenTimer2(T2_ON | T2_SOURCE_INT | T2_PS_1_1,
1600);

 
// set up the timer interrupt with a priority of 2
 ConfigIntTimer2(T2_INT_ON | T2_INT_PRIOR_2);
 mT2ClearIntFlag();
// and clear the interrupt flag

 
// control CS for DAC
 mPORTBSetPinsDigitalOut(BIT_4);
 mPORTBSetBits(BIT_4);
 
// SCK2 is pin 26
 
// SDO2 (MOSI) is in PPS output group 2, could be connected to RB5 which is pin 14
 PPSOutput(
2, RPB5, SDO2);
 
// 16 bit transfer CKP=1 CKE=1
 
// possibles SPI_OPEN_CKP_HIGH;   SPI_OPEN_SMP_END;  SPI_OPEN_CKE_REV
 
// For any given peripherial, you will need to match these
 
// NOTE!! IF you are using the port expander THEN
 
// -- clk divider must be set to 4 for 10 MHz
 SpiChnOpen(SPI_CHANNEL2, SPI_OPEN_ON | SPI_OPEN_MODE16 | SPI_OPEN_MSTEN | SPI_OPEN_CKE_REV,
4);
 
// end DAC setup

 
// === build the sine lookup table =======
 
// scaled to produce values between 0 and 4096
 
int ii;
 
for (ii = 0; ii < sine_table_size; ii++)
 {
   sin_table[ii] = (
int)(2047 * sin((float)ii * 6.283 / (float)sine_table_size));
 }

 
// FINAL PROJECT THINGS
 mPORTBSetPinsDigitalOut(BIT_3);
 mPORTBSetPinsDigitalOut(BIT_7);
 mPORTBSetPinsDigitalOut(BIT_8);
 mPORTBSetPinsDigitalOut(BIT_13);

 mPORTBClearBits(BIT_3);
 mPORTBClearBits(BIT_7);
 mPORTBClearBits(BIT_8);
 mPORTBClearBits(BIT_13);

 
// END FINAL PROJECT THINGS

 
// === config threads ==========
 
// turns OFF UART support and debugger pin, unless defines are set
 PT_setup();

 
// === setup system wide interrupts  ========
 INTEnableSystemMultiVectoredInt();

 
// init the threads
 PT_INIT(&pt_timer);
 PT_INIT(&pt_serial);
 PT_INIT(&pt_key);

 
// init the display
 
// NOTE that this init assumes SPI channel 1 connections
 tft_init_hw();
 tft_begin();
 tft_fillScreen(ILI9340_BLACK);
 
//240x320 vertical display
 tft_setRotation(
0); // Use tft_setRotation(1) for 320x240

 
// seed random color
 srand(
1);

 
// round-robin scheduler for threads
 
while (1)
 {
   PT_SCHEDULE(protothread_timer(&pt_timer));
   PT_SCHEDULE(protothread_serial(&pt_serial));
   PT_SCHEDULE(protothread_key(&pt_key));
 }
}
// main

// === end  ======================================================

Appendix C: Schematics

Figure C.1: Audio Socket and Surrounding Circuitry. DACA and GND are from PIC32

Figure C.2: Photoresistors and Surrounding Circuitry. 3.3V and GND are from PIC32

Figure C.3: Stepper Motor and Surrounding Circuitry. 5V power supply from lab is replaced with a battery.




Figure C.4: PIC32 Large Dev Board Circuitry

Source: http://people.ece.cornell.edu/land/courses/ece4760/PIC32/Target_board/Large_board_sch_image.PNG

Appendix D: Bill of Materials

Part

Quantity

Cost and Vendor

Vendor

Stepper Motor

1

$2.25

All Electronics

ULN2003

1

$0.75

All Electronics

Light Dependent Resistors

7

$6.65

All Electronics

Green/Blue Laser Safety Goggles

1

$10.99

Amazon

Small Mirror

1

$0.25

Amazon

Green Laser Pointer

1

$18.17

Amazon

Foam Core Board

1

$0.00

Phillips Lab

1-inch thick Styrofoam Board

2

$0.00

Phillips Hall Recycling Bin

4.7KΩ Resistors

7

$0.00

Phillips Lab

Jumper Cables

18

$1.80

Phillips Lab

Breadboard

2

$12.00

Phillips Lab

Power Supply

1

$5.00

Phillips Lab

Lab Speakers

1

$2.00

Phillips Lab

PIC32 Large Dev. Board

1

$10.00

Phillips Lab

MicroStickII

1

$1.00

Phillips Lab

PIC32MX250F128B

1

$5.00

Phillips Lab

Total:

$75.86

Appendix E: Work Distribution

Caroline

Ashley

Mira

  • Assembly and alignment of stepper motor and Photoresistors
  • Figure out how to operate the stepper motor and writing code for control and setup
  • Assembly of piano
  • Writing Introduction, Rationale, Schematics, Background Math sections of lab report
  • Assembly and alignment of stepper motor and Photoresistors
  • Ordering parts
  • Assembly of the piano frame
  • Debugging code for port expander
  • Writing code for stepper motor control and setup
  • Writing Stepper Motor Software, Conclusion, Appendix sections of lab report
  • Testing the Photoresistors for response time
  • Writing code for DAC sound generation, Photoresistors input reading with filter, and port expander
  • Writing Software section except stepper motor, Hardware/ Software Tradeoff, Hardware Photoresistor, Results, schematics, Appendix sections of lab report

Appendix F: References