A Jedi light saber controller program with the following "features": - Using RGB LEDs - User can change light colors with a button - Motion dependent (PWM) sounds with a MPU6050 motion sensor - Low voltage detection

Dependencies:   L152RE_USBDevice STM32_USB48MHz Watchdog mbed

Revision:
0:0bb3687e39da
Child:
2:59a7d4677474
diff -r 000000000000 -r 0bb3687e39da main.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Tue Mar 22 10:30:16 2016 +0000
@@ -0,0 +1,641 @@
+#include "mbed.h"
+#include "MPU6050.h"
+#ifdef __SERIAL_DEBUG__ 
+#include "USBSerial.h"
+#include "STM32_USB48MHz.h"
+#endif
+#include "Watchdog.h"
+#include "LightSaber.h"
+#include "LightSaber_sounds.h"
+
+
+
+// Light Controls: (Timer 3, ch 1-3)
+PwmOut Red_LED(PA_6);
+PwmOut Green_LED(PA_7);
+PwmOut Blue_LED(PB_0);
+
+//Speaker drive (Timer 2 ch 2)
+PwmOut Speaker_OUT(PA_1);
+
+//Color change button
+DigitalIn Color_Button(PA_3, PullUp);
+
+//startup config IOs
+DigitalIn Startup_Config_IN1(PB_5, PullDown);
+DigitalIn Startup_Config_IN2(PB_7, PullDown);
+DigitalOut Startup_Config_OUT(PB_6, 0);
+
+//Spare Buttons
+//DigitalIn Spare1_Button(PB_12);
+//DigitalIn Spare2_Button(PB_13);
+//DigitalIn Spare3_Button(PB_14);
+
+//Watchdog
+//Watchdog wd;
+
+bool color_button_released = TRUE;
+volatile int Color_Button_Count = 0;
+
+//sounds
+int sound_to_play;
+volatile int sound_count = 1;
+volatile int sound_line = 0;
+volatile int next_sound_time = 0;
+volatile bool sound_is_playing_flag = FALSE;
+
+//Battery voltage monitor
+AnalogIn V_bat(PA_2);
+
+//Ticker
+Ticker button_ticker, sound_ticker;
+
+//MPU 6050
+float sum = 0;
+uint32_t sumCount = 0;
+
+//Set up I2C, (SDA,SCL)
+//I2C MPU_i2c(PB_9, PB_8); //defined in MPU6050.h...
+
+//MPU interrupt line
+DigitalIn MPU_Intr(PB_1);
+
+MPU6050 mpu6050;
+   
+Timer t;
+
+void button_inth()
+{
+    int Color_Button_state;
+    
+    Color_Button_state = Color_Button.read(); //pressed = 0, released = 1
+    
+    if (color_button_released) {
+        Color_Button_state = (~Color_Button_state) & 0x01;
+    }
+    Color_Button_Count = Color_Button_state*(Color_Button_Count + 1);
+    
+}
+
+bool sound_player(const int sound_table[][6], int table_lines)
+{
+    int sound_period;
+    int sound_pulse_width_us;
+    if (sound_count > sound_table[sound_line][NUM_STEPS_IDX])
+    {
+        sound_count = 1;
+        sound_line++;
+        if (sound_line >= table_lines)
+        {
+            return FALSE;
+        }
+    }
+    
+    sound_period = sound_table[sound_line][INIT_PERIOD_IDX] + ((sound_count-1) * sound_table[sound_line][STEP_PERIOD_IDX]);
+    sound_pulse_width_us = ( sound_period * (sound_table[sound_line][INIT_VOL_IDX] + ((sound_count-1) * sound_table[sound_line][STEP_VOL_IDX])) )/ 200;
+    
+    // there are no checks for 0/negative values of the above - need to make sure that tables are valid.
+    // set PWM parameters for current step
+    
+    Speaker_OUT.period_us(sound_period);
+    Speaker_OUT.pulsewidth_us(sound_pulse_width_us);
+        
+    //update next_sound_time, step count        
+    
+    next_sound_time = t.read_ms() + sound_table[sound_line][STEP_DUR_IDX];
+    sound_count++;
+    
+    return TRUE;
+}
+
+void sound_inth()
+{
+    if ((t.read_ms() >= next_sound_time) && sound_is_playing_flag )
+    {
+        switch (sound_to_play) {
+        
+            case STARTUP_SOUND:
+                sound_is_playing_flag = sound_player(startup_sound_table, STARTUP_TBL_N_LINES);
+                break;
+            case MOVEMENT_SOUND:
+                sound_is_playing_flag = sound_player(movement_sound_table, MOVEMENT_TBL_N_LINES);
+                break;
+            case CLASH_SOUND:
+                sound_is_playing_flag = sound_player(clash_sound_table, CLASH_TBL_N_LINES);
+                break;
+            default:
+                break;
+        }       
+    }
+}
+int main()
+{
+    bool motion_is_init = FALSE;
+    bool new_data = FALSE;
+    
+    int current_color;
+    int light_intensity = NORM_LIGHT_INTENS;
+    
+    int v_bat_avg_mem[VBAT_AVG_LEN];
+    int v_bat_avg_value;
+    int v_bat_avg_pointer = 0;
+    bool v_bat_ready = FALSE;
+    bool vbat_low_flag = FALSE;
+    int t_vbat_count = 0;
+    int t_vbat_start = 0;
+    
+    MPU_data_type MPU_data[MPU_BUFFER_LEN];
+    MPU_data_type MPU_avg_data;
+    int mpu_pointer = 0;
+    int mpu_data_count = 0;
+    bool accel_thr_crossed = FALSE;
+    bool ypr_thr_crossed = FALSE;
+    int count = 0;
+    
+    int temp_diff;
+    
+    int i = 0;
+#ifdef __SERIAL_DEBUG__   
+    STM32_HSI_USB48MHz(); // HSI,USB48MHz,SYSCLK32MHz
+    
+    // serial port for debug etc.
+    USBSerial serial;
+
+    //USBSerial serial;
+
+    serial.printf("Initializing main");
+#endif
+
+//initialize LEDs : set rate, intensity, and startup color (according to jumper settings)
+    
+    init_color(&current_color,light_intensity); 
+
+//Set up I2C
+    MPU_i2c.frequency(300000);  // use fast (400 kHz) I2C
+
+// start "clock"
+    t.start();
+    
+// attach and set sound interrupt handler (every 10msec)   
+    sound_ticker.attach(&sound_inth, 0.01);
+// play startup sound    
+    sound_to_play = STARTUP_SOUND;
+    sound_line = 0;
+    sound_count = 1;
+    next_sound_time = t.read_ms();
+    sound_is_playing_flag = TRUE;
+    
+// initialize the motion sensor
+    motion_is_init = motion_sensor_init();
+    
+    button_ticker.attach(&button_inth, 0.025);
+        
+    wait_ms(1000); //from MPU example
+    
+//Initialize watchdog with a 2 second interval
+ //   wd.Configure(2.0);       
+
+//main loop
+    while(1) {
+        
+              
+    
+        if (vbat_low_flag) //low battery
+        {
+            light_intensity = LOW_LIGHT_INTENS; //lower light intensity save some battery
+            if (t_vbat_start == 0) //starting a new cycle
+            {
+                t_vbat_start = t.read_ms();
+                if (t_vbat_count < 3) //flash only in first 3 cycles
+                {
+                    change_color(RED , light_intensity);
+                }
+            } else if ( ((t.read_ms() - t_vbat_start) > 1000) && (t_vbat_count < 3)) // dark period and flash only in first 3 cycles
+            {
+                change_color(RED, 0);
+            } else // after first 3 cycles return to normal operation (with lower light intensity)
+            {
+                change_color((color_type) current_color, light_intensity);
+            }
+            if ( (t.read_ms() - t_vbat_start) > 2000) //end of cycle
+            {
+                t_vbat_start = 0;
+                t_vbat_count ++;
+            }
+            if (t_vbat_count > 30) // once a minute, starting over
+            {
+                t_vbat_count  = 0;
+            }
+            
+        }
+        //battery ok or between low bat flashes, check if button was pressed or released
+        if (!vbat_low_flag || (t_vbat_count >=3))
+        {        
+            if ( (Color_Button_Count >= BUTTON_PRESS_THR) && color_button_released )
+            {
+                __disable_irq();
+                color_button_released = FALSE;
+                Color_Button_Count = 0;
+                __enable_irq(); 
+                current_color++;
+                if (current_color >= NUM_OF_COLORS)
+                {
+                    current_color = BLUE;
+                }
+#ifdef __SERIAL_DEBUG__
+                serial.printf("Color Button pressed");
+#endif
+                change_color((color_type) current_color, light_intensity);
+            }   
+        
+            if ((Color_Button_Count >= BUTTON_RELEASE_THR) && !color_button_released)
+            {
+                __disable_irq();
+                color_button_released = TRUE;
+                Color_Button_Count = 0;
+                __enable_irq(); 
+#ifdef __SERIAL_DEBUG__
+                serial.printf("Color Button released");
+#endif
+            }
+        }
+            
+        
+        
+        if (motion_is_init)
+        {
+            new_data = motion_update_data(&MPU_data[mpu_pointer]);
+        }
+        
+        if (new_data)
+        {
+            
+            if ((mpu_data_count > MPU_DATA_STABLE) && !sound_is_playing_flag )
+            {
+                //calculate new average
+                mpu_calc_avg(MPU_data, mpu_pointer, &MPU_avg_data, MPU_AVG_LEN);
+                
+                //check if any thresholds are crossed
+                //acceleration
+                if ((abs(MPU_avg_data.ax - MPU_data[mpu_pointer].ax) > ACCEL_THR) 
+                        || (abs(MPU_avg_data.ay - MPU_data[mpu_pointer].ay) > ACCEL_THR) 
+                        || (abs(MPU_avg_data.az - MPU_data[mpu_pointer].az) > ACCEL_THR))
+                {
+                    accel_thr_crossed = TRUE;
+                }
+                // YPR
+                temp_diff = abs(MPU_avg_data.yaw - MPU_data[mpu_pointer].yaw);
+                if ((temp_diff > YPR_THR) && (temp_diff < (3600-YPR_THR)))
+                {
+                    ypr_thr_crossed = TRUE;
+                }
+                
+                temp_diff = abs(MPU_avg_data.pitch - MPU_data[mpu_pointer].pitch);
+                if ((temp_diff > YPR_THR) && (temp_diff < (3600-YPR_THR)))
+                {
+                    ypr_thr_crossed = TRUE;
+                }
+                
+                temp_diff = abs(MPU_avg_data.roll - MPU_data[mpu_pointer].roll);
+                if ((temp_diff > YPR_THR) && (temp_diff < (3600-YPR_THR)))
+                {
+                    ypr_thr_crossed = TRUE;
+                }
+                
+                if (accel_thr_crossed)
+                {
+                    //play clash sound
+                    sound_to_play = CLASH_SOUND;
+                    sound_line = 0;
+                    sound_count = 1;
+                    next_sound_time = t.read_ms();
+                    sound_is_playing_flag = TRUE;
+                
+                }
+                if (ypr_thr_crossed && !accel_thr_crossed)
+                {
+                    //play movement sound
+                    sound_to_play = MOVEMENT_SOUND;
+                    sound_line = 0;
+                    sound_count = 1;
+                    next_sound_time = t.read_ms();
+                    sound_is_playing_flag = TRUE;
+                }
+                
+                accel_thr_crossed = FALSE;
+                ypr_thr_crossed = FALSE;
+                                
+            } else {
+                mpu_data_count++;
+            }
+            
+            mpu_pointer++;
+            
+            if (mpu_pointer >= MPU_BUFFER_LEN)
+            {
+                mpu_pointer = 0;
+            }
+            
+            new_data = FALSE;   
+        }
+        
+        delt_t = t.read_ms() - count;
+        if (delt_t > 1000) 
+        { // update serial port once per second independent of read rate
+            
+#ifdef __SERIAL_DEBUG__
+            //serial.printf("Yaw, Pitch, Roll: %f %f %f\n\r", (float) (yaw / 16384.0f), (float) (pitch/16384.0f), (float) (roll/16384.0f));
+            serial.printf("Yaw, Pitch, Roll: %i %i %i\n\r", (int)(yaw * 10), (int)(pitch * 10), (int)(roll * 10));
+            serial.printf("Yaw, Pitch, Roll (average): %i %i %i\n\r", MPU_avg_data.yaw, MPU_avg_data.pitch, MPU_avg_data.roll);
+            
+            serial.printf("Ax, Ay, Az : %i %i %i\n\r", (int)(ax * 1000), (int)(ay * 1000), (int)(az * 1000));
+            serial.printf("Ax, Ay, Az (average): %i %i %i\n\r", MPU_avg_data.ax , MPU_avg_data.ay, MPU_avg_data.az);
+            
+            serial.printf("average rate = %f\n\r", (float) sumCount/sum);
+            
+            if (sound_is_playing_flag)
+            {
+                serial.printf("Sound is playing!  \n\r", (float) sumCount/sum);
+            }
+#endif           
+            count = t.read_ms();
+            sum = 0;
+            sumCount = 0;
+                      
+// Battery voltage monitor
+            v_bat_avg_mem[v_bat_avg_pointer] = V_bat.read_u16();
+            v_bat_avg_pointer++;
+            if (v_bat_avg_pointer >= VBAT_AVG_LEN)
+            {
+                v_bat_avg_pointer = 0;
+                v_bat_ready = TRUE;
+            }
+            if (v_bat_ready)
+            {
+                //calculate the average
+                v_bat_avg_value = 0;
+                for (i = 0 ; i < VBAT_AVG_LEN ; i++)
+                {
+                    v_bat_avg_value += v_bat_avg_mem[i];
+                }
+                
+                v_bat_avg_value = v_bat_avg_value/VBAT_AVG_LEN;
+#ifdef __SERIAL_DEBUG__                
+                serial.printf("Battery average voltage: %i \n\r", v_bat_avg_value);
+#endif               
+                //check voltage
+                if (v_bat_avg_value < VBAT_THR)
+                {
+                    vbat_low_flag = TRUE;
+                } else if ((vbat_low_flag == TRUE) && (v_bat_avg_value > (VBAT_THR+VBAT_HYST)))
+                {
+                    vbat_low_flag = FALSE;
+                }
+            }
+            
+            
+        }
+//handle timer overflow - even if a sound is playing there are 5 minutes before overflow
+        if ((t.read_ms() > (30*60*1000)) && !sound_is_playing_flag)
+        {
+            t.reset();
+            // reset mpu statistics as well
+            count = t.read_ms();
+            sum = 0;
+            sumCount = 0;
+            //reset the low battery variables
+            t_vbat_start = 0;
+            t_vbat_count = 0;
+        }
+// kick the dog before the timeout       
+//       wd.Service();      
+    }
+}
+
+
+bool motion_sensor_init()
+{
+
+    
+// Read the WHO_AM_I register, this is a good test of communication
+    uint8_t whoami = mpu6050.readByte(MPU6050_ADDRESS, WHO_AM_I_MPU6050);  // Read WHO_AM_I register for MPU-6050
+    //serial.printf("I AM 0x%x\n\r", whoami);
+    //serial.printf("I SHOULD BE 0x68\n\r");
+
+    if (whoami == 0x68) { // WHO_AM_I should always be 0x68
+       // serial.printf("MPU6050 is online...");
+        wait(1);
+
+
+        mpu6050.MPU6050SelfTest(SelfTest); // Start by performing self test and reporting values
+/*
+        serial.printf("x-axis self test: acceleration trim within : ");
+        serial.printf("%f", SelfTest[0]);
+        serial.printf("% of factory value \n\r");
+        serial.printf("y-axis self test: acceleration trim within : ");
+        serial.printf("%f", SelfTest[1]);
+        serial.printf("% of factory value \n\r");
+        serial.printf("z-axis self test: acceleration trim within : ");
+        serial.printf("%f", SelfTest[2]);
+        serial.printf("% of factory value \n\r");
+        serial.printf("x-axis self test: gyration trim within : ");
+        serial.printf("%f", SelfTest[3]);
+        serial.printf("% of factory value \n\r");
+        serial.printf("y-axis self test: gyration trim within : ");
+        serial.printf("%f", SelfTest[4]);
+        serial.printf("% of factory value \n\r");
+        serial.printf("z-axis self test: gyration trim within : ");
+        serial.printf("%f", SelfTest[5]);
+        serial.printf("% of factory value \n\r");
+*/
+        wait(1);
+
+        if(SelfTest[0] < 1.0f && SelfTest[1] < 1.0f && SelfTest[2] < 1.0f && SelfTest[3] < 1.0f && SelfTest[4] < 1.0f && SelfTest[5] < 1.0f) {
+            mpu6050.resetMPU6050(); // Reset registers to default in preparation for device calibration
+            mpu6050.calibrateMPU6050(gyroBias, accelBias); // Calibrate gyro and accelerometers, load biases in bias registers
+            mpu6050.initMPU6050();
+            //serial.printf("MPU6050 initialized for active data mode....\n\r"); // Initialize device for active mode read of acclerometer, gyroscope, and temperature
+
+            return TRUE;
+        } else {
+            //serial.printf("Device did not the pass self-test!\n\r");
+            return FALSE;
+
+        }
+    } else {
+        //serial.printf("Could not connect to MPU6050: \n\r");
+        //serial.printf("%#x \n",  whoami);
+
+        return FALSE;
+    }
+
+
+}
+
+bool motion_update_data(MPU_data_type *new_data)
+{
+    if(mpu6050.readByte(MPU6050_ADDRESS, INT_STATUS) & 0x01) {
+        mpu6050.readAccelData(accelCount);  // Read the x/y/z adc values
+        mpu6050.getAres();
+
+        // Now we'll calculate the accleration value into actual g's
+        ax = (float)accelCount[0]*aRes - accelBias[0];  // get actual g value, this depends on scale being set
+        ay = (float)accelCount[1]*aRes - accelBias[1];
+        az = (float)accelCount[2]*aRes - accelBias[2];
+
+        mpu6050.readGyroData(gyroCount);  // Read the x/y/z adc values
+        mpu6050.getGres();
+
+        // Calculate the gyro value into actual degrees per second
+        gx = (float)gyroCount[0]*gRes; // - gyroBias[0];  // get actual gyro value, this depends on scale being set
+        gy = (float)gyroCount[1]*gRes; // - gyroBias[1];
+        gz = (float)gyroCount[2]*gRes; // - gyroBias[2];
+
+        tempCount = mpu6050.readTempData();  // Read the x/y/z adc values
+        temperature = (tempCount) / 340. + 36.53; // Temperature in degrees Centigrade
+
+
+        Now = t.read_us();
+        deltat = (float)((Now - lastUpdate)/1000000.0f) ; // set integration time by time elapsed since last filter update
+        lastUpdate = Now;
+
+        sum += deltat;
+        sumCount++;
+
+        if(lastUpdate - firstUpdate > 10000000.0f) {
+            beta = 0.04;  // decrease filter gain after stabilized
+            zeta = 0.015; // increase bias drift gain after stabilized
+        }
+
+        // Pass gyro rate as rad/s
+        mpu6050.MadgwickQuaternionUpdate(ax, ay, az, gx*PI/180.0f, gy*PI/180.0f, gz*PI/180.0f);
+
+
+
+
+        // Define output variables from updated quaternion---these are Tait-Bryan angles, commonly used in aircraft orientation.
+        // In this coordinate system, the positive z-axis is down toward Earth.
+        // Yaw is the angle between Sensor x-axis and Earth magnetic North (or true North if corrected for local declination, looking down on the sensor positive yaw is counterclockwise.
+        // Pitch is angle between sensor x-axis and Earth ground plane, toward the Earth is positive, up toward the sky is negative.
+        // Roll is angle between sensor y-axis and Earth ground plane, y-axis up is positive roll.
+        // These arise from the definition of the homogeneous rotation matrix constructed from quaternions.
+        // Tait-Bryan angles as well as Euler angles are non-commutative; that is, the get the correct orientation the rotations must be
+        // applied in the correct order which for this configuration is yaw, pitch, and then roll.
+        // For more see http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles which has additional links.
+        yaw   = atan2(2.0f * (q[1] * q[2] + q[0] * q[3]), q[0] * q[0] + q[1] * q[1] - q[2] * q[2] - q[3] * q[3]);
+        pitch = -asin(2.0f * (q[1] * q[3] - q[0] * q[2]));
+        roll  = atan2(2.0f * (q[0] * q[1] + q[2] * q[3]), q[0] * q[0] - q[1] * q[1] - q[2] * q[2] + q[3] * q[3]);
+        pitch *= 180.0f / PI;
+        yaw   *= 180.0f / PI;
+        roll  *= 180.0f / PI;
+/*
+        new_data->ax = (int) (ax / 16384.0f);
+        new_data->ay = (int) (ay / 16384.0f);
+        new_data->az = (int) (az / 16384.0f);
+        new_data->yaw = (int) (yaw / 16384.0f);
+        new_data->pitch = (int) (pitch / 16384.0f);
+        new_data->roll = (int) (roll / 16384.0f);
+*/
+        new_data->ax = (int) (ax * 1000);
+        new_data->ay = (int) (ay * 1000);
+        new_data->az = (int) (az * 1000);
+        new_data->yaw = (int) (yaw * 10);
+        new_data->pitch = (int) (pitch * 10);
+        new_data->roll = (int) (roll * 10);
+        return TRUE;
+
+    } else {
+        return FALSE;
+    }
+
+}
+
+void mpu_calc_avg(MPU_data_type * MPU_data,int mpu_pointer, MPU_data_type * MPU_avg_data, int avg_len)
+{
+    int i = 0;
+    //MPU_data_type MPU_avg_data;
+    
+    MPU_avg_data->ax = 0;
+    MPU_avg_data->ay = 0;
+    MPU_avg_data->az = 0;
+    MPU_avg_data->yaw = 0;
+    MPU_avg_data->pitch = 0;
+    MPU_avg_data->roll = 0;
+    
+    for (i=0 ; i < avg_len ; i++)
+    {
+        mpu_pointer++;
+        if (mpu_pointer >= MPU_BUFFER_LEN)
+            {
+                mpu_pointer = 0;
+            }
+        MPU_avg_data->ax += MPU_data[mpu_pointer].ax;
+        MPU_avg_data->ay += MPU_data[mpu_pointer].ay;
+        MPU_avg_data->az += MPU_data[mpu_pointer].az;
+        MPU_avg_data->yaw += MPU_data[mpu_pointer].yaw;
+        MPU_avg_data->pitch += MPU_data[mpu_pointer].pitch;
+        MPU_avg_data->roll += MPU_data[mpu_pointer].roll; 
+    }
+    
+    MPU_avg_data->ax = MPU_avg_data->ax / avg_len;
+    MPU_avg_data->ay = MPU_avg_data->ay / avg_len;
+    MPU_avg_data->az = MPU_avg_data->az / avg_len;
+    MPU_avg_data->yaw = MPU_avg_data->yaw / avg_len;
+    MPU_avg_data->pitch = MPU_avg_data->pitch / avg_len;
+    MPU_avg_data->roll = MPU_avg_data->roll / avg_len;
+    
+    //return MPU_avg_data;
+}
+
+void init_color(int *color, int pulsewidth)
+{
+    Startup_Config_OUT.write(1);
+    wait(1);
+    if (Startup_Config_IN1.read() == 1)
+    {
+       *color = GREEN;
+    }else if (Startup_Config_IN2.read() == 1)
+    {
+        *color = PURPLE;
+    }else
+    {
+        
+        *color = BLUE;
+    }
+    Startup_Config_OUT.write(0);
+    
+    Blue_LED.period_ms(LED_PWM_PERIOD);
+    
+    change_color((color_type)*color, pulsewidth);
+}
+        
+
+void change_color(color_type new_color, int new_pulsewidth)
+{
+    Blue_LED.pulsewidth_us(0);
+    Green_LED.pulsewidth_us(0);
+    Red_LED.pulsewidth_us(0);
+
+    switch (new_color) {
+        case BLUE:
+            Blue_LED.pulsewidth_us(new_pulsewidth);
+            break;
+
+        case GREEN:
+            Green_LED.pulsewidth_us(new_pulsewidth);
+            break;
+
+        case PURPLE:
+            Blue_LED.pulsewidth_us(new_pulsewidth);
+            Red_LED.pulsewidth_us(new_pulsewidth);
+            break;
+
+        case RED:
+            Red_LED.pulsewidth_us(new_pulsewidth);
+            break;
+
+        default:
+            break;
+    }
+}
+    
\ No newline at end of file