系统软件设计
5.1 系统初始化
为了防止程序运行过程中跑飞而造成的人身安全的伤害,系统主控制循环程序中添加独立看门狗程序,在程序死锁或跑飞状态下可自动复位,确保行车安全。系统初始化流程图如图5.1所示:
图5.3.1 直立环PD控制代码
以上为角度闭环控制也就是直立PD控制的关键部分程序,一般的控制系统而言,只需使用单纯的比例控制或者PI控制即可,但是相对于那些需要对干扰做出迅速响应的高度灵敏系统来说,还需要引入微分控制来消除抖动。
5.3.2速度环PI控制
一般的速度控制皆采用PI控制器,平衡车系统中线性控制器也选择了这种类型。根据公式可得:偏差=测量值-目标值。我们使用左右两个编码器所测得的数据相加作为测量值,在这里我们需要的是一个可以表示速度变化的变量,所以不用纠结是否要对这个测量值做除以2的计算,因为这样的话会引入舍去误差。速度闭环控制的目的是为了使得系统在保持直立的同时速度为零,所以我们设定目标值速度为零可以得到以下公式:
Encoder_Least =(Encoder_Right-Encoder_Left)-0;
定义速度控制PWM波,做编码器,右编码器数据类型为整形,再将速度、编码器偏差、编码器积分等参数定义为静态全局变量。将上试所计算的最新速度偏差赋值给Encoder_Least变量,并对编码器加权滤波,积分10毫秒得到位移,将积分得到的位移减去速度变化量Movement再赋值给Encoder_Integral,并限制Encoder_Integral的积分幅度,也就是对平衡车最大行进速度的限制,以防止驾驶过快导致的生命危险。速度乘以比例加上速度积分乘以积分赋值给速度控制的PWM,如果检测到电机关闭的话,就清除积分,防止多次积分所带来的误差,最后返回速度控制PWM。核心程序如下图5.3.2所示。
5.4 编码器M法测速
编码器M法测速是一项非常实用的技术,它可以将编码器的测量精度切切实实的提高4倍。如图5.4所示设计中用到的是旋转正交编码器,输出的是正交的A,B两相矩形波,一般的测量方法就是测量单位时间内A相或者B相输出的脉冲次数来计算得到电机转轴当前的速度信息[9]。常规运用中,普遍的做法是利用单片机的输入捕获功能测量任意一相的上升沿或者下降沿,也就是测量对应图5.4的1234数字中的任何一个,这样的话就只能计数三次,而M法测量则是利用软件四倍频的方法同时捕获A,B,相的上升沿与下降沿,在相同的时间里,可以捕获到12次数据,测量精度也随之大大提高。

图5.4 A,B相正交矩形波
5.5 OLED显示程序
设计中使用的是6线的SPI串行通信的OLED显示屏,它的分辨率为128*64,能显示汉字、图片、字符等多样化的信息。如下程序所示,首先在主函数里初始化OLED_Init()子程序,然后打开OLED的显示函数。可以看到,当前模式下OLED第一行显示的内容为当前小车所使用的是内置的DMP滤波算法,速度处于普通模式下。第二行则显示的是左编码器测量电机转轴的实时速度信息,第三行显示的是右编码器测量电机转轴的实时速度信息,非常的直观明了,有助于驾驶人实时动态的了解车子运行速度以控制在安全速度以内。第五行则是显示当前电池的电压值,让人们时刻知道电池电量以确保电池不会过放而造成不必要的经济损失。第六行显示的信息对于调试来说是非常使用的,它显示的是当前车身的倾角信息,给后期的测试带来极大的便利。核心代码如下:
u8 OLED_GRAM[128][8];
void OLED_Refresh_Gram(void)
{
u8 i,n;
for(i=0;i {
OLED_WR_Byte (0xb0+i,OLED_CMD); //éè??ò3μ??·£¨0~7£?
OLED_WR_Byte (0x00,OLED_CMD); //éè????ê??????aáDμíμ??·
OLED_WR_Byte (0x10,OLED_CMD); //éè????ê??????aáD??μ??·
for(n=0;n }
}
void OLED_WR_Byte(u8 dat,u8 cmd)
{
u8 i;
if(cmd)
OLED_RS_Set();
else
OLED_RS_Clr();
for(i=0;i {
OLED_SCLK_Clr();
if(dat&0x80)
OLED_SDIN_Set();
else
OLED_SDIN_Clr();
OLED_SCLK_Set();
dat }
OLED_RS_Set();
}
{
OLED_WR_Byte(0xb0+y,OLED_CMD);
OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);
OLED_WR_Byte((x&0x0f)|0x01,OLED_CMD);
}
void OLED_Display_On(void)
{
OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC?üá?
OLED_WR_Byte(0X14,OLED_CMD); //DCDC ON
OLED_WR_Byte(0XAF,OLED_CMD); //DISPLAY ON
}
void OLED_Display_Off(void)
{
OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC?üá?
OLED_WR_Byte(0X10,OLED_CMD); //DCDC OFF
OLED_WR_Byte(0XAE,OLED_CMD); //DISPLAY OFF
}
void OLED_Clear(void)
{
u8 i,n;
for(i=0;i OLED_Refresh_Gram();//?üD???ê?
}
void OLED_DrawPoint(u8 x,u8 y,u8 t)
{
u8 pos,bx,temp=0;
if(x>127||y>63)return;
pos=7-y/8;
bx=y%8;
temp=1 if(t)OLED_GRAM[x][pos]|=temp;
else OLED_GRAM[x][pos]&=~temp;
}
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode)
{
u8 temp,t,t1;
u8 y0=y;
chr=chr-’ ‘;
for(t=0;t
if(size==12)temp=oled_asc2_1206[chr][t];
else temp=oled_asc2_1608[chr][t];
for(t1=0;t1 {
if(temp&0x80)OLED_DrawPoint(x,y,mode);
else OLED_DrawPoint(x,y,!mode);
temp y++;
if((y-y0)==size)
{
y=y0;
x++;
break;
}
}
}
}
//m^noˉêy
u32 oled_pow(u8 m,u8 n)
{
u32 result=1;
while(n–)result*=m;
return result;
}
void OLED_ShowNumber(u8 x,u8 y,u32 num,u8 len,u8 size)
{
u8 t,temp;
u8 enshow=0;
for(t=0;t
temp=(num/oled_pow(10,len-t-1))%10;
if(enshow0&&t {
if(temp0)
{
OLED_ShowChar(x+(size/2)*t,y,’ ‘,size,1);
continue;
}else enshow=1;
}
void OLED_ShowString(u8 x,u8 y,const u8 *p)
{
#define MAX_CHAR_POSX 122
#define MAX_CHAR_POSY 58
while(p!=’ ’)
{
if(x>MAX_CHAR_POSX){x=0;y+=16;}
if(y>MAX_CHAR_POSY){y=x=0;OLED_Clear();}
OLED_ShowChar(x,y,p,12,1);
x+=8;
p++;
}
}
void OLED_ShowCHinese(u8 x,u8 y,u8 no)
{
u8 t,adder=0;
OLED_Set_Pos(x,y);
for(t=0;t {
OLED_WR_Byte(Myzk[2no][t],OLED_DATA);
adder+=1;
}
OLED_Set_Pos(x,y+1);
for(t=0;t {
OLED_WR_Byte(Myzk[2no+1][t],OLED_DATA);
adder+=1;
}
}
void OLED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 |GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
5.6 主板温度监测显示
主板的温度监测,是笔者在纵观多种电机驱动设计方案后所设计出来的一种基于自然环境下的PCBA的保护。一般的PCB单层电路板多数采用铝基板,多层板则是采用FR-4材料的,他们都有一个正常工作的情况下耐温度值,超过了一定的温度,板子会发生变形、扭曲等状态。而功率场效应管也是一般耐最高温度值多数为150°C或者175°C,那么正常工作的时候肯定是不会到达这么高的温度的,但是当平衡车遇到坑洼,障碍物的时候,电机会几近处于堵转的状态,此时MOS管的输出电流会急剧上升,随之而来的是巨大的发热量。出于安全考虑,在散热片表贴安装多个温度传感器来实时检测MOS管的发热状态以确保行车的安全。
int Read_Temperature(void)
{
float Temp;
Temp=(I2C_ReadOneByte(devAddr,MPU6050_RA_TEMP_OUT_H) if(Temp>32768) Temp-=65536;
Temp=(36.53+Temp/340)*10;
return (int)Temp;
}
5.7 高亮LED车灯闪烁设计
高亮的车灯显示,在行进过程中这是必须的。既可以作为照明使用,也可以指示当前车子运行状态。如下程序所示,当MCU判断车身向左转弯的时候,随即以1/200ms的频率不断地闪烁左车灯,当车子向右转往的时候,右车灯也同样以1/200ms的闪烁频率不停地闪烁以表示车子当前为向右转状态。在直线行驶情况下,中间的车灯一直高亮显示,起装饰和照明作用。
int turn(int encoder_left,int encoder_right,float gyro)
{
static float Turn_Target=0,Turn,Turn_Convert=3,Turn_Count;
float Turn_Bias,Turn_Bias_Integral,Turn_Amplitude=1800;
}
文章知识点与官方知识档案匹配,可进一步学习相关知识算法技能树首页概览35116 人正在系统学习中
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!