单片机、智能小车套装(电机、电机驱动、电源、车轮、杜邦线)
接通VCC,GND 模块电源指示灯亮,以下资料来源官方,官方给的数据有误。(实际要通过自己代码来调试电机正转反转)
IA1输入高电平,IA1输入低电平,【OA1 OB1】电机正转;
IA1输入低电平,IA1输入高电平,【OA1 OB1】电机反转;
IA2输入高电平,IA2输入低电平,【OA2 OB2】电机正转;
IA2输入低电平,IA2输入高电平,【OA2 OB2】电机反转;
#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;
}
#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();
}
}
配置串口中断模式,串口不断的接收数据,如果串口接收到我们自己设置【标记】,让小车进行相对应的操作。
直接将自己之前做的串口接收数据的代码复制粘贴过来 (下面也有附代码),串口初始化和串口发送和接收的代码基本通用的,不用改。
1.配置串口通信中断模式(避免与主函数main执行的程序发生冲突);
2.每次接收到数据都会进行串口中断。如果接收到"M"开头的数据,把数组下标"i"设置为0,让数组从下标0开始保存数据;
3.判断数组存的数据进行相对应的操作;
4.串口处于一直接收数据的状态,如果一直没有接收到我们需要的M开头的数据,存数据的数组慢了(越界了),要记得把数组清0;
5.使用STC-ISP软件的串口助手调试(如下图)。
重点:
①要记得配置串口工作方式1,REN位要使能可以接收数据
②打开串口中断,用串口中断来接收数据
③下面有附上串口通信UART的代码
#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;
}
}
}
1.把蓝牙RX、TX、GND、VCC插上小车的单片机,
2.然后手机微信扫描蓝牙模块自带的蓝牙助手(或者自己开发的手机APP有蓝牙连接功能),
3.选择蓝牙的名字连接上,发送文本数据即可。
【蓝牙的知识点】:
1.因为蓝牙是透明传输,不对数据进行任何的解析和处理,只负责将数据从源地址传原封不动输到目的地址。
2.所以蓝牙
需要接收数据就直接插上RX口,
需要发送数据就直接插上TX口即可。
3.AT指令
可以通过AT指令来改蓝牙名字,重启模块等等操作。具体的指令,模块的官网有。
把小车的效果做的跟遥控小车一样,比如不按它就不动,一直按着前进的按键它才会向前。
1.在主函数循环执行stop()函数,让小车一直保持停止的状态
2.那么当有人按下前进按键,串口接收到数据后中断主函数来执行串口中断中的前进函数,一直按着前进的按键就一直中断,一直往前。如果松手,就会停止。
虽然我们一直按的前进,单片机串口也不断地接收到了前进的指令,并执行前进的程序,但是这过程太耗时了。
每一次的单片机接收到的前进信号是我们手机按键按下前进按钮,然后手机的蓝牙把数据发送到单片机的蓝牙,单片机的蓝牙再传输给单片机的串口,串口接收到数据后传给单片机的cpu,单片机才执行串口中断并进行前进的操作。
所以单片机接收到前进信号的间隔太久,虽然我们一直按着前进,但是等到单片机接收到下一次的前进信号这中间的间隔时间,由于单片机执行的速度非常的快,例如51单片机的12MHZ,执行一条指令的机器周期只需要1us起步,所以单片机等接收到你下一次漫长的前进命令时,单片机早就又执行了几千上万次的主函数中的stop()函数。
所以虽然我们一直按的前进,但是小车根本不会动,你一直按的前进,单片机串口刚接收到小车前进的指令并执行,还看不出啥反应,单片机主函数马上又来了几千上万条停止的指令。
在串口中断的小车执行前后左右的操作后面延时一会(这里延时10ms),让它先看到一点前后左右操作。具体延时多久,这个时间自己慢慢调试,找到一个平衡点。
#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;
}
}
}
#include "motor.h"
#include "delay.h"
#include "uart.h"
void main()
{
UartInit();
while(1){
stop();
}
}
PWM,英文名Pulse Width Modulation,是脉冲宽度调制缩写,它是通过对一系列脉冲的宽度进行调
制,等效出所需要的波形 (包含形状以及幅值),对模拟信号电平进行数字编码,也就是说通过调节占
空比的变化来调节信号、能量等的变化,占空比就是指在一个周期内,信号处于高电平的时间占据整个
信号周期的百分比,例如方波的占空比就是50%.
①脉冲宽度调制
②通过占空比编码模拟信号
③占空比:一个周期内,高电平占据时长的百分比(通俗点就是有效的数据占空的比例)
【自己的理解】
如下图,
绿色部分一个周期是20ms,在一个周期内,高电平占0.5ms,低电平占19.5ms。占空比是:0.5ms/20ms =2.5%。
红色部分一个周期是20ms,在一个周期内,高电平占2ms,低电平占18ms。占空比是:2ms/20ms=10%。
这个高、低电平在每个周期里的持续时间我们可以自己调制,这就是脉冲宽度调制,这就是PWM。
按照上面的波形,我们可以用定时器来设置20ms为一个周期,设置0.5ms为一个单位(也可以设置1ms、2ms为一个单位,看个人),
按照这个设置,我们定时器计数0.5ms就进入定时器中断函数一次,每次进去统计次数+1,进去定时器中断函数40次的时候就是一个周期也就是20ms。
ok,我们用定时器模拟出了一个20ms的周期,接下来写代码模拟红色的波形图,在20ms内,我们给io口 2ms 的高电平,剩下18ms为低电平,然后以此为周期。
那用PWM给小车调速也是一样的原理,由于直接给电机电平是全速的状态,那我们可以通过pwm在一个周期内,一段时间给小车电平,一段时间不给电平,这样就可以很好的控制小车的速度。在不给电平的状态下,其实电机是有惯性的,也不会完全停止。
除了可以调速,通过PWM调速这样更有利于小车的转弯。因为小车在转弯的过程中,需要一个轮子动,一个轮子不动。如果在小车直行的过程中,突然让一个轮子不动,让小车转弯,这样会显得转弯不够丝滑,通过pwm调速,可以让轮子的速度降下来,而不是完全的不动,这样转弯会丝滑很多。(看下一章,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
}
}
#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();
}
}
通过PWM调速这样更有利于小车的转弯。因为小车在转弯的过程中,需要一个轮子动,一个轮子不动。如果在小车直行的过程中,突然让一个轮子不动,让小车转弯,这样会显得转弯不够丝滑,通过pwm调速,可以让轮子的速度降下来,而不是让轮子完全的不动,这样转弯会丝滑很多。
原理还是跟第四章的PWM调速小车一样,由于需要把小车左右两边的轮子分开控制,所以我们需要两个定时器来分别模拟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
}
}
#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();
}
}
#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;
}