#include #include "Balance.h" int32_t gYZero; int32_t angle; // millidegrees int32_t angleRate; // degrees/s int32_t distanceLeft; int32_t speedLeft; int32_t driveLeft; int32_t distanceRight; int32_t speedRight; int32_t driveRight; int16_t motorSpeed; bool isBalancingStatus = false; bool balanceUpdateDelayedStatus; bool isBalancing() { return isBalancingStatus; } bool balanceUpdateDelayed() { return balanceUpdateDelayedStatus; } void balanceSetup() { // Initialize IMU. Wire.begin(); if (!imu.init()) { while(true) { Serial.println("Failed to detect and initialize IMU!"); delay(200); } } imu.enableDefault(); imu.writeReg(LSM6::CTRL2_G, 0b01011000); // 208 Hz, 1000 deg/s // Wait for IMU readings to stabilize. delay(1000); // Calibrate the gyro. int32_t total = 0; for (int i = 0; i < CALIBRATION_ITERATIONS; i++) { imu.read(); total += imu.g.y; delay(1); } gYZero = total / CALIBRATION_ITERATIONS; } // This function contains the core algorithm for balancing a // Balboa 32U4 robot. void balance() { // Adjust toward angle=0 with timescale ~10s, to compensate for // gyro drift. More advanced AHRS systems use the // accelerometer as a reference for finding the zero angle, but // this is a simpler technique: for a balancing robot, as long // as it is balancing, we know that the angle must be zero on // average, or we would fall over. angle = angle * 999 / 1000; // This variable measures how close we are to our basic // balancing goal - being on a trajectory that would cause us // to rise up to the vertical position with zero speed left at // the top. This is similar to the fallingAngleOffset used // for LED feedback and a calibration procedure discussed at // the end of Balancer.ino. // // It is in units of millidegrees, like the angle variable, and // you can think of it as an angular estimate of how far off we // are from being balanced. int32_t risingAngleOffset = angleRate * ANGLE_RATE_RATIO + angle; // Combine risingAngleOffset with the distance and speed // variables, using the calibration constants defined in // Balance.h, to get our motor response. Rather than becoming // the new motor speed setting, the response is an amount that // is added to the motor speeds, since a *change* in speed is // what causes the robot to tilt one way or the other. motorSpeed += ( + ANGLE_RESPONSE * risingAngleOffset + DISTANCE_RESPONSE * (distanceLeft + distanceRight) + SPEED_RESPONSE * (speedLeft + speedRight) ) / 100 / GEAR_RATIO; if (motorSpeed > MOTOR_SPEED_LIMIT) { motorSpeed = MOTOR_SPEED_LIMIT; } if (motorSpeed < -MOTOR_SPEED_LIMIT) { motorSpeed = -MOTOR_SPEED_LIMIT; } // Adjust for differences in the left and right distances; this // will prevent the robot from rotating as it rocks back and // forth due to differences in the motors, and it allows the // robot to perform controlled turns. int16_t distanceDiff = distanceLeft - distanceRight; motors.setSpeeds( motorSpeed + distanceDiff * DISTANCE_DIFF_RESPONSE / 100, motorSpeed - distanceDiff * DISTANCE_DIFF_RESPONSE / 100); } void lyingDown() { // Reset things so it doesn't go crazy. motorSpeed = 0; distanceLeft = 0; distanceRight = 0; motors.setSpeeds(0, 0); if (angleRate > -2 && angleRate < 2) { // It's really calm, so we know the angles. if (imu.a.z > 0) { angle = 85000; } else { angle = -85000; } distanceLeft = 0; distanceRight = 0; } } void integrateGyro() { // Convert from full-scale 1000 deg/s to deg/s. angleRate = (imu.g.y - gYZero) / 29; angle += angleRate * UPDATE_TIME_MS; } void integrateEncoders() { static int16_t lastCountsLeft; int16_t countsLeft = encoders.getCountsLeft(); speedLeft = (countsLeft - lastCountsLeft); distanceLeft += countsLeft - lastCountsLeft; lastCountsLeft = countsLeft; static int16_t lastCountsRight; int16_t countsRight = encoders.getCountsRight(); speedRight = (countsRight - lastCountsRight); distanceRight += countsRight - lastCountsRight; lastCountsRight = countsRight; } void balanceDrive(int16_t leftSpeed, int16_t rightSpeed) { driveLeft = leftSpeed; driveRight = rightSpeed; } void balanceDoDriveTicks() { distanceLeft -= driveLeft; distanceRight -= driveRight; speedLeft -= driveLeft; speedRight -= driveRight; } void balanceResetEncoders() { distanceLeft = 0; distanceRight = 0; } void balanceUpdateSensors() { imu.read(); integrateGyro(); integrateEncoders(); } void balanceUpdate() { static uint16_t lastMillis; uint16_t ms = millis(); static uint8_t count = 0; // Perform the balance updates at 100 Hz. if ((uint16_t)(ms - lastMillis) < UPDATE_TIME_MS) { return; } balanceUpdateDelayedStatus = ms - lastMillis > UPDATE_TIME_MS + 1; lastMillis = ms; balanceUpdateSensors(); balanceDoDriveTicks(); if (isBalancingStatus) { balance(); // Stop trying to balance if accelerometer X axis measures < 0.34 g for 5 counts // (corresponding to 20 degree tilt). if (imu.a.x < 5600) { if (++count > 5) { isBalancingStatus = false; count = 0; } } else { count = 0; } } else { lyingDown(); // Start trying to balance if accelerometer X axis measures > 0.71 g for 5 counts // (corresponding to 45 degree tilt). if (imu.a.x > 11600) { if (++count > 5) { isBalancingStatus = true; count = 0; } } else { count = 0; } } }