/* Perfect Pitch: code for the sensor and sounder This file contains the code that runs on the sensor. See details in http://www.tau.ac.il/~stoledo/lego/msp430-perfect-pitch/ Copyright 2007, Sivan Toledo (except for the i2c routines) */ #include typedef unsigned char uint8; typedef signed char int8; typedef unsigned short uint16; typedef signed short int16; typedef unsigned long uint32; typedef signed long int32; // Hamming window coefficients and Goertzel sine/cosine pair // Generated automatically by a Matlab program #include "coefficients.h" void Init_USI_I2C_SLAVE(void); void USI2_BUGFIX(void); //define LED_ENABLE (P1DIR |= 0x01) #define GREEN_LED_ON (P1OUT |= 0x01) #define GREEN_LED_OFF (P1OUT &= ~0x01) #define GREEN_LED_TOGGLE (P1OUT ^= 0x01) #define RED_LED_ON (P1OUT |= 0x02) #define RED_LED_OFF (P1OUT &= ~0x02) #define RED_LED_TOGGLE (P1OUT ^= 0x02) volatile uint16 dummy; volatile uint16 sample; //volatile uint8 sample_ready = 0; // variables for the low-pass averaging filter uint16 sample_count; uint16 sample_sum; // commands with a 0 upper nibble are // i2c commands that should be sent to // the main loop typedef enum { idle = 0xff, goertzel_start = 0x01, sound_start = 0x02, sound_stop = 0x03, goertzel_iter = 0x10, error = 0x20 } cmd_t; volatile uint8 main_loop_cmd; int32 amplitude_total; int32 amplitude_f; uint8 amplitude_iter; int32 d1, d2, y1, y2, sum_abs; int16 s; uint8 i; int16 shift; void main(void) { WDTCTL = WDTPW+WDTHOLD; // Stop watchdog timer DCOCTL = CALDCO_16MHZ; // Set DCO for 1MHz using BCSCTL1 = CALBC1_16MHZ; // calibration registers //BCSCTL3 |= LFXT1S1; // use VLOCLK for ACLK //BCSCTL3 |= XCAP_2; // 10pf // set up the IO ports P1SEL = 0x00; // P1.x port function = GPIO P1DIR = 0x07; // P1.x direction: output on 0,1 (LEDs) and 2 (sounder), input on rest P1OUT = 0x00; // All P1.x reset P1REN = 0x08; // enable pull up/down on P1.3 (the switch) P1OUT |= 0x08; // pull it up P1IE = 0x08; // enable interrupt on P1.3 P1IES = 0x08; // high-to-low transition Init_USI_I2C_SLAVE(); // Initialize I2C as Slave // set up the ADC ADC10CTL0 = SREF_0 // Vss to Vcc + ADC10SHT_0 // 4 cycle sample hold hold + ADC10SR // only up to 50ksps + ADC10ON // turn it on + ADC10IE // interrupt enable ; ADC10CTL1 = INCH_5 // A5 input pin + ADC10SSEL_3 // SMCLK ; ADC10AE0 |= (1 << 5); // enable analog input 5 ADC10CTL0 |= ENC; // enable; // set up sampling to get the shift to zero sample_count = 0; sample_sum = 0; // we first stop the timer TACTL &= ~MC_0; TACCR0 = 2 - 1; TACTL = TASSEL0+TACLR+MC_1+TAIE; // ACLK, Clear TA, up mode, enable interrupt TACCTL0 |= CCIE; // sample to estimate the shift __enable_interrupt(); y1 = 0; for (shift=0; shift<1024; shift++) { LPM0; __disable_interrupt(); s = sample; //sample_ready = 0; __enable_interrupt(); y1 += s; } // stop the timer TACTL &= ~MC_0; shift = y1 >> 10; main_loop_cmd = goertzel_start; while (1) { switch (main_loop_cmd) { case sound_start: //GREEN_LED_ON; // stop the timer TACTL &= ~MC_0; // now set the PWM output TA1 for 2048Hz, 50% duty cycle TACCR0 = 16 - 1; TACTL = TASSEL0+TACLR; // ACLK, Clear TA //TACTL = TASSEL1+TACLR; // SMCLK, Clear TA TACCR1 = 8; // CCR1 PWM Duty Cycle TACCTL1 = OUTMOD_7; // CCR1 reset/set TACCTL0 &= ~CCIE; // no interrupts TACTL |= MC0; // Start TA in up mode P1SEL |= 0x04; // enable TA1 on P1.2 main_loop_cmd = idle; break; case sound_stop: //GREEN_LED_OFF; P1SEL &= ~0x04; // disable TA1 on P1.2, return to GPIO P1OUT &= ~0x04; // set to 0 // stop the timer TACTL &= ~MC_0; main_loop_cmd = idle; break; case goertzel_start: amplitude_iter = 0; // mark the sample buffer as empty //sample_ready = 0; // initialize the low-pass averaging filter sample_count = 0; sample_sum = 0; // initialize the Goerzel algorithm d1 = 0; d2 = 0; sum_abs = 0; i = 0; TACTL &= ~MC_0; TACCR0 = 2 - 1; TACTL = TASSEL0+TACLR+MC_1+TAIE; // ACLK, Clear TA, up mode, enable interrupt TACCTL0 |= CCIE; main_loop_cmd = goertzel_iter; break; case goertzel_iter: __disable_interrupt(); s = sample; //sample_ready = 0; __enable_interrupt(); s -= shift; y1 = ((int32) scaling[i]) * ((int32) s); y1 >>= log2_one_rep; y2 = realW * d1; y2 >>= (log2_one_rep - 1); y2 += y1 - d2; if (y1 > 0) sum_abs += y1; else sum_abs -= y1; d2 = d1; d1 = y2; i++; if (i == window_size) { y1 = realW * d1; y1 >>= log2_one_rep; y1 = y1 - d2; // this is the real part of the Fourier coefficient y2 = imagW * d1; y2 >>= log2_one_rep-1; // this is the complex part if (y1 < 0) y1 = -y1; if (y2 < 0) y2 = -y2; y1 += y2; if (y1>5000 && y1 > (sum_abs >> 3)) GREEN_LED_ON; else GREEN_LED_OFF; __disable_interrupt(); amplitude_total = sum_abs; amplitude_f = y1; amplitude_iter++; __enable_interrupt(); // initialize for the next window d1 = 0; d2 = 0; sum_abs = 0; i = 0; } break; default: break; } LPM0; } } //volatile uint16 c = 0; #ifdef __ICC430__ #pragma vector=ADC10_VECTOR #endif __interrupt void Adc10 (void) { /* c++; if (c==32768) { RED_LED_TOGGLE; c = 0; } */ sample_count++; sample_sum += ADC10MEM; if (sample_count == 4) { sample = sample_sum; /* if (sample_ready) { RED_LED_ON; } else { sample = sample_sum; sample_ready = 1; } */ LPM0_EXIT; sample_count = 0; sample_sum = 0; } } #ifdef __TI_COMPILER_VERSION__ ADC10_ISR(Adc10) #endif //volatile uint16 tc = 0; #ifdef __ICC430__ #pragma vector=TIMERA0_VECTOR #endif __interrupt void Timer_A (void) { ADC10CTL0 |= ADC10SC; /* tc++; if (tc == 32768) { tc = 0; GREEN_LED_TOGGLE; } */ } #ifdef __TI_COMPILER_VERSION__ TIMERA0_ISR(Timer_A) #endif #ifdef __ICC430__ #pragma vector=TIMERA1_VECTOR #endif __interrupt void Timer_Ax (void) { ADC10CTL0 |= ADC10SC; dummy = TAIV; // read to clear interrupt flag /* tc++; if (tc == 32768) { tc = 0; GREEN_LED_TOGGLE; } */ } #ifdef __TI_COMPILER_VERSION__ TIMERA1_ISR(Timer_Ax) #endif /*** Button Interrupt ***/ #ifdef __ICC430__ #pragma vector=PORT1_VECTOR #endif __interrupt void p1_isr (void) { if (P1IN & 0x08) { P1IES = 0x08; // high-to-low main_loop_cmd = sound_stop; //RED_LED_OFF; } else { P1IES = 0x00; main_loop_cmd = sound_start; //RED_LED_ON; } LPM0_EXIT; P1IFG = 0x00; // clear the interrupt } #ifdef __TI_COMPILER_VERSION__ PORT1_ISR(p1_isr) #endif /***********************************************************/ /* i2c code */ /***********************************************************/ //static short USI_OA; // Storage for Own Address Compare Value uint8 USI_SA; // Storage for Slave Address Received uint8 USIData; // Storage for Received Data uint8 i2c_byte_count; #define I2C_OA (0x44) //#define I2C_OA (0x48) uint16 ProgramMode; // Current program mode enum { PM_IDLE, // Program Mode - IDLE (Slave Idle Mode) PM_SA_RECD, // Program Mode - SA_RECD (Slave Address Receive) PM_RCVR_DATA_PREP, // Program Mode - RCVR_DATA_PREP (Receiver Data Control Setup) PM_XMIT_DATA_PREP, // Program Mode - XMIT_DATA_PREP (Transmitter Data Control Setup) PM_DATA_RECD, // Program Mode - DATA_REC (Data Receive) PM_DATA_SENT // Program Mode - DATA_SEND (Data Send) }; union { struct { int32 total; int32 f; uint8 iter; }; uint8 u8[9]; } i2c_send_packet; //volatile int32 i2c_amplitude_total; //volatile int32 i2c_amplitude_f; //int8 i2c_amplitude_iter; void Init_USI_I2C_SLAVE(void) { //USI_OA = I2C_OA*2; USIData = 0x00; ProgramMode = PM_IDLE; USISRL = 0xFF; USICKCTL = USICKPL; // USICLK=SCL (Inverted) USICTL1 |= USII2C+USISTTIE; // USI in I2C Mode, Enable USISTTIFG Interrupts USICTL0 = USIPE7+USIPE6+USISWRST; // USI Port Control for P1.7 and P1.6 P1REN |= 0xC0; // Enable Pull-up/down Option P1OUT |= 0xC0; // Select Pull-up for P1.7 and P1.6 //P1DIR = 0xFF; // P1.0, P1.1, and P1.4 As output //P1SEL |= 0xC0; // USICNT = 0x08; // USICNT 8-bits USICTL0 &= ~USISWRST; // Clear Reset of USI } #ifdef __ICC430__ #pragma vector=USI_VECTOR #endif __interrupt void usi_isr (void) { if (USICTL1 & USISTTIFG) { // Was Start Condition Detected? USICTL0 &= ~USIOE; // Clear Output Enable USICNT = 0x08; // Load USICNT for 8-bits Slave Address receive USICTL1 &= ~USISTTIFG; // Clear USISTTIFG Start Interrupt Flag USICTL1 |= USIIE; // Enable USIIFG Interrupt ProgramMode = PM_SA_RECD; // Receive Slave Address } else { switch (ProgramMode) { case PM_IDLE : // Program Mode - IDLE (Slave Idle Mode) USICNT = 0x08; // Keep Loading USICNT While IDLE break; case PM_SA_RECD : // Program Mode - SA_RECD (Slave Address Received, If Match then Acknowledge, else Idle) USI_SA = USISRL; USI_SA &= 0xFE; // Remove Read/Write Bit if (USI_SA == 0x44) { // Slave Address Match --> Send Acknowledge i2c_byte_count = 0; if (USISRL & 0x01) { ProgramMode = PM_XMIT_DATA_PREP; // Generate Slave Address Ack -> Transmitter Data Control Setup i2c_send_packet.total = amplitude_total; i2c_send_packet.f = amplitude_f; i2c_send_packet.iter = amplitude_iter; } else { //USIData = 0; // clear data storage ProgramMode = PM_RCVR_DATA_PREP; // Generate Slave Address Ack -> Receiver Data Control Setup } USICTL0 |= USIOE; // Set Output Enable USISRL = 0x00; // Load USISRL for Acknowledge // USICNT = 0x01; // Load USICNT 1-bit transmit ACK (Expected Operation) USI2_BUGFIX(); // Bug Fix for Issue with USI Ack Generation break; } else { // Slave Address Not Matched --> Do not Acknowledge and return to IDLE ProgramMode = PM_IDLE; // Idle State - Wait For USISTTIFG USICTL0 &= ~USIOE; // Clear Output Enable USISRL = 0xFF; // Load USISRL for No Acknowledge // USICNT = USISCLREL+0x01; // Load USICNT 1-bit transmit NAck (Expected Operation) // // Disable SCL Holding (Start Condition Must Reset) USI2_BUGFIX(); // Bug Fix for Issue with USI NAck Generation USICNT = USISCLREL; // Disable SCL Holding (Start Condition Must Reset) USICTL1 &= ~USIIE; // Clear USIIFG Interrupt Enable break; } case PM_RCVR_DATA_PREP : // Program Mode - RCVR_DATA_PREP (Receiver Data Setup). USICTL0 &= ~USIOE; // Clear Output Enable USICNT = 0x08; // Load USICNT for 8-bits Data to Receive ProgramMode = PM_DATA_RECD; // Data Received - I2C Slave received Data from Master break; case PM_XMIT_DATA_PREP : // Program Mode - XMIT_ACK_SENT (Transmitter Acknowledge Sent) --> Transmit Data Setup. USIData = i2c_send_packet.u8[i2c_byte_count++]; USICTL0 |= USIOE; // Set Output Enable USISRL = USIData; // Fixed Data Load USIData++; // sivan, increment USICNT = 0x08; // Load USICNT for 8-bits Data to Send ProgramMode = PM_DATA_SENT; // Data Sent - I2C Slave sent Data to Master break; case PM_DATA_RECD : // Program Mode - DATA_RECD (Data Received) --> Send Acknowledge USIData = USISRL; if ((USIData & 0xF0) == 0) { main_loop_cmd=USIData; LPM0_EXIT; } /* if (i2c_byte_count == 0) { // command if ((USIData & 0xF0) == 0) { main_loop_cmd=USIData; LPM0_EXIT; } } i2c_byte_count++; */ USICTL0 |= USIOE; // Set Output Enable USISRL = 0x00; // Load USISRL for Acknowledge //USICNT = 0x01; // Load USICNT 1-bit transmit ACK (Expected Operation) USI2_BUGFIX(); // Bug Fix for Issue with USI Ack Generation ProgramMode = PM_RCVR_DATA_PREP; // Receiver Acknowledge Sent break; case PM_DATA_SENT : // Program Mode - DATA_SENT (Data Sent) --> Check Acknowledge USICTL0 &= ~USIOE; // Clear Output Enable // USICNT = 0x01; // Load USICNT 1-bit transmit ACK (Expected Operation) USI2_BUGFIX(); // Bug Fix for Issue with USI Ack Generation if (USISRL & 0x01) { USICNT = 0x02; // Master NAck, Load USICNT 2-bit To accept Stop Condition or Handle Re-start USICTL1 &= ~USIIE; // Clear USIIFG Interrupt Enable ProgramMode = PM_IDLE; // Idle State - Wait For USISTTIFG } else ProgramMode = PM_XMIT_DATA_PREP; // Transmitter Acknowledge Sent break; } } } #ifdef __TI_COMPILER_VERSION__ USI_ISR(usi_isr) #endif //################################################################################ // USI2 Bug fix routines // //################################################################################ void USI2_BUGFIX(void) { //P1OUT ^= 0x02; while (P1IN & 0x40){} // Test SCL P1(6) LOW USICNT = USISCLREL+0x01; while ((P1IN & 0x40) == 0){} // Test SCL P1(6) HIGH while (P1IN & 0x40){} // Test SCL P1(6) LOW USICNT = 0x00; // Clear USISCLREL //P1OUT &= ~0x02; }