笔者碎念:此作品为某大学大一小学期的项目作品,笔者从0基础开始学习制作,摸鱼几周完成,完成后并没有进行更多优化代码规范和功能,所以许多模块仅限于能用就行,本文只适合为刚接触的同学提供一些我的思路和心得。
接线操作https://www.bilibili.com/video/BV1Ca4y1s7JA/(有接线不同可能是一些模块接上时需要改线)
关于两个不同代码版本
因为最初编写代码时没有蓝牙模块只有2个空余可操作按键(若是会该线应该可以有更多)而通过这2个按键实现了切换模块和一些操作,之后有蓝牙又对代码进行修改,使用蓝牙传输数据来实现切换模式,按钮只实现一些操作。
无蓝牙
main.c
主函数包括引入最基本的函数库REGX52.H
和关于一些引脚定义和基本操作定义
延时函数可以在STC-ISP中生成(在一些地方添加延时函数可能可以处理一些非代码引起的问题,比如突然修改速度等函数调用之后)
然后是计时器T0和T1的初始化,其中在T0用于前几个模块的PWM的计时,在T1用于测距(魔术手中是使用T2作为PWM的计时),因为两者共用一个寄存器(意味着不能同时使用),所以需要更改TMOD值来改变使用的计时器
然后是包含不同模块函数的头文件引入
主函数最开始初始化T0和T1以及LCD1602,然后使用一个无限循环包含在各个模块下的无限循环,使用Pattern.h中的Change_Pa()来break,进入下一个模块
#include <REGX52.H> // or #include <AT89x51.H>?
#include <INTRINS.h>
#include <QXA51.h>
void Delay1ms(int time) //@11.0592MHz
{
unsigned char data i, j;
int a;
for(a=0;a<time;a++)
{
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
void timer01_init()
{
//TMOD = 0x10; //8-bit自动重载模式,设T0,
//TMOD = 0x02;
TH0 = 220;
TL0 = 220;//11.0592M晶振下占空比最大是256,输出100HZ
TR0 = 1;//启动定时器0
ET0 = 1;//允许定时器0中断
TH1=0;
TL1=0;
ET1=1;//允许T1中断
TR1=0;//关闭定时器1,在特定模块中才需要打开
EA=1;//开启总中断
}
#include <PWM.h>
#include <LCD1602.h>
#include <Distance.h>
#include <Pattern.h>
#include <Controll.h>
#include <Follow.h>
#include <Avoidance.h>
void main()
{
timer01_init();
Lcd1602_init();
while(1)
{
while(1)
{
TMOD = 0x02;
Delay1ms(100);
LRspeed(150,150);
controll();
if(Change_Pa()==1)
break;
}
while(1)
{
TMOD = 0x02;
Delay1ms(100);
follow();
if(Change_Pa()==1)
break;
}
while(1)
{
TMOD = 0x02;
Delay1ms(100);
avoidance();
if(Change_Pa()==1)
break;
}
TMOD = 0x10;
while(1)
{
LCD_ShowNum(distance());
Delay1ms(100);
Lcd1602_Write_Cmd(0x01);//清屏作用
Delay1ms(200);
if(Change_Pa()==1)
break;
}
T2_Init();
while(1)
{
flag_dis=distance();
keep_dis();
if(Change_Pa()==1)
break;
}
}
}
//It is difficult to switch from mode 2 to mode 1
//You are advised to restart the power supply to return to mode 1
QXA51.h
主要是
#ifndef __QXA51_H__
#define __QXA51_H__
/*电机驱动IO定义*/
sbit IN1 = P1^2; //为1左电机反转
sbit IN2 = P1^3; //为1左电机正转
sbit IN3 = P1^6; //为1右电机正转
sbit IN4 = P1^7; //为1右电机反转
sbit EN1 = P1^4; //为1左电机使能
sbit EN2 = P1^5; //为1右电机使能
sbit TrackSensorLeft = P1^1;//左循迹信号为0则没有识别到黑线,为1则识别到黑线
sbit TrackSensorRight = P1^0;//右循迹信号
sbit left_avoid = P2^7;//左避障信号
sbit right_avoid = P2^6;//右避障信号为0,识别到障碍物,为1则没有识别到障碍物
/*按键定义*/
sbit key_s4 = P3^2;
sbit key_s5 = P3^3;
sbit beep = P2^3;//蜂鸣器
unsigned int flag_dis;//记录距离
#define left_motor_en EN1 = 1 //左电机使能
#define right_motor_en EN2 = 1 //右电机使能
#define left_motor_stops IN1 = 0, IN2 = 0//左电机停止
#define right_motor_stops IN3 = 0, IN4 = 0//右电机停止
#define left_motor_go IN1 = 0, IN2 = 1//左电机正转
#define left_motor_back IN1 = 1, IN2 = 0//左电机反转
#define right_motor_go IN3 = 1, IN4 = 0//右电机正转
#define right_motor_back IN3 = 0, IN4 = 1//右电机反转
#endif
Controll.h
按键按下,则对应引脚变成低电平
快速单按s5时,小车前进,长按时小车原地左转(长按时间大于200ms),快速单按s4时,小车后退,长按时小车原地右转。通过一个段较长的时间来区分想要进行的操作
void controll()
{
if(key_s5==0&&key_s4==1)
{
Delay1ms(200);
if(key_s5==1&&key_s4==1)
{
left_motor_en;
right_motor_en;
left_motor_go;
right_motor_go;
Delay1ms(500);
left_motor_stops;
right_motor_stops;
}
else if(key_s5==0&&key_s4==1)
{
while(!key_s5);
left_motor_en;
right_motor_en;
left_motor_back;
right_motor_go;
Delay1ms(150);
left_motor_stops;
right_motor_stops;
}
}
else if(key_s4==0&&key_s5==1)
{
Delay1ms(200);
if(key_s4==1&&key_s5==1)
{
left_motor_en;
right_motor_en;
left_motor_back;
right_motor_back;
Delay1ms(500);
left_motor_stops;
right_motor_stops;
}
else if(key_s4==0&&key_s5==1)
{
while(!key_s4);
left_motor_en;
right_motor_en;
left_motor_go;
right_motor_back;
Delay1ms(150);
left_motor_stops;
right_motor_stops;
}
}
}
Pattern.h
当同时按下两个按键时,则返回1,主函数中对应的模块循环跳出循环
int Change_Pa() {
if (key_s4==0||key_s5==0) {
Delay1ms(5);
if(key_s4==0&&key_s5==0)
{
while(!key_s4||!key_s5);
right_motor_stops;
left_motor_stops;
return 1;
}
} //More sensitive after removing "return 0"
return 0; //Very important for the second pattern
}
Follow.h
void follow()
{
//当小车检测到前方有障碍物时,自动掉头回到黑线上
if(left_avoid == 0 && right_avoid == 0) //Rotate slowly in place until a black line is detected
{
LRspeed(150,150);
Delay1ms(1);
left_motor_en;
right_motor_en;
left_motor_go;
right_motor_back;
Delay1ms(200);
while(1)
{
if(TrackSensorLeft==1&&TrackSensorRight==1)
break;
}
}
//前进
if(TrackSensorLeft==1&&TrackSensorRight==1)
{
LRspeed(150,150);
Delay1ms(5);
left_motor_en;
right_motor_en;
left_motor_go;
right_motor_go;
}
//左转
else if(TrackSensorLeft==1&&TrackSensorRight==0)
{
/*LRspeed(250,20);
Delay1ms(10);
left_motor_en;
right_motor_en;
left_motor_en;
right_motor_go;
注释的为两轮相差更大的差速转弯,实际可能出现一些问题*/
LRspeed(250,170);
Delay1ms(1);
left_motor_stops;
right_motor_en;
right_motor_go;
}
//右转
else if(TrackSensorLeft==0&&TrackSensorRight==1)
{
/*LRspeed(20,250);
Delay1ms(10);
right_motor_en;
left_motor_en;
left_motor_go;
right_motor_go;
注释的为两轮相差更大的差速转弯,实际可能出现一些问题*/
LRspeed(170,250);
Delay1ms(1);
right_motor_stops;
left_motor_en;
left_motor_go;
}
else
//防出线操作 if(TrackSensorLeft==0&&TrackSensorRight==0)
{
LRspeed(200,200);
Delay1ms(1); //Overheat protection
left_motor_stops;
right_motor_stops;
Delay1ms(1);
left_motor_en;
right_motor_en;
left_motor_back;
right_motor_back;
}
}
Avoidance.h
int avoidance()
{
if(left_avoid == 1 && right_avoid == 1)//左右都没有识别到障碍物
{
LRspeed(200,200);
Delay1ms(10);
left_motor_en;
right_motor_en;
left_motor_go;
right_motor_go;
}
else if(left_avoid == 1 && right_avoid == 0)//小车右侧识别到障碍物
{
beep = 0; //使能有源蜂鸣器
Delay1ms(100);//100ms计时
beep = 1; //关闭蜂鸣器
LRspeed(150,150);
Delay1ms(10);
left_motor_en;
right_motor_en;
left_motor_back;
right_motor_go;
}
else if(left_avoid == 0 && right_avoid == 1)//小车左侧识别到障碍物
{
beep = 0; //使能有源蜂鸣器
Delay1ms(100);//100ms计时
beep = 1; //关闭蜂鸣器
LRspeed(150,150);
Delay1ms(10);
left_motor_en;
right_motor_en;
left_motor_go;
right_motor_back;
}
else if(left_avoid == 0 && right_avoid == 0)//都识别障碍物,实现一个原地掉头,根据实际而时间会不同
{
beep = 0; //ʹÄÜÓÐÔ´·äÃùÆ÷
Delay1ms(100);//200ºÁÃëÑÓʱ
beep = 1; //¹Ø±ÕÓÐÔ´·äÃùÆ÷
LRspeed(150,150);
Delay1ms(10);
left_motor_en;
right_motor_en;
left_motor_go;
right_motor_back;
Delay1ms(250);
}
}
//Issue forward and backward commands
LCD1602.h
sbit RS = P3^5;
sbit RW = P3^6;
sbit EN = P3^4;
void Lcd1602_Write_Cmd(unsigned char cmd)
{
EN=0;
RS=0;
RW=0;
Delay1ms(5);
P0=cmd;
Delay1ms(5);
EN=1;
Delay1ms(5);
EN=0;
Delay1ms(5);
}
void Lcd1602_Write_Data(unsigned char dat)
{
EN=0;
RS=1;
RW=0;
Delay1ms(5);
P0=dat;
Delay1ms(5);
EN=1;
Delay1ms(5);
EN=0;
Delay1ms(5);
}
void simle()
{
Lcd1602_Write_Cmd(0x40);
Lcd1602_Write_Data(0x00);
Lcd1602_Write_Cmd(0x41);
Lcd1602_Write_Data(0x00);
Lcd1602_Write_Cmd(0x42);
Lcd1602_Write_Data(0x0A);
Lcd1602_Write_Cmd(0x43);
Lcd1602_Write_Data(0x0A);
Lcd1602_Write_Cmd(0x44);
Lcd1602_Write_Data(0x00);
Lcd1602_Write_Cmd(0x45);
Lcd1602_Write_Data(0x11);
Lcd1602_Write_Cmd(0x46);
Lcd1602_Write_Data(0x0E);
Lcd1602_Write_Cmd(0x47);
Lcd1602_Write_Data(0x00);
Lcd1602_Write_Cmd(0xC0);
Lcd1602_Write_Data(0x00);
}//一个简单的笑脸实现
int LCD_Pow(unsigned int X,unsigned int Y) //X^Y
{
unsigned char i;
unsigned int Result=1;
for(i=0;i<Y;i++)
{
Result*=X;
}
return Result;
}//实现一个数学POW的功能
void LCD_ShowNum(unsigned int Number)
{
int i;//very important.Because then it have to get out of the loop
simle();
if(Number==0)
{
Lcd1602_Write_Cmd(0x80);
Lcd1602_Write_Data(0x45);
Lcd1602_Write_Data(0x52);
Lcd1602_Write_Data(0x52);
Lcd1602_Write_Data(0x4F);
Lcd1602_Write_Data(0x52);
}
else
{
i=0;
Lcd1602_Write_Cmd(0x80);
while(1)
{
if(Number%LCD_Pow(10,i)==Number)
break;
i++;
}
i--;
Lcd1602_Write_Data(Number/LCD_Pow(10,i)+'0');
i--;
for(i=i;i>=0;i--)
Lcd1602_Write_Data(Number/LCD_Pow(10,i)%10+'0');
}//显示数字
}//写入数据
void Lcd1602_init()
{
Delay1ms(15);
Lcd1602_Write_Cmd(0x38);
Delay1ms(5);
Lcd1602_Write_Cmd(0x38);
Lcd1602_Write_Cmd(0x01);
Lcd1602_Write_Cmd(0x06);
Lcd1602_Write_Cmd(0x0c);
}//对1062初始化操作
Distance.h
此处使用定时器2作为PWM的计时器,之所以不使用一个定时器始终作为PWM定时器是因为原本的超声波魔术手并没有考虑控制速度,在之后添加控制速度的功能时因为改动整体比较麻烦,所以只把定时器2放在超声波魔术手模块中实现PWM
#define RX P2_0 //ECHO
#define TX P2_1 //TRIG
unsigned int time=0;
unsigned int S=0;
bit flag=0;
unsigned int pwm_t2;
unsigned int pwm2_left_val;
unsigned int pwm2_right_val;
void T2_Init()
{
T2MOD=0;
T2CON=0;
EXEN2=0;
TH2=0xFF;
TL2=0xCB;
RCAP2L = 0xCB;
RCAP2H = 0xFF;
TR2=1;
ET2=1;
EA=1;
}//T2初始化
void timer2() interrupt 5 //定时器2中断
{
TF2=0;
pwm_t2++;
if(pwm_t2 == 255)
pwm_t2 = EN1 = EN2 = 0;
if(pwm2_left_val == pwm_t2)
EN1 = 1;
if(pwm2_right_val == pwm_t2)
EN2 = 1;
}
int Conut()
{
time=TH1*256+TL1;
TH1=0;
TL1=0;
S=(int)((float)(time*1.085)*0.17); //计算结果为mm
if((S>=7000)||flag==1)
{
flag=0;
return 0;
}
return S;
}
void zd1() interrupt 3 //T1中断用来表示计数器溢出,超过测距范围
{ //中断溢出标志
flag=1;
}
int distance()
{
TMOD = 0x10;
Delay1ms(80);
TX=1; //80MS启动一次模块
Delay1ms(1);
TX=0;
while(!RX); //当RX(ECHO信号回响)为0时等待
TR1=1; //开启计数
while(RX); //当RX为1是计数并等待
TR1=0; //关闭计数
return Conut(); //计算
}
void keep_dis()
{
pwm2_left_val=180;
pwm2_right_val=180;
if(flag_dis<130) //A lot of speed, but good results
{
Delay1ms(10);
left_motor_en;
right_motor_en;
right_motor_back;
left_motor_back;
}
else if(flag_dis>230)
{
Delay1ms(10);
left_motor_en;
right_motor_en;
right_motor_go;
left_motor_go;
}
else
{
right_motor_stops;
left_motor_stops;
}
}
PWM.h
unsigned char pwm_left_val;//Left motor duty cycle
unsigned char pwm_right_val;//Right motor duty cycle
unsigned char pwm_t;//cycle
void timer0() interrupt 1 //¶¨Ê±Æ÷0ÖжÏ
{
TF0=0;
pwm_t++;
if(pwm_t == 255)
pwm_t = EN1 = EN2 = 0;
if(pwm_left_val == pwm_t)
EN1 = 1;
if(pwm_right_val == pwm_t)
EN2 = 1;
}
void LRspeed(unsigned char L_speed,unsigned char R_speed)
{
pwm_left_val=L_speed;
pwm_right_val=R_speed;
}