找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 20411|回复: 36
收起左侧

Arduino自平衡小车……你值得拥有

  [复制链接]
ID:100312 发表于 2016-4-9 10:44 | 显示全部楼层 |阅读模式
  1. #include  "Wire.h"`
  2. const uint8_t IMUAddress = 0x68; // AD0 is logic low on the PCB
  3. const uint16_t I2C_TIMEOUT = 100; // Used to check for errors in I2C communication

  4. uint8_t i2cWrite(uint8_t registerAddress, uint8_t data, bool sendStop) {
  5.   return i2cWrite(registerAddress, &data, 1, sendStop); // Returns 0 on success
  6. }

  7. uint8_t i2cWrite(uint8_t registerAddress, uint8_t *data, uint8_t length, bool sendStop) {
  8.     Wire.beginTransmission(IMUAddress);
  9.     Wire.write(registerAddress);
  10.     Wire.write(data, length);
  11.     uint8_t rcode = Wire.endTransmission(sendStop); // Returns 0 on success
  12.     if (rcode) {
  13.       Serial.print(F("i2cWrite failed: "));
  14.       Serial.println(rcode);
  15.     }
  16.     return rcode; // See: http://arduino.cc/en/Reference/WireEndTransmission
  17. }

  18. uint8_t i2cRead(uint8_t registerAddress, uint8_t *data, uint8_t nbytes) {
  19.     uint32_t timeOutTimer;
  20.     Wire.beginTransmission(IMUAddress);
  21.     Wire.write(registerAddress);
  22.     uint8_t rcode = Wire.endTransmission(false); // Don't release the bus
  23.     if (rcode) {
  24.       Serial.print(F("i2cRead failed: "));
  25.       Serial.println(rcode);
  26.       return rcode; // See: http://arduino.cc/en/Reference/WireEndTransmission
  27.     }
  28.     Wire.requestFrom(IMUAddress, nbytes, (uint8_t)true); // Send a repeated start and then release the bus after reading
  29.     for (uint8_t i = 0; i < nbytes; i++) {
  30.       if (Wire.available())
  31.         data[i] = Wire.read();
  32.       else {
  33.         timeOutTimer = micros();
  34.         while (((micros() - timeOutTimer) < I2C_TIMEOUT) && !Wire.available());
  35.         if (Wire.available())
  36.           data[i] = Wire.read();
  37.         else {
  38.           Serial.println(F("i2cRead timeout"));
  39.           return 5; // This error value is not already taken by endTransmission
  40.         }
  41.       }
  42.     }
  43.     return 0; // Success
  44. }

  45. /******************************************************/

  46. //2560 pin map  引脚定义好即可,然后改变一下PID的几个值(kp,kd,ksp,ksi)即可,剩下的全部都是固定的程序,
  47. //可能小车会有一点重心不在中点的现象,加一下角度值或者减一点即可
  48. //至于每个MPU6050的误差,自己调节一下即可,不是很难
  49. //调试时先将速度环的ksp,ksi=0,调到基本可以站起来,然后可能会出现倒,或者自动跑起来的时候加上速度环
  50. //这时就会很稳定的站起来,然后用小力气的手推不会倒。
  51. int ENA=11;
  52. int ENB=12;

  53. int IN1=4;
  54. int IN2=5;
  55. int IN3=6;
  56. int IN4=7;

  57. int MAS,MBS;


  58. /* IMU Data */
  59. double accX, accY, accZ;
  60. double gyroX, gyroY, gyroZ;
  61. int16_t tempRaw;

  62. double gyroXangle, gyroYangle; // Angle calculate using the gyro only
  63. double compAngleX, compAngleY; // Calculated angle using a complementary filter
  64. double kalAngleX, kalAngleY; // Calculated angle using a Kalman filter
  65. uint8_t i2cData[14]; // Buffer for I2C data
  66. uint32_t timer;
  67. unsigned long lastTime;      

  68. /***************************************/
  69. double P[2][2] = {{ 1, 0 },{ 0, 1 }};
  70. double Pdot[4] ={ 0,0,0,0};
  71. static const double Q_angle=0.001, Q_gyro=0.003, R_angle=0.5,dtt=0.005,C_0 = 1;
  72. double q_bias, angle_err, PCt_0, PCt_1, E, K_0, K_1, t_0, t_1;
  73. double angle,angle_dot,aaxdot,aax;
  74. double position_dot,position_dot_filter,positiono;

  75. /*-------------Encoder---------------*/

  76. #define LF 0
  77. #define RT 1

  78. //The balance PID
  79. float kp,kd,ksp,ksi;

  80. int Lduration,Rduration;
  81. boolean LcoderDir,RcoderDir;
  82. const byte encoder0pinA = 2;
  83. const byte encoder0pinB = 8;
  84. byte encoder0PinALast;
  85. const byte encoder1pinA = 3;
  86. const byte encoder1pinB = 9;
  87. byte encoder1PinALast;

  88. int RotationCoder[2];
  89. int turn_flag=0;
  90. float move_flag=0;
  91. float right_need = 0, left_need = 0;;

  92. int pwm;
  93. int pwm_R,pwm_L;
  94. float range;
  95. float range_error_all;
  96. float wheel_speed;
  97. float last_wheel;
  98. float error_a=0;

  99. void Kalman_Filter(double angle_m,double gyro_m)
  100. {
  101.     angle+=(gyro_m-q_bias) * dtt;
  102.     Pdot[0]=Q_angle - P[0][1] - P[1][0];
  103.     Pdot[1]=- P[1][1];
  104.     Pdot[2]=- P[1][1];
  105.     Pdot[3]=Q_gyro;
  106.     P[0][0] += Pdot[0] * dtt;
  107.     P[0][1] += Pdot[1] * dtt;
  108.     P[1][0] += Pdot[2] * dtt;
  109.     P[1][1] += Pdot[3] * dtt;
  110.     angle_err = angle_m - angle;
  111.     PCt_0 = C_0 * P[0][0];
  112.     PCt_1 = C_0 * P[1][0];
  113.     E = R_angle + C_0 * PCt_0;
  114.     K_0 = PCt_0 / E;
  115.     K_1 = PCt_1 / E;
  116.     t_0 = PCt_0;
  117.     t_1 = C_0 * P[0][1];
  118.     P[0][0] -= K_0 * t_0;
  119.     P[0][1] -= K_0 * t_1;
  120.     P[1][0] -= K_1 * t_0;
  121.     P[1][1] -= K_1 * t_1;
  122.     angle+= K_0 * angle_err;
  123.     q_bias += K_1 * angle_err;
  124.     angle_dot = gyro_m-q_bias;//也许应该用last_angle-angle
  125. }

  126. void setup() {
  127.     Wire.begin();
  128.     Serial.begin(9600);
  129.     pinMode(IN1, OUTPUT);
  130.     pinMode(IN2, OUTPUT);
  131.     pinMode(IN3, OUTPUT);
  132.     pinMode(IN4, OUTPUT);  
  133.     pinMode(ENA, OUTPUT);
  134.     pinMode(ENB, OUTPUT);
  135.     TWBR = ((F_CPU / 400000L) - 16) / 2; // Set I2C frequency to 400kHz

  136.     i2cData[0] = 7; // Set the sample rate to 1000Hz - 8kHz/(7+1) = 1000Hz
  137.     i2cData[1] = 0x00; // Disable FSYNC and set 260 Hz Acc filtering, 256 Hz Gyro filtering, 8 KHz sampling
  138.     i2cData[2] = 0x00; // Set Gyro Full Scale Range to ±250deg/s
  139.     i2cData[3] = 0x00; // Set Accelerometer Full Scale Range to ±2g
  140.     while (i2cWrite(0x19, i2cData, 4, false)); // Write to all four registers at once
  141.     while (i2cWrite(0x6B, 0x01, true)); // PLL with X axis gyroscope reference and disable sleep mode

  142.     while (i2cRead(0x75, i2cData, 1));
  143.     if (i2cData[0] != 0x68) { // Read "WHO_AM_I" register
  144.         Serial.print(F("Error reading sensor"));
  145.         while (1);
  146.     }

  147.     delay(20); // Wait for sensor to stabilize

  148.     while (i2cRead(0x3B, i2cData, 6));
  149.     accX = (i2cData[0] << 8) | i2cData[1];
  150.     accY = (i2cData[2] << 8) | i2cData[3];
  151.     accZ = (i2cData[4] << 8) | i2cData[5];


  152.     double roll  = atan2(accX, accZ) * RAD_TO_DEG;
  153.     EnCoderInit();
  154.     timer = micros();

  155.       //The balance PID
  156.     kp= 42;//24.80;43
  157.     kd= 1.9;//9.66;1.4
  158.     ksp= 8.5;//4.14;
  159.     ksi= 2.1;//0.99; 0.550
  160. }

  161. void EnCoderInit()
  162. {
  163.     pinMode(8,INPUT);
  164.     pinMode(9,INPUT);
  165.     attachInterrupt(LF, LwheelSpeed, RISING);
  166.     attachInterrupt(RT, RwheelSpeed, RISING);
  167. }

  168. void pwm_calculate()
  169. {
  170.     unsigned long  now = millis();       // 当前时间(ms)
  171.     int Time = now - lastTime;
  172.     int range_error;
  173.     range += (Lduration + Rduration) * 0.5;
  174.     range *= 0.9;
  175.     range_error = Lduration - Rduration;
  176.     range_error_all += range_error;
  177.    
  178.     wheel_speed = range - last_wheel;   
  179.     //wheel_speed = constrain(wheel_speed,-800,800);
  180.     //Serial.println(wheel_speed);
  181.     last_wheel = range;  
  182.     pwm = (angle + 0.825) * kp + angle_dot * kd + range * ksp + wheel_speed * ksi;     
  183.     if(pwm > 255)pwm = 255;
  184.     if(pwm < -255)pwm = -255;
  185.    
  186.     if(turn_flag == 0)
  187.     {
  188.          pwm_R = pwm + range_error_all;
  189.          pwm_L = pwm - range_error_all;
  190.     }
  191.     else if(turn_flag == 1)     //左转
  192.     {
  193.         pwm_R = pwm ;  //Direction PID control
  194.         pwm_L = pwm + left_need * 68;
  195.         range_error_all = 0;     //clean
  196.     }
  197.     else if(turn_flag == 2)
  198.     {
  199.         pwm_R = pwm + right_need * 68;  //Direction PID control
  200.         pwm_L = pwm ;
  201.         range_error_all = 0;     //clean
  202.     }
  203.       
  204.        Lduration = 0;//clean
  205.        Rduration = 0;
  206.        lastTime = now;
  207. }

  208. void PWMD()
  209. {  
  210.       if(pwm>0)
  211.       {
  212.           digitalWrite(IN1, HIGH);
  213.           digitalWrite(IN2, LOW);
  214.           digitalWrite(IN3, LOW);
  215.           digitalWrite(IN4, HIGH);   
  216.       }
  217.       else if(pwm<0)
  218.       {
  219.           digitalWrite(IN1, LOW);
  220.           digitalWrite(IN2, HIGH);
  221.           digitalWrite(IN3, HIGH);
  222.           digitalWrite(IN4, LOW);
  223.       }
  224.       int PWMr = abs(pwm);
  225.       int PWMl = abs(pwm);
  226.    
  227.       analogWrite(ENB, max(PWMl,60)); //PWM调速a==0-255  51
  228.       analogWrite(ENA, max(PWMr,60)); //PWM调速a==0-255  54
  229.       
  230. }

  231. void LwheelSpeed()
  232. {
  233.       if(digitalRead(encoder0pinB))
  234.         Lduration++;
  235.       else Lduration--;
  236. }


  237. void RwheelSpeed()
  238. {
  239.       if(digitalRead(encoder1pinB))
  240.         Rduration--;
  241.       else Rduration++;
  242. }

  243. void control()
  244. {
  245.     if(Serial.available()){
  246.       int val;
  247.       val=Serial.read();
  248.       switch(val){
  249.         case 'w':
  250.           if(move_flag<5)move_flag += 0.5;
  251.           else  move_flag = 0;
  252.           break;
  253.         case 's':
  254.           if(move_flag<5)move_flag -= 0.5;
  255.           else  move_flag = 0;
  256.          Serial.println("back");
  257.           break;
  258.         case 'a':
  259.           turn_flag = 1;
  260.           left_need = 0.6;
  261.           Serial.println("zuo");
  262.           break;
  263.         case 'd':
  264.           turn_flag = 2;
  265.           right_need = 0.6;
  266.           Serial.println("you");
  267.           break;
  268.         case 't':
  269.           move_flag=0;
  270.           turn_flag=0;
  271.           right_need = left_need = 0;
  272.           Serial.println("stop");
  273.           break;
  274.         default:
  275.           break;
  276.           }
  277.       }
  278. }

  279. void loop()
  280. {

  281.     //control();
  282.     while (i2cRead(0x3B, i2cData, 14));
  283.     accX = ((i2cData[0] << 8) | i2cData[1]);
  284.     //accY = ((i2cData[2] << 8) | i2cData[3]);
  285.     accZ = ((i2cData[4] << 8) | i2cData[5]);
  286.     //gyroX = (i2cData[8] << 8) | i2cData[9];
  287.     gyroY = (i2cData[10] << 8) | i2cData[11];
  288.     //gyroZ = (i2cData[12] << 8) | i2cData[13];

  289.     double dt = (double)(micros() - timer) / 1000000; // Calculate delta time
  290.     timer = micros();

  291.     double roll  = atan2(accX, accZ) * RAD_TO_DEG - move_flag;


  292.     //double gyroXrate = gyroX / 131.0; // Convert to deg/s
  293.     double gyroYrate = -gyroY / 131.0; // Convert to deg/s

  294.     //gyroXangle += gyroXrate * dt; // Calculate gyro angle without any filter
  295.     //gyroYangle += gyroYrate * dt;

  296.     Kalman_Filter(roll,gyroYrate);   
  297.     if(abs(angle)<35){
  298.         //Serial.println(angle);   
  299.         pwm_calculate();
  300.         PWMD();
  301.     }
  302.     else{
  303.         analogWrite(ENB, 0); //PWM调速a==0-255
  304.         analogWrite(ENA, 0); //PWM调速a==0-255
  305.     }  
  306.     delay(2);
  307. }
复制代码

器件:Arduino UNO或者Mega2560、LM298模块,MPU6050传感器、两个编码电机,
          公母杜邦线,蓝牙HT-06、锂电池18650(4)——(成本大概200左右)

如果用手机控制可以下载应用宝里面的一个“小蜜蜂机器人”APP,个人觉得这个APP功能超强大,大神写的APP就是屌!专门连接蓝牙的串口助手,还有调试舵机的功能,简直可以上天了。

调试过程中会学习到很多东西,希望大家可以看看,虽然我也做的很好,不过在这期间学习到了很多东西……
几天就可以看到你想看到的现象哟,还等什么,开始动手吧,慢慢看资料慢慢理解(不对之处,敬请谅解)
                                                                                                                                  ——初学者心声
http://v.youku.com/v_show/id_XMTUyODUwMDkxMg==.html




评分

参与人数 1黑币 +5 收起 理由
蔡定银 + 5 很给力!

查看全部评分

回复

使用道具 举报

ID:1 发表于 2016-4-9 12:27 | 显示全部楼层
谢谢分享 楼主能传个压缩包吗 好像还有些头文件没有
回复

使用道具 举报

ID:100312 发表于 2016-4-9 16:55 | 显示全部楼层
附件里面有wire的库文件,MPU6050原始数据分析,以及PWM输出的原理,还有小蜜蜂蓝牙助手的调试APP……

Wire_bluetooth_MPU6050数据分析.zip

5 MB, 下载次数: 145, 下载积分: 黑币 -5

值得看看

回复

使用道具 举报

ID:91641 发表于 2016-4-15 22:35 | 显示全部楼层
s414545584 发表于 2016-4-9 16:55
附件里面有wire的库文件,MPU6050原始数据分析,以及PWM输出的原理,还有小蜜蜂蓝牙助手的调试APP……

arduino平衡车自动跑起来的时候怎么加上速度环呢?
回复

使用道具 举报

ID:100312 发表于 2016-4-16 20:07 | 显示全部楼层
xiaobolinux 发表于 2016-4-15 22:35
arduino平衡车自动跑起来的时候怎么加上速度环呢?

直接加呀,代码里面有
回复

使用道具 举报

ID:91641 发表于 2016-4-17 00:00 | 显示全部楼层
s414545584 发表于 2016-4-16 20:07
直接加呀,代码里面有

但是我的平衡车能平衡但是只是往一个方向跑,跑一段就倒了,不能原地控制。
回复

使用道具 举报

ID:91641 发表于 2016-4-17 09:54 | 显示全部楼层
s414545584 发表于 2016-4-16 20:07
直接加呀,代码里面有

哪句是关于速度环的呀
回复

使用道具 举报

ID:91641 发表于 2016-4-17 10:23 | 显示全部楼层
然后可能会出现倒,或者自动跑起来的时候加上速度环
//这时就会很稳定的站起来,然后用小力气的手推不会倒。


这个不行啊,一直往前,倒。 然后代码里面没有加速度环呀,,怎么加呀,,速度环是啥呀,,求教
回复

使用道具 举报

ID:100312 发表于 2016-4-17 12:21 | 显示全部楼层
xiaobolinux 发表于 2016-4-17 10:23
然后可能会出现倒,或者自动跑起来的时候加上速度环
//这时就会很稳定的站起来,然后用小力气的手推不会倒 ...

加q414545584,私聊,交流一下
回复

使用道具 举报

ID:134558 发表于 2016-7-21 23:21 | 显示全部楼层
  你好 请问在哪儿可以调初始角?
回复

使用道具 举报

ID:134558 发表于 2016-7-22 19:49 | 显示全部楼层
  请问一下楼主  那个电机编码器的线 接到车上的脚是要怎么定义?
回复

使用道具 举报

ID:100312 发表于 2016-7-23 12:21 | 显示全部楼层
yangtao 发表于 2016-7-21 23:21
你好 请问在哪儿可以调初始角?

double roll  = atan2(accX, accZ) * RAD_TO_DEG - move_flag;
这个就是x轴方向的角度,move_flag就是偏移量,调偏移量,用串口读数直到基本为0
回复

使用道具 举报

ID:100312 发表于 2016-7-23 12:22 | 显示全部楼层
yangtao 发表于 2016-7-22 19:49
请问一下楼主  那个电机编码器的线 接到车上的脚是要怎么定义?

编码电机上的AB相线一个用来检测高低电平然后自加或者自减,另外一个用来触发中断函数,你可以看上面的引脚定义就知道啦
回复

使用道具 举报

ID:134558 发表于 2016-7-23 23:34 | 显示全部楼层
s414545584 发表于 2016-7-23 12:22
编码电机上的AB相线一个用来检测高低电平然后自加或者自减,另外一个用来触发中断函数,你可以看上面的引 ...

谢谢了 已经调平衡了
回复

使用道具 举报

ID:116662 发表于 2016-8-2 14:11 来自手机 | 显示全部楼层
好资料,辛苦楼主了
回复

使用道具 举报

ID:137339 发表于 2016-8-22 22:46 | 显示全部楼层
赞一个
回复

使用道具 举报

ID:137396 发表于 2016-8-23 15:28 | 显示全部楼层
這個還ˊ需要再修改一下 使可以適應四處移動
回复

使用道具 举报

ID:100312 发表于 2016-8-25 12:21 | 显示全部楼层
a98765432180 发表于 2016-8-23 15:28
這個還ˊ需要再修改一下 使可以適應四處移動

里面不是有个control函数吗,那就是用蓝牙控制的呀……
回复

使用道具 举报

ID:139027 发表于 2016-9-10 19:32 | 显示全部楼层
楼主可以分享个接线图吗?
回复

使用道具 举报

ID:189412 发表于 2017-4-14 06:36 | 显示全部楼层
thank you so much.
回复

使用道具 举报

ID:173821 发表于 2017-4-17 03:09 | 显示全部楼层
本帖最后由 bonzewu1213 于 2017-4-19 03:16 编辑

請問樓主,目前我按您貼的源碼,上傳後,僅有右輪正常運轉,傾斜加速,反向傾斜,反向加速,但僅有右輪會動,左輪完全不動,是不是源碼有地方被註記起來,沒有打開,謝謝。

评分

参与人数 1黑币 +20 收起 理由
admin + 20 回帖助人的奖励!

查看全部评分

回复

使用道具 举报

ID:173821 发表于 2017-4-17 20:54 | 显示全部楼层
哈哈,ENA及ENB應接11,12,接錯了,接成10,11,改正後就可以了,已經解決,只是重心還要調一下,基本上已正常運作,太棒了,感謝樓主,大大感謝。

评分

参与人数 1黑币 +20 收起 理由
admin + 20 回帖助人的奖励!

查看全部评分

回复

使用道具 举报

ID:191204 发表于 2017-4-19 14:07 | 显示全部楼层
bonzewu1213 发表于 2017-4-17 03:09
請問樓主,目前我按您貼的源碼,上傳後,僅有右輪正常運轉,傾斜加速,反向傾斜,反向加速,但僅有右輪會動 ...

我上传后怎么两个轮子都没动
回复

使用道具 举报

ID:173821 发表于 2017-4-20 00:36 | 显示全部楼层
仔仔12 发表于 2017-4-19 14:07
我上传后怎么两个轮子都没动

據我所知,L298N 有分A,B 兩型, 你要買那種 6根排針上面都沒有jumper 的那種,馬達電源需要有12V,12V接線要接對,接錯,很容易把線燒著了,得趕緊滅火 XD 如果接線都對了,應該跑得起來才對,原始作者,應該給他大大讚一個!
回复

使用道具 举报

ID:216395 发表于 2017-7-2 17:55 | 显示全部楼层
不错,真的不错,我正在找呢
回复

使用道具 举报

ID:216395 发表于 2017-7-2 17:56 | 显示全部楼层
这些代码我正需要,没想到在这个网站找到了,不错!
回复

使用道具 举报

ID:232830 发表于 2017-9-13 10:54 | 显示全部楼层
这些代码我正需要,没想到在这个网站找到了,不错!
回复

使用道具 举报

ID:232830 发表于 2017-9-13 10:54 | 显示全部楼层
这些代码我正需要,没想到在这个网站找到了,不错!
回复

使用道具 举报

ID:214383 发表于 2017-9-16 00:50 来自手机 | 显示全部楼层
学习了
回复

使用道具 举报

ID:238956 发表于 2017-10-12 19:40 | 显示全部楼层
学习一下
回复

使用道具 举报

ID:177577 发表于 2017-11-27 19:54 | 显示全部楼层
楼主有接线图吗?
回复

使用道具 举报

ID:254763 发表于 2017-11-28 16:40 | 显示全部楼层
好棒,不知运行结果如何。
回复

使用道具 举报

ID:233519 发表于 2018-5-1 10:04 | 显示全部楼层
这个厉害了!!!
回复

使用道具 举报

ID:315589 发表于 2018-5-3 11:21 | 显示全部楼层
真的很溜哦
回复

使用道具 举报

ID:79544 发表于 2018-7-29 18:10 | 显示全部楼层
楼主您好!分享出原理图啊谢谢!!
回复

使用道具 举报

ID:161201 发表于 2019-1-15 02:28 | 显示全部楼层
好棒,不知运行结果如何
回复

使用道具 举报

ID:540025 发表于 2019-5-16 17:17 | 显示全部楼层
感谢楼主的分享,刚从百度上搜到这篇文章,小蜜蜂app的大神真棒。就是app资源确实难找
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表