Balboa examples

I am not sure if it is too messy. Here is the code, though it will almost certainly require some modification for your application (the PID constants will probably need to be modified). You might be able to use the code for reading the RC receiver values with our other balancing examples (which are explained in this series of blog posts). The balancing algorithm in my code is a little bit different from (and probably not as robust as) the algorithm described in those blog posts.

-Nathan

RC_Balancer.ino (12.6 KB)

#include <Wire.h>
#include <Balboa32U4.h>
#include <LSM6.h>


LSM6 imu;
Balboa32U4Encoders encoders;
Balboa32U4Buzzer buzzer;
Balboa32U4Motors motors;
Balboa32U4ButtonA buttonA;
Balboa32U4ButtonB buttonB;
Balboa32U4ButtonC buttonC;
Balboa32U4LCD lcd;




//IMU internal sampling rates are 1.66kHz (1 sample every 0.60ms)
//Reading data takes about 1.9ms
//Default gyro is 245dps   (-32766 -> +32766)
//Default accel is +- 2g   (-32764 -> +32749)

//RC transmitter pulses at 60Hz (1 sample every 16ms)
//Reading data takes about 36ms
//ISRs for these take about 8-16us

//Encoder reads are very quick (about 4-16us).
//12 ticks/Motor rpm
//50 Motor rpm/output rpm
//220mm / output rpm
//1 tick = 0.366mm
  
int16_t   countsLeft = 0;                                //Left encoder count
int16_t   countsRight = 0;                               //Right encoder count
int16_t   targetEncoderCount = 0;                        //Encoder count to target
double    errorEncoderCount = 0;                         //Error in the encoder count

int16_t   countsLeftLast = 0;                            //Left encoder count on last measurement
int16_t   countsRightLast = 0;                           //Right encoder count on last measurement
uint32_t  countsLastTS = 0;                              //Timestamp of the last encoder count measurement 

double    countRateLeft = 0;                             //Right channel encoder tick rate (ticks per millisecond) 
double    countRateRight = 0;                            //Right channel encoder tick rate (ticks per millisecond)
int16_t   targetEncoderRate = 0;                         //Encoder change rate to target
double    errorEncoderRate = 0;                          //Error in the encoder rate

//RC Control Variables
uint8_t   xAxisRCPin = 1;                                //Arduino pin number of the xAxis RC signal (rotation) Channel 1
uint8_t   yAxisRCPin = 0;                                //Arduino pin number of the yAxis RC signal (translation) Channel 2
int16_t   xAxisPulseWidth = 1500;                        //Pulse width of the RC signal for X-axis rotation
int16_t   yAxisPulseWidth = 1500;                        //Pulse width of the RC signal for Y-axis translation 

uint32_t  lastXrcTimeStamp;                              //microsecond timestamp of the last X axis RC pin change
uint32_t  lastYrcTimeStamp;                              //microsecond timestamp of the last Y axis RC pin change
bool      lastXRCState = LOW;
bool      lastYRCState = LOW;

//Sensor Variables
int16_t   yGyro;                                         //y axis gyro reading
uint32_t  lastImuTimeStamp;                              //microsecond timestamp 
uint32_t  thisImuTimeStamp;                              //microsecond timestamp 

int16_t   xAccel;                                        //x axis accelerometer   
int16_t   zAccel;                                        //z axis accelerometer
double    xzAngle;                                       //angle of acceleration between X and Z axes (degrees, 180 is up)
double    filteredAngle;                                 //angle from the complimentary filter
double    balanceAngle = 172.2;                          //balance angle of the robot (degrees)
double    targetAngle = balanceAngle;                    //target angle to balance at (degrees)
double    errorAngle = 0;                                //Angle error (degrees)


double    Ki = 0.11;
double    Kp = 7.0;
double    Kd = -1.10;
double    iDecay = .95;
uint8_t   activeK = 1;                                   //K variable that is currently being adjusted

/*
const double    Ki = 0.050;
const double    Kp = 5.5;
const double    Kd = -0.8;
const double    iDecay = 0.90;
*/


double    gyroRotationRate;                              //rotation rate after correction from offset (degrees per second)
double    gyroZeroRotationRate;                          //rotation rate offset to apply (degrees per second)
double    targetRotationRate = 0;                        //rotation rate to target (degrees per second)
double    errorRotationRate = 0;                         //error in the roation rate
uint32_t  imuNextSample = 0;                             //timestamp to take next gyro sample at (microseconds)
const uint32_t imuNextSampleInterval = 5000;             //gyro sampling interval (microseconds)

double    angleIntegral = 0;                             //(degrees * milliseconds)
double    angleIntegralTarget = 0;

uint32_t  pidNextAdjust = 0;                             //timestamp to run PID calculations and adjust the motor speed (microseconds)
const uint32_t pidNextAdjustInterval = 10000;            //interval for control speed adjustments (microseconds)

double    speedDifference;                              
int32_t   motorSpeed;
double    LRSpeedDifference = 0;                         //Speed different for Left and Right motor speeds
const uint16_t maxSpeed = 300;

uint32_t  tsNextReport = 100000;                              //timestamp to make next report at
const uint32_t tsNextReportInterval = 1000000;           //serial reporting interval (microseconds)

int killSwitch = 0;                                      //If parameters are out of spec, turn robot off
int firstNote = 0;                                       //If note played once

//Mode 0 - If robot is upright, it will perform a controlled fall onto its arms. Otherwise, no movement.
//Mode 1 - If robot is upright it will balance. //Not fully implemented. Otherwise, it will (slowly) get onto maximum arm inclination and try to pop up.
uint8_t robotMode = 1;
uint8_t robotModePrevious = 1;
uint8_t runArmAdjust = 0;
double  aaAngle[6];

uint32_t  armAdjustNext = 0; 
const uint32_t armAdjustNextInterval = 50000;

//==============================
//Pin 7 ISR Function
//==============================
void xAxisRCPinISR(){
  uint32_t thisTimeStamp = micros();    
  bool xAxisPinState = digitalRead(xAxisRCPin);
  
  if(xAxisPinState != lastXRCState){             
    if(xAxisPinState==LOW){
      xAxisPulseWidth = thisTimeStamp - lastXrcTimeStamp;
    }
    lastXrcTimeStamp = thisTimeStamp;
    lastXRCState=xAxisPinState;
  } 

  //xAxisPulseWidth=1500;   //no rc reciever hooked up, so use center point
}

void yAxisRCPinISR(){
  uint32_t thisTimeStamp = micros();    
  bool yAxisPinState = digitalRead(yAxisRCPin);
  
  if(yAxisPinState != lastYRCState){             
    if(yAxisPinState==LOW){
      yAxisPulseWidth = thisTimeStamp - lastYrcTimeStamp;
    }
    lastYrcTimeStamp = thisTimeStamp;
    lastYRCState=yAxisPinState;
  } 
  
  //yAxisPulseWidth=1500;   //no rc reciever hooked up, so use center point
}


//==============================
//Setup Function
//==============================
void setup()
{
  Wire.begin();
  Serial.begin (115200);

  //imu stuff
  if (!imu.init())
  {
    Serial.println("Failed to detect and initialize IMU!");
    while (1);
  }
  imu.enableDefault();

  //calibrate the gyro (2 degrees error observed between accel angle and filtered if not calibrated)
  for(int i=0;i<200;i++){    
    imu.read();
    gyroZeroRotationRate = gyroZeroRotationRate + imu.g.y;
  }
  gyroZeroRotationRate = gyroZeroRotationRate * 245.0 / 32749.0 / 200.0;
  filteredAngle = 0;

  //other stuff
  pinMode(xAxisRCPin, INPUT_PULLUP);
  pinMode(yAxisRCPin, INPUT_PULLUP);
  motors.flipLeftMotor(true);
  motors.flipRightMotor(true);
  attachInterrupt(digitalPinToInterrupt(xAxisRCPin), xAxisRCPinISR, CHANGE);
  attachInterrupt(digitalPinToInterrupt(yAxisRCPin), yAxisRCPinISR, CHANGE);

  lcdPrint();
}

//==============================
//Loop Function ****************
//==============================
void loop()
{
  uint32_t currentTime = micros();

  //Operations priority  
  //   (1) Check for next imu point 
  //   (3) Robot setdown on arms
  //   (4) Update motor speeds
  //   (5) Reporting

  //-------------------------------------------
  //   (1) Check for next IMU point 
  //-------------------------------------------
  if (currentTime>imuNextSample){
    //add a imu sample
    imu.read();

    lastImuTimeStamp = thisImuTimeStamp;  
    thisImuTimeStamp = currentTime;
    
    //32749 = 245 dps --> 1 dps = 133.67;
    gyroRotationRate = imu.g.y * 245.0 / 32749.0;
    gyroRotationRate = gyroRotationRate - gyroZeroRotationRate;
    filteredAngle = filteredAngle + gyroRotationRate * (thisImuTimeStamp - lastImuTimeStamp)/1000000;

    //Serial.print(gyroRotationRate);
    //Serial.print("  ");
    //Serial.println(gyroRotationRate * (thisImuTimeStamp - lastImuTimeStamp)/1000000.0);

    //Serial.print(filteredAngle);
    //Serial.print("  ");

    xzAngle = atan2(-imu.a.z,imu.a.x);
    xzAngle = ((xzAngle / 2.0 / 3.14159 * 360.0) + 180.0 );
    filteredAngle = (filteredAngle * 0.925) + (xzAngle * 0.075);

    //Serial.print("  ");
    //Serial.print(xzAngle);
    //Serial.print("  ");
    //Serial.println(filteredAngle);

    imuNextSample = imuNextSample + imuNextSampleInterval;
  }


  //-------------------------------------------
  //   (4) Update motor speeds
  //-------------------------------------------
  
  else if (currentTime>pidNextAdjust){
    targetAngle = balanceAngle - 9.0 / 500.0 * (double)(yAxisPulseWidth - 1500);
    LRSpeedDifference = -25.0 / 500.0 * (double)(xAxisPulseWidth - 1500);
    
    //Update encoder counts, calculate rates, prime variables for the next pass
    countsLeft = encoders.getCountsLeft();
    countsRight = encoders.getCountsRight();
    countRateLeft = ((double)countsLeft - (double)countsLeftLast ) / ((double)currentTime - (double)countsLastTS);
    countRateRight = ((double)countsRight - (double)countsRightLast) / ((double)currentTime - (double)countsLastTS);

    
    angleIntegral = 0.95 * angleIntegral + (targetAngle - filteredAngle) * (currentTime - countsLastTS) / 1000;
    angleIntegral = angleIntegral * iDecay;
    
    //Calculate errors for different targets
    errorEncoderCount = targetEncoderCount - (countsLeft + countsRight)/2.0;
    errorEncoderRate = targetEncoderRate - (countRateLeft + countRateRight)/2.0;
    errorAngle = targetAngle - filteredAngle; 
    errorRotationRate = targetRotationRate - gyroRotationRate;

    //Calculate the motor speed difference from the error terms
    speedDifference = Ki * angleIntegral + Kp * errorAngle + Kd * errorRotationRate;
    motorSpeed = speedDifference;
    motorSpeed = constrain(motorSpeed, -(int16_t)maxSpeed, (int16_t)maxSpeed);
    buzzer.playFrequency(660 + motorSpeed, 11, 9);

    if(currentTime < 5000000){
      motors.setSpeeds(0, 0);
    }
    else {
      motors.setSpeeds(motorSpeed + LRSpeedDifference, motorSpeed - LRSpeedDifference);
      ledGreen(0);
    }
    
    //targetAngle = targetAngle - errorEncoderRate * 1;
    //targetEncoderCount = 0.99 * targetEncoderCount + (countsLeft + countsRight)/2.0*0.01;

    //Clean up state variables
    pidNextAdjust = pidNextAdjust + pidNextAdjustInterval;
    countsLastTS = currentTime;
    countsLeftLast = countsLeft;
    countsRightLast = countsRight;
  }

  //-------------------------------------------
  //   (5) Reporting
  //-------------------------------------------
  else if (currentTime>tsNextReport ){
    tsNextReport += tsNextReportInterval;

    Serial.print(Ki * angleIntegral);
    Serial.print("  ");
    Serial.print(Kp* errorAngle);
    Serial.print("  ");
    Serial.print(Kd * errorRotationRate);
    Serial.print("  ");
    Serial.print(Ki * angleIntegral + Kp* errorAngle + Kd * errorRotationRate);
    Serial.print("  ");
    Serial.print(yAxisPulseWidth);
    Serial.print("  ");
    Serial.println(xAxisPulseWidth);
  } 

  if (killSwitch==1 && firstNote == 0){
    buzzer.playNote(NOTE_A(4), 750, 10);
    firstNote = 1;
  }

  if(buttonA.getSingleDebouncedPress()){
    if(activeK==1)
      Ki = Ki * 1.1;
    else if(activeK==2)
      Kp = Kp * 1.1;
    else if(activeK==3)
      Kd= Kd * 1.1;
    lcdPrint();
    
    //balanceAngle =  balanceAngle + 0.1;
    //lcd.clear();
    //lcd.print(balanceAngle);
  }
  if(buttonB.getSingleDebouncedPress()){
    if(activeK==1)
      Ki = Ki / 1.1;
    else if(activeK==2)
      Kp = Kp / 1.1;
    else if(activeK==3)
      Kd= Kd / 1.1;
    lcdPrint();

    //balanceAngle =  balanceAngle - 0.1;
    //lcd.clear();
    //lcd.print(balanceAngle);
  }
  
  if(buttonC.getSingleDebouncedPress()){
    if(activeK<3)
      activeK++;
    else
      activeK=1;
    lcdPrint();
  }
  
}

void lcdPrint(){
    lcd.clear();
    lcd.print("i");
    lcd.print(Ki*1000);
    lcd.setCursor(4,0);
    lcd.print("p");
    lcd.print(Kp*100);
    lcd.setCursor(0,1);
    lcd.print("d");
    lcd.print(Kd*100);
    lcd.setCursor(7,1);
    if(activeK==1)
      lcd.print("i");
    else if(activeK==2)
      lcd.print("p");
    else if(activeK==3)
      lcd.print("d");
    
}



'''