智能小车循迹模块
一. PWM电机调速原理
控制电机的时候,电源并非连续地向电机供电,而是在一个特定的频率下以方波脉冲的形式提供电能。电机实际上是一个大电感,它有阻碍输入电流和电压突变的能力,因此脉冲输入信号被平均分配到作用时间上,这样,改变在始能端EN1 和EN2 上输入方波的占空比就能改变加在电机两端的电压大小,从而改变了转速。
方法:
(1)软件:设置不同的延时时间得到不同的占空比(本实验使用),采用定时器中断来产生PWM波形
(2)硬件:用具有硬件PWM功能的芯片
注:占空比和电机转速并不是简单的线性关系。(电机调速后的能量,很大一部分会损耗在三极管上发热)
二. 循迹实现原理
平面为黑色:红外光反射量很少,传感器输出高电平1(达不到传感器动作的水平),L1(L2)信号灯不亮
平面为白色:红外光反射量很多,传感器输出低电平0,L1(L2)信号灯亮
各种循迹方法:
- 红外对管循迹法:利用黑、白色对红外线的吸收
作用不同; - 摄像头循迹法:利用摄像头读取赛道信息,分为
模拟和数字 - 激光管循迹法:和红外循迹法原理相似,但是检
测距离远; - 电磁循迹法
三. 循迹硬件调试
W1,W2调节----顺时针灵敏度增加,逆时针灵敏度降低
灵敏度越高越不易检测到黑线,因为灵敏度太高黑色反射的红外光都能被传感器识别
四. 程序
#include<AT89X52.H> //包含51单片机头文件,内部有各种寄存器定义
#include<HJ-4WD_PWM.H> //包含智能小车驱动IO口定义等函数
//主函数
void main(void)
{
TMOD=0X01;
TH0= 0XFc;
TL0= 0X18;
TR0= 1;
ET0= 1;
EA = 1; //1ms定时
while(1) //无限循环
{
if(Left_1_led==0&&Right_1_led==0)
run(); //调用前进函数
else
{
if(Left_1_led==1&&Right_1_led==0) //左边检测到黑线
{
leftrun(); //调用小车左转函数
}
if(Right_1_led==1&&Left_1_led==0) //右边检测到黑线
{
rightrun(); //调用小车右转函数
}
}
}
}
HJ-4WD_PWM.H文件:
#ifndef _LED_H_
#define _LED_H_
//定义小车驱动模块输入IO口
sbit IN1=P1^2;
sbit IN2=P1^3;
sbit IN3=P1^6;
sbit IN4=P1^7;
sbit EN1=P1^4;
sbit EN2=P1^5;
#define Left_1_led P3_3 //左传感器
#define Right_1_led P3_2 //右传感器
#define Left_moto_pwm P1_5 //PWM信号端
#define Right_moto_pwm P1_4 //PWM信号端
#define Left_moto_go {P1_2=0,P1_3=1;} //左电机向前走
#define Left_moto_back {P1_2=1,P1_3=0;} //左边电机向后转
#define Left_moto_Stop {P1_5=0;} //左边电机停转
#define Right_moto_go {P1_6=1,P1_7=0;} //右边电机向前走
#define Right_moto_back {P1_6=0,P1_7=1;} //右边电机向后走
#define Right_moto_Stop {P1_4=0;} //右边电机停转
unsigned char pwm_val_left =0;//变量定义
unsigned char push_val_left =0;// 左电机占空比N/20
unsigned char pwm_val_right =0;
unsigned char push_val_right=0;// 右电机占空比N/20
bit Right_moto_stop=1;
bit Left_moto_stop =1;
unsigned int time=0;
/************************************************************/
//延时函数
void delay(unsigned int k)
{
unsigned int x,y;
for(x=0;x<k;x++)
for(y=0;y<2000;y++);
}
/************************************************************/
//前进
void run(void)
{
push_val_left=10; //速度调节变量 0-20(0最小,20最大)
push_val_right=10;
Left_moto_go ; //左电机往前走
Right_moto_go ; //右电机往前走
}
//左转
void leftrun(void)
{
push_val_left=10;
push_val_right=10;
Right_moto_go ; //右电机往前走
Left_moto_back ; //左电机后走
}
//右转
void rightrun(void)
{
push_val_left=10;
push_val_right=10;
Left_moto_go ; //左电机往前走
Right_moto_back ; //右电机往后走
}
/************************************************************/
/*PWM调制电机转速,调节push_val_left的值改变电机转速,占空比(设置前10ms工作,后10ms不工作)*/
//左电机调速
void pwm_out_left_moto(void)
{
if(Left_moto_stop)
{
if(pwm_val_left<=push_val_left) //计数器的值小于等于10
{
Left_moto_pwm=1; //输出高电平1
}
else
{
Left_moto_pwm=0; //输出低电平0
}
if(pwm_val_left>=20) //计数超过20
pwm_val_left=0; //计数器重置为0
}
else
{
Left_moto_pwm=0;
}
}
//右电机调速
void pwm_out_right_moto(void)
{
if(Right_moto_stop)
{
if(pwm_val_right<=push_val_right)
{
Right_moto_pwm=1;
}
else
{
Right_moto_pwm=0;
}
if(pwm_val_right>=20)
pwm_val_right=0;
}
else
{
Right_moto_pwm=0;
}
}
/***************************************************/
/*TIMER0中断服务子函数产生PWM信号*/
void timer0()interrupt 1 using 2
{
TH0=0XFc; //1Ms定时
TL0=0X18;
time++;
pwm_val_left++;
pwm_val_right++;
pwm_out_left_moto();
pwm_out_right_moto();
}
/*********************************************************************/
#endif
超声波测距模块
一. 有关HC-SR04
- HC-SR04超声波模块接口定义:
Vcc、 Trig(控制端)、 Echo(接收端)、 Gnd - HC-SR04产品特点:
1、典型工作用电压:5V。
2、超小静态工作电流:小于2mA。
3、感应角度:不大于15 度。
4、探测距离:2cm-400cm
5、高精度:可达0.3cm。
6、盲区(2cm)超近。
二. 超声波测距原理
控制口发一个10us以上的高电平,就可以在接收口等待高电平输出,一有输出就可以开定时器计时,当此口变为低电平时就可以读定时器的值,此时就为此次测距的时间,方可算出距离.如此不断的周期测,就可以达到你移动测量的值了。
测距公式:L=C×T (L :测量距离;C :超声波在空气中的传播速度344m/s (20℃室温);T :发射到接收时间数值的一半)
注:超声波的传播速度受空气的密度所影响,空气的密度越高则超声波的传播速度就越快,而空气的密度又与温度有着密切的关系,近似公式为:C=C0+0.607×T℃ 式中:C0为零度时的声波速度332m/s;T 为实际温度(℃)。
对于超声波测距精度要求达到1mm 时,就必须把超声波传播的环境温度考虑进去。
三. 模块工作原理:
1.采用 IO 触发测距,给至少10us 的高电平信号;
2.模块自动发送8 个40khz 的方波,自动检测是否有信号返回;
3.有信号返回,通过IO 输出一高电平,高电平持续的时间就是超声波从发射到返回的时间
4.测试距离=(高电平时间*声速(340M/S))/2;
四. 程序
#include <AT89x51.H> //器件配置文件
#include <intrins.h>
#define RX P2_0 //超声波模块发射端口
#define TX P2_1 //超声波模块接收端口
#define LCM_RW P1_1 //定义LCD引脚
#define LCM_RS P1_0
#define LCM_E P2_5
#define LCM_Data P0
#define Busy 0x80 //用于检测LCM状态字中的Busy标识
sbit DU = P2^6;
sbit WE = P2^7;
void cmg88()//关数码管
{
DU=1;
P0=0X00;
DU=0;
}
void LCMInit(void);//LCD初始化函数
void DisplayOneChar(unsigned char X, unsigned char Y, unsigned char DData);//LCD显示一个字符函数
void DisplayListChar(unsigned char X, unsigned char Y, unsigned char code *DData);//LCD显示一个字符串函数
void Delay5Ms(void);//延时5毫秒函数
void Delay400Ms(void);//延时400毫秒函数
void Decode(unsigned char ScanCode);
void WriteDataLCM(unsigned char WDLCM);//LCD1602写数据函数
void WriteCommandLCM(unsigned char WCLCM,BuysC);//LCD写命令函数
//unsigned char ReadDataLCM(void);
unsigned char ReadStatusLCM(void);
unsigned char code Range[] ="==Range Finder==";//LCD1602显示格式
unsigned char code ASCII[13] = "0123456789.-M";
unsigned char code table[]="Distance:000.0cm";
unsigned char code table1[]="!!! Out of range";
//static unsigned char DisNum = 0; //显示用指针
unsigned int time=0;
unsigned long S=0;
bit flag =0;
unsigned char disbuff[4]={
0,0,0,0,};
//写数据
void WriteDataLCM(unsigned char WDLCM)
{
ReadStatusLCM(); //检测忙
LCM_Data = WDLCM;
LCM_RS = 1;
LCM_RW = 0;
LCM_E = 0; //若晶振速度太高可以在这后加小的延时
LCM_E = 0; //延时
LCM_E = 1;
}
//写指令
void WriteCommandLCM(unsigned char WCLCM,BuysC) //BuysC为0时忽略忙检测
{
if (BuysC) ReadStatusLCM(); //根据需要检测忙
LCM_Data = WCLCM;
LCM_RS = 0;
LCM_RW = 0;
LCM_E = 0;
LCM_E = 0;
LCM_E = 1;
}
//读数据
/*unsigned char ReadDataLCM(void)
{
LCM_RS = 1;
LCM_RW = 1;
LCM_E = 0;
LCM_E = 0;
LCM_E = 1;
return(LCM_Data);
}*/
//读状态
unsigned char ReadStatusLCM(void)
{
LCM_Data = 0xFF;
LCM_RS = 0;
LCM_RW = 1;
LCM_E = 0;
LCM_E = 0;
LCM_E = 1;
while (LCM_Data & Busy); //检测忙信号
return(LCM_Data);
}
void LCMInit(void) //LCM初始化
{
LCM_Data = 0;
WriteCommandLCM(0x38,0); //三次显示模式设置,不检测忙信号
Delay5Ms();
WriteCommandLCM(0x38,0);
Delay5Ms();
WriteCommandLCM(0x38,0);
Delay5Ms();
WriteCommandLCM(0x38,1); //显示模式设置,开始要求每次检测忙信号
WriteCommandLCM(0x08,1); //关闭显示
WriteCommandLCM(0x01,1); //显示清屏
WriteCommandLCM(0x06,1); // 显示光标移动设置
WriteCommandLCM(0x0c,1); // 显示开及光标设置
}
//按指定位置显示一个字符
void DisplayOneChar(unsigned char X, unsigned char Y, unsigned char DData)
{
Y &= 0x1;
X &= 0xF; //限制X不能大于15,Y不能大于1
if (Y) X |= 0x40; //当要显示第二行时地址码+0x40;
X |= 0x80; //算出指令码
WriteCommandLCM(X, 1); //发命令字
WriteDataLCM(DData); //发数据
}
//按指定位置显示一串字符
void DisplayListChar(unsigned char X, unsigned char Y, unsigned char code *DData)
{
unsigned char ListLength;
ListLength = 0;
Y &= 0x1;
X &= 0xF; //限制X不能大于15,Y不能大于1
while (DData[ListLength]>0x19) //若到达字串尾则退出
{
if (X <= 0xF) //X坐标应小于0xF
{
DisplayOneChar(X, Y, DData[ListLength]); //显示单个字符
ListLength++;
X++;
}
}
}
//5ms延时
void Delay5Ms(void)
{
unsigned int TempCyc = 5552;
while(TempCyc--);
}
//400ms延时
void Delay400Ms(void)
{
unsigned char TempCycA = 5;
unsigned int TempCycB;
while(TempCycA--)
{
TempCycB=7269;
while(TempCycB--);
};
}
/********************************************************/
void Conut(void) //超声波距离计算函数
{
time=TH0*256+TL0;
TH0=0;
TL0=0;
S=(time*1.7)/10+10; //算出来是mm
if((S>=7000)||flag==1) //超出测量范围
{
flag=0;
DisplayListChar(0, 1, table1);
}
else
{
disbuff[0]=S%10;
disbuff[1]=S/10%10;
disbuff[2]=S/100%10;
disbuff[3]=S/1000;
DisplayListChar(0, 1, table);
DisplayOneChar(9, 1, ASCII[disbuff[3]]);
DisplayOneChar(10, 1, ASCII[disbuff[2]]);
DisplayOneChar(11, 1, ASCII[disbuff[1]]);
DisplayOneChar(12, 1, ASCII[10]);
DisplayOneChar(13, 1, ASCII[disbuff[0]]);
}
}
/********************************************************/
void zd0() interrupt 1 //T0中断用来计数器溢出,超过测距范围
{
flag=1; //中断溢出标志
RX=0;
}
/********************************************************/
void StartModule() //启动模块
{
TX=1; //启动一次模块
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
TX=0;
}
/********************************************************/
/*void delayms(unsigned int ms)
{
unsigned char i=100,j;
for(;ms;ms--)
{
while(--i)
{
j=10;
while(--j);
}
}
}*/
void Timer_Count(void) //超声波高电平脉冲宽度计算函数
{
TR0=1; //开启计数
while(RX); //当RX为1计数并等待
TR0=0; //关闭计数
Conut(); //计算
}
/*********************************************************/
void main(void)
{
unsigned int valA;
Delay400Ms(); //启动等待,等LCM讲入工作状态
cmg88();//关数码管
LCMInit(); //LCM初始化
Delay5Ms(); //延时片刻
DisplayListChar(0, 0, Range);
DisplayListChar(0, 1, table);
//ReadDataLCM();//测试用句无意义
TMOD=0x01;//设T0为方式1,GATE=1;
EA=1;
TH0=0;
TL0=0;
ET0=1;
while(1)
{
RX=1;
StartModule(); //启动模块
for(valA=7510;valA>0;valA--) //60ms
{
if(RX==1)
{
Timer_Count(); //超声波高电平脉冲宽度计算函数
}
}
}
}