您好,欢迎来到吉趣旅游网。
搜索
您的当前位置:首页用51单片机做小车的控制

用51单片机做小车的控制

来源:吉趣旅游网


用51做小车的相关思路和程序

51小车作为一种入门小车,其基本原理与飞思卡尔比赛的小车相似,共分为硬件和软件两个部分。其中软件部分可分为传感器数据采集,舵机控制,速度控制等,这里现在对51小车的软件进行一个详细的介绍

1.程序思路概述:

首先51单片机需要通过红外传感器监测道路信息,然后将传感器得到的数据进行转换。最后将转换后的数据通过某种控制算法,计算出舵机和电机的控制量,使小车能够沿着跑道行驶。主体控制流程可以表示为如下图形:

2.程序算法

2.1数据采集

小车传感器为8个红外对管,经过电压比较器后,每个红外对管都有0和1两种状态。因此,8个红外对管产生的数据刚好为1个字节。因此程序有两种方式将这8位数据读入:1、可以将I/O口数据当做一个字节,直接赋给一个字符变量;2、将8位数据按位读取,分别赋给字符变量的每一位。我们的示例程序中采用的就是第二种方法。

1

2.2数据处理

由于读入的数据并不方便直接参与控制计算,因此先将该数据集分成16类,分别对应于小车不同的位置信息,由-7~+7表示,其中+7表示引导线位于小车最左侧,0表示引导线位于小车中部,-7表示引导线位于小车最右侧,8表示未检测到引导线或其他错误情况。

2.3控制算法

上面的转换后的数据可以作为控制计算的输入,即小车与赛道的偏差信息。方向的控制算法可以采用位置型PD算法。

其中P为比例环节,简单的说就是根据当前小车与赛道的偏差直接进行控制,偏离多少就转多少,偏离越多,舵机转向越多。可以通过调节比例系数改变转向的幅度。系数越大,转向幅度越大,转向越快。

D称为微分环节,这里的微分信息指上面偏差信息的微分,可以简单理解为小车偏离赛道的速率。可以简单的将本次的偏差信息与上一次偏差的差值算作微分项。微分项的控制方法是说,小车偏离跑道越快,方向就要转的越多,反之则转向越小。因此当小车逐渐靠近跑道时,微分项可以使小车偏转量减小,使小车不至于偏向另一边。可以通过改变微分项的系数来改变这种调节作用的程度。一般来讲,微分系数越大,小车对于弯道的响应就越灵敏,行驶时也更稳定。

由于小车在不同的位置时,可能需要不同的比例系数或微分系数,因此可以考虑在不同的情况下,采用不同的系数进行运算。示例程序中采用的就是这种方法。

2

速度控制也可以采用PD算法进行控制。为了使程序简单化,示例程序中采用了匀速的控制方式。想要提高成绩的同学可以自行加入速度控制程序

2.4生成PWM

由于51单片机没有PWM模块,因此需要通过通用I/O口进行模拟来输出舵机和电机所需的PWM波。

可以分别使用一个定时器来作为一路PWM波的计时器。先将I/O口置位,通过高电平时间确定好定时器的初值,当定时器产生中断时,再将I/O口清零,并设定低电平时间,由此循环即可产生PWM波。其中,高电平时间由上面的控制计算得出,低电平时间由PWM周期减去高电平时间得到。

鲫鱼电子工作室开发的51智能车是一种入门型智能小车,基本原理和参加飞思卡尔比赛的小车相似,但是其制作要比正式比赛的小车简单。目的是为了让大家更快地进入飞思卡尔小车的制作,对智能小车有个更好的理解。

51小车分为硬件和软件两个部分,其中硬件又分为三个主要模块,51核心板模块,传感器模块,扩展板模块。软件部分分为传感器数据采集,舵机控制,速度控制等。

51核心板

51核心板包括几个小的部分,下载部分,单片机最小系统部分,led用户灯,通用IO接口。这次开发的51核心板的主控芯片为STC,选择这块芯片的一个重要原因是他的下载调试方便,可以通过串口直接下载,同时又有51单片机的所有资源。

3

下载部分主要使用了一片PL2303,它是一片USB转串口功能的芯片,由于现在大部分计算机都不包括串口,但每台计算机都有USB口,于是我们想是否可以通过USB下载,最后在网上找到了一片USB转串口的芯片,这样我们下载程序将更简单,不需要再单独购买51下载器,极大地方便了同学们的调试。

下载部分具体原理图如下:

#include //包括一个52 标准内核的头文件

#include

/*****************定义数据类型(方便移植)*****************/

typedef unsigned char UINT8;

typedef char SINT8;

typedef unsigned int UINT16;

typedef int SINT16;

/**********************************************************/

/*****************定义布尔值(增强程序可读性)*************/

#define HIGH_LEVEL 1

4

#define LOW_LEVEL 0

/***********************************************************/

/**********定义相关常数(移植于不同的小车时,需根据实际情况改变该常数***********/

#define SERVOMID 1270 //舵机中值

#define SERVOMOSTLEFT 1680 //舵机左值

#define SERVOMOSTRIGHT 870 //舵机右值

#define SERVO_PERIOD 20000 //舵机PWM 周期:20ms,晶振12M 公式:计数值=定时时间*晶振

//频率/12,如20ms: 计数值=0.02 s * 12 000 000 Hz/12 = 20000

#define MOTOR_PERIOD 10000 //电机PWM 周期:10ms,晶振12M

/**************************************************************/

/*****************输出口定义*************************/

sbit SevorPort = P0^1;

5

sbit MotorPort = P0^6;

sbit P20 = P2^0;

sbit P21 = P2^1;

sbit P22 = P2^2;

sbit P23 = P2^3;

sbit P24 = P2^4;

sbit P25 = P2^5;

sbit P26 = P2^6;

sbit P27 = P2^7;

/*******************************************************/

//*****************定义公共变量**************************/

UINT8 KServoD=10; //舵机D 参数

UINT8 KServoP[5]={65,40,10,40,65}; //舵机分段P 参数

6

UINT8 SampleData=0; //采样数据

SINT8 Offset=0; //当前赛道位置

SINT8 LastOffset[2]={0}; //上一次赛道位置

UINT16 SevorPWM=0; //舵机PWM 高电平时间

UINT16 MotorPWM=0; //电机PWM 高电平时间

/*******************************************************/

/********************初始化函数*************************/

void Init(void)

{

TMOD=0x11; //定时器0,16 位工作方式;定时器1,16 位工作方式

TR0=1; //启动定时器0

TR1=1; //启动定时器1

ET0=1; //打开定时器0 中断

7

ET1=1; //打开定时器0 中断

EA=1; //打开总中断

} //开启中断和定时器

/**************************************************************/

/********************数据采样函数************************/

void Sample(void)

{

SampleData=0;

if(P20==0)

SampleData|=0x01;

if(P22==0)

SampleData|=0x02;

if(P21==0)

8

SampleData|=0x04;

if(P23==0)

SampleData|=0x08;

if(P24==0)

SampleData|=0x10;

if(P26==0)

SampleData|=0x20;

if(P25==0)

SampleData|=0x40;

if(P27==0)

SampleData|=0x80;

}

/***********************************************************/

9

/********************确定赛道位置*************************/

void ConfirmLocation(void)

{

switch(SampleData) //根据采集到的值进行判断

{

case 0x80:Offset=7;break; //1000 0000 最左边1 个光电传感器检测到黑线

case 0xc0:Offset=6;break; //1100 0000 最左边2 个光电传感器检测到黑线

case 0x40:Offset=5;break; //0100 0000 依次类推

case 0x60:Offset=4;break; //0110 0000

case 0x20:Offset=3;break; //0010 0000

case 0x30:Offset=2;break; //0011 0000

case 0x10:Offset=1;break; //0001 0000

case 0x18:Offset=0;break; //0001 1000

10

case 0x08:Offset=-1;break; //0000 1000

case 0x0c:Offset=-2;break; //0000 1100

case 0x04:Offset=-3;break; //0000 0100

case 0x06:Offset=-4;break; //0000 0110

case 0x02:Offset=-5;break; //0000 0010

case 0x03:Offset=-6;break; //0000 0011

case 0x01:Offset=-7;break; //0000 0001

default: Offset=8;break; //其余情况包括跑道丢失于检测错误

}

}

/****************************************************************************/

/***********赛道位置数据滤波(减少某次采样错误对系统的干扰)****************/

void Filter(void)

11

{

if (Offset==8) //滤除错误信号,没有检测到黑线(是需要保持上一次测量值的)

{

Offset=LastOffset[0];

}

else if(abs(Offset-LastOffset[0])>5) //滤除尖峰信号

{

Offset=LastOffset[0];

}

}

/**********************舵机控制函数******************************************/

void ServoCtrl(void)

{

12

UINT8 p=0,d=0;

SINT16 PID=0;

if(Offset>=-7&&Offset<-4) /***********************/

p=KServoP[0]; else if (Offset>=-4&&Offset<-1) p=KServoP[1]; else if (Offset>=-1&&Offset<2) p=KServoP[2]; else if (Offset>=2&&Offset<5)

p=KServoP[3];

else if (Offset>=5&&Offset<=7)

p=KServoP[4]; d=KServoD;

/*分*/

/*段*/

/*P*/

/*参*/

/*数*/

/***********************/

13

PID=p*Offset+d*(Offset-LastOffset[0]); //PID 位置型算式

SevorPWM=SERVOMID+PID; //舵机输出量

if(SevorPWM>SERVOMOSTLEFT)SevorPWM=SERVOMOSTLEFT; //限幅

if(SevorPWM/*********************舵机测试函数*************************************/

ServoTest()

{

// SevorPWM=SERVOMID; //测试舵机中值

SevorPWM=SERVOMOSTLEFT; //测试舵机极左值

// SevorPWM=SERVOMOSTRIGHT; //测试舵机极右值

}

/********************速度控制函数*************************************/

14

//限幅

void MotorCtrl(void)

{

MotorPWM=3000;

}

/*********************数据存储函数*******************************************/

void SaveData(void)

{

static UINT8 TCnt2=0;

if(TCnt2==20)

{

LastOffset[1]=LastOffset[0];

TCnt2=0;

}

15

TCnt2++;

LastOffset[0]=Offset;

}

/**********************主函数*******************************/

void main(void)

{

Init(); //初始化

while (1)

{

Sample(); //采样

ConfirmLocation(); //确认黑线位置

Filter(); //滤波

ServoCtrl(); //舵机控制

16

//ServoTest(); //舵机测试

MotorCtrl(); //电机控制

SaveData();

}

}

/**********************舵 机 定 时 器 中******************************/

void TC0Isr(void) interrupt 1

{

static UINT8 TC20msFlag=LOW_LEVEL; //舵机输出状态标志

UINT16 LowlvlTime=0;

UINT16 HighlvlTime=0;

if(TC20msFlag==HIGH_LEVEL)

{

17

断 函数

LowlvlTime=65535-(SERVO_PERIOD-SevorPWM);

TH0=LowlvlTime/256;

TL0=LowlvlTime%256;

SevorPort=LOW_LEVEL;

TC20msFlag=LOW_LEVEL;

}

else

{

HighlvlTime=65535-SevorPWM;

TH0=HighlvlTime/256;

TL0=HighlvlTime%256;

SevorPort=HIGH_LEVEL;

TC20msFlag=HIGH_LEVEL;

18

}

}

/****************************电机定时器中断函数********************************/

void TC1Isr(void) interrupt 3

{

static UINT8 TC10msFlag=LOW_LEVEL; //电机输出状态标志

UINT16 LowlvlTime=0;

UINT16 HighlvlTime=0;

if(TC10msFlag==HIGH_LEVEL)

{

LowlvlTime=65535-(MOTOR_PERIOD-MotorPWM);

TH1=LowlvlTime/256;

TL1=LowlvlTime%256;

19

MotorPort=LOW_LEVEL;

TC10msFlag=LOW_LEVEL;

}

else

{

HighlvlTime=65535-MotorPWM;

TH1=HighlvlTime/256;

TL1=HighlvlTime%256;

MotorPort=HIGH_LEVEL;

TC10msFlag=HIGH_LEVEL;

}

}

传感器部分相对比较容易,由8个相同的传感器单元构成,每个传感器单元由红外对管和比较器构成,红外对管使用的是ST178,正向电流最大为50mA,电压最大为6V;

20

比较器用的是LM324,是一个四比较器集成的芯片;基本原理是利用红外接收管随接受强度不一样,导通程度不一样,从而使信号输出端的电压不同,与设置的阈值进行比较,得到二值化的数据。

备注:传感器模块中有两个比较器,比较器的参考电压是REF1和REF2,REF1,2是通过配置上图左边的电阻分压而得,具体取多少,可以根据实验效果得到,上图我们取的阈值电压是2.5V也就是电源电压的一半,同学们可以通过改变上下两边的电阻调整阈值电压,注意电阻的单位最好是K欧级,5~100K内更换电阻。关于上面的电阻功能及替换介绍,上面的R1是红外发射端的限流电阻,电阻的大小根据所采用的元件特性决定,我们这次使用的是ST178,他的电流范围为0~50mA左右,因此电阻大小在100以上都是可以的,不过建议150欧以上,另外有的同学买的可能是与ST178兼容的红外对管,这时电阻大小要根据你们使用的元件选择。图中R6是下拉电阻,使用的原因是我们希望红外对管在黑线上能稳定输出电压很低,能够确保在黑线上输出稳定的低电平。R3是上拉电阻,使用上拉的原因是LM324是集电极开路的,所以加上拉电阻才能输出高电平,注意点上拉和下拉电阻的范围5K~20K。另外在焊接红外对管时要注意的是我们这次的传感器模块是前面发射后面接收,因此,同学们在焊接时最好先测试下你们所买的元件,哪边是发射哪边是接收,我们统一购买的是白色是发射,黑色是接收。

扩展板模块

扩展板的结构稍微复杂些,里面包含的部分较多,其中包括电源模块,舵机驱动模块,电机驱动模块,拨码开关配置模块,通用IO口等。

电源模块使用的是一片低压差的5V线性稳压芯片LM2940,压差可以小到1V,非常适合这次大众组使用,大众组的电池电压是6V,充满电在7V左右,因此可以保证系统稳

21

定工作。下图是电源模块的原理图:

备注:这个电路没什么特别的地方,参考芯片提供的典型应用电路图就可以了。

舵机控制部分,此部分主要是一个三极管放大,作用是IO口直接驱动电流不够,无法驱动舵机,通过三极管的经典放大电路放大电流驱动舵机,没什么特别的地方,就不多说了,看图,舵机控制部分原理图:

半桥驱动的实质就是控制两个开关的开断,当上开下闭时,电流流过电机,电机转动,当上闭下开的时候电机短路,进行反馈制动,通过控制上开下闭的占空比可以实现电机的调速。具体把开关换成MOS管后的电路图如下,增加了逻辑译码控制,MOS管延时控制,及光电隔离部分,目的是保证系统能更稳定的工作。扩展板上驱动模块的电路原理图:

22

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- jqkq.cn 版权所有 赣ICP备2024042794号-4

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务