2025年2月24日 星期一 甲辰(龙)年 腊月廿四 设为首页 加入收藏
rss
您当前的位置:首页 > 电子 > 机器人与智能物联

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

时间:11-24来源:作者:点击数:57
城东书院 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
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐