Project tutorial
Controlling Servos with a Stylophone: Pitch Detection

Controlling Servos with a Stylophone: Pitch Detection © GPL3+

The stylophone was one of the wackiest forgotten toys of the 1960s and '70s, and today, "we have the technology" to make it better.

  • 422 views
  • 3 comments
  • 6 respects

Components and supplies

Necessary tools and machines

Hy gluegun
Hot glue gun (generic)
09507 01
Soldering iron (generic)

Apps and online services

About this project

The Inspiration

From the beginning, I knew I needed to incorporate a retro toy into my project. I had considered the Tickle Me Elmo, as those re-entered production a few years ago, but didn't think I could do the project justice under my time constraints. I first discovered the stylophone after watching a 7 second video of it on YouTube, and saw that it could be had for less than $25 USD on Amazon! It went downhill from there.

First Steps

Upon receiving my very own stylophone, I took the included male to male 3.5mm audio cable (and three not-so-included AA batteries) and scoped the output signal with one hand holding the wires together and the other holding the stylus to the keypad.

Since the stylophone outputs a mono signal, I used the tip (T) as the input and the sleeve (S) as the ground (R stands for ring, by the way).

The basic stylophone has a switch for 3 sound profiles, a vibrato switch, a power switch, a volume dial, and a tuning knob. I'd recommend using profile 1 at max volume and no vibrato, and all results below use these settings. When in tune, the stylophone's lowest note is A (110Hz), and the highest note is E (329.63 Hz). The output of the stylophone has a minimum voltage from anywhere from -1.7 V to -100 mV as the stylophone turns on ("warms up" so to speak) and a peak voltage of anywhere from 1.7 V to 3.5 V. Since the TI MSP430G2553 cannot read negative voltages and the Uno can only read negative voltages if you shift the reference voltage, I decided to build a gain and shift circuit using a TI LM741 op amp.

The Circuit

A simple circuit can be built to shift [-2, 2] V to [0, 4[ V and apply a gain to get the output [0, 3] V.

The non-inverting input can be controlled with the 3.3 V supply from the MSP430G2553 and a 10k potentiometer. R1 and Rf in the diagram were chosen to be 9.1 kOhm and 12 kOhm to achieve the 9.1k/12k ≅ 3/4 gain. Somewhat large resistors were also chosen to limit the amount of current flowing. With this in mind, I designed and simulated the circuit in LTSpice (which you can download for free here).

An accurate simulation like this is possible since TI thankfully provides a SPICE model for many of their parts. (You can download the LM741.mod here). The simulations give the expected shift from [-2, 2] V to [0, 3.3] V, so time to build the circuit!

Building the Circuit

The only parts used in the circuit were a 9.1k and 12k Ohm resistors, a 0.1μ Farad capacitor, a 10k potentiometer, the TI LM741, a 3.5mm audio jack, and some wires.

After soldering up the circuit, it was powered using a +12/-12 V DC power supply and the 3.3 V off the MSP430G2553. Giving an input sine wave of 4 V peak-to-peak from a function generator yielded exactly what I wanted to see.

Now giving the input from the stylophone and adjusting the potentiometer shift...

You'll notice that the high note (E 329.63 Hz) is much more compact than the low note (A 110 Hz) -- by definition. This creates problems with sampling that will be explained later.

The MSP430G2553 (Master)

The role of the MSP4302553 is to sample the audio signal, send it to the arduino, and based on the received information, move the servo. The ADC10 and Timer A0 is set up to sample at 8kHz.

// Init ADC10
// Control Register 0
ADC10CTL0 = SREF_0; // Vr+ = Vcc and Vr- = Vss (default)
ADC10CTL0 += ADC10SHT_1; // 8 X ADC10CLKs
// ADC10SR: 2000 ksps (default)
// REFOUT: reference output off (default)
// REFBURST: continuous (default)
// MSC: single conversion mode (default)
// REFON: reference generator off (default)
ADC10CTL0 += ADC10ON; // ADC10 On
ADC10CTL0 += ADC10IE; // interrupt enabled
// Control Register 1
ADC10CTL1 = INCH_3; //input channel A3 (default)
ADC10CTL1 += SHS_0; // sample and hold source ADC10SC (default)
// ADC10DF: straight binary data format (default)
ADC10CTL1 += ADC10DIV_0; // Clock divider, try values and check noise
ADC10CTL1 += ADC10SSEL_0; // clock source ADC10SC
ADC10CTL1 += CONSEQ_0; // Single channel single conversion
// analog enable control register 0
ADC10AE0 = 8; // Enable A3 as ADC channel

Pin 1.4 is made (arbitrarily) the slave select and SPI is initialized

// Initialize Port 1, P1.0 LED, P1.4 SS
P1SEL &= ~(BIT0 + BIT4); // P1.0 and P1.4 GPIO
P1SEL &= ~(BIT0 + BIT4); // P1.0 and P1.4 GPIO
P1REN &= ~(BIT0 + BIT4); // P1.0 and P1.4 Resistor disabled
P1DIR |= BIT0 + BIT4; // Set P1.0 and P1.4 to output direction
P1OUT &= BIT4; // Initially set P1.4/SS high
// Port 1 SPI pins, P1.5 SCLK, P1.6 MISO, P1.7 MOSI
P1SEL |= BIT5 + BIT7 + BIT6; // Secondary Peripheral Module Function for P1.5-1.7
P1SEL2 |= BIT5 + BIT7 + BIT6; // Secondary Peripheral Module Function for P1.5-1.7
// Polarity and SCLK for SPI
UCB0CTL0 = UCCKPH + UCCKPL; // first edge: data capture, following edge: data update , SCLK inactive state High
// Initialize SPI
UCB0CTL0 |= UCMSB + UCMST + UCSYNC + UCMODE_0; // MSB first, master, 3-pin, 8-bit synchronous
UCB0CTL1 = UCSSEL_2 + UCSWRST; // SMCLK, enable SW Reset
// bit rate: SMCLK/x=SCLK
// smclk same as arduino uno (16 Mhz)
UCB0BR0 = 32; // low byte, divide by
// 4 divider or greater, 8 and 16 causes issues, 32 seems okay
UCB0BR1 = 0; // same as 1 divider, high byte
UCB0CTL1 &= ~UCSWRST; // **Initialize USCI state machine**
IFG2 &= ~UCB0RXIE; // Clear RX interrupt flag in case it was set during init
IE2 |= UCB0RXIE; // Enable USCI0 RX interrupt

Port 2 is setup to use TA1 and TA2 pins to create a PWM signal to control the servos.

// Init Port 2
// setup P2.1 with Timer1_A3.TA1 and P2.4 with Timer1_A3.TA2
P2DIR |= 0x12;
P2SEL |= 0x12;
P2SEL2 = 0;

Stepping back, lets talk about SPI.

SPI

There's a lot to it, but it boils down to a communication interface specification created by Motorola in the 1980s that works as a two way street. There are four major lines that the master (MSP430G2553) and the slave (Uno) need to share: SCLK, MISO, MOSI, and SS. The SCLK (Serial Clock) is generated by the master so that communication stays in sync. MISO (Master In Slave Out) and MOSI (Master Out Slave In are the two sides of the street that tell which direction traffic is travelling (sometimes called SOMI and SIMO). The SS (Slave Select) tells when the slave should listen to the master, and when to ignore it. This allows one master to have multiple slaves without causing miscommunication. There are different ways to configure SPI, but the settings I've chosen (as specified in the code above) look like this.

This diagram can be found on page 448 of the MSP430x2xx Family User's Guide.

Arduino Uno (Slave)

Arduino proves a library to make SPI communication easy but is only for making Arduino the master (go figure).

pinMode(MISO, OUTPUT);
// turn on SPI with interrupts, slave mode, msbit first
// clock idle when high, sample on falling edge of clock
SPCR = _BV(SPE) + _BV(SPIE) + _BV(CPOL) +_BV(CPHA);
/*
* SPIE - Enables the SPI interrupt when 1
* SPE - Enables the SPI when 1
* DORD - Sends data least Significant Bit First when 1, most Significant Bit first when 0
* MSTR - Sets the Arduino in master mode when 1, slave mode when 0
* CPOL - Sets the data clock to be idle when high if set to 1, idle when low if set to 0
* CPHA - Samples data on the falling edge of the data clock when 1, rising edge when 0
* SPR1 and SPR0 - Sets the SPI speed, 00 is fastest (4MHz) 11 is slowest (250KHz)
*
*/

The setup only requires two lines of code, but most of the difficulty in the communication lies in getting the Uno to cooperate as the slave. The Uno and the MSP430G2553 both operate at 16Mhz, but the MSP430G2553 can communicate through SPI faster by setting the baud rate control register with a smaller divider (UCB0BRx). The Uno requires at least a 4 divider (4 Mhz) but I found occasional mis-transmissions (0x1234 becomes 0x3400) until a 32 divider (500 kHz). One of the reasons I chose to make the MSP430G2553 the master is due to the ADC taking a 10 bit sample, whereas the Uno can only take 8 bit samples. More is better here as it gives more resolution, meaning that the MSP430G2553 may be able to see a difference in 1 mV whereas the Uno may not. While SPI between these two devices sends 8 bits at a time, one can simply send 2 sets of 8 bits to get the MSP430G2553's 10 bit value across. The only issue is making sure the slower Arduino doesn't fall behind.

The Rest of the Owl

The original intent was to use FFT or pitch detection library on the Uno, but that only made things slower. Ultimately, since I knew the shape (one rising edge and one falling edge per cycle) of the signal coming from the stylophone, I took a simpler and faster approach. I simply waited for the average of the last 4 samples to be greater than a threshold when the previous average was less than the threshold. This told me I had triggered on the rising edge. Then I counted how long until this happened again and stored it. Then I'd simply take the median of the last 10 counts/periods to filter out any outliers. This happens instantaneously and allows for the servo to react in real time.

The servo is simply moved by changing the duty cycle of the PWM signal on the MSP430G2553. For example:

TA1CCR2 = 2100; TA1CCR1 = 2100;

Final Thoughts

I may try to refine the code so that the servo is more stable at high frequencies at a later time. Although the Nyquist sampling criterion tells us that the sampling frequency only needs to be double the highest desired recoverable frequency, this faster method doesn't use the FFT or any type of interpolation, so the sampling frequency simply isn't fast enough.

The code for the TI MSP430G2553 and the Arduino is attached if you'd like to take a look at it. A video of the final product is also there.

Code

MSP430G2553 Master CodeC/C++
/******************************************************************************
MSP430G2553 Project Creator

SE 423  - Dan Block
        Spring(2019)

        Written(by) : Steve(Keres)
College of Engineering Control Systems Lab
University of Illinois at Urbana-Champaign
*******************************************************************************/

#include "msp430g2553.h"
#include "UART.h"

void print_every(int rate);

char newprint = 0;
int timecheck = 0;
long millivolts = 0;
int statevar = 0;
unsigned int adc_raw = 0;
char receive_lsb = 0;
unsigned char MISO_val = 0;

void main(void) {

    WDTCTL = WDTPW + WDTHOLD;                 // Stop WDT

    if (CALBC1_16MHZ ==0xFF || CALDCO_16MHZ == 0xFF) while(1);

    DCOCTL = CALDCO_16MHZ;    // Set uC to run at approximately 16 Mhz
    BCSCTL1 = CALBC1_16MHZ;

    // Init ADC10
        // Control Register 0
    ADC10CTL0 = SREF_0; // Vr+ = Vcc and Vr- = Vss (default)
    ADC10CTL0 += ADC10SHT_1; // 8 X ADC10CLKs
    // ADC10SR: 2000 ksps (default)
    // REFOUT: reference output off (default)
    // REFBURST: continuous (default)
    // MSC: single conversion mode (default)
    // REFON: reference generator off (default)
    ADC10CTL0 += ADC10ON; // ADC10 On
    ADC10CTL0 += ADC10IE; // interrupt enabled
        // Control Register 1
    ADC10CTL1 = INCH_3; //input channel A3 (default)
    ADC10CTL1 += SHS_0; // sample and hold source ADC10SC (default)
    // ADC10DF: straight binary data format (default)
    ADC10CTL1 += ADC10DIV_0; // Clock divider, try values and check noise
    ADC10CTL1 += ADC10SSEL_0; // clock source ADC10SC
    ADC10CTL1 += CONSEQ_0; // Single channel single conversion
        // analog enable control register 0
    ADC10AE0 = 8; // Enable A3 as ADC channel

    // Init Port 2
    // setup P2.1 with Timer1_A3.TA1 and P2.4 with Timer1_A3.TA2
    P2DIR |= 0x12;
    P2SEL |= 0x12;
    P2SEL2 = 0;

    // Initialize Port 1, P1.0 LED, P1.4 SS
    P1SEL &= ~(BIT0 + BIT4);                  // P1.0 and P1.4 GPIO
    P1SEL &= ~(BIT0 + BIT4);                  // P1.0 and P1.4 GPIO
    P1REN &= ~(BIT0 + BIT4);                  // P1.0 and P1.4 Resistor disabled
    P1DIR |= BIT0 + BIT4;                     // Set P1.0 and P1.4 to output direction
    P1OUT &= BIT4;  // Initially set P1.4/SS high

    // Port 1 SPI pins, P1.5 SCLK, P1.6 MISO, P1.7 MOSI
    P1SEL |= BIT5 + BIT7 + BIT6;              // Secondary Peripheral Module Function for P1.5-1.7
    P1SEL2 |= BIT5 + BIT7 + BIT6;             // Secondary Peripheral Module Function for P1.5-1.7

    // Polarity and SCLK for SPI
    UCB0CTL0 = UCCKPH + UCCKPL;    // first edge: data capture, following edge: data update , SCLK inactive state High

    // Initialize SPI
    UCB0CTL0 |= UCMSB + UCMST + UCSYNC + UCMODE_0;  // MSB first, master, 3-pin, 8-bit synchronous
    UCB0CTL1 = UCSSEL_2 + UCSWRST;               // SMCLK, enable SW Reset
    // bit rate: SMCLK/x=SCLK
    // smclk same as arduino uno (16 Mhz)
    UCB0BR0 = 32;                                 // low byte, divide by
    // 4 divider or greater, 8 and 16 causes issues, 32 seems okay
    UCB0BR1 = 0;                                 // same as 1 divider, high byte
    UCB0CTL1 &= ~UCSWRST;                        // **Initialize USCI state machine**
    IFG2 &= ~UCB0RXIE;                        // Clear RX interrupt flag in case it was set during init
    IE2 |= UCB0RXIE;                          // Enable USCI0 RX interrupt


    // Timer A Config
    // use Timer0_A3 to init 1ms timed interrupt
    TACCTL0 = CCIE;             // Enable Periodic interrupt
    TACCR0 = 2000;                // period = 1ms
    TACTL = TASSEL_2 + MC_1; // source SMCLK, up mode

    // change TA1CCR0 counting rate since it is a 16 bit register
    TA1CCR0 = 40000;                             // PWM Freq = 50Hz, 1/(50 Hz/16MHz)/8 = 655
    TA1CCTL0 = 0;
    TA1CCTL1 = OUTMOD_7;                         // TA1CCR1 reset/set
    TA1CCR1 = 2000;                               // TA1CCR1 PWM duty cycle, TA1CCR0/TA1CCR1
    TA1CCTL2 = OUTMOD_7;
    TA1CCR2 = 2000;                           // 0 deg: (0.6ms/20ms)*40000
    TA1CTL = TASSEL_2 + MC_1 + ID_3;                  // source SMCLK, up mode, divide 8



    Init_UART(115200,1);    // Initialize UART for 115200 baud serial communication

    _BIS_SR(GIE);       // Enable global interrupt


    while(1) {  // Low priority Slow computation items go inside this while loop.  Very few (if anyt) items in the HWs will go inside this while loop

// for use if you want to use a method of receiving a string of chars over the UART see USCI0RX_ISR below
//      if(newmsg) {
//          newmsg = 0;
//      }

        // The newprint variable is set to 1 inside the function "print_every(rate)" at the given rate
        if ( (newprint == 1) && (senddone == 1) )  { // senddone is set to 1 after UART transmission is complete

            // only one UART_printf can be called every 15ms
            UART_printf("mV:%ld MISO:%d\n\r",millivolts,MISO_val);

            newprint = 0;
        }

    }
}


// Timer A0 interrupt service routine
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A (void)
{
    timecheck++; // Keep track of time for main while loop.
    print_every(5000);  // units determined by the rate Timer_A ISR is called, print every "rate" calls to this function
    // make sure to not print too long of a string, stops working
    ADC10CTL0 |= ENC + ADC10SC; // Sampling and conversion start
    switch(MISO_val){
    case 10: //A
        TA1CCR2 = 2000;     // 0 deg: (0.6ms/20ms)*40k
        TA1CCR1 = 2000;
        break;
    case 15:
        TA1CCR2 = 2100;
        TA1CCR1 = 2100;
        break;
    case 20: //B
        TA1CCR2 = 2200;
        TA1CCR1 = 2200;
        break;
    case 30: //C
        TA1CCR2 = 2400;
        TA1CCR1 = 2400;
        break;
    case 35:
        TA1CCR2 = 2500;
        TA1CCR1 = 2500;
        break;
    case 40: //D
        TA1CCR2 = 2600;
        TA1CCR1 = 2600;
        break;
    case 45:
        TA1CCR2 = 2700;
        TA1CCR1 = 2700;
        break;
    case 50: //E
        TA1CCR2 = 2800;
        TA1CCR1 = 2800;
        break;
    case 60: //F
        TA1CCR2 = 3000;
        TA1CCR1 = 3000;
        break;
    case 65:
        TA1CCR2 = 3100;
        TA1CCR1 = 3100;
        break;
    case 70: //G
        TA1CCR2 = 3200;
        TA1CCR1 = 3200;
        break;
    case 75:
        TA1CCR2 = 3300;
        TA1CCR1 = 3300;
        break;
    case 80: //A
        TA1CCR2 = 3400;
        TA1CCR1 = 3400;
        break;
    case 85:
        TA1CCR2 = 3500;
        TA1CCR1 = 3500;
        break;
    case 90: //B
        TA1CCR2 = 3600;
        TA1CCR1 = 3600;
        break;
    case 100: //C
        TA1CCR2 = 3800;
        TA1CCR1 = 3800;
        break;
    case 105:
        TA1CCR2 = 3900;
        TA1CCR1 = 3900;
        break;
    case 110: //D
        TA1CCR2 = 4000;     // 180 deg: (3ms/20ms)*40k
        TA1CCR1 = 4000;
        break;
    case 115:
        TA1CCR2 = 4100;
        TA1CCR1 = 4100;
        break;
    case 120: //E
        TA1CCR2 = 4200;
        TA1CCR1 = 4200;
        break;
    default:
        break;
    }
}



// ADC 10 ISR - Called when a sequence of conversions (A7-A0) have completed
#pragma vector=ADC10_VECTOR
__interrupt void ADC10_ISR(void) {
    millivolts = (ADC10MEM*3300L)/1023L;


    // ADC A3 to DAC
    adc_raw = ADC10MEM; // echo sampled voltages

    P1OUT &= ~0x10; // pull SS (P1.4) low
    receive_lsb = 0;
    UCB0TXBUF = (adc_raw >> 8); // send MSByte

}



// USCI Transmit ISR - Called when TXBUF is empty (ready to accept another character)
#pragma vector=USCIAB0TX_VECTOR
__interrupt void USCI0TX_ISR(void) {

    if(IFG2&UCA0TXIFG) {        // USCI_A0 requested TX interrupt
        if(printf_flag) {
            if (currentindex == txcount) {
                senddone = 1;
                printf_flag = 0;
                IFG2 &= ~UCA0TXIFG;
            } else {
                UCA0TXBUF = printbuff[currentindex];
                currentindex++;
            }
        } else if(UART_flag) {
            if(!donesending) {
                UCA0TXBUF = txbuff[txindex];
                if(txbuff[txindex] == 255) {
                    donesending = 1;
                    txindex = 0;
                }
                else txindex++;
            }
        } else {  // interrupt after sendchar call so just set senddone flag since only one char is sent
            senddone = 1;
        }

        IFG2 &= ~UCA0TXIFG;
    }

    if(IFG2&UCB0TXIFG) {    // USCI_B0 requested TX interrupt (UCB0TXBUF is empty)

        IFG2 &= ~UCB0TXIFG;   // clear IFG
    }
}


// USCI Receive ISR - Called when shift register has been transferred to RXBUF
// Indicates completion of TX/RX operation
#pragma vector=USCIAB0RX_VECTOR
__interrupt void USCI0RX_ISR(void) {



    if(IFG2&UCB0RXIFG) {  // USCI_B0 requested RX interrupt (UCB0RXBUF is full)
        if (receive_lsb == 0){
            // received slave msbyte
            P1OUT |= 0x10; // pull SS (P1.4) high
            receive_lsb = 1; // next byte is lsbyte
            MISO_val = (UCB0RXBUF & 0xFF);
            P1OUT &= ~0x10; // pull SS (P1.4) low
            UCB0TXBUF = adc_raw & 0xFF; // send LSByte
        }
        else {
            // received slave lsbyte
            P1OUT |= 0x10; // pull SS (P1.4) high
            receive_lsb = 0; // next byte is msbyte
            MISO_val = (UCB0RXBUF & 0xFF);
        }
        IFG2 &= ~UCB0RXIFG;   // clear IFG
    }

    if(IFG2&UCA0RXIFG) {  // USCI_A0 requested RX interrupt (UCA0RXBUF is full)

//    Uncomment this block of code if you would like to use this COM protocol that uses 253 as STARTCHAR and 255 as STOPCHAR
/*      if(!started) {  // Haven't started a message yet
            if(UCA0RXBUF == 253) {
                started = 1;
                newmsg = 0;
            }
        }
        else {  // In process of receiving a message
            if((UCA0RXBUF != 255) && (msgindex < (MAX_NUM_FLOATS*5))) {
                rxbuff[msgindex] = UCA0RXBUF;

                msgindex++;
            } else {    // Stop char received or too much data received
                if(UCA0RXBUF == 255) {  // Message completed
                    newmsg = 1;
                    rxbuff[msgindex] = 255; // "Null"-terminate the array
                }
                started = 0;
                msgindex = 0;
            }
        }
*/

        IFG2 &= ~UCA0RXIFG;
    }

}

// This function takes care of all the timing for printing to UART
// Rate determined by how often the function is called in Timer ISR
int print_timecheck = 0;
void print_every(int rate) {
    if (rate < 15) {
        rate = 15;
    }
    if (rate > 10000) {
        rate = 10000;
    }
    print_timecheck++;
    if (print_timecheck == rate) {
        print_timecheck = 0;
        newprint = 1;
    }

}
Arduino Uno Slave CodeArduino
/*
 * SPI Slave, Arduino Uno
 * 10 (SS), 11 (MOSI), 12 (MISO), 13 (SCK)
*/
#include "pins_arduino.h"

/* 
 *  SPI global vars
 *  modified code from 
 *  http://www.gammon.com.au/forum/?id=10892&reply=1#reply1
 */
volatile byte pos;
volatile boolean process_it;
long loop_count = 0;
byte MOSI_val = 0;
unsigned int adc_raw = 0;
char receive_msb = 1; 
unsigned long millivolts = 0;
unsigned char note = 1; //max val of 127, wraps back around

// variables for frequency detection
float frequency = 0.0;
int clocks = 0;
int clocks_arr[] = {0,0,0,0,0,0,0,0,0,0};
int clocks_arr_sorted[] = {0,0,0,0,0,0,0,0,0,0};
int clocks_arr_size = 10;
unsigned int samps[] = {0,0,0,0};
int samps_arr_size = 4;
unsigned long adc_avg = 0;
unsigned long adc_avg_prev = 0;


void setup (void) {
 Serial.begin (9600);   // debugging

 // have to send on master in, *slave out*
 pinMode(MISO, OUTPUT);
 
 // turn on SPI with interrupts, slave mode, msbit first
 // clock idle when high, sample on falling edge of clock (see MSP430x2xx user guide pg448, 451)
 SPCR = _BV(SPE) + _BV(SPIE) + _BV(CPOL) +_BV(CPHA);
 
 /* 
  *  SPIE - Enables the SPI interrupt when 1
  *  SPE - Enables the SPI when 1
  *  DORD - Sends data least Significant Bit First when 1, most Significant Bit first when 0
  *  MSTR - Sets the Arduino in master mode when 1, slave mode when 0
  *  CPOL - Sets the data clock to be idle when high if set to 1, idle when low if set to 0
  *  CPHA - Samples data on the falling edge of the data clock when 1, rising edge when 0
  *  SPR1 and SPR0 - Sets the SPI speed, 00 is fastest (4MHz) 11 is slowest (250KHz)
  *  
 */
 
 pos = 0;
 process_it = false;
}  // end of setup

void quickSort(int arr[], int left, int right) {
     int i = left, j = right;
     int tmp;
     int pivot = arr[(left + right) / 2];

     /* partition */
     while (i <= j) {
           while (arr[i] < pivot)
                 i++;
           while (arr[j] > pivot)
                 j--;
           if (i <= j) {
                 tmp = arr[i];
                 arr[i] = arr[j];
                 arr[j] = tmp;
                 i++;
                 j--;
           }
     };

     /* recursion */
     if (left < j)
           quickSort(arr, left, j);
     if (i < right)
           quickSort(arr, i, right);
}

void newSample(){
  // stores last few adc raw values
  int i = 0;
  for (i=0; i<samps_arr_size; i++){
    if (i == samps_arr_size-1) samps[i] = adc_raw;
    else samps[i] = samps[i+1];
  }
  
  //calculates average
  adc_avg_prev = adc_avg;
  adc_avg = 0;
  for (i=0; i<samps_arr_size; i++){
    adc_avg += (long)samps[i];
  }
  adc_avg = adc_avg/(long)samps_arr_size;

  /* 
   *  since input is like sine wave, one peak per period
   *  trigger on rise at 700 (~2V), near the top of the wave
   */
  if (adc_avg > 700 && adc_avg < 720 && adc_avg_prev< 700){ // if triggered
    if (clocks > 10 && clocks < 80){
      // keep last few clock values
      for (i=0; i<clocks_arr_size; i++){
        if (i == clocks_arr_size-1) clocks_arr[i] = clocks;
        else clocks_arr[i] = clocks_arr[i+1];
      }
      for (i=0; i<clocks_arr_size; i++){
        clocks_arr_sorted[i] = clocks_arr[i];
      }
      // sort values
      quickSort(clocks_arr_sorted,0,clocks_arr_size-1);
      // get median value
      clocks = clocks_arr_sorted[(clocks_arr_size+1)/2];
      // find frequency using curve of best fit
      if (clocks >= 38){
        frequency = (float)((long)clocks*(long)clocks*(long)clocks)*-0.0023 + (float)((long)clocks*(long)clocks)*0.4059 - 26.337*(float)clocks + 735.56;
      }
      else if (clocks >= 36) frequency = 207.65; 
      else if (clocks >= 34) frequency = 220.0;
      else if (clocks >= 32) frequency = 233.08; 
      else if (clocks >= 30) frequency = 246.94;
      else if (clocks >= 28) frequency = 261.63;
      else if (clocks >= 26) frequency = 277.18;
      else if (clocks >= 25) frequency = 293.66;
      else if (clocks >= 23) frequency = 311.13;
      else if (clocks >= 22) frequency = 329.63;
      else frequency = 330.;
        
    }
    clocks = 0;
  }
  else clocks++;

}

// SPI interrupt routine
ISR (SPI_STC_vect) {
  MOSI_val = SPDR;
  SPDR = note*2; // send back char to MSP430
  // transferred value is haved for some reason
  if (receive_msb == 1) {
    // received msbyte
    adc_raw = (MOSI_val << 8);
    receive_msb = 0;
  }
  else {
    // received lsbyte
    adc_raw |= (MOSI_val & 0xFF);
    receive_msb = 1;
    process_it = true;
  }

}

void noteDetect(){
  if(frequency > 0){
    if (frequency < 113) note = 10;         //A 110 + 3.0 Hz
    else if (frequency < 119.54) note = 15; //  116.54 + 3.0 Hz
    else if (frequency < 126.47) note = 20; //B 123.47 + 3.0 Hz
    else if (frequency < 134.81) note = 30; //C 130.81 + 4.0 Hz
    else if (frequency < 142.59) note = 35; //  138.59 + 4.0 Hz
    else if (frequency < 150.83) note = 40; //D 146.83 + 4.0 Hz
    else if (frequency < 159.56) note = 45; //  155.56 + 4.0 Hz
    else if (frequency < 169.81) note = 50; //E 164.81 + 5.0 Hz
    else if (frequency < 179.61) note = 60; //F 174.61 + 5.0 Hz
    else if (frequency < 190) note = 65;    //  185 + 5.0 Hz
    else if (frequency < 201) note = 70;    //G 196 + 5.0 Hz
    else if (frequency < 212.65) note = 75; //  207.65 + 5.0 Hz
    else if (frequency < 226) note = 80;    //A 220 + 6.0 Hz
    else if (frequency < 239.08) note = 85; //  233.08 + 6.0 Hz
    else if (frequency < 253.94) note = 90; //B 246.94 + 7.0 Hz
    else if (frequency < 268.63) note = 100; //C 261.63 + 7.0 Hz
    else if (frequency < 285.18) note = 105; // 277.18 + 8.0 Hz
    else if (frequency < 302.66) note = 110; //D 293.66 + 9.0 Hz
    else if (frequency < 320.13) note = 115; // 311.13 + 9.0 Hz
    else if (frequency < 338.63) note = 120; //E 329.63 + 9.0 Hz
    else {
      // debugging
      if (note == 10) note = 120;
      //else note = 10;
    }
  }
}

// main loop - wait for flag set in interrupt routine
void loop (void) {
  loop_count++;
  
  
  if (process_it) {  
    millivolts = (adc_raw*3300L)/1023L; 
    //Serial.print("mV: ");
    //Serial.println(millivolts);
    //Serial.println(adc_raw, HEX);
    pos = 0;
    process_it = false;
    newSample();
    noteDetect();
    if  (loop_count%1000 == 0){
      //Serial.print(millivolts);
      //Serial.print(" mv ");
      Serial.print(frequency);
      Serial.println(" hz ");
    }
   }  // end of flag set
   
}  // end of loop

Schematics

Gain and shift circuit
requires the LM741 SPICE model to work
Gain and shift circuit schematic
Gands schematic n3ku4uflwu

Comments

Similar projects you might like

Calibrating My Servos

Project tutorial by Jeremy Lindsay

  • 19,682 views
  • 11 comments
  • 83 respects

Integrated Solar ChargeController, Inverter, PowerBank, Lamp

Project tutorial by Shahariar

  • 9,892 views
  • 25 comments
  • 40 respects

Programmable Pocket Power Supply with OLED Display

Project tutorial by Shahariar

  • 7,114 views
  • 8 comments
  • 37 respects

Controlling a Robot with a PlayStation Controller

Project tutorial by Andrew R Gross and Jonathan Gross

  • 3,509 views
  • 1 comment
  • 9 respects

Gesture Drive: Accelerate with Freedom

Project tutorial by Shahariar

  • 3,096 views
  • 0 comments
  • 12 respects

Aquarium Controlling and Monitoring Using Arduino + 1Sheeld

Project tutorial by Khaled Mohamed

  • 2,989 views
  • 1 comment
  • 15 respects
Add projectSign up / Login