您当前的位置:首页 > 电子 > 机器人与智能物联

【智能小车综合项目】(超详细!)—蓝牙、PWM调速、差速转弯等智能小车问题

时间:11-24来源:作者:点击数:
城东书院 www.cdsy.xyz

一、先让小车能动起来

1.1 硬件准备

单片机、智能小车套装(电机、电机驱动、电源、车轮、杜邦线)

1.11 L9110s—电机驱动模块

接通VCC,GND 模块电源指示灯亮,以下资料来源官方,官方给的数据有误。(实际要通过自己代码来调试电机正转反转)

IA1输入高电平,IA1输入低电平,【OA1 OB1】电机正转;

IA1输入低电平,IA1输入高电平,【OA1 OB1】电机反转;

IA2输入高电平,IA2输入低电平,【OA2 OB2】电机正转;

IA2输入低电平,IA2输入高电平,【OA2 OB2】电机反转;

L9110s—电机驱动模块
1.12 L9110驱动模块与单片机、电源、轮子的接线图
在这里插入图片描述

1.2 代码

① motor.c —— 封装小车向前后左右的功能代码
#include "reg52.h"
sbit RightCon1A = P3^2;  //右轮
sbit RightCon1B = P3^3;
sbit LeftCon1A = P3^4;   //左轮
sbit LeftCon1B = P3^5;

//前进
void goForward()
{
	LeftCon1A = 0;   //左轮向前
	LeftCon1B = 1;
	
	RightCon1A = 0;  //右轮向前
	RightCon1B = 1;
}
//后退
void goBack()
{
	LeftCon1A = 1;
	LeftCon1B = 0;
	
	RightCon1A = 1;
	RightCon1B = 0;
}
//左转
void goLeft()
{
	LeftCon1A = 0;  //左轮不动
	LeftCon1B = 0;
	
	RightCon1A = 0;  //右轮向前
	RightCon1B = 1;
}
//右转
void goRight()
{
	LeftCon1A = 0;  //左轮向前
	LeftCon1B = 1;
	
	RightCon1A = 0;  //右轮不动
	RightCon1B = 0;
}
② main.c —— 主函数
#include "reg52.h"
#include "intrins.h"
#include "motor.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);
}

void main()
{
	while(1){
		goForward();
		Delay1000ms();
		Delay1000ms();
		goBack();
		Delay1000ms();
		Delay1000ms();
		goLeft();
		Delay1000ms();
		Delay1000ms();
		goRight();
		Delay1000ms();
		Delay1000ms();
	}
}

二、蓝牙小车

2.1 先通过串口控制小车

原理

配置串口中断模式,串口不断的接收数据,如果串口接收到我们自己设置【标记】,让小车进行相对应的操作。

步骤

直接将自己之前做的串口接收数据的代码复制粘贴过来 (下面也有附代码),串口初始化和串口发送和接收的代码基本通用的,不用改。

1.配置串口通信中断模式(避免与主函数main执行的程序发生冲突);

2.每次接收到数据都会进行串口中断。如果接收到"M"开头的数据,把数组下标"i"设置为0,让数组从下标0开始保存数据;

3.判断数组存的数据进行相对应的操作;

4.串口处于一直接收数据的状态,如果一直没有接收到我们需要的M开头的数据,存数据的数组慢了(越界了),要记得把数组清0;

5.使用STC-ISP软件的串口助手调试(如下图)。

在这里插入图片描述

重点

①要记得配置串口工作方式1,REN位要使能可以接收数据

②打开串口中断,用串口中断来接收数据

③下面有附上串口通信UART的代码

在这里插入图片描述

2.2 代码

①uart.c —— 串口通信的功能代码
#include "reg52.h"
#include "motor.h"
#include "string.h"

#define SIZE 12
sfr AUXR = 0x8E;
char buffer[SIZE];

void UartInit(void)		//9600bps@11.0592MHz
{
	AUXR = 0x01;
	SCON = 0x50; //配置串口工作方式1,REN使能接收
	TMOD &= 0x0F;
	TMOD |= 0x20;//定时器1工作方式位8位自动重装
	
	TH1 = 0xFD;
	TL1 = 0xFD;//9600波特率的初值
	TR1 = 1;//启动定时器
	
	EA = 1;//开启总中断
	ES = 1;//开启串口中断
}

//M1前  M2后 M3左  M4右
void Uart_Handler() interrupt 4
{
	static int i = 0;//静态变量,被初始化一次
	char tmp;

	if(RI)//中断处理函数中,对于接收中断的响应
	{
			RI = 0; //清除接收中断标志位
			
			tmp = SBUF;  //①获取SBUF寄存器接收到的数据
			if(tmp == 'M'){   //②如果接收数据里有字母M,就让buffer数组从0开始把
				i = 0;
			}
			//③然后buffer[0]把M存起来, 
			//i加加, buffer[1]存接下来的数据
			buffer[i++] = tmp;   //buffer[i]=tmp; i++;
		
			//④buffer[0]=M的情况下,buffer[1]为1,2,3,4做不同的操作
			if(buffer[0] == 'M'){
				switch(buffer[1]){
					case '1':
						goForward();
						break;
					case '2':
						goBack();
						break;
					case '3':
						goLeft();
						break;
					case '4':
						goRight();
						break;
					default:
						stop();
						break;
				}
			}
			
			//如果接收数据一直没有遇到M,数组满了,把数组清空
			if(i == 12) {
				memset(buffer, '\0', SIZE);
				i = 0;
			}
	}
		
}
②motor.c —— 封装小车向前后左右的功能代码
③main.c —— 主函数

2.3 把蓝牙和小车结合起来

1.把蓝牙RX、TX、GND、VCC插上小车的单片机,

2.然后手机微信扫描蓝牙模块自带的蓝牙助手(或者自己开发的手机APP有蓝牙连接功能),

3.选择蓝牙的名字连接上,发送文本数据即可。

【蓝牙的知识点】:

1.因为蓝牙是透明传输,不对数据进行任何的解析和处理,只负责将数据从源地址传原封不动输到目的地址。

2.所以蓝牙

需要接收数据就直接插上RX口

需要发送数据就直接插上TX口即可。

3.AT指令

可以通过AT指令来改蓝牙名字,重启模块等等操作。具体的指令,模块的官网有。

三、点动效果——小车的改良

把小车的效果做的跟遥控小车一样,比如不按它就不动,一直按着前进的按键它才会向前。

3.1 思路

【怎么做?】

1.在主函数循环执行stop()函数,让小车一直保持停止的状态

2.那么当有人按下前进按键,串口接收到数据后中断主函数来执行串口中断中的前进函数,一直按着前进的按键就一直中断,一直往前。如果松手,就会停止。

在这里插入图片描述
【重点!!遇到的问题】

虽然我们一直按的前进,单片机串口也不断地接收到了前进的指令,并执行前进的程序,但是这过程太耗时了。

每一次的单片机接收到的前进信号是我们手机按键按下前进按钮,然后手机的蓝牙把数据发送到单片机的蓝牙,单片机的蓝牙再传输给单片机的串口,串口接收到数据后传给单片机的cpu,单片机才执行串口中断并进行前进的操作。

所以单片机接收到前进信号的间隔太久,虽然我们一直按着前进,但是等到单片机接收到下一次的前进信号这中间的间隔时间,由于单片机执行的速度非常的快,例如51单片机的12MHZ,执行一条指令的机器周期只需要1us起步,所以单片机等接收到你下一次漫长的前进命令时,单片机早就又执行了几千上万次的主函数中的stop()函数。

所以虽然我们一直按的前进,但是小车根本不会动,你一直按的前进,单片机串口刚接收到小车前进的指令并执行,还看不出啥反应,单片机主函数马上又来了几千上万条停止的指令。

【如何解决】

在串口中断的小车执行前后左右的操作后面延时一会(这里延时10ms),让它先看到一点前后左右操作。具体延时多久,这个时间自己慢慢调试,找到一个平衡点。

在这里插入图片描述

3.2 修改后的代码

①uart.c —— 串口通信的功能代码
#include "reg52.h"
#include "motor.h"
#include "string.h"
#include "delay.h"
sbit D5 = P3^7;
#define SIZE 12

sfr AUXR = 0x8E;
char buffer[SIZE];

void UartInit(void)		//9600bps@11.0592MHz
{
	AUXR = 0x01;
	SCON = 0x50; //配置串口工作方式1,REN使能接收
	TMOD &= 0x0F;
	TMOD |= 0x20;//定时器1工作方式位8位自动重装
	
	TH1 = 0xFD;
	TL1 = 0xFD;//9600波特率的初值
	TR1 = 1;//启动定时器
	
	EA = 1;//开启总中断
	ES = 1;//开启串口中断
}

//M1qian  M2 hou M3 zuo  M4 you
void Uart_Handler() interrupt 4
{
	static int i = 0;//静态变量,被初始化一次
	char tmp;

	if(RI)//中断处理函数中,对于接收中断的响应
	{
			RI = 0;//清除接收中断标志位
			tmp = SBUF;
			if(tmp == 'M'){
				i = 0;
			}
			buffer[i++] = tmp;
		
			//小车前后左右指令
			if(buffer[0] == 'M'){
				switch(buffer[1]){
					case '1':
						goForward();
						Delay10ms();
						break;
					case '2':
						goBack();
						Delay10ms();
						break;
					case '3':
						goLeft();
						Delay10ms();
						break;
					case '4':
						goRight();
						Delay10ms();
						break;
					default:
						stop();
						break;
				}
			}
		
			if(i == 12) {
				memset(buffer, '\0', SIZE);
				i = 0;
			}
	}
}
②main.c —— 主函数
#include "motor.h"
#include "delay.h"
#include "uart.h"

void main()
{
	UartInit();
	
	while(1){
		stop();
	}
}

四、PWM调速小车

pwm是什么?
 PWM,英文名Pulse Width Modulation,是脉冲宽度调制缩写,它是通过对一系列脉冲的宽度进行调
 制,等效出所需要的波形 (包含形状以及幅值),对模拟信号电平进行数字编码,也就是说通过调节占
 空比的变化来调节信号、能量等的变化,占空比就是指在一个周期内,信号处于高电平的时间占据整个
 信号周期的百分比,例如方波的占空比就是50%.

①脉冲宽度调制

②通过占空比编码模拟信号

③占空比:一个周期内,高电平占据时长的百分比(通俗点就是有效的数据占空的比例)

【自己的理解】

如下图,

绿色部分一个周期是20ms,在一个周期内,高电平占0.5ms,低电平占19.5ms。占空比是:0.5ms/20ms =2.5%。

红色部分一个周期是20ms,在一个周期内,高电平占2ms,低电平占18ms。占空比是:2ms/20ms=10%。

这个高、低电平在每个周期里的持续时间我们可以自己调制,这就是脉冲宽度调制,这就是PWM。

在这里插入图片描述
用定时器来模拟PWM波

按照上面的波形,我们可以用定时器来设置20ms为一个周期,设置0.5ms为一个单位(也可以设置1ms、2ms为一个单位,看个人),

按照这个设置,我们定时器计数0.5ms就进入定时器中断函数一次,每次进去统计次数+1,进去定时器中断函数40次的时候就是一个周期也就是20ms

ok,我们用定时器模拟出了一个20ms的周期,接下来写代码模拟红色的波形图,在20ms内,我们给io口 2ms 的高电平,剩下18ms为低电平,然后以此为周期

在这里插入图片描述
用PWM给小车调速

那用PWM给小车调速也是一样的原理,由于直接给电机电平是全速的状态,那我们可以通过pwm在一个周期内,一段时间给小车电平,一段时间不给电平,这样就可以很好的控制小车的速度。在不给电平的状态下,其实电机是有惯性的,也不会完全停止。

在这里插入图片描述
其它用途:

除了可以调速,通过PWM调速这样更有利于小车的转弯。因为小车在转弯的过程中,需要一个轮子动,一个轮子不动。如果在小车直行的过程中,突然让一个轮子不动,让小车转弯,这样会显得转弯不够丝滑,通过pwm调速,可以让轮子的速度降下来,而不是完全的不动,这样转弯会丝滑很多。(看下一章,pwm差速转弯)

代码

①time.c——定时器模拟PWM
#include "motor.h"
#include "reg52.h"

char speed;
char cnt = 0;

void Time0Init()
{
	//1. 配置定时器0工作模式位16位计时
	TMOD = 0x01;
	//2. 给初值,定一个0.5出来
	TL0=0x33;
	TH0=0xFE;
	//3. 开始计时
	TR0 = 1;
	TF0 = 0;
	//4. 打开定时器0中断
	ET0 = 1;
	//5. 打开总中断EA
	EA = 1;
}

void Time0Handler() interrupt 1
{
	cnt++;  //统计爆表的次数. cnt=1的时候,报表了1
	//重新给初值
	TL0=0x33;
	TH0=0xFE;
	
	//控制PWM波
	if(cnt < speed){
		//前进
		goForward();
	}else{
		//停止
		stop();
	}
	
	if(cnt == 40){//爆表40次,经过了20ms
		cnt = 0;  //当40次表示20ms,重新让cnt从0开始,计算下一次的20ms
	}	
}
②main.c
#include "motor.h"
#include "delay.h"
#include "uart.h"
#include "time.h"

extern char speed;

void main()
{
	Time0Init();
	//UartInit();
	
	while(1){
		speed = 10; //10份单位时间0.5ms全速运行,30份停止,所以慢
		Delay1000ms();
		Delay1000ms();
		speed = 20;   //20份单位时间0.5ms的全速运行,20份的0.5ms停止
		Delay1000ms();
		Delay1000ms();
		speed = 40;   //全速前进
		Delay1000ms();
		Delay1000ms();
	}
}
③motor.c —— 封装小车向前后左右的功能代码
④delay.c ——自己去软件生成
⑤uart.c —— 串口通信的功能代码(这里其实没用到它)

五、PWM小车差速转弯(丝滑转弯)

用途:

通过PWM调速这样更有利于小车的转弯。因为小车在转弯的过程中,需要一个轮子动,一个轮子不动。如果在小车直行的过程中,突然让一个轮子不动,让小车转弯,这样会显得转弯不够丝滑,通过pwm调速,可以让轮子的速度降下来,而不是让轮子完全的不动,这样转弯会丝滑很多。

如何实现:

原理还是跟第四章的PWM调速小车一样,由于需要把小车左右两边的轮子分开控制,所以我们需要两个定时器分别模拟PWM控制左右两边的电机。所以配置两个定时器写两个中断函数即可,直接看代码。

代码:

①time.c——两个定时器分别模拟PWM来控制左右轮(与前面不同)
#include "motor.h"
#include "reg52.h"

char speedLeft;
char cntLeft = 0;

char speedRight;
char cntRight = 0;

void Time0Init()
{
	//1. 配置定时器0工作模式位16位计时
	TMOD = 0x01;
	//2. 给初值,定一个0.5出来
	TL0=0x33;
	TH0=0xFE;
	//3. 开始计时
	TR0 = 1;
	TF0 = 0;
	//4. 打开定时器0中断
	ET0 = 1;
	//5. 打开总中断EA
	EA = 1;
}

void Time1Init()
{
	//1. 配置定时器1工作模式位16位计时
	TMOD &= 0x0F;     //保留低四位,定时器0的配置不变,把高四位清0
	TMOD |= 0x01 << 4;  //0x01左移4位0x10,再或TMOD。
	//2. 给初值,定一个0.5出来
	TL1=0x33;
	TH1=0xFE;
	//3. 开始计时
	TR1 = 1;
	TF1 = 0;
	//4. 打开定时器1中断
	ET1 = 1;
	//5. 打开总中断EA
	EA = 1;
}


void Time1Handler() interrupt 3
{
	cntRight++;  //统计爆表的次数. cnt=1的时候,报表了1
	//重新给初值
	TL1=0x33;
	TH1=0xFE;
	
	//控制PWM波
	if(cntRight < speedRight){
		//右轮前进
		goForwardRight();
	}else{
		//右轮停止
		stopRight();
	}
	
	if(cntRight == 40){//爆表40次,经过了20ms
		cntRight = 0;  //当40次表示20ms,重新让cnt从0开始,计算下一次的20ms
	}
		
}

void Time0Handler() interrupt 1
{
	cntLeft++;  //统计爆表的次数. cnt=1的时候,报表了1
	//重新给初值
	TL0=0x33;
	TH0=0xFE;
	
	//控制PWM波
	if(cntLeft < speedLeft){
		//左轮前进
		goForwardLeft();
	}else{
		//左轮停止
		stopLeft();
	}
	
	if(cntLeft == 40){//爆表40次,经过了20ms
		cntLeft = 0;  //当40次表示20ms,重新让cnt从0开始,计算下一次的20ms
	}
}
②main.c —— 主函数(与前面不同)
#include "motor.h"
#include "delay.h"
#include "uart.h"
#include "time.h"

extern char speedLeft;
extern char speedRight;

void main()
{
	Time0Init(); //先初始化定时器0,因为定时器0我们配置的比较暴力
	Time1Init();  //再初始化定时器1
	//UartInit();
	
	while(1){
		//向左转弯:右边轮子快,左边轮子慢
		speedLeft = 10;//10份单位时间全速运行,30份停止,所以慢,20ms是40份的500us
		speedRight = 40;
		Delay1000ms();
		Delay1000ms();
	
		//向右转弯:左边轮子快,右边轮子慢
		speedLeft = 40;
		speedRight = 10;
		Delay1000ms();
		Delay1000ms();
	}
}
③motor.c —— 封装小车向前后左右的功能代码(与前面不同)
#include "reg52.h"

sbit RightCon1A = P3^2;
sbit RightCon1B = P3^3;

sbit LeftCon1A = P3^4;
sbit LeftCon1B = P3^5;


//①通过PWM来丝滑的左右转弯
//左轮前进
void goForwardLeft()
{
	LeftCon1A = 0;
	LeftCon1B = 1;
}
//左轮停止
void stopLeft()
{
	LeftCon1A = 0;
	LeftCon1B = 0;
}
//右轮前进
void goForwardRight()
{
	RightCon1A = 0;
	RightCon1B = 1;
}
//右轮停止
void stopRight()
{
	RightCon1A = 0;
	RightCon1B = 0;
}

//————————————————————————————————
//②全速的前进后退左转右转
void goForward()
{
	LeftCon1A = 0;
	LeftCon1B = 1;
	
	RightCon1A = 0;
	RightCon1B = 1;
}

void goRight()
{
	LeftCon1A = 0;
	LeftCon1B = 1;
	
	RightCon1A = 0;
	RightCon1B = 0;
}


void goLeft()
{
	LeftCon1A = 0;
	LeftCon1B = 0;
	
	RightCon1A = 0;
	RightCon1B = 1;
}

void goBack()
{
	LeftCon1A = 1;
	LeftCon1B = 0;
	
	RightCon1A = 1;
	RightCon1B = 0;
}

void stop()
{
	LeftCon1A = 0;
	LeftCon1B = 0;
	
	RightCon1A = 0;
	RightCon1B = 0;
}
④delay.c ——自己去软件生成
⑤uart.c —— 串口通信的功能代码(这里其实没用到它)
城东书院 www.cdsy.xyz
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐