文章目录
智能小车项目
最近花时间做了一个小车,所以在这里做个总结。
材料选型
电机选型:1:48的减速电机(这里的1:48是指减速比,是指电机通过牺牲速度来获得扭矩的方法。减速比越大,它的速度就越慢,扭矩就越大)
轮子:麦克纳姆轮(麦克纳姆轮是一种全向轮,由于其独特的构造,才使得麦克纳姆轮随意改变行驶方向的特性,但在安装的时候要注意麦克纳姆轮的安装方向)
轮子的正确分布方向:
关于麦克纳姆轮的详细介绍请看这篇博文:麦克纳姆轮(万向轮)驱动和玩法_麦克纳姆轮转向_
电机驱动选型:L298N
由于麦轮对扭矩要求比较大,所以普通的L9110S满足不了需求。而且当给L298N输入7V以上的电压时,可以向板外供电5V,所以优先选用L298N。
L298N的详细资料:链接:https://pan.baidu.com/s/1gyQNSwDTgpNtM-_t4V5dSQ 提取码:52x7
电池选型:选用18650锂电池,有试过别的电池,结果都是供电有问题。
小车启动
怎么让小车动起来?即怎么用单片机控制电机转动?
很简单,给电机驱动模块的两个引脚一高一低,电机就会转动
代码实现:
/*****main.c*****/
#include "motor.h"
void main()
{
while(1)
{
Car_Move();
}
}
/*****motor.c*****/
#include "pin.h"
#include "delay.h"
void Go_Forward() //正转
{
A1_1 = 0; //A1轮
A1_2 = 1;
B1_1 = 0; //B1轮
B1_2 = 1;
B2_1 = 0; //B2轮
B2_2 = 1;
A2_1 = 0; //A2轮
A2_2 = 1;
}
void Go_Back() //反转
{
A1_1 = 1; //A1轮
A1_2 = 0;
B1_1 = 1; //B1轮
B1_2 = 0;
B2_1 = 1; //B2轮
B2_2 = 0;
A2_1 = 1; //A2轮
A2_2 = 0;
}
void Right_Tran() //向右平移
{
A1_1 = 0; //A1轮
A1_2 = 1;
B1_1 = 1; //B1轮
B1_2 = 0;
B2_1 = 1; //B2轮
B2_2 = 0;
A2_1 = 0; //A2轮
A2_2 = 1;
}
void Left_Tran() //向左平移
{
A1_1 = 1; //A1轮
A1_2 = 0;
B1_1 = 0; //B1轮
B1_2 = 1;
B2_1 = 0; //B2轮
B2_2 = 1;
A2_1 = 1; //A2轮
A2_2 = 0;
}
void Turn_Right() //右转
{
A1_1 = 0; //A1轮
A1_2 = 1;
B1_1 = 1; //B1轮
B1_2 = 0;
B2_1 = 0; //B2轮
B2_2 = 1;
A2_1 = 1; //A2轮
A2_2 = 0;
}
void Turn_Left() //左转
{
A1_1 = 1; //A1轮
A1_2 = 0;
B1_1 = 0; //B1轮
B1_2 = 1;
B2_1 = 1; //B2轮
B2_2 = 0;
A2_1 = 0; //A2轮
A2_2 = 1;
}
void Car_Move()
{
Go_Forward();
Delay1000ms();
Go_Back();
Delay1000ms();
Turn_Left();
Delay1000ms();
Turn_Right();
Delay1000ms();
Left_Tran();
Delay1000ms();
Right_Tran();
Delay1000ms();
}
/*****delay.c*****/
#include "intrins.h"
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 8;
j = 1;
k = 243;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
/*****pin.h*****/
#include <reg52.h>
sbit A1_1 = P1^0; //麦轮分布:ABBA
sbit A1_2 = P1^1;
sbit A2_1 = P1^6;
sbit A2_2 = P1^7;
sbit B1_1 = P1^4;
sbit B1_2 = P1^5;
sbit B2_1 = P1^2;
sbit B2_2 = P1^3;
串口(蓝牙)控制小车
只让小车自己跑感觉少了点意思,所以加入串口控制,发送命令来控制小车进行前后左右等运动,就像之前用串口控制灯的亮灭。
/*****main.c*****/
#include "uart.h"
void main()
{
UART_Init();
while(1)
{
}
}
/*****uart.c****/
#include "reg52.h"
#include "string.h"
#include "motor.h"
#define SIZE 12
sfr AUXR = 0x8E;
char buffer[SIZE];
void UART_Init()
{
SCON = 0x50; //配置SCON为8位UART,波特率可变,REN允许串行接收
TMOD &= 0x0F; //定时器1高位清零,地位不变
TMOD |= 0x20; //定时器1采用8位重装计数
TL1 = 0xFD;
TH1 = 0xFD; //9600波特率对应初值
TR1 = 1; //启动定时器1
AUXR = 0x01; //禁止ALE信号输出
EA = 1; //开启总中断
ES = 1; //开启串口中断
}
void UART_handler () interrupt 4
{
static int i = 0; //静态变量,初始化执行一次
char tmp;
if(RI) //如果接收到数据,硬件将RI自动置1,需要软件清0
{
RI = 0;
tmp = SBUF;
if(tmp == 'M')
{
i = 0;
}
buffer[i] = tmp;
i++;
if(buffer[0] == 'M')
{
switch(buffer[1])
{
case '1':
Go_Forward(); //发送M1前进,依此类推
break;
case '2':
Go_Back();
break;
case '3':
Turn_Left();
break;
case '4':
Turn_Right();
break;
case '5':
Left_Tran();
break;
case '6':
Right_Tran();
break;
default:
Car_Stop();
break;
}
}
if(i == SIZE)
{
i = 0;
memset(buffer,'\0',SIZE);
}
}
}
小车点动
通过蓝牙模块能够控制小车的运动状态,如果想实现按下按键启动,松开按键停止的效果可以借助STC-ISP的工具来实现。
/*****main.c*****/
#include "uart.h"
#include "motor.h"
void main()
{
UART_Init();
while(1)
{
Car_Stop(); //通过在main函数离不断地调用Car_Stop();使小车一直处于停止状态,在串口那里发指令给延时让小车运动20ms,这样就会形成发一次指令运动20ms的效果,如果一直发指令,小车就会一直运动,停止发指令,小车就会调用Car_Stop();函数停止运动
}
}
/*****uart.c*****/
#include "reg52.h"
#include "string.h"
#include "motor.h"
#include "delay.h"
sbit LED1 = P3^7;
#define SIZE 12
sfr AUXR = 0x8E;
char buffer[SIZE];
void UART_Init()
{
SCON = 0x50; //配置SCON为8位UART,波特率可变,REN允许串行接收
TMOD &= 0x0F; //定时器1高位清零,地位不变
TMOD |= 0x20; //定时器1采用8位重装计数
TL1 = 0xFD;
TH1 = 0xFD; //9600波特率对应初值
TR1 = 1; //启动定时器1
AUXR = 0x01; //禁止ALE信号输出
EA = 1; //开启总中断
ES = 1; //开启串口中断
}
void UART_handler () interrupt 4
{
static int i = 0; //静态变量,初始化执行一次
char tmp;
if(RI) //如果接收到数据,硬件将RI自动置1,需要软件清0
{
RI = 0;
tmp = SBUF;
if(tmp == 'M')
{
i = 0;
}
buffer[i] = tmp;
i++;
if(buffer[0] == 'M')
{
switch(buffer[1])
{
case '1':
Go_Forward();
Delay20ms(); //给延时让小车运动一会儿,以下同上
break;
case '2':
Go_Back();
Delay20ms();
break;
case '3':
Turn_Left();
Delay20ms();
break;
case '4':
Turn_Right();
Delay20ms();
break;
case '5':
Left_Tran();
Delay20ms();
break;
case '6':
Right_Tran();
Delay20ms();
break;
default:
Car_Stop();
break;
}
}
if(i == SIZE)
{
i = 0;
memset(buffer,'\0',SIZE);
}
}
}
PWM控制小车速度
在前边的运动过程中,小车一直处于全速运行的状态,那么如何实现小车转的慢一会儿快一会儿?
用PWM来控制小车速度
前边用PWM来控制舵机,在20ms的一个脉冲内保持0.5ms的高电平,剩下的都为低电平,舵机就会转动0°;当在20ms的一个脉冲内保持2ms的高电平,剩下的都为低电平,舵机就会转动135°。
这里用PWM控制小车速度就是在20ms的一个周期内,如果小车在20ms内全速运行,那么小车的速度就达到了峰值;如果小车在10ms内全速运行,剩下时间内一直停止,那么小车的速度就是全速运行的一半。依此类推,如果小车全速运行的时间越短,停止的时间越长,那么小车的速度就会越慢,以此可以实现小车控制小车速度的功能。
代码实现:
#include "motor.h"
#include "delay.h"
#include "timer.h"
extern char speed;
void main()
{
Timer0Init();
while(1)
{
speed = 25;
Delay2000ms();
speed = 50;
Delay2000ms();
speed = 80;
Delay2000ms();
}
}
/*
extern的作用:
C51头文件使用extern的目的是外部变量或函数声明。
使用时要注意:
1.在使用extern进行外部变量声明时,不能重新给变量赋值。例如:
extern unsigned char i=0;(错误,编译器会报错)
extern unsigned char i;(正确)
其他文件使用extern外部变量声明的变量时(i),可以赋值。(unsigned char i=0;)
2.extern只是声明不是定义。extern只是告诉编译器声明的变量(i),是在之前已经定义过的,且在这里要用到。
*/
/*****timer.c*****/
#include "reg52.h"
#include "motor.h"
char cnt = 0;
char speed;
void Timer0Init()
{
TMOD &= 0xF0;
TMOD |= 0x01;
TL0 = 0x1A;
TH0 = 0xFF;
EA =1;
ET0 = 1;
TR0 = 1;
TF0 = 0;
}
void Timer0 () interrupt 1 //PWM是50Hz,也就是20ms,将20ms分为80份,定一个0.25ms出来
{
cnt++;
TL0 = 0x1A;
TH0 = 0xFF;
if(cnt < speed)
{
Go_Forward();
}
else
{
Car_Stop();
}
if(cnt == 80)
{
cnt = 0;
}
}
PWM控制小车差速运动
前边实现了对小车速度的控制,现在要实现差速运动,即左右轮速度不一样,那么就需要各自的前进函数和停止函数,以此来实现小车的差速运动。
代码实现:
/*****main.c*****/
#include "motor.h"
#include "delay.h"
#include "timer.h"
extern char speedLeft;
extern char speedRight;
void main()
{
Timer0Init();
Timer1Init();
while(1)
{
speedLeft = 40;
speedRight = 80;
Delay1000ms();
speedLeft = 80;
speedRight = 40;
Delay1000ms();
}
}
/*****motor.c*****/
#include "pin.h"
#include "delay.h"
void Go_Forward_Right() //正转
{
B1_1 = 0; //B1轮
B1_2 = 1;
A2_1 = 0; //A2轮
A2_2 = 1;
}
void Go_Forward_Left()
{
A1_1 = 0; //A1轮
A1_2 = 1;
B2_1 = 0; //B2轮
B2_2 = 1;
}
void Car_Stop_Right()
{
B1_1 = 0; //B1轮
B1_2 = 0;
A2_1 = 0; //A2轮
A2_2 = 0;
}
void Car_Stop_Left()
{
A1_1 = 0; //A1轮
A1_2 = 0;
B2_1 = 0; //B2轮
B2_2 = 0;
}
void Go_Back() //反转
{
A1_1 = 1; //A1轮
A1_2 = 0;
B1_1 = 1; //B1轮
B1_2 = 0;
B2_1 = 1; //B2轮
B2_2 = 0;
A2_1 = 1; //A2轮
A2_2 = 0;
}
void Right_Tran() //向右平移
{
A1_1 = 0; //A1轮
A1_2 = 1;
B1_1 = 1; //B1轮
B1_2 = 0;
B2_1 = 1; //B2轮
B2_2 = 0;
A2_1 = 0; //A2轮
A2_2 = 1;
}
void Left_Tran() //向左平移
{
A1_1 = 1; //A1轮
A1_2 = 0;
B1_1 = 0; //B1轮
B1_2 = 1;
B2_1 = 0; //B2轮
B2_2 = 1;
A2_1 = 1; //A2轮
A2_2 = 0;
}
void Turn_Left() //左转
{
A1_1 = 0; //A1轮
A1_2 = 1;
B1_1 = 1; //B1轮
B1_2 = 0;
B2_1 = 0; //B2轮
B2_2 = 1;
A2_1 = 1; //A2轮
A2_2 = 0;
}
void Turn_Right() //右转
{
A1_1 = 1; //A1轮
A1_2 = 0;
B1_1 = 0; //B1轮
B1_2 = 1;
B2_1 = 1; //B2轮
B2_2 = 0;
A2_1 = 0; //A2轮
A2_2 = 1;
}
/*****timer.c*****/
#include "reg52.h"
#include "motor.h"
char cntLeft = 0;
char speedLeft;
char cntRight = 0;
char speedRight;
void Timer0Init()
{
TMOD &= 0xF0;
TMOD |= 0x01;
TL0 = 0x1A;
TH0 = 0xFF;
EA =1;
ET0 = 1;
TR0 = 1;
TF0 = 0;
}
void Timer1Init()
{
TMOD &= 0x0F;
TMOD |= 0x10;
TL1 = 0x1A;
TH1 = 0xFF;
EA =1;
ET1 = 1;
TR1 = 1;
TF1 = 0;
}
void Timer0_Handler () interrupt 1 //PWM是50Hz,也就是20ms,将20ms分为80份,定一个0.25ms出来
{
cntLeft++;
TL0 = 0x1A;
TH0 = 0xFF;
if(cntLeft < speedLeft)
{
Go_Forward_Left();
}
else
{
Car_Stop_Left();
}
if(cntLeft == 80)
{
cntLeft = 0;
}
}
void Timer1_Handler () interrupt 3 //PWM是50Hz,也就是20ms,将20ms分为80份,定一个0.25ms出来
{
cntRight++;
TL1 = 0x1A;
TH1 = 0xFF;
if(cntRight < speedRight)
{
Go_Forward_Right();
}
else
{
Car_Stop_Right();
}
if(cntRight == 80)
{
cntRight = 0;
}
}
循迹小车
循迹小车就是利用TCRT5000红外循迹传感器,利用红外容易被黑色吸收,白色反射的原理进行循迹。
下面是TCRT5000的工作原理
根据循迹模块的工作原理我们就能对小车的运动方向做出判断:
当左循迹和右循迹都照到白色的地图上时,左右模块都能接收到返回来的红外输出低电平,此时开关指示灯亮,小车前进;
当左循迹模块压线,右循迹模块照到白色地图上时,左循迹模块发出的红外被黑线吸收,输出高电平,此时开关指示灯灭,右循迹模块能够反射回来,输出低电平,开关指示灯亮,所以此时小车应该左转;
当右循迹模块压线,左循迹模块照到白色地图上时,右循迹模块发出的红外被黑线吸收,输出高电平,此时开关指示灯灭,左循迹模快能够反射回来,输出低电平,开关指示灯亮,所以此时小车应该右转;
当小车跑出地图后,左右循迹模块发出的红外都被吸收,输出高电平,此时开关指示灯灭,所以此时小车应该停下。
代码实现:
/*****main.c*****/
#include "motor.h"
#include "pin.h"
//直行:左右传感器都反射回来输出低电平亮
//左转:左传感器压线被吸收返回高电平灭,右传感器反射回来输出低电平亮
//右转:右传感器压线被吸收返回高电平灭,左传感器反射回来输出低电平亮
//停止:左右传感器都检测跑出跑道,输高电平灭,停止
void main()
{
while(1)
{
if(Lefttrack == 0 && Righttrack == 0)
{
Go_Forward();
}
if(Lefttrack == 1 && Righttrack == 0)
{
Turn_Left();
}
if(Lefttrack == 0 && Righttrack == 1)
{
Turn_Right();
}
if(Lefttrack == 1 && Righttrack == 1)
{
Car_Stop();
}
}
}
/*****pin.h*****/
#include <reg52.h>
sbit A1_1 = P1^0; //麦轮分布:ABBA
sbit A1_2 = P1^1;
sbit A2_1 = P1^6;
sbit A2_2 = P1^7;
sbit B1_1 = P1^4;
sbit B1_2 = P1^5;
sbit B2_1 = P1^2;
sbit B2_2 = P1^3;
sbit Lefttrack = P2^7;
sbit Righttrack = P2^6;
循迹小车优化
由于上个代码在左右转弯的时候,每个电机都是全速运行,所以会看到小车在拐弯地时候一卡一卡的。真正实际情况中,在转弯的时候每个轮子的速度都不一样,所以下面加入电机调速来优化循迹小车。
电机调速就利用上边PWM来调速,在转弯的时候给左右轮子不同的速度,使它转弯变得更平滑一些。还可以调节上边的电位器来改变红外反射的距离,配合代码调节循迹小车。
代码实现:
/*****main.c*****/
#include "motor.h"
#include "pin.h"
#include "timer.h"
extern char speedLeft;
extern char speedRight;
void main()
{
Timer0Init();
Timer1Init();
while(1)
{
if(Lefttrack == 0 && Righttrack == 0)
{
speedLeft = 70;
speedRight = 70;
}
if(Lefttrack == 1 && Righttrack == 0) //左转
{
speedLeft = 25;
speedRight = 75; //speedRight>speedLeft,左转
}
if(Lefttrack == 0 && Righttrack == 1) //右转
{
speedLeft = 70;
speedRight = 25; //speedLeft>speedRight右转
}
if(Lefttrack == 1 && Righttrack == 1)
{
speedLeft = 0;
speedRight = 0;
}
}
}
/*****timer.c*****/
#include "reg52.h"
#include "motor.h"
char cntLeft = 0;
char speedLeft;
char cntRight = 0;
char speedRight;
void Timer0Init()
{
TMOD &= 0xF0;
TMOD |= 0x01;
TL0 = 0x1A;
TH0 = 0xFF;
EA =1;
ET0 = 1;
TR0 = 1;
TF0 = 0;
}
void Timer1Init()
{
TMOD &= 0x0F;
TMOD |= 0x10;
TL1 = 0x1A;
TH1 = 0xFF;
EA =1;
ET1 = 1;
TR1 = 1;
TF1 = 0;
}
void Timer0_Handler () interrupt 1 //PWM是50Hz,也就是20ms,将20ms分为80份,定一个0.25ms出来
{
cntLeft++;
TL0 = 0x1A;
TH0 = 0xFF;
if(cntLeft < speedLeft)
{
Go_Forward_Left();
}
else
{
Car_Stop_Left();
}
if(cntLeft == 80)
{
cntLeft = 0;
}
}
void Timer1_Handler () interrupt 3 //PWM是50Hz,也就是20ms,将20ms分为80份,定一个0.25ms出来
{
cntRight++;
TL1 = 0x1A;
TH1 = 0xFF;
if(cntRight < speedRight)
{
Go_Forward_Right();
}
else
{
Car_Stop_Right();
}
if(cntRight == 80)
{
cntRight = 0;
}
}
跟随小车
跟随小车用的模块和循迹的是一样的,都是TCRT5000红外传感器,只不过循迹的传感器是在下方,跟随用到的是在前方。
根据跟随模块的工作原理对小车的运动方向做出判断:
当物体在正前方的时候,左跟随模块和右跟随模块检测到前边有物体被挡住的时候,红外就会反射回来,输出低电平,此时开关指示灯亮,所以小车应该前进;
当物体在左前方的时候,左跟随模块检测到左边有物体时,左跟随模块发射的红外就会被反射回来,输出低电平,此时开关指示灯亮;右跟随模块没有检测到物体,红外不会反射回来,输出高电平,开关指示灯灭,所以小车应该左转;
当物体在右前方的时候,右跟随模块检测到右边有物体时,右跟随模块发射的红外就会被反射回来,输出低电平,此时开关指示灯亮;左跟随模块没有检测到物体,红外不会反射回来,输出高电平,开关指示灯灭,所以小车应该右转;
当小车前面没有物体的时候,左右跟随模块发射红外都不会反射回来,输出高电平,此时开关指示灯灭,所以小车应该停下来。
代码实现:(具体的参数需要根据场地去调整)
/*****main.c*****/
#include "motor.h"
#include "pin.h"
//直行:左右传感器都反射回来输出低电平亮
//左转:左传感器检测到前方有物体,反射回来输出低电平亮,右传感器没有检测到输出高电平灭
//右转:右传感器检测到前方有物体,反射回来输出低电平亮,左传感器没有检测到输出高电平灭
//停止:左右传感器都没有检测到前方有物体,输出高电平灭
void main()
{
while(1)
{
if(Leftfollow == 0 && Rightfollow == 0)
{
Go_Forward();
}
if(Leftfollow == 0 && Rightfollow == 1)
{
Turn_Left();
}
if(Leftfollow == 1 && Rightfollow == 0)
{
Turn_Right();
}
if(Leftfollow == 1 && Rightfollow == 1)
{
Car_Stop();
}
}
}
/*****pin.h*****/
#include <reg52.h>
sbit A1_1 = P1^0; //麦轮分布:ABBA
sbit A1_2 = P1^1;
sbit A2_1 = P1^6;
sbit A2_2 = P1^7;
sbit B1_1 = P1^4;
sbit B1_2 = P1^5;
sbit B2_1 = P1^2;
sbit B2_2 = P1^3;
sbit Lefttrack = P2^7;
sbit Righttrack = P2^6;
sbit Rightfollow = P2^4;
sbit Leftfollow = P2^5;
避障小车
避障小车的原理是通过超声波配合舵机获取障碍物的距离,从而小车作出相应的前进、后退、左转、右转运动。
代码实现:
/*****main.c*****/
#include "SG90.h"
#include "HC04.h"
#include "delay.h"
#include "motor.h"
void main()
{
double disMiddle;
double disLeft;
double disRight;
Timer0Init();
Timer1Init();
sgMiddle();
Delay300ms();
Delay300ms();
while(1)
{
disMiddle = Get_distance();
if(disMiddle > 30)
{
Go_Forward();//前进
}
else if(disMiddle < 10)
{
Go_Back();//后退
// Delay200ms();
// Car_Stop();
}
else
{
Car_Stop();
sgLeft(); //测左边距离
Delay300ms();
disLeft = Get_distance();
sgMiddle();
Delay300ms();
sgRight(); //测右边距离
Delay300ms();
disRight = Get_distance();
if(disLeft < disRight)
{
Turn_Right();
Delay150ms();
Car_Stop();
}
if(disRight < disLeft)
{
Turn_Left();
Delay150ms();
Car_Stop();
}
}
sgMiddle();
Delay300ms();
}
}
/*****Hc04.c*****/
#include "pin.h"
#include "delay.h"
void Timer1Init()
{
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x10;
TL1 = 0;
TH1 = 0;
}
void HC_START()
{
Trig = 0;
Trig = 1;
Delay10us();
Trig = 0;
}
double Get_distance()
{
double time;
TL1 = 0;
TH1 = 0;
HC_START();
while(Echo == 0);
TR1 = 1;
while(Echo == 1);
TR1 = 0;
time = (TH1*256+TL1)*1.085;
return (time*0.017);
}
/*****SG90.c*****/
#include "reg52.h"
#include "pin.h"
int cnt = 0;
int angel;
int angel_back;
void Timer0Init()
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01;
TL0 = 0x33;
TH0 = 0xFE;
EA =1;
ET0 = 1;
TR0 = 1;
TF0 = 0;
}
void sgLeft()
{
angel = 5;
angel_back = angel;
cnt = 0;
}
void sgMiddle()
{
angel = 3;
if(angel_back != angel)
{
cnt = 0;
}
angel_back = angel;
}
void sgRight()
{
angel = 1;
angel_back = angel;
cnt = 0;
}
void Timer0 () interrupt 1
{
cnt++;
TL0 = 0x33;
TH0 = 0xFE;
if(cnt < angel)
{
sg90 = 1;
}
else
{
sg90 = 0;
}
if(cnt == 40)
{
cnt = 0;
sg90 = 1;
}
}
/*****pin.h*****/
#include <reg52.h>
sbit LED1 = P3^7;
sbit LED2 = P3^6;
sbit sg90 = P2^3;
sbit Trig = P2^2;
sbit Echo = P2^1;
sbit A1_1 = P1^0; //麦轮分布:ABBA
sbit A1_2 = P1^1;
sbit A2_1 = P1^6;
sbit A2_2 = P1^7;
sbit B1_1 = P1^4;
sbit B1_2 = P1^5;
sbit B2_1 = P1^2;
sbit B2_2 = P1^3;
测速小车
测速模块的原理:接好VCC和GND,模块信号指示灯会亮,当模块的槽中无遮挡时,接收管导通,模块OUT输出高电平,遮挡时,OUT输出低电平,信号指示灯灭。将测速模块接到外部中断引脚,检测是否有遮挡来检测电机的转速。
小车的直径是5.8cm,周长为18.212cm,小车的码盘有20个格子,每经过一个各自会产生(遮挡)高电平和(不遮挡)低电平,那么一个脉冲就是5.8 * 3.14 / 20 = 0.9106 cm
定时器可以定时0.25ms,那么经过3600次爆表就经过了0.9s,此时统计脉冲数,即速度。
小车测速并发送到串口
代码实现:
/*****main.c*****/
/*测速模块接到P3.2外部中断0引脚,设置下降沿触发,每当码盘经过测速模块时就会触
发一次下降沿,此时启动外部中断0,对speedCnt的值进行累加,同时定时器0在工作,
定时一个单位0.25ms,经过3600次爆表计时0.9s。此时统计speedCnt的值,将speedCnt的值
保存在speed中,然后复位进行下一次测速,最后通过sprintf完成数据转换将speed的值发
送到串口上去
*/
#include "uart.h"
#include "timer.h"
#include "stdio.h"
extern unsigned int speedB1;
extern unsigned int speedA1;
extern char signal;
char speedMeg1[24];
char speedMeg2[24];
void main()
{
Timer0Init();
INT0_Init();
INT1_Init();
UART_Init();
while(1)
{
if(signal == 1)
{
sprintf(speedMeg1,"speedA1: %d cm/s\r\n",speedA1);
//sprintf 格式化输出函数,将"speedA1: %d cm/s\r\n"这一串装到speedMeg1中,其中%d以speedA1来填充
SendString(speedMeg1);
sprintf(speedMeg2,"speedB1: %d cm/s\r\n",speedB1);
SendString(speedMeg2);
signal = 0;
}
}
}
/*****timer.c*****/
#include "reg52.h"
#include "motor.h"
unsigned int cnt = 0;
char signal = 0;
unsigned int speedA1;
unsigned int speedCntA1;
unsigned int speedB1;
unsigned int speedCntB1;
void Timer0Init()
{
TMOD &= 0xF0;
TMOD |= 0x01;
TL0 = 0x1A;
TH0 = 0xFF;
EA =1;
ET0 = 1;
TR0 = 1;
TF0 = 0;
}
void INT0_Init()
{
EA = 1;
EX0 = 1; //设置外部中断0打开
IT0 = 1; //设置外部中断由下降沿触发
}
void INT1_Init()
{
EA = 1;
EX1 = 1;
IT1 = 1;
}
void Timer0 () interrupt 1 //PWM是50Hz,也就是20ms,将20ms分为80份,定一个0.25ms出来
{
cnt++;
TL0 = 0x1A;
TH0 = 0xFF;
if(cnt == 3600) //报表3600次,经过了0.9s
{
cnt = 0;
signal = 1;
speedA1 = speedCntA1; //测速模块A1轮
speedCntA1 = 0;
speedB1 = speedCntB1; //测速模块B1轮
speedCntB1 = 0;
}
}
void INT0_Handler () interrupt 0
{
speedCntA1++;
}
void INT1_Handler () interrupt 2
{
speedCntB1++;
}
/*****pin.h*****/
#include <reg52.h>
sbit A1_1 = P1^0; //麦轮分布:ABBA
sbit A1_2 = P1^1;
sbit A2_1 = P1^6;
sbit A2_2 = P1^7;
sbit B1_1 = P1^4;
sbit B1_2 = P1^5;
sbit B2_1 = P1^2;
sbit B2_2 = P1^3;
sbit speedA1 = P3^2;
sbit speedB1 = P3^3;
这个既然能发送到串口,也就能发送到蓝牙模块,通过蓝牙模块进行小车的控制,并且小车的速度会实时的传到手机上。
小车测速并发送到oled屏幕上
利用之前开发oled屏幕的经验,把小车的速度实时的显示到oled屏幕上去,同时还发送到串口。
代码实现:
/*****main.c*****/
/*测速模块接到P3.2外部中断0引脚,设置下降沿触发,每当码盘经过测速模块时就会触
发一次下降沿,此时启动外部中断0,对speedCnt的值进行累加,同时定时器0在工作,
定时一个单位0.25ms,经过3600次爆表计时0.9s。此时统计speedCnt的值,将speedCnt的值
保存在speed中,然后复位进行下一次测速,最后通过sprintf完成数据转换将speed的值发
送到串口上去
*/
#include "uart.h"
#include "timer.h"
#include "stdio.h"
#include "oled.h"
extern unsigned int speedB1;
extern unsigned int speedA1;
extern char signal;
char speedMeg1[24];
char speedMeg2[24];
void main()
{
Timer0Init();
INT0_Init();
INT1_Init();
UART_Init();
Oled_RST();
Oled_Init();
Oled_Clean();
while(1)
{
if(signal == 1)
{
sprintf(speedMeg1,"speed_A1: %d cm/s",speedA1);
SendString(speedMeg1);
SendString("\r\n");
sprintf(speedMeg2,"speed_B1: %d cm/s",speedB1);
SendString(speedMeg2);
signal = 0;
}
Oled_Show_String(1,1,speedMeg1); //在第一行第一列显示A1轮的速度
Oled_Show_String(2,1,speedMeg2); //在第二行第一列显示B1轮的速度
}
}
/*****oled.c*****/
#include "IIC.h"
#include "intrins.h"
#include "pin.h"
#include "oledFun.h"
void Oled_Write_Cmd(char cmd)
{
IIC_Start(); //起始信号
IIC_SendByte(0x78); //发送从机地址
IIC_ACK(); //应答信号
IIC_SendByte(0x00); //写命令0x00
IIC_ACK(); //应答信号
IIC_SendByte(cmd); //发送命令
IIC_ACK(); //应答信号
IIC_Stop(); //停止信号
}
void Oled_Write_Data(char Data)
{
IIC_Start(); //起始信号
IIC_SendByte(0x78); //发送从机地址
IIC_ACK(); //应答信号
IIC_SendByte(0x40); //写数据0x40
IIC_ACK(); //应答信号
IIC_SendByte(Data); //发送命令
IIC_ACK(); //应答信号
IIC_Stop(); //停止信号
}
void Oled_Init(void)
{
Oled_Write_Cmd(0xAE);//--display off
Oled_Write_Cmd(0x00);//---set low column address
Oled_Write_Cmd(0x10);//---set high column address
Oled_Write_Cmd(0x40);//--set start line address
Oled_Write_Cmd(0xB0);//--set page address
Oled_Write_Cmd(0x81); // contract control
Oled_Write_Cmd(0xFF);//--128
Oled_Write_Cmd(0xA1);//set segment remap
Oled_Write_Cmd(0xA6);//--normal / reverse
Oled_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)
Oled_Write_Cmd(0x3F);//--1/32 duty
Oled_Write_Cmd(0xC8);//Com scan direction
Oled_Write_Cmd(0xD3);//-set display offset
Oled_Write_Cmd(0x00);//
Oled_Write_Cmd(0xD5);//set osc division
Oled_Write_Cmd(0x80);//
Oled_Write_Cmd(0xD8);//set area color mode off
Oled_Write_Cmd(0x05);//
Oled_Write_Cmd(0xD9);//Set Pre-Charge Period
Oled_Write_Cmd(0xF1);//
Oled_Write_Cmd(0xDA);//set com pin configuartion
Oled_Write_Cmd(0x12);//
Oled_Write_Cmd(0xDB);//set Vcomh
Oled_Write_Cmd(0x30);//
Oled_Write_Cmd(0x8D);//set charge pump enable
Oled_Write_Cmd(0x14);//
Oled_Write_Cmd(0xAF);//--turn on oled panel
}
void Oled_Clean()
{
unsigned char i,j; //用char会造成越界-128 - 127
for(i=0;i<8;i++)
{
Oled_Write_Cmd(0xB0 + i); //Page 0
Oled_Write_Cmd(0x00);
Oled_Write_Cmd(0x10); //第0列到第127列,写入数据后,列地址会自动偏移
for(j=0;j<128;j++)
{
Oled_Write_Data(0); //Page 0 -Page 7,第0列到第127列都写入0清屏
}
}
}
void Delay200ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 2;
j = 103;
k = 147;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void Oled_RST()
{
RES = 0;
Delay200ms();
RES = 1;
}
void Oled_Show_Byte(char rows,char columns,char oledByte)
{
unsigned int i;
//显示字符的上半部分
Oled_Write_Cmd(0xb0+(rows*2-2)); //选择行
//选择列
Oled_Write_Cmd(0x00+(columns&0x0f));
Oled_Write_Cmd(0x10+(columns>>4));
//显示数据
for(i=((oledByte-32)*16);i<((oledByte-32)*16+8);i++){
Oled_Write_Data(F8X16[i]);
}
//显示字符的下半部分
Oled_Write_Cmd(0xb0+(rows*2-1)); //选择行
//选择列
Oled_Write_Cmd(0x00+(columns&0x0f));
Oled_Write_Cmd(0x10+(columns>>4));
//显示数据
for(i=((oledByte-32)*16+8);i<((oledByte-32)*16+8+8);i++){
Oled_Write_Data(F8X16[i]);
}
}
//OLED显示一个字符串
void Oled_Show_String(char rows,char columns,char *str)
{
while(*str != '\0'){
Oled_Show_Byte(rows,columns,*str);
str++;
columns += 8;
}
}
/*****pin.h*****/
#include <reg52.h>
sbit A1_1 = P1^0; //麦轮分布:ABBA
sbit A1_2 = P1^1;
sbit A2_1 = P1^6;
sbit A2_2 = P1^7;
sbit B1_1 = P1^4;
sbit B1_2 = P1^5;
sbit B2_1 = P1^2;
sbit B2_2 = P1^3;
sbit speedA1 = P3^2;
sbit speedB1 = P3^3;
sbit SCL = P3^4;
sbit SDA = P3^5;
sbit RES = P3^6;
WiFi控制小车并实时传输
前边用过WIFi模块控制LED,它有两个模式,一个是当作客户端接入家里的路由器;另一个是它当作路由器,然后由别的设备当作客户端接入,这里采用第二种方式来控制小车。
这里我主要介绍用手机软件来进行连接小车WiFi模块的网络
- 首先用usb转ttl模块获得esp8266的IP地址,端口号默认333,然后将8266接到单片机上,使用单片机来将8266配置成路由模式。
- 然后用手机搜索WiFi名字,可能是安信可,也可能是ESP开头的名字,点击连接(这一步很重要,目的是为了两个设备在同一ip下,如果不连接网络,连接8266的路由时绝对连接不上的!!!)
- 然后打开手机软件,点击客户端,输入8266的ip和端口号就可以连接了。
- 然后通过手机软件发送指令就可以控制小车运动,同时速度也会传到手机上。
代码实现:
/*****main.c*****/
#include "uart.h"
#include "timer.h"
#include "stdio.h"
#include "oled.h"
#include "esp8266.h"
#include "delay.h"
extern unsigned int speedB1;
extern unsigned int speedA1;
extern char signal;
char speedMeg1[24];
char speedMeg2[24];
code char Send_Data[] = "AT+CIPSEND=0,34\r\n"; //这里如果字节开辟的不够大,串口会自动截取前边的数据进行发送
void main()
{
Timer0Init();
UART_Init();
Delay1000ms();
InitWifi();
Link_Client();
INT0_Init();
INT1_Init();
Oled_RST();
Oled_Init();
Oled_Clean();
while(1)
{
if(signal == 1)
{
SendString(Send_Data);
Delay1000ms();
sprintf(speedMeg1,"speed_A1: %d cm/s",speedA1);
SendString(speedMeg1);
SendString("\r\n");
sprintf(speedMeg2,"speed_B1: %d cm/s",speedB1);
SendString(speedMeg2);
signal = 0;
}
Oled_Show_String(1,1,speedMeg1);
Oled_Show_String(2,1,speedMeg2);
}
}
/*****uart.c*****/
#include "reg52.h"
#include "string.h"
#include "motor.h"
#define SIZE 12
sfr AUXR = 0x8E;
char buffer[SIZE];
extern char AT_OK_Flag; //发送AT指令返回标志
extern char Con_Client_Flag; //连接服务器成功标志
void UART_Init()
{
SCON = 0x50; //配置SCON为8位UART,波特率可变,REN允许串行接收
TMOD &= 0x0F; //定时器1高位清零,地位不变
TMOD |= 0x20; //定时器1采用8位重装计数
TL1 = 0xFD;
TH1 = 0xFD; //9600波特率对应初值
TR1 = 1; //启动定时器1
AUXR = 0x01; //禁止ALE信号输出
EA = 1; //开启总中断
ES = 1; //开启串口中断
}
void SendByte(char Senddata)
{
SBUF = Senddata;
while(!TI);
TI = 0;
}
void SendString(char *str)
{
while(*str != '\0')
{
SendByte(*str);
str++;
}
}
void UART_handler () interrupt 4
{
static int i = 0; //静态变量,初始化执行一次
char tmp;
if(RI) //如果接收到数据,硬件将RI自动置1,需要软件清0
{
RI = 0;
tmp = SBUF;
if(tmp == 'M' || tmp == 'O' || tmp == '0')
{
i = 0;
}
buffer[i] = tmp;
i++;
if(buffer[0] == 'O' && buffer[1] == 'K')
{
AT_OK_Flag = 1;
memset(buffer,'\0',SIZE);
}
if(buffer[0] == '0' && buffer[2] == 'C')
{
Con_Client_Flag = 1;
memset(buffer,'\0',SIZE);
}
if(buffer[0] == 'M')
{
switch(buffer[1])
{
case '1':
Go_Forward();
break;
case '2':
Go_Back();
break;
case '3':
Turn_Left();
break;
case '4':
Turn_Right();
break;
case '5':
Left_Tran();
break;
case '6':
Right_Tran();
break;
default:
Car_Stop();
break;
}
}
if(i == SIZE)
{
i = 0;
memset(buffer,'\0',SIZE);
}
}
}
/*****esp8266.c*****/
#include "uart.h"
code char Double_Mode[] = "AT+CWMODE=2\r\n";
code char More_Con[] = "AT+CIPMUX=1\r\n";
code char Start_Ser[] = "AT+CIPSERVER=1\r\n";
char AT_OK_Flag = 0;
char Con_Client_Flag = 0;
void InitWifi()
{
SendString(Double_Mode); //配置双模模式
while(!AT_OK_Flag);
AT_OK_Flag = 0;
SendString(More_Con); //使能更多链接
while(!AT_OK_Flag);
AT_OK_Flag = 0;
}
void Link_Client()
{
SendString(Start_Ser); //连接服务器
while(!Con_Client_Flag);
AT_OK_Flag = 0;
}
4G模块控制实时传输
前边用4G模块控制LED,它的原理是家里使用的路由器的网络是局域网,外网不能访问,所以要有一个桥梁将局域网(内网)变成一个公网(外网)以供外网设备进行连接,可以借助花生壳软件进行内网穿透,然后外网设备就可以进行连接,最后由服务器去控制外网设备。
这里还是用手机软件来搭建一个服务器,然后用花生壳软件进行内网穿透,4G模块连接穿透后的IP就连接上了服务器,最后通过服务器发送指令就可以控制小车运动了,同时小车的速度还会实时传输到手机上。
- 首先建立一个TCP服务器
- 将服务器的ip和端口号输入到花生壳软件中
- 用花生壳软件内网穿透后的ip和端口号来配置4G模块
注意:首先要建立TCP服务器,要不然花生壳软件会连接失败!!!
因为4G模块自己有心跳包,但我们这里要发送速度,为了避免造成影响,所以配置4G模块不发送心跳包。
代码没有改变(和蓝牙模块一样),4G模块主要是配置问题,配置好了它会自动进入透传模式。
语音控制小车
语音小车用语音模块SU-03T,通过配置语音词条可以实现对小车的控制,
设置A25,A26,A27这三个引脚的初始状态都为高电平,然后通过配置三个引脚的电平状态来控制小车在避障、循迹、跟随、停止四种模式下切换。
- 当A25 == 0 && A26 == 1 && A27 == 1时,小车处于循迹模式;
- 当A25 == 1 && A26 == 0 && A27 == 1时,小车处于跟随模式;
- 当A25 == 1 && A26 == 1 && A27 == 0时,小车处于避障模式;
- 当A25 == 0 && A26 == 0 && A27 == 0时,小车处于停止模式。
最后将这三个引脚与单片机进行连接来控制小车的状态
代码实现:
/*****main.c*****/
#include "SG90.h"
#include "HC04.h"
#include "delay.h"
#include "oled.h"
#include "mode.h"
#include "pin.h"
#include "motor.h"
#define Avoid 1
#define Follow 2
#define Track 3
#define Stop 4
extern char dir;
void main()
{
int mark = 0;
Timer0Init();
Timer1Init();
sgMiddle();
Delay300ms();
Delay300ms();
Oled_RST();
Oled_Init();
Oled_Clean();
Oled_Show_String(2,5,"****Ready****");
while(1)
{
if(A25 == 0 && A26 == 1 && A27 == 1)
{
if(mark != Track) //如果第二次循环进来还是循迹模式,那就不执行清屏函数,避免它一直刷屏
{
Oled_Clean();
Oled_Show_String(2,1,"**Track_Mode**");
}
Track_Mode();
mark = Track;
}
if(A25 == 1 && A26 == 0 && A27 == 1)
{
if(mark != Follow)
{
Oled_Clean();
Oled_Show_String(2,1,"**Follow_Mode**");
}
Follow_Mode();
mark = Follow;
}
if(A25 == 1 && A26 == 1 && A27 == 0)
{
if(mark != Avoid)
{
Oled_Clean();
Oled_Show_String(2,1,"***Avoid_Mode***");
}
Avoid_Mode();
mark = Avoid;
}
if(A25 == 0 && A26 == 0 && A27 == 0)
{
if(mark != Stop)
{
Oled_Clean();
Oled_Show_String(2,1,"***Stop_Mode***");
}
Car_Stop();
mark = Stop;
}
}
}
/*****mode.c*****/
#include "pin.h"
#include "SG90.h"
#include "HC04.h"
#include "motor.h"
#include "delay.h"
double disMiddle;
double disLeft;
double disRight;
void Avoid_Mode()
{
disMiddle = Get_distance();
if(disMiddle > 30)
{
Go_Forward();//前进
}
else if(disMiddle < 10)
{
Go_Back();//后退
// Delay200ms();
// Car_Stop();
}
else
{
Car_Stop();
sgLeft(); //测左边距离
Delay300ms();
disLeft = Get_distance();
sgMiddle();
Delay300ms();
sgRight(); //测右边距离
Delay300ms();
disRight = Get_distance();
if(disLeft < disRight)
{
Turn_Left();
Delay150ms();
Car_Stop();
}
if(disRight < disLeft)
{
Turn_Right();
Delay150ms();
Car_Stop();
}
}
sgMiddle();
Delay300ms();
}
void Track_Mode()
{
if(Lefttrack == 0 && Righttrack == 0)
{
Go_Forward();
}
if(Lefttrack == 1 && Righttrack == 0)
{
Turn_Left();
}
if(Lefttrack == 0 && Righttrack == 1)
{
Turn_Right();
}
if(Lefttrack == 1 && Righttrack == 1)
{
Car_Stop();
}
}
void Follow_Mode()
{
if(Leftfollow == 0 && Rightfollow == 0)
{
Go_Forward();
}
if(Leftfollow == 0 && Rightfollow == 1)
{
Turn_Left();
}
if(Leftfollow == 1 && Rightfollow == 0)
{
Turn_Right();
}
if(Leftfollow == 1 && Rightfollow == 1)
{
Car_Stop();
}
}
显示效果:视频发到抖音去了(DX_dingding)
总结
小车项目中遇到的问题:
- 小车不按自己设定的方向走,可能是没共地;
- 小车上电以后只能向前走,可能是电源电压不足,小车进行转向的时候,需要比较的的电流;
- 蓝牙模块在发送指令控制小车进行运动时会发生断联的情况,蓝牙模块供电不足;
- 如果使用金属底板,要注意模块和单片机不能直接放到金属板上,可能会导电烧毁板子。可以在底下垫一层纸起到绝缘的效果;
- 当小车组装起来的时候,可以用万用表测量一下,防止模块正负极接反导致模块烧毁;
- 电池选用18650锂电池。