"无限交互,全新驾驶体验!智能语音小车,与您共同开创未来出行。”#51单片机最终项目《智能语音小车》【上】
前言
本篇博文介绍的是用51单片机的最终项目《智能语音小车》【上】,包含L9110S电机控制器接线, L9110前后左右控制小车,电机相关代码封装–分文件,串口控制小车,手机通过蓝牙控制小车–自定义按键,蓝牙小车的点动控制,PWM软件调速,左右电机的各自调速管理。看到这篇博文的朋友,可以先赞再看吗?
预备知识
一、需要我之前写的所有博文的知识,如果还没看我之前的的博文,可以去看看后再看本篇博文

二、C变量
三、基本输入输出
四、流程控制
五、函数
六、指针
七、字符串
如果以上知识不清楚,请自行学习后再来浏览。如果我有没例出的,请在评论区写一下。谢谢啦!
1. L9110S电机控制器接线
1.1 L9110S概述
L9110S是一种双路H桥驱动器芯片,通常用于控制直流电机或步进电机。该芯片具有内置的双H桥驱动器,可实现电机的双向控制。它可以通过控制输入信号来控制电机的转向和速度,并且通常被广泛应用于各种小型电动车辆、机器人以及其他需要电机控制的项目中。L9110S还具有过流保护功能,能够保护电机和驱动器免受损坏。
1.2 L9110S IO口描述
以下资料来源官方,但是不对
IA1输入高电平,IA1输入低电平,【OA1 OB1】电机正转;
IA1输入低电平,IA1输入高电平,【OA1 OB1】电机反转;
IA2输入高电平,IA2输入低电平,【OA2 OB2】电机正转;
IA2输入低电平,IA2输入高电平,【OA2 OB2】电机反转;
1.3 L9110S 实物图

1.4 L9110S与单片机接线
- 注意:电源线千万别接错,不然会在0.5秒内烧坏模块,电源正常接通电源指示灯会亮。

2. L9110前后左右控制小车
2.1 L9110前后左右控制小车
- 分别用高低电平测试
B-1A和B-1B ``L9110电机驱动模块引脚,A-1A和A-1B ``L9110电机驱动模块引脚。 - 将测试结果封装函数
- 封装前后左右行走函数
2.1分别用高低电平测试B-1A和B-1B L9110电机驱动模块引脚,A-1A和A-1B L9110电机驱动模块引脚。
-
将
B-1A置1,B-1B置0,将A-1A置1,A-1B置0。看一下电机的转向。 -
代码体现
RightControA = 1;
RightControB = 0;
LeftControA = 1;
LeftControB = 0;
测试结果为小车向后跑,也就电机反转。
2.2 将测试结果封装函数
-
测试结果为小车向前跑,封装向前函数。
-
代码体现。
void goBack()
{
RightControA = 1;
RightControB = 0;
LeftControA = 1;
LeftControB = 0;
}
2.3封装前后左右行走函数
- 向前函数封装思路
将向后函数的高低电平兑换就实现了向前功能。
- 代码体现
void goFront()
{
RightControA = 0;
RightControB = 1;
LeftControA = 0;
LeftControB = 1;
}
- 向左和向右行走函数思路
如果要实现向左行走就将控制左轮的引脚置零,实现向右行走就将控制右轮的引脚置零。这样就实现了向左和向右行走。
- 代码体现
void goLeft()
{
RightControA = 0;
RightControB = 1;
LeftControA = 0;
LeftControB = 0;
}
void goRight()
{
RightControA = 0;
RightControB = 0;
LeftControA = 0;
LeftControB = 1;
}
2.4主函数内进行间隔2秒调用前后左右函数
- 代码体现
void main()
{
while(1)
{
goFront();
Delay2000ms();
goBack();
Delay2000ms();
goLeft();
Delay2000ms();
goRight();
Delay2000ms();
}
}
2.5完整程序代码
#include "reg52.h"
#include "intrins.h"
sbit RightControA = P3^2; //右轮控制A
sbit RightControB = P3^3; //右轮控制B
sbit LeftControA = P3^4; //左轮控制A
sbit LeftControB = P3^5; //左轮控制B
void Delay2000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 15;
j = 2;
k = 235;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void goLeft()
{
RightControA = 0;
RightControB = 1;
LeftControA = 0;
LeftControB = 0;
}
void goRight()
{
RightControA = 0;
RightControB = 0;
LeftControA = 0;
LeftControB = 1;
}
void goBack()
{
RightControA = 1;
RightControB = 0;
LeftControA = 1;
LeftControB = 0;
}
void goFront()
{
RightControA = 0;
RightControB = 1;
LeftControA = 0;
LeftControB = 1;
}
void main()
{
while(1)
{
goFront();
Delay2000ms();
goBack();
Delay2000ms();
goLeft();
Delay2000ms();
goRight();
Delay2000ms();
}
}
3.电机相关代码封装_分文件
3.1电机相关代码封装_分文件核心思路
-
将控制电机前进,后退,向左,向右的函数封装到电机对应的C文件中。
-
建立电机头文件,里面声明电机对应C文件中的函数
-
将延时2秒的函数封装到延时C文件中
-
建立延时头文件,里面声明延时C文件中的函数。
-
主函数中包含电机头文件和延时头文件即可完美运行程序
注:此工程基于L9110前后左右控制小车工程开发
3.2将控制电机前进,后退,向左,向右的函数封装到电机对应的C文件中。
- 将声明
电机控制引脚代码移动到电机对应的C文件中(具体如何建立分文件请看温湿度检测系统博文),代码体现如下。
sbit RightControA = P3^2; //右轮控制A
sbit RightControB = P3^3; //右轮控制B
sbit LeftControA = P3^4; //左轮控制A
sbit LeftControB = P3^5; //左轮控制B
- 将控制电机前进,后退,向左,向右的函数移动到电机对应的C文件中。代码体现如下
void goLeft()
{
RightControA = 0;
RightControB = 1;
LeftControA = 0;
LeftControB = 0;
}
void goRight()
{
RightControA = 0;
RightControB = 0;
LeftControA = 0;
LeftControB = 1;
}
void goBack()
{
RightControA = 1;
RightControB = 0;
LeftControA = 1;
LeftControB = 0;
}
void goFront()
{
RightControA = 0;
RightControB = 1;
LeftControA = 0;
LeftControB = 1;
}
- 电机对的C文件代码截图

3.3建立电机头文件,里面声明电机对应C文件中的函数
- 需要声明的函数代码如下。
void goLeft();
void goRight();
void goBack();
void goFront();
- 电机头文件代码截图

3.4将延时2秒的函数封装到延时C文件中
- 将延时2秒的函数封装到延时C文件中代码体现
void Delay2000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 15;
j = 2;
k = 235;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
- 延时C文件代码截图

3.5建立延时头文件,里面声明延时C文件中的函数。
- 需要声明的函数代码如下。
void Delay2000ms();
- 延时头文件代码截图

3.6主函数中包含电机头文件和延时头文件即可完美运行程序
- 主函数中包含电机头文件和延时头文件代码体现
#include "motor.h"
#include "delay.h"
- 主函数代码截图

4.串口控制小车
4.1串口控制小车核心思路
- 找到
wifi开灯项目中优化8266,捕获联网失败的状态工程打开主函数 - 拷贝
串口使用相关声明定义,串口初始化函数,串口中断函数,建立串口C文件和头文件 - 在电机对应C文件中构造
小车停止函数 - 在串口中断函数中通过接收指令操作小车运行
注:此工程由电机相关代码封装_分文件工程开发
4.2找到wifi开灯项目中优化8266,捕获联网失败的状态工程打开

4.3拷贝串口使用相关声明定义,串口初始化函数,串口中断函数,建立串口C文件和头文件。
- 拷贝串口使用相关声明定义
#include "reg52.h"
#include "string.h"
#define SIZE 32
sfr AUXR = 0x8e; //声明AUXR寄存器地址
char buffer[SIZE];
- 拷贝串口初始化函数
void UartInit(void) //自己配
{
//配置串口工作方式为方式1,从只收不发改为能收能发
SCON = 0x50;
//配置辅助寄存器,减少电磁辐射,稳定晶振频率
AUXR = 0x01;
//设置定时器工作方式为定时器1的8位自动重装
TMOD &= 0x0F;
TMOD |= 0x20;
//设置串口波特率为9600,0误差
TH1 = 0xFD;
TL1 = 0xFD;
//打开定时器1
TR1 = 1;
//打开总中断
EA = 1;
//打开串口中断
ES = 1;
}
- 拷贝串口中断函数
void UART_handler() interrupt 4
{
//定义临时变量tmp用于判断接收的字符是否是需要的首字符。
char tmp;
//定义一个静态整型变量,在多次函数调用中只被执行一次初始化
static int i = 0;
//在串口中段函数中可以对发送接收中断标志进行处理
if(RI == 1)
{
RI = 0; //必须软件置零
tmp = SBUF;
if(tmp=='W' || tmp=='O' || tmp=='L' || tmp=='F')
{
i = 0;
}
buffer[i++] = tmp;
/*if(buffer[0]=='W' && buffer[5]=='G')
{
con_Net_Flag = 1;
memset(buffer,'\0',SIZE);
}*/
if(buffer[0]=='O' && buffer[1]=='K')
{
at_OK_Flag = 1;
memset(buffer,'\0',SIZE);
}
if(buffer[0]=='F' && buffer[1] == 'A')
{
statusLED1();
sendString(RESET);
Delay1000ms();
sendString(cNetwork);
memset(buffer,'\0',SIZE);
}
//如果用1指令开灯,0指令关灯
if(buffer[0]=='L' && buffer[2]=='1')
{
LED1 = 0;
memset(buffer,'\0',SIZE);
}
if(buffer[0]=='L' && buffer[2]=='0')
{
LED1 = 1;
memset(buffer,'\0',SIZE);
}
if(i == SIZE)
{
i = 0;
}
}
if(TI);
}
- 建立串口C文件和头文件。
一、将拷贝的所有代码整合到一起文件命名为uart.c。
#include "reg52.h"
#include "string.h"
#define SIZE 32
sfr AUXR = 0x8e; //声明AUXR寄存器地址
char buffer[SIZE];
void UartInit(void) //自己配
{
//配置串口工作方式为方式1,从只收不发改为能收能发
SCON = 0x50;
//配置辅助寄存器,减少电磁辐射,稳定晶振频率
AUXR = 0x01;
//设置定时器工作方式为定时器1的8位自动重装
TMOD &= 0x0F;
TMOD |= 0x20;
//设置串口波特率为9600,0误差
TH1 = 0xFD;
TL1 = 0xFD;
//打开定时器1
TR1 = 1;
//打开总中断
EA = 1;
//打开串口中断
ES = 1;
}
void UART_handler() interrupt 4
{
//定义临时变量tmp用于判断接收的字符是否是需要的首字符。
char tmp;
//定义一个静态整型变量,在多次函数调用中只被执行一次初始化
static int i = 0;
//在串口中段函数中可以对发送接收中断标志进行处理
if(RI == 1)
{
RI = 0; //必须软件置零
tmp = SBUF;
if(tmp=='W' || tmp=='O' || tmp=='L' || tmp=='F')
{
i = 0;
}
buffer[i++] = tmp;
/*if(buffer[0]=='W' && buffer[5]=='G')
{
con_Net_Flag = 1;
memset(buffer,'\0',SIZE);
}*/
if(buffer[0]=='O' && buffer[1]=='K')
{
at_OK_Flag = 1;
memset(buffer,'\0',SIZE);
}
if(buffer[0]=='F' && buffer[1] == 'A')
{
statusLED1();
sendString(RESET);
Delay1000ms();
sendString(cNetwork);
memset(buffer,'\0',SIZE);
}
//如果用1指令开灯,0指令关灯
if(buffer[0]=='L' && buffer[2]=='1')
{
LED1 = 0;
memset(buffer,'\0',SIZE);
}
if(buffer[0]=='L' && buffer[2]=='0')
{
LED1 = 1;
memset(buffer,'\0',SIZE);
}
if(i == SIZE)
{
i = 0;
}
}
if(TI);
}
二、建立uart.h文件,里面声明串口初始化函数。
void UartInit(void);
4.4在电机对应C文件中构造小车停止函数
- 函数思路
将左右轮控制信号的A、B给与低电平信号,实现轮子停止转动。
- 函数代码
void stop()
{
RightControA = 0;
RightControB = 0;
LeftControA = 0;
LeftControB = 0;
}
4.5在串口中断函数中通过接收指令操作小车运行
- 在串口中断函数中通过接收指令操作小车运行思路
一、将串口中断函数中的13行if(tmp\=='W' || tmp\=='O' || tmp\=='L' || tmp=='F')改为if(tmp\=='M')
二、删除串口中断函数中19到47行代码
三、使用if和switch嵌套进行操作小车
四、重要bug:串口能收到指令却无法操作小车。解决办法:不在if和switch嵌套中使用清理字符串函数,在防止指针越界函数中使用。
- 在串口中断函数中通过接收指令操作小车运行代码
void UART_handler() interrupt 4
{
//定义临时变量tmp用于判断接收的字符是否是需要的首字符。
char tmp;
//定义一个静态整型变量,在多次函数调用中只被执行一次初始化
static int i = 0;
//在串口中段函数中可以对发送接收中断标志进行处理
if(RI == 1)
{
RI = 0; //必须软件置零
tmp = SBUF;
if(tmp=='M')
{
i = 0;
}
buffer[i++] = tmp;
if(buffer[0]=='M')
{
switch(buffer[1])
{
case '1':
goFront();
break;
case '2':
goBack();
break;
case '3':
goLeft();
break;
case '4':
goRight();
break;
default:
stop();
break;
}
}
if(i == SIZE)
{
i = 0;
memset(buffer,'\0',SIZE);
}
}
if(TI);
}
4.6串口C文件代码
#include "reg52.h"
#include "motor.h"
#include "string.h"
#define SIZE 32
sfr AUXR = 0x8e; //声明AUXR寄存器地址
char buffer[SIZE];
void UartInit() //自己配
{
//配置串口工作方式为方式1,从只收不发改为能收能发
SCON = 0x50;
//配置辅助寄存器,减少电磁辐射,稳定晶振频率
AUXR = 0x01;
//设置定时器工作方式为定时器1的8位自动重装
TMOD &= 0x0F;
TMOD |= 0x20;
//设置串口波特率为9600,0误差
TH1 = 0xFD;
TL1 = 0xFD;
//打开定时器1
TR1 = 1;
//打开总中断
EA = 1;
//打开串口中断
ES = 1;
}
void UART_handler() interrupt 4
{
//定义临时变量tmp用于判断接收的字符是否是需要的首字符。
char tmp;
//定义一个静态整型变量,在多次函数调用中只被执行一次初始化
static int i = 0;
//在串口中段函数中可以对发送接收中断标志进行处理
if(RI == 1)
{
RI = 0; //必须软件置零
tmp = SBUF;
if(tmp=='M')
{
i = 0;
}
buffer[i++] = tmp;
if(buffer[0]=='M')
{
switch(buffer[1])
{
case '1':
goFront();
break;
case '2':
goBack();
break;
case '3':
goLeft();
break;
case '4':
goRight();
break;
default:
stop();
break;
}
}
if(i == SIZE)
{
i = 0;
memset(buffer,'\0',SIZE);
}
}
if(TI);
}
5.手机通过蓝牙控制小车_自定义按键
5.1手机通过蓝牙控制小车_自定义按键核心思路
- 将蓝牙模块正确接线连接51单片机串口
- 给51单片机,蓝牙模块
上电,打开手机APPHC蓝牙助手进行连接 - 通过自定义按钮使HC蓝牙助手发送相应控制小车指令
5.2将蓝牙模块正确接线连接51单片机串口
相关接线请看我写的串口通信博文
5.3给51单片机,蓝牙模块上电,打开手机APP HC蓝牙助手进行连接


5.4通过自定义按钮使HC蓝牙助手发送相应控制小车指令
- 点击自定义按钮

- 点击设置方向按钮

- 在设置
按钮名称处填写你需要设置的按钮名称,在设置按钮内容处填写你需要发送的指令,最后点击确认

- 依次设置完
前、后、左、右、停、五个指令后便可实现手机蓝牙控制小车。
6.蓝牙小车的点动控制
6.1蓝牙小车的点动控制核心思路
- 在
主C文件主函数while(1)死循环中调用电机停止函数。 - 在串口中断函数中接收指令并执行调用电机相关函数时延时
10毫秒实现点动 - 通过串口助手自动发送模拟长按一直发送一直运行的状态。
注:此工程由串口控制小车工程开发
6.2在主C文件主函数while(1)死循环中调用电机停止函数。
- 函数调用代码
while(1)
{
stop();
}
- 主C文件代码
#include "motor.h"
#include "delay.h"
#include "uart.h"
void main()
{
UartInit();
while(1)
{
stop();
}
}
6.3在串口中断函数中接收指令并执行调用电机相关函数时延时10毫秒实现点动
- 延时10毫秒函数代码
void Delay10ms() //@11.0592MHz
{
unsigned char i, j;
i = 18;
j = 235;
do
{
while (--j);
} while (--i);
}
- 在串口中断函数中调用代码
if(buffer[0]=='M')
{
switch(buffer[1])
{
case '1':
goFront();
Delay10ms();
break;
case '2':
goBack();
Delay10ms();
break;
case '3':
goLeft();
Delay10ms();
break;
case '4':
goRight();
Delay10ms();
break;
default:
stop();
break;
}
}
- 串口C文件代码
#include "reg52.h"
#include "motor.h"
#include "string.h"
#include "delay.h"
#define SIZE 32
sfr AUXR = 0x8e; //声明AUXR寄存器地址
char buffer[SIZE];
void UartInit() //自己配
{
//配置串口工作方式为方式1,从只收不发改为能收能发
SCON = 0x50;
//配置辅助寄存器,减少电磁辐射,稳定晶振频率
AUXR = 0x01;
//设置定时器工作方式为定时器1的8位自动重装
TMOD &= 0x0F;
TMOD |= 0x20;
//设置串口波特率为9600,0误差
TH1 = 0xFD;
TL1 = 0xFD;
//打开定时器1
TR1 = 1;
//打开总中断
EA = 1;
//打开串口中断
ES = 1;
}
void UART_handler() interrupt 4
{
//定义临时变量tmp用于判断接收的字符是否是需要的首字符。
char tmp;
//定义一个静态整型变量,在多次函数调用中只被执行一次初始化
static int i = 0;
//在串口中段函数中可以对发送接收中断标志进行处理
if(RI == 1)
{
RI = 0; //必须软件置零
tmp = SBUF;
if(tmp=='M')
{
i = 0;
}
buffer[i++] = tmp;
if(buffer[0]=='M')
{
switch(buffer[1])
{
case '1':
goFront();
Delay10ms();
break;
case '2':
goBack();
Delay10ms();
break;
case '3':
goLeft();
Delay10ms();
break;
case '4':
goRight();
Delay10ms();
break;
default:
stop();
break;
}
}
if(i == SIZE)
{
i = 0;
memset(buffer,'\0',SIZE);
}
}
if(TI);
}
6.4通过串口助手自动发送模拟长按一直发送一直运行的状态。
- 打开单片机下载器点击串口助手

- 打开串口,写入指令M1,设置发送周期为1ms,点击自动发送。

- 观察小车车轮,小车正在前行,说明模拟成功。
7.PWM软件调速
7.1 PWM软件调速核心思路
- 打开
感应开盖垃圾桶项目_舵机编程实战工程主C文件 - 在工程内新建
time0.c文件 - 拷贝
相关变量,定时器0初始化函数,定时器零中断函数 - 修改定时器0中断函数,使之调用电机控制函数,实现调速
- 建立
time.h文件 - 主C文件内每隔1.5秒改变速度变量,使小车电机变速
注:此工程由蓝牙小车的点动控制工程开发
7.2打开感应开盖垃圾桶项目_舵机编程实战工程主C文件

7.3在工程内新建time0.c文件

7.4拷贝相关变量,定时器0初始化函数,定时器零中断函数
- 拷贝相关变量代码
int cnt = 0; //记录爆表次数
int angle; //记录舵机角度变量
修改angle为speed,也就是修改记录舵机角度变量为记录小车速度变量。把这两个变量类型修改为char型,节省空间
char cnt = 0; //记录爆表次数
char speed = 0; //调速变量
- 拷贝定时器0初始化函数代码
void initTime0() //初始化定时器T0
{
//1.设置定时器为T0,16位模式
TMOD &= 0xF0;
TMOD |= 0x01;
//2.设置初值,定时0.5毫秒
TL0 = 0x33;
TH0 = 0xFE;
//3.启动定时器,开始计时
TR0 = 1; //TR0为启动定时器标志,值为1启动定时器
TF0 = 0;
//4.打开定时器0的中断 赋1打开
ET0 = 1;
//5.打开总中断EA 赋1打开
EA = 1;
}
- 拷贝定时器零中断函数
void Time0Handler() interrupt 1 //定时器T0中断函数,中断号为interrupt 1
{
cnt++;
//重新给定时器赋初值,定时0.5毫秒
TL0 = 0x33;
TH0 = 0xFE;
//判读记录爆表变量cnt的值是否小于角度变量的值,小于继续给sg90舵机控制线高电平,大于等于则给低电平
if(cnt < angle)
{
sg90_con = 1;
}
else
{
sg90_con = 0;
}
//判断cnt是否加到40,定时是否20毫秒PWM波形周期
if(cnt == 20)
{
cnt = 0; //定时到20豪秒,cnt从0开始加
sg90_con = 1;//定时到20豪秒,重新给舵机控制线一个高电平
}
}
7.5修改定时器0中断函数,使之调用电机控制函数,实现调速
- 把定时器零中断函数中第9行
if(cnt < angle)修改为if(cnt < speed) - 把定时器零中断函数中第11行
sg90_con = 1修改为goFront(); - 把定时器零中断函数中第15行
sg90_con = 0修改为stop(); - 把定时器零中断函数中第21行
sg90_con = 1;删除。 - 添加
motor.h头文件。 - 修改后的函数代码
void Time0Handler() interrupt 1 //定时器T0中断函数,中断号为interrupt 1
{
cnt++;
//重新给定时器赋初值,定时0.5毫秒
TL0 = 0x33;
TH0 = 0xFE;
//判读记录爆表变量cnt的值是否小于角度变量的值,小于继续给sg90舵机控制线高电平,大于等于则给低电平
if(cnt < speed)
{
//前进
goFront();
}
else
{
//停止
stop();
}
//判断cnt是否加到40,定时是否20毫秒PWM波形周期
if(cnt == 40)
{
cnt = 0; //定时到20豪秒,cnt从0开始加
}
}
- time0.c文件中的代码
#include "reg52.h"
#include "motor.h"
char cnt = 0; //记录爆表次数
char speed = 0; //调速变量
void initTime0() //初始化定时器T0
{
//1.设置定时器为T0,16位模式
TMOD &= 0xF0;
TMOD |= 0x01;
//2.设置初值,定时0.5毫秒
TL0 = 0x33;
TH0 = 0xFE;
//3.启动定时器,开始计时
TR0 = 1; //TR0为启动定时器标志,值为1启动定时器
TF0 = 0;
//4.打开定时器0的中断 赋1打开
ET0 = 1;
//5.打开总中断EA 赋1打开
EA = 1;
}
void Time0Handler() interrupt 1 //定时器T0中断函数,中断号为interrupt 1
{
cnt++;
//重新给定时器赋初值,定时0.5毫秒
TL0 = 0x33;
TH0 = 0xFE;
//判读记录爆表变量cnt的值是否小于角度变量的值,小于继续给sg90舵机控制线高电平,大于等于则给低电平
if(cnt < speed)
{
//前进
goFront();
}
else
{
//停止
stop();
}
//判断cnt是否加到40,定时是否20毫秒PWM波形周期
if(cnt == 40)
{
cnt = 0; //定时到20豪秒,cnt从0开始加
}
}
7.6建立time.h文件
- 在time0.h中声明定时器0初始化函数
- 代码体现
void initTime0();
7.7主C文件内每隔1.5秒改变速度变量,使小车电机变速
- 在主函数中调用定时器0初始化函数
initTime0();
- 在主C文件中运用extern关键字声明速度变量char speed。
extern char speed;
- 在主函数while(1)死循环中每隔1.5秒改变速度变量的值。
while(1)
{
speed = 10;
Delay1500ms();
speed = 15;
Delay1500ms();
speed = 20;
Delay1500ms();
speed = 25;
Delay1500ms();
speed = 30;
Delay1500ms();
speed = 35;
Delay1500ms();
speed = 40;
Delay1500ms();
}
- 在delay.c文件中建立延时1.5秒函数。
void Delay1500ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 11;
j = 130;
k = 111;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
- 主C文件代码
#include "motor.h"
#include "delay.h"
#include "uart.h"
#include "time0.h"
extern char speed;
void main()
{
initTime0();
UartInit();
while(1)
{
speed = 10; //10为最小速度驱动,再小就无法运行
Delay1500ms();
speed = 15;
Delay1500ms();
speed = 20;
Delay1500ms();
speed = 25;
Delay1500ms();
speed = 30;
Delay1500ms();
speed = 35;
Delay1500ms();
speed = 40;
Delay1500ms();
}
}
7.8 PWM软件调速原理图

8.左右电机的各自调速管理
8.1左右电机的各自调速管理核心思路
-
在
motor.c文件内建立单独控制左右轮前进和停止函数,并在motor.h文件中声明。 -
建立
time1.c和time1.h文件用于对右轮的控制和初始化。 -
修改
time0.c为左轮控制 -
主C文件内每隔1.5秒改变左右轮速度变量,使小车左右轮电机差速运行
注:此工程由PWM软件调速开发
8.2在motor.c文件内建立单独控制左右轮前进和停止函数,并在motor.h文件中声明。
- 复制前进函数,修改名字为
goFrontLeft()。删除右轮代码。
修改前
void goFront()
{
RightControA = 0;
RightControB = 1;
LeftControA = 0;
LeftControB = 1;
}
修改后
void goFrontLeft()
{
LeftControA = 0;
LeftControB = 1;
}
按照以上步骤可以建立右轮控制函数
void goFrontRight()
{
RightControA = 0;
RightControB = 1;
}
- 复制停止函数,修改名为
stopLeft()。删除右轮代码
修改前
void stop()
{
RightControA = 0;
RightControB = 0;
LeftControA = 0;
LeftControB = 0;
}
修改后
void stopLeft()
{
LeftControA = 0;
LeftControB = 0;
}
按照以上步骤可以建立右轮停止函数函数
void stopRight()
{
RightControA = 0;
RightControB = 0;
}
- 在motor.h中声明这四个函数
void goFrontLeft();
void goFrontRight();
void stopLeft();
void stopRight();
- motor.c文件中的代码
#include "reg52.h"
sbit RightControA = P3^2; //右轮控制A
sbit RightControB = P3^3; //右轮控制B
sbit LeftControA = P3^4; //左轮控制A
sbit LeftControB = P3^5; //左轮控制B
void goLeft()
{
RightControA = 0;
RightControB = 1;
LeftControA = 0;
LeftControB = 0;
}
void goRight()
{
RightControA = 0;
RightControB = 0;
LeftControA = 0;
LeftControB = 1;
}
void goBack()
{
RightControA = 1;
RightControB = 0;
LeftControA = 1;
LeftControB = 0;
}
void goFront()
{
RightControA = 0;
RightControB = 1;
LeftControA = 0;
LeftControB = 1;
}
void stop()
{
RightControA = 0;
RightControB = 0;
LeftControA = 0;
LeftControB = 0;
}
void goFrontLeft()
{
LeftControA = 0;
LeftControB = 1;
}
void goFrontRight()
{
RightControA = 0;
RightControB = 1;
}
void stopLeft()
{
LeftControA = 0;
LeftControB = 0;
}
void stopRight()
{
RightControA = 0;
RightControB = 0;
}
- motor.h文件中的代码
void goLeft();
void goRight();
void goBack();
void goFront();
void stop();
void goFrontLeft();
void goFrontRight();
void stopLeft();
void stopRight();
8.3建立time1.c和time1.h文件用于对右轮的控制和初始化。
- 拷贝
time0.c文件代码到time1.c文件中
#include "reg52.h"
#include "motor.h"
char cnt = 0; //记录爆表次数
char speed = 0; //调速变量
void initTime0() //初始化定时器T0
{
//1.设置定时器为T0,16位模式
TMOD &= 0xF0;
TMOD |= 0x01;
//2.设置初值,定时0.5毫秒
TL0 = 0x33;
TH0 = 0xFE;
//3.启动定时器,开始计时
TR0 = 1; //TR0为启动定时器标志,值为1启动定时器
TF0 = 0;
//4.打开定时器0的中断 赋1打开
ET0 = 1;
//5.打开总中断EA 赋1打开
EA = 1;
}
void Time0Handler() interrupt 1 //定时器T0中断函数,中断号为interrupt 1
{
cnt++;
//重新给定时器赋初值,定时0.5毫秒
TL0 = 0x33;
TH0 = 0xFE;
//判读记录爆表变量cnt的值是否小于角度变量的值,小于继续给sg90舵机控制线高电平,大于等于则给低电平
if(cnt < speed)
{
//前进
goFront();
}
else
{
//停止
stop();
}
//判断cnt是否加到40,定时是否20毫秒PWM波形周期
if(cnt == 40)
{
cnt = 0; //定时到20豪秒,cnt从0开始加
}
}
- 将
initTime0()修改为initTime1(),将所有的TH0,TL0替换为TH1和TL1。将TMOD的两个运算表达式后面的变量替换为0x0F和0x01 << 4;实现使用定时器1,16位模式。将TR0,TF0,ET0修改为TR1,TF1,ET1。
void initTime1() //初始化定时器T0
{
//1.设置定时器为T0,16位模式
TMOD &= 0x0F;
TMOD |= 0x01 << 4;
//2.设置初值,定时0.5毫秒
TL1 = 0x33;
TH1 = 0xFE;
//3.启动定时器,开始计时
TR1 = 1; //TR0为启动定时器标志,值为1启动定时器
TF1 = 0;
//4.打开定时器0的中断 赋1打开
ET1 = 1;
//5.打开总中断EA 赋1打开
EA = 1;
}
- 将
Time0Handler()修改为Time1Handler(),interrupt 1修改为interrupt 3,cnt修改为cntRight,speed修改为speedRight,goFront();修改为goFrontRight();,stop();修改为stopRight();。
void Time1Handler() interrupt 3 //定时器T0中断函数,中断号为interrupt 1
{
cntRight++;
//重新给定时器赋初值,定时0.5毫秒
TL1 = 0x33;
TH1 = 0xFE;
//判读记录爆表变量cnt的值是否小于角度变量的值,小于继续给sg90舵机控制线高电平,大于等于则给低电平
if(cntRight < speedRight)
{
//前进
goFrontRight();
}
else
{
//停止
stopRight();
}
//判断cnt是否加到40,定时是否20毫秒PWM波形周期
if(cntRight == 40)
{
cntRight = 0; //定时到20豪秒,cnt从0开始加
}
}
- 修改后的代码为
#include "reg52.h"
#include "motor.h"
char cntRight = 0; //记录爆表次数
char speedRight = 0; //调速变量
void initTime1() //初始化定时器T0
{
//1.设置定时器为T0,16位模式
TMOD &= 0x0F;
TMOD |= 0x01 << 4;
//2.设置初值,定时0.5毫秒
TL1 = 0x33;
TH1 = 0xFE;
//3.启动定时器,开始计时
TR1 = 1; //TR0为启动定时器标志,值为1启动定时器
TF1 = 0;
//4.打开定时器0的中断 赋1打开
ET1 = 1;
//5.打开总中断EA 赋1打开
EA = 1;
}
void Time1Handler() interrupt 3 //定时器T0中断函数,中断号为interrupt 1
{
cntRight++;
//重新给定时器赋初值,定时0.5毫秒
TL1 = 0x33;
TH1 = 0xFE;
//判读记录爆表变量cnt的值是否小于角度变量的值,小于继续给sg90舵机控制线高电平,大于等于则给低电平
if(cntRight < speedRight)
{
//前进
goFrontRight();
}
else
{
//停止
stopRight();
}
//判断cnt是否加到40,定时是否20毫秒PWM波形周期
if(cntRight == 40)
{
cntRight = 0; //定时到20豪秒,cnt从0开始加
}
}
8.4修改time0.c为左轮控制
- 将
cnt修改为cntLeft,speed修改为speedLeft,goFront();修改为goFrontLeft();,stop();修改为stopLeft();。 - 修改后的代码为
#include "reg52.h"
#include "motor.h"
char cntLeft = 0; //记录爆表次数
char speedLeft = 0; //调速变量
void initTime0() //初始化定时器T0
{
//1.设置定时器为T0,16位模式
TMOD &= 0xF0;
TMOD |= 0x01;
//2.设置初值,定时0.5毫秒
TL0 = 0x33;
TH0 = 0xFE;
//3.启动定时器,开始计时
TR0 = 1; //TR0为启动定时器标志,值为1启动定时器
TF0 = 0;
//4.打开定时器0的中断 赋1打开
ET0 = 1;
//5.打开总中断EA 赋1打开
EA = 1;
}
void Time0Handler() interrupt 1 //定时器T0中断函数,中断号为interrupt 1
{
cntLeft ++;
//重新给定时器赋初值,定时0.5毫秒
TL0 = 0x33;
TH0 = 0xFE;
//判读记录爆表变量cnt的值是否小于角度变量的值,小于继续给sg90舵机控制线高电平,大于等于则给低电平
if(cntLeft < speedLeft)
{
//前进
goFrontLeft();
}
else
{
//停止
stopLeft();
}
//判断cnt是否加到40,定时是否20毫秒PWM波形周期
if(cntLeft == 40)
{
cntLeft = 0; //定时到20豪秒,cnt从0开始加
}
}
8.5主C文件内每隔1.5秒改变左右轮速度变量,使小车左右轮电机差速运行
- 调速变量修改代码
while(1)
{
speedRight = 40;
speedLeft = 40;
Delay1500ms();
speedRight = 10; //10为最小速度驱动,再小就无法运行
speedLeft = 40;
Delay1500ms();
speedRight = 15;
speedLeft = 35;
Delay1500ms();
speedRight = 20;
speedLeft = 30;
Delay1500ms();
speedRight = 25;
speedLeft = 25;
Delay1500ms();
speedRight = 30;
speedLeft = 20;
Delay1500ms();
speedRight = 35;
speedLeft = 15;
Delay1500ms();
speedRight = 40;
speedLeft = 10;
Delay1500ms();
}
- 主C文件代码
#include "motor.h"
#include "delay.h"
#include "uart.h"
#include "time0.h"
#include "time1.h"
extern char speedRight;
extern char speedLeft;
void main()
{
initTime0();
initTime1();
UartInit();
while(1)
{
speedRight = 40;
speedLeft = 40;
Delay1500ms();
speedRight = 10; //10为最小速度驱动,再小就无法运行
speedLeft = 40;
Delay1500ms();
speedRight = 15;
speedLeft = 35;
Delay1500ms();
speedRight = 20;
speedLeft = 30;
Delay1500ms();
speedRight = 25;
speedLeft = 25;
Delay1500ms();
speedRight = 30;
speedLeft = 20;
Delay1500ms();
speedRight = 35;
speedLeft = 15;
Delay1500ms();
speedRight = 40;
speedLeft = 10;
Delay1500ms();
}
}
结束语
很高兴您能看到这里,点个赞再走呗。谢谢您啦!!!
本文围绕51单片机的智能语音小车项目展开,介绍了L9110S电机控制器接线、L9110前后左右控制小车、电机代码分文件封装、串口控制、蓝牙控制、点动控制、PWM软件调速以及左右电机调速管理等内容,还给出了各部分的核心思路和代码实现。
13万+

被折叠的 条评论
为什么被折叠?



