目 录
1.绪论 1
1.1研究背景与意义 1
1.2两轮自平衡车的关键技术 2
1.2.1系统设计 2
1.2.2数学建模 2
1.2.3姿态检测系统 2
1.2.4控制算法 3
1.3本文主要研究目标与内容 3
1.4论文章节安排 3
2.系统原理分析 5
2.1控制系统要求分析 5
2.2平衡控制原理分析 5
2.3自平衡小车数学模型 6
2.3.1两轮自平衡小车受力分析 6
2.3.2自平衡小车运动微分方程 9
2.4 PID控制器设计 10
2.4.1 PID控制器原理 10
2.4.2 PID控制器设计 11
2.5姿态检测系统 12
2.5.1陀螺仪 12
2.5.2加速度计 13
2.5.3基于卡尔曼滤波的数据融合 14
2.6本章小结 16
3.系统硬件电路设计 17
3.1 MC9SXS128单片机介绍 17
3.2单片机最小系统设计 19
3.3 电源管理模块设计 21
3.4倾角传感器信号调理电路 22
3.4.1加速度计电路设计 22
3.4.2陀螺仪放大电路设计 22
3.5电机驱动电路设计 23
3.5.1驱动芯片介绍 24
3.5.2 驱动电路设计 24
3.6速度检测模块设计 25
3.6.1编码器介绍 25
3.6.2 编码器电路设计 26
3.7辅助调试电路 27
3.8本章小结 27
4.系统软件设计 28
4.1软件系统总体结构 28
4.2单片机初始化软件设计 28
4.2.1锁相环初始化 28
4.2.2模数转换模块(ATD)初始化 29
4.2.3串行通信模块(SCI)初始化设置 30
4.2.4测速模块初始化 31
4.2.5 PWM模块初始化 32
4.3姿态检测系统软件设计 32
4.3.1陀螺仪与加速度计输出值转换 32
4.3.2卡尔曼滤波器的软件实现 34
4.4平衡PID控制软件实现 35
4.5两轮自平衡车的运动控制 37
4.6本章小结 39
5. 系统调试 40
5.1系统调试工具 40
5.2系统硬件电路调试 40
5.3姿态检测系统调试 41
5.4控制系统PID参数整定 44
5.5两轮自平衡小车动态调试 44
5.6本章小结 45
6. 总结与展望 46
6.1 总结 46
6.2 展望 46
参考文献 47
附 录 48
附录一 系统电路原理图 48
附录二 系统核心源代码 49
致谢 52
2. 系统原理分析
2.1控制系统要求分析
根据系统要求,小车必须要能够在无外界干预下依靠一对平行的车轮保持平衡,并完成前进,后退,左右转弯等动作。分析系统要求可知,保持小车直立和运动的动力都来自于小车的两只车轮,车轮由两只直流电机驱动。因此,从控制角度来看,可以将小车作为一个控制对象,控制输入量是两个车轮的转动速度。整个控制系统可以分为三个子系统:
(1)小车平衡控制:以小车倾角为输入量,通过控制两个电机的正反转保持小车衡。
(2)小车速度控制:在保持平衡的基础上,通过调节小车倾角实现对速度的控制,实际上还是演变为对电机的控制实现小车的速度控制。
(3)小车方向控制:通过控制两个电机间的转速不同实现转向。
小车直立和方向控制任务都是直接通过控制车模两个后轮驱动电机完成的,而速度控制则是通过调节小车倾角完成的。小车不同的倾角会引起车模的加减速,从而达到对小车速度的控制。
三个子系统各自独立进行控制。由于最终都是对同一个控制对象(小车的电机)进行控制,所以各个子系统之间存在着耦合。为了方便分析,在分析其中之一时,假设其它控制对象都已经达到稳定。比如在速度控制时,需要小车已经能够保持直立控制;在方向控制时,需要小车能够保持平衡和速度恒定;同样,在小车平衡控制时,也需要速度和方向控制已经达到平稳。这三个任务中保持小车平衡是关键。由于小车同时受到三种控制的影响,从小车平衡控制的角度来看,其它两个控制就成为干扰。因此对小车速度、方向的控制应该尽量保持平滑,以减少对平衡控制的干扰。以速度调节为例,需要通过改变车模平衡控制中小车倾角设定值,从而改变车模实际倾斜角度,达到速度控制的要求。为了避免影响车模平衡控制,这个车模倾角的改变需要非常缓慢的进行。其中平衡控制是系统的最基本要求,也是整个控制系统的难点。
2.2平衡控制原理分析
控制小车平衡的直观经验来自人类日常生活经验。如人类身体拥有丰富的感知器官,通过大脑调节便可以控制腰部及腿部肌肉保持人体的直立。而一般人通过简单训练就可以让一根直木棍在手指尖保持直立不倒。这需要两个条件:一个是托着木棍的手指可以自由移动;另一个是人的眼睛可以观察木棍的倾斜角度与倾斜趋势(角速度)。这两个条件缺一不可,实际上这就是控制系统中的负反馈机制,如图2-1所示。
图2-1 保持木棍直立的反馈控制系统
自平衡车的控制也是通过负反馈来实现的,与在指尖保持木棍直立比较则相对简单。由于小车只依靠两个车轮着地,车轮与地面会发生相对滚动使得小车倾斜。而小车上装载的姿态检测系统能够对小车的倾斜状况进行实时检测,通过控制器控制车轮转动,抵消在这个维度上的倾斜力矩便可以保持小车平衡,本文转载自http://www.biyezuopin.vip/onews.asp?id=12737如图2-2所示。
图2-2 通过车轮转动保持小车平衡
/*****************************************************
普通IO模拟I2C通信
STC12C5A60S2 IT单片机 AXTL;11.0592MHz
*****************************************************/
#ifndef __I2C_H__
#define __I2C_H__
#include <REG52.H>
#include <math.h> //Keil library
#include <stdio.h> //Keil library
#include <INTRINS.H>
//********端口定义**********************************
sbit SCL=P2^1; //IIC时钟引脚定义
sbit SDA=P2^0; //IIC数据引脚定义
#define SlaveAddress 0xD0 //IIC写入时的地址字节数据,+1为读取
//********函数初始定义******************************
void Delay5us();
void I2C_Start();
void I2C_Stop();
void I2C_SendACK(bit ack);
bit I2C_RecvACK();
void I2C_SendByte(uchar dat);
uchar I2C_RecvByte();
void I2C_ReadPage();
void I2C_WritePage();
void display_ACCEL_x();
void display_ACCEL_y();
void display_ACCEL_z();
uchar Single_ReadI2C(uchar REG_Address); //读取I2C数据
void Single_WriteI2C(uchar REG_Address,uchar REG_data); //向I2C写入数据
//**************************************
//延时5微秒(STC12C5A60S2@12M)
//不同的工作环境,需要调整此函数
//此延时函数是使用1T的指令周期进行计算,与传统的12T的MCU不同
//**************************************/
void Delay5us()
{
uchar n = 4;
while (n--)
{
_nop_();
_nop_();
}
}
//**************************************
//I2C起始信号
//**************************************
void I2C_Start()
{
SDA = 1; //拉高数据线
SCL = 1; //拉高时钟线
Delay5us(); //延时
SDA = 0; //产生下降沿
Delay5us(); //延时
SCL = 0; //拉低时钟线
}
//**************************************
//I2C停止信号
//**************************************
void I2C_Stop()
{
SDA = 0; //拉低数据线
SCL = 1; //拉高时钟线
Delay5us(); //延时
SDA = 1; //产生上升沿
Delay5us(); //延时
}
//**************************************
//I2C发送应答信号
//入口参数:ack (0:ACK 1:NAK)
//**************************************
void I2C_SendACK(bit ack)
{
SDA = ack; //写应答信号
SCL = 1; //拉高时钟线
Delay5us(); //延时
SCL = 0; //拉低时钟线
Delay5us(); //延时
}
//**************************************
//I2C接收应答信号
//**************************************
bit I2C_RecvACK()
{
SCL = 1; //拉高时钟线
Delay5us(); //延时
CY = SDA; //读应答信号
SCL = 0; //拉低时钟线
Delay5us(); //延时
return CY;
}
//**************************************
//向I2C总线发送一个字节数据
//**************************************
void I2C_SendByte(uchar dat)
{
uchar i;
for (i=0; i<8; i++) //8位计数器
{
dat <<= 1; //移出数据的最高位
SDA = CY; //送数据口
SCL = 1; //拉高时钟线
Delay5us(); //延时
SCL = 0; //拉低时钟线
Delay5us(); //延时
}
I2C_RecvACK();
}
//**************************************
//从I2C总线接收一个字节数据
//**************************************
uchar I2C_RecvByte()
{
uchar i;
uchar dat = 0;
SDA = 1; //使能内部上拉,准备读取数据,
for (i=0; i<8; i++) //8位计数器
{
dat <<= 1;
SCL = 1; //拉高时钟线
Delay5us(); //延时
dat |= SDA; //读数据
SCL = 0; //拉低时钟线
Delay5us(); //延时
}
return dat;
}
//**************************************
//向I2C设备写入一个字节数据
//**************************************
void Single_WriteI2C(uchar REG_Address,uchar REG_data)
{
I2C_Start(); //起始信号
I2C_SendByte(SlaveAddress); //发送设备地址+写信号
I2C_SendByte(REG_Address); //内部寄存器地址
I2C_SendByte(REG_data); //内部寄存器数据
I2C_Stop(); //发送停止信号
}
//**************************************
//从I2C设备读取一个字节数据
//**************************************
uchar Single_ReadI2C(uchar REG_Address)
{
uchar REG_data;
I2C_Start(); //起始信号
I2C_SendByte(SlaveAddress); //发送设备地址+写信号
I2C_SendByte(REG_Address); //发送存储单元地址,从0开始
I2C_Start(); //起始信号
I2C_SendByte(SlaveAddress+1); //发送设备地址+读信号
REG_data=I2C_RecvByte(); //读出寄存器数据
I2C_SendACK(1); //接收应答信号
I2C_Stop(); //停止信号
return REG_data;
}
#endif