Self-Balancing bot for MAX32630 and two wheel car.
Dependencies: mbed BMI160 SDFileSystem max32630fthr
main.cpp
- Committer:
- j3
- Date:
- 2017-01-09
- Revision:
- 3:7807a40b2459
- Parent:
- 2:5af1d818331f
- Child:
- 4:4144ef007743
File content as of revision 3:7807a40b2459:
/********************************************************************** * Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * Except as contained in this notice, the name of Maxim Integrated * Products, Inc. shall not be used except as stated in the Maxim Integrated * Products, Inc. Branding Policy. * * The mere transfer of this software does not imply any licenses * of trade secrets, proprietary technology, copyrights, patents, * trademarks, maskwork rights, or any other form of intellectual * property whatsoever. Maxim Integrated Products, Inc. retains all * ownership rights. **********************************************************************/ /*References * * Circuit Cellar Issue 316 November 2016 * BalanceBot: A Self-Balancing, Two-Wheeled Robot * * Reading a IMU Without Kalman: The Complementary Filter * http://www.pieter-jan.com/node/11 * */ #include "mbed.h" #include "max32630fthr.h" #include "bmi160.h" #include "SDFileSystem.h" //Setup interrupt in from imu InterruptIn imuIntIn(P3_6); bool drdy = false; void imuISR() { drdy = true; } //Setup start/stop button DigitalIn startStopBtn(P2_3, PullUp); InterruptIn startStop(P2_3); bool start = false; void startStopISR() { start = !start; } //Setup callibrate button DigitalIn callibrateBtn(P5_2, PullUp); InterruptIn callibrate(P5_2); bool cal = false; void callibrateISR() { cal = !cal; } //Ping sensor trigger DigitalOut pingTrigger(P3_2, 0); Ticker pingTriggerTimer; void triggerPing() { pingTrigger = !pingTrigger; wait_us(1); pingTrigger = !pingTrigger; } DigitalIn p51(P5_1, PullNone); InterruptIn pingEchoRiseInt(P5_1); DigitalIn p45(P4_5, PullNone); InterruptIn pingEchoFallInt(P4_5); Timer pingTimer; float cm = 0; bool pingReady = false; const float US_PER_CM = 29.387; void echoStartISR() { pingTimer.reset(); } void echoStopISR() { uint32_t us = pingTimer.read_us()/2; cm = (us/ US_PER_CM); pingReady = true; } int main() { MAX32630FTHR pegasus; pegasus.init(MAX32630FTHR::VIO_3V3); static const float MAX_PULSEWIDTH_US = 40.0F; static const float MIN_PULSEWIDTH_US = -40.0F; static const bool FORWARD = 0; static const bool REVERSE = 1; //Setup left motor(from driver seat) DigitalOut leftDir(P5_6, FORWARD); PwmOut leftPwm(P4_0); leftPwm.period_us(40); leftPwm.pulsewidth_us(0); //Make sure P4_2 and P4_3 are Hi-Z DigitalIn p42_hiZ(P4_2, PullNone); DigitalIn p43_hiZ(P4_3, PullNone); //Setup right motor(from driver seat) DigitalOut rightDir(P5_4, FORWARD); PwmOut rightPwm(P5_5); rightPwm.period_us(40); rightPwm.pulsewidth_us(0); //Turn off RGB LEDs DigitalOut rLED(LED1, LED_OFF); DigitalOut gLED(LED2, LED_OFF); DigitalOut bLED(LED3, LED_OFF); uint32_t failures = 0; DigitalIn uSDdetect(P2_2, PullUp); SDFileSystem sd(P0_5, P0_6, P0_4, P0_7, "sd"); // mosi, miso, sclk, cs static const uint32_t N = 8000; uint32_t samples = 0; float pitchBuff[N]; float gyroBuff[N]; float accBuff[N]; FILE *fp; //Erase old log and start a new one if(!uSDdetect) { fp = fopen("/sd/balBot.txt", "w"); if(fp != NULL) { fprintf(fp, "Balance Bot Data,\n"); fclose(fp); } else { printf("Failed to open file\n"); failures++; } } //Setup I2C bus for IMU I2C i2cBus(P5_7, P6_0); i2cBus.frequency(400000); //Get IMU instance and configure it BMI160_I2C imu(i2cBus, BMI160_I2C::I2C_ADRS_SDO_LO); BMI160::AccConfig accConfig; BMI160::AccConfig accConfigRead; accConfig.range = BMI160::SENS_2G; accConfig.us = BMI160::ACC_US_OFF; accConfig.bwp = BMI160::ACC_BWP_2; accConfig.odr = BMI160::ACC_ODR_12; if(imu.setSensorConfig(accConfig) == BMI160::RTN_NO_ERROR) { if(imu.getSensorConfig(accConfigRead) == BMI160::RTN_NO_ERROR) { if((accConfig.range != accConfigRead.range) || (accConfig.us != accConfigRead.us) || (accConfig.bwp != accConfigRead.bwp) || (accConfig.odr != accConfigRead.odr)) { printf("ACC read data desn't equal set data\n\n"); printf("ACC Set Range = %d\n", accConfig.range); printf("ACC Set UnderSampling = %d\n", accConfig.us); printf("ACC Set BandWidthParam = %d\n", accConfig.bwp); printf("ACC Set OutputDataRate = %d\n\n", accConfig.odr); printf("ACC Read Range = %d\n", accConfigRead.range); printf("ACC Read UnderSampling = %d\n", accConfigRead.us); printf("ACC Read BandWidthParam = %d\n", accConfigRead.bwp); printf("ACC Read OutputDataRate = %d\n\n", accConfigRead.odr); failures++; } } else { printf("Failed to read back accelerometer configuration\n"); failures++; } } else { printf("Failed to set accelerometer configuration\n"); failures++; } BMI160::GyroConfig gyroConfig; BMI160::GyroConfig gyroConfigRead; gyroConfig.range = BMI160::DPS_2000; gyroConfig.bwp = BMI160::GYRO_BWP_2; gyroConfig.odr = BMI160::GYRO_ODR_12; if(imu.setSensorConfig(gyroConfig) == BMI160::RTN_NO_ERROR) { if(imu.getSensorConfig(gyroConfigRead) == BMI160::RTN_NO_ERROR) { if((gyroConfig.range != gyroConfigRead.range) || (gyroConfig.bwp != gyroConfigRead.bwp) || (gyroConfig.odr != gyroConfigRead.odr)) { printf("GYRO read data desn't equal set data\n\n"); printf("GYRO Set Range = %d\n", gyroConfig.range); printf("GYRO Set BandWidthParam = %d\n", gyroConfig.bwp); printf("GYRO Set OutputDataRate = %d\n\n", gyroConfig.odr); printf("GYRO Read Range = %d\n", gyroConfigRead.range); printf("GYRO Read BandWidthParam = %d\n", gyroConfigRead.bwp); printf("GYRO Read OutputDataRate = %d\n\n", gyroConfigRead.odr); failures++; } } else { printf("Failed to read back gyroscope configuration\n"); failures++; } } else { printf("Failed to set gyroscope configuration\n"); failures++; } //Power up sensors in normal mode if(imu.setSensorPowerMode(BMI160::GYRO, BMI160::NORMAL) != BMI160::RTN_NO_ERROR) { printf("Failed to set gyroscope power mode\n"); failures++; } wait(0.1); if(imu.setSensorPowerMode(BMI160::ACC, BMI160::NORMAL) != BMI160::RTN_NO_ERROR) { printf("Failed to set accelerometer power mode\n"); failures++; } if(failures == 0) { printf("ACC Range = %d\n", accConfig.range); printf("ACC UnderSampling = %d\n", accConfig.us); printf("ACC BandWidthParam = %d\n", accConfig.bwp); printf("ACC OutputDataRate = %d\n\n", accConfig.odr); printf("GYRO Range = %d\n", gyroConfig.range); printf("GYRO BandWidthParam = %d\n", gyroConfig.bwp); printf("GYRO OutputDataRate = %d\n\n", gyroConfig.odr); wait(1.0); //Sensor data vars BMI160::SensorData accData; BMI160::SensorData gyroData; BMI160::SensorTime sensorTime; //Complementary Filter vars, filter weight K static const float K = 0.9993F; float thetaGyro = 0.0F; float thetaAcc = 0.0F; float pitch = 0.0F; //PID coefficients static const float DT = 0.000625F; static const float KP = 4.25F; static const float KI = (8.5F * DT); static const float KD = (0.0425F / DT); float setPoint = 0.0F; //Control loop vars float currentError, previousError; float integral = 0.0F; float derivative = 0.0F; float pulseWidth; //Enable data ready interrupt from imu for constant loop timming imu.writeRegister(BMI160::INT_EN_1, 0x10); //Active High PushPull output on INT1 imu.writeRegister(BMI160::INT_OUT_CTRL, 0x0A); //Map data ready interrupt to INT1 imu.writeRegister(BMI160::INT_MAP_1, 0x80); //Tie INT1 to callback fx imuIntIn.rise(&imuISR); //Tie SW2 to callback fx startStop.fall(&startStopISR); //Tie P5_2 to callback fx callibrate.fall(&callibrateISR); bool firstCal = true; //Callbacks for echo measurement pingEchoRiseInt.fall(&echoStartISR); pingEchoFallInt.rise(&echoStopISR); //Timer for echo measurements pingTimer.start(); //Timer for trigger pingTriggerTimer.attach(&triggerPing, 0.05); //Position control vars/constants static const float PING_SP = 20.0F; float pingCurrentError = 0.0F; //control loop timming indicator DigitalOut loopPulse(P5_3, 0); //Count for averaging setPoint offset, 2 seconds of data uint32_t offsetCount = 0; while(1) { if(cal || (firstCal == true)) { cal = false; firstCal = false; pitch = 0.0F; setPoint = 0.0F; rLED = LED_ON; gLED = LED_ON; while(offsetCount < 3200) { if(drdy) { //Clear data ready flag drdy = false; //Read feedback sensors imu.getGyroAccXYZandSensorTime(accData, gyroData, sensorTime, accConfig.range, gyroConfig.range); //Feedback Block, Complementary filter //Prediction estimate thetaGyro = (pitch + (gyroData.xAxis.scaled * DT)); //Measurement estimate thetaAcc = ((180.0F/3.14F) * atan(accData.yAxis.scaled/accData.zAxis.scaled)); //Feedback, Pitch estimate in degrees pitch = ((K * thetaGyro) + ((1.0F - K) * thetaAcc)); //Accumalate pitch measurements setPoint += pitch; offsetCount++; } } //Average measurements setPoint = setPoint/3200.0F; printf("setPoint = %5.2f\n\n", setPoint); //Clear count for next time offsetCount = 0; } while(start && (pitch > -30.0F) && (pitch < 30.0F)) { rLED = LED_OFF; gLED = LED_ON; if(drdy) { //Start, following takes ~456us on MAX32630FTHR with 400KHz I2C bus //At 1600Hz ODR, ~73% of loop time is active doing something loopPulse = !loopPulse; //Clear data ready flag drdy = false; //Read feedback sensors imu.getGyroAccXYZandSensorTime(accData, gyroData, sensorTime, accConfig.range, gyroConfig.range); //Feedback Block, Complementary filter //Prediction estimate thetaGyro = (pitch + (gyroData.xAxis.scaled * DT)); //Measurement estimate thetaAcc = ((180.0F/3.14F) * atan(accData.yAxis.scaled/accData.zAxis.scaled)); //Feedback, Pitch estimate in degrees pitch = ((K * thetaGyro) + ((1.0F - K) * thetaAcc)); if(samples < N) { pitchBuff[samples] = pitch; gyroBuff[samples] = thetaGyro; accBuff[samples] = thetaAcc; samples++; } //PID Block //Error signal currentError = (setPoint - pitch); //Integral term, dt is included in KI //If statement addresses integral windup if(currentError == 0.0F) { integral = 0.0F; } else if(((currentError < 0.0F) && (previousError > 0.0F)) || ((currentError > 0.0F) && (previousError < 0.0F))) { integral = 0.0F; } else { integral = (integral + currentError); } //Derivative term, dt is included in KD derivative = (currentError - previousError); //Set right/left pulsewidth //Just balance for now, so both left/right are the same pulseWidth = ((KP * currentError) + (KI * integral) + (KD * derivative)); if(pingReady) { //Get error signal pingReady = false; pingCurrentError = PING_SP - cm; } //pulseWidth += pingCurrentError * -1.0F; //Clamp to maximum duty cycle and check for forward/reverse //based on sign of error signal (negation of pitch since setPoint = 0) if(pulseWidth > MAX_PULSEWIDTH_US) { rightDir = FORWARD; leftDir = FORWARD; rightPwm.pulsewidth_us(40); leftPwm.pulsewidth_us(40); } else if(pulseWidth < MIN_PULSEWIDTH_US) { rightDir = REVERSE; leftDir = REVERSE; rightPwm.pulsewidth_us(40); leftPwm.pulsewidth_us(40); } else { if(pulseWidth < 0.0F) { rightDir = REVERSE; leftDir = REVERSE; pulseWidth = (1 - pulseWidth); rightPwm.pulsewidth_us(static_cast<uint32_t>(pulseWidth)); leftPwm.pulsewidth_us(static_cast<uint32_t>(pulseWidth)); } else { rightDir = FORWARD; leftDir = FORWARD; rightPwm.pulsewidth_us(static_cast<uint32_t>(pulseWidth)); leftPwm.pulsewidth_us(static_cast<uint32_t>(pulseWidth)); } } //save current error for next loop previousError = currentError; //Stop loopPulse = !loopPulse; } } rightPwm.pulsewidth_us(0); leftPwm.pulsewidth_us(0); rLED = LED_ON; gLED = LED_ON; wait(0.25); rLED = LED_OFF; gLED = LED_OFF; wait(0.25); if(!uSDdetect && (samples > 0)) { samples = 0; fp = fopen("/sd/balBot.txt", "a"); if(fp != NULL) { bLED = LED_ON; fprintf(fp, "KI and KD have DT rolled into them; ie * or / respectively.\n"); fprintf(fp, "Setpoint,%5.2f,\n", setPoint); fprintf(fp, "K, %f,\n", K); fprintf(fp, "KP, %f,\n", KP); fprintf(fp, "KI, %f,\n", KI); fprintf(fp, "KD, %f,\n", KD); fprintf(fp, "DT, %f,\n", DT); fprintf(fp, "Time, Pitch, Gyro, Acc,\n"); for(uint32_t idx = 0; idx < N; idx++) { fprintf(fp, "%f,", idx*DT); fprintf(fp, "%5.2f,", pitchBuff[idx]); fprintf(fp, "%5.2f,", gyroBuff[idx]); fprintf(fp, "%5.2f,", accBuff[idx]); fprintf(fp, "\n"); } fprintf(fp, "\n"); fclose(fp); bLED = LED_OFF; } else { printf("Failed to open file\n"); } } } } else { while(1) { rLED = !rLED; wait(0.25); } } }