介紹作品:
本次主題是要製作一個平衡車,如下照片所示,是一個兩輪的電動車,透過陀螺儀可感測不同方向的特性,來平衡車子本身。
簡單概念就是車身向前或向後傾時,車子就會以該傾斜方向移動,車子可以因此平衡不倒下。
照片:
CODE:
#include "I2Cdev.h"
#include <PID_v1.h> //From https://github.com/br3ttb/Arduino-PID-Library/blob/master/PID_v1.h
#include "MPU6050_6Axis_MotionApps20.h" //https://github.com/jrowberg/i2cdevlib/tree/master/Arduino/MPU6050
MPU6050 mpu;
// MPU control/status vars
bool dmpReady = false; // 如果DMP初始化成功,則設置為1
uint8_t mpuIntStatus; // 保存mpu中斷狀態
uint8_t devStatus; // 運行後返回狀態0表示成功,!0有錯誤
uint16_t packetSize; // DMP數據包的大小
uint16_t fifoCount; // 統計當前在FIFO的字節數
uint8_t fifoBuffer[64]; // FIFO儲存緩衝區
// orientation/motion vars
Quaternion q; // [w, x, y, z] // (方位,運動)便量
VectorFloat gravity; // [x, y, z] //重力矢量
float ypr[3]; //yaw/pitch/roll(偏航/俯仰/滾動)數組
/*********Tune these 4 values for your BOT*********/
double setpoint= 182; //平衡車垂直於地面時的值(目標值),從序列監控取得小車在直立平衡狀況下的值
//(依照P->D->I順序調參)
double Kp = 20; //1.設置偏差比例係數(調節施予外力的直立,給的過大會震盪)
double Kd = 0.8; //2.調积分(消抖,給的過大會震盪)
double Ki = 140; //3.調微分(調節快速平衡)
/******End of values setting*********/
double input, output;
PID pid(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT);
volatile bool mpuInterrupt = false; // MPU中斷
void dmpDataReady()
{
mpuInterrupt = true;
}
void Forward() //馬達前進
{
analogWrite(6,output);
analogWrite(9,0);
analogWrite(10,output);
analogWrite(11,0);
Serial.print("F"); //Debugging information
}
void Reverse() //馬達後退
{
analogWrite(6,0);
analogWrite(9,output*-1);
analogWrite(10,0);
analogWrite(11,output*-1);
Serial.print("R");
}
void Stop() //馬達停止
{
analogWrite(6,0);
analogWrite(9,0);
analogWrite(10,0);
analogWrite(11,0);
Serial.print("S");
}
void setup() {
Serial.begin(115200);
// initialize device
Serial.println(F("Initializing I2C devices..."));
mpu.initialize();
// verify connection
Serial.println(F("Testing device connections..."));
Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));
// load and configure the DMP
devStatus = mpu.dmpInitialize();
// supply your own gyro offsets here, scaled for min sensitivity
mpu.setXGyroOffset(57);
mpu.setYGyroOffset(-29);
mpu.setZGyroOffset(3);
mpu.setZAccelOffset(967);
// make sure it worked (returns 0 if so)
if (devStatus == 0)
{
// turn on the DMP, now that it's ready
Serial.println(F("Enabling DMP..."));
mpu.setDMPEnabled(true);
// enable Arduino interrupt detection
Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
attachInterrupt(0, dmpDataReady, RISING);
mpuIntStatus = mpu.getIntStatus();
// set our DMP Ready flag so the main loop() function knows it's okay to use it
Serial.println(F("DMP ready! Waiting for first interrupt..."));
dmpReady = true;
// get expected DMP packet size for later comparison
packetSize = mpu.dmpGetFIFOPacketSize();
//setup PID
pid.SetMode(AUTOMATIC);
pid.SetSampleTime(10);
pid.SetOutputLimits(-210, 210);
}
else
{
// ERROR!
// 1 = initial memory load failed
// 2 = DMP configuration updates failed
// (if it's going to break, usually the code will be 1)
Serial.print(F("DMP Initialization failed (code "));
Serial.print(devStatus);
Serial.println(F(")"));
}
//初始化馬達輸出引腳
pinMode (6, OUTPUT);
pinMode (9, OUTPUT);
pinMode (10, OUTPUT);
pinMode (11, OUTPUT);
//默認情況下關閉馬達
analogWrite(6,LOW);
analogWrite(9,LOW);
analogWrite(10,LOW);
analogWrite(11,LOW);
}
void loop() {
// 如果程式失敗直接return。停止
if (!dmpReady) return;
// 等待知道MPU中斷返回值可用、數據包正常
while (!mpuInterrupt && fifoCount < packetSize)
{
//無mpu數據,運行PID並輸出到馬達
pid.Compute();
//Print the value of Input and Output on serial monitor to check how it is working.
Serial.print(input); Serial.print(" =>"); Serial.println(output); //
if (input>150 && input<200){//If the Bot is falling
if (output<0) //Falling towards front
Forward(); //Rotate the wheels forward
else if (output>0) //Falling towards back
Reverse(); //Rotate the wheels backward
}
else //If Bot not falling
Stop(); //Hold the wheels still
}
// 重置中斷標誌,並獲取INT_STATUS數據
mpuInterrupt = false;
mpuIntStatus = mpu.getIntStatus();
// 獲取當前FIFO計數
fifoCount = mpu.getFIFOCount();
// check for overflow (this should never happen unless our code is too inefficient)
if ((mpuIntStatus & 0x10) || fifoCount == 1024)
{
// 重置
mpu.resetFIFO();
Serial.println(F("FIFO overflow!"));
// 否則,檢查DMP數據準備中斷
}
else if (mpuIntStatus & 0x02)
{
// 等待正確的可用的數據
while (fifoCount < packetSize) fifoCount = mpu.getFIFOCount();
// 讀取先進先出的包
mpu.getFIFOBytes(fifoBuffer, packetSize);
// track FIFO count here in case there is > 1 packet available
// (this lets us immediately read more without waiting for an interrupt)
fifoCount -= packetSize;
mpu.dmpGetQuaternion(&q, fifoBuffer); //獲取q值
mpu.dmpGetGravity(&gravity, &q); //獲取重力值
mpu.dmpGetYawPitchRoll(ypr, &q, &gravity); //獲取ypr值
input = ypr[1] * 180/M_PI + 180;
}
}
心得:
陳一平:好難做,車子要動不動,平衡不了,不過車型超出預期的剛好,蠻棒的(?),
以下心得是我們這次做平衡車遇到的問題。
廖勇昱:這一過程既煎熬又滿想放棄的,尤其是在看到有錯誤但不知道問題在哪的時候。而往往我們一開始的假說都和真真的問題有些距離,例如:馬達不會動會不會是馬達壞了,結果後來發現是感測器接觸不良,導致沒有訊號。類似的問題也在過程中發生數次,像是感測器用焊錫連接後還會有接觸不良的狀況,以為是銲錫沒銲好,結果是感測器針腳太短導致和麵包板接觸不良。或是Arduino當機,以為是程式問題,結果是感測器訊號以及供電系統等問題所導致的。除錯的過程中非常痛苦,我到最後還是沒除錯完,這告訴我們,有時候還是不要嘗試太有夢想的東西。