Arduino+ESP8266的远程控制小车

本文最后更新于:2021年12月14日 晚上

简单介绍如何利用Arduino单片和ESP8266开发板,结合物联网云平台,制作一款能远程遥控的小车。
KeyWords:单片机控制、WiFi、远程遥控、手机App

Arduino+ESP8266的远程控制小车

硬件选型

小车主控板:Arduino UNO

​ Aruduino平台于2005年诞生,两位就职于米兰设计学院的教授,希望开发一款具有软件开发功能的开源的硬件实验平台,于是在他们的合作下,Arduino应运而生。Arduino的编程的语言脱胎于C,如果你有了C语言的学习经验的话,在了解了基本的语言特点和语法之后,便能够非常快速的在Arduino上进行独立的开发工作。关于Arduino的语言,主要有一下两部分,其一是setup()函数,该函数的作用只要在于对系统进行初始化,对相应的引脚进行定义,比如设置输入还是输出模式。第二部分是loop()函数,该函数是用来编写对应的功能的,同时loop()函数将启动硬件部分。不仅如此,Arduino拥有丰富的库文件,分为:官方提供和开发者自定义两种类型。

​ 正如上文提到的,Arduino同时兼具软件开发的功能,拥有着自己的软件开发环境——Arduino IDE。它最为显著的特点就是可以跨多个平台运行,极大方便了使用。目前支持的平台包括:Linux、Windows和MacOS等。Arduino IDE是针对Arduino开发板的一个集成开源的编译软件,操作界面简单、语言风格与C语言相近、使用的库文件是开源的并且简单的代码可以直接通过串口进行下载。对于使用Arduino的开发者来说,这是一款首选的开发环境。
ArduinoIDE

​ ArduinoIDE是基于processing IDE开发的,是初学者十分的友好,能够快速的掌握,并具有很高的灵活性。Arduino的语言对于avr-gcc库二次封装,因而不需要太多的单片机和编程基础,便可以进行快速的开发。它的工作流程为,在开发者编写好代码,编译后,将语言转换成C++,再用avr-gcc将编译语言转换成二进制,这样一来,开发者编写的内容才能够背Arduino的硬件识别,程序才能够运行。这串代码通过USB接口连接到电脑,再上传到Arduino的存储器进行存储。

​ 在开发者使用Arduino IDE的时候,要做好以下的准备工作:在PC端下载对应硬件的驱动,选择合适的端口。这些步骤完成之后,便可开始程序的编写。Arduino还有第三方的图形化编程软件,极大地降低了对开发者的编程能力的要求,只要能够符合逻辑的设计出功能,按照规定的方式设置对应的参数,便可以下载程序到硬件开发板进行使用。

Arduino软件下载
Arduino中文社区

​ 小车选用的是Arduino UNO的开发板,搭载芯片为ATmega328p。

ArduinoUNO

​ 同时为了方便对控制小车其他配件,选用了一块配套的功能转接板,如下所示:

功能转接板

远程连接模块:ESP8266开发板

小车采用ESP-12E开发板(NoteMCU)作为WIFI连接的模块,通过该块开发板连接物联网云平台,实现远程的控制实现。
EPS8266

其他配件

小车还需要的配件包括:四个直流电机,底盘,车轮,螺丝(若干),铜柱(若干),电池盒,可充电电池。

云平台选择——点灯科技(Blinker)

官网首页

开发文档

远程控制设计

小车控制

远程控制功能主要分为两个主要部分,分别是对小车的动作控制和云平台连接。

小车的行动通过控制电机的转速来实现,单片机的I/O口输出不同的信号,改变驱动电路中PWM的比例值,进而对电机的转速进行调控。将对应的电机状态用函数进行封装,配合不同指令调用对应的函数来实现对小车的控制。

小车基本动作的函数如下表所示:

​ 小车基本动作函数

函数名 功能 备注
run() 小车向前进 左右电机前进
brake() 小车停止 左右电机停止
left() 小车左转 右电机前进左电机停止
right() 小车右转 右电机停止左电机前进
back() 小车后退 左右电机反转

云平台连接

云平台远程连接采用ESP-12E开发板实现,首先通过ESP-12E板和物联网云平台(采用的是Blinker平台)进行远程连接,首先给开发板刷入Blinker的固件,当设备连接启动后,Arduino板通过串口向ESP-12E开发板发送AT指令,进行WIFI连接。连接成功后,就可以通过手机端向云平台发送指令,再由云平台发送给ESP-12E,Arduino通过串口读取指令,调用相应的动作函数。

​ AT指令字段说明

AT指令 BLINKER_WIFI=0 3275431b24545 f55 bruce6545555
说明 模块工作于透传模式 绑定设备的密钥 WIFI名称 WIFI密码

Bilnker云平台远程连接,主要分为新建组件、设置回调函数、绑定组件与回调函数三个步骤,主要的函数说明如下。

​ 远程连接主要函数说明

函数名 功能 备注
BlinkerButton ButtonRun(“btn-run”); 创建Button对象,键值为“btn-run” 对应Blinker app上的按键控件
void buttonRun_callback(const String & state) 获取按键的状态 根据按键的触发状态执行对应语句
ButtonRun.attach(buttonRun_callback) 绑定控件和对应的回调函数

源代码

小车控制部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/***********************************************
前进 按下发出 ONA
后退:按下发出 ONB
左转:按下发出 ONC
右转:按下发出 OND
停止:按下发出 ONE
************************************************/
int Left_motor=8; //左电机(IN3) 输出0 前进 输出1 后退
int Left_motor_pwm=9; //左电机PWM调速

int Right_motor=11; //右电机(IN1) 输出0 前进 输出1 后退
int Right_motor_pwm=10; // 右电机PWM调速

char buffer[18]; //串口缓冲区的字符数组
void setup() //设定串口和引脚模式
{
Serial.begin(9600);
Serial.flush(); //清空串口缓存

Serial.println("AT+BLINKER_WIFI=0,3275431b255e,f55,bruce6545555");

pinMode(Left_motor,OUTPUT); // PIN 8 8脚无PWM功能
pinMode(Left_motor_pwm,OUTPUT); // PIN 9 (PWM)
pinMode(Right_motor_pwm,OUTPUT);// PIN 10 (PWM)
pinMode(Right_motor,OUTPUT);// PIN 11
}
void run() // 前进
{
digitalWrite(Right_motor,LOW); // 右电机前进
digitalWrite(Right_motor_pwm,HIGH); // 右电机前进
analogWrite(Right_motor_pwm,90);//PWM比例0~255调速,左右轮差异略增减


digitalWrite(Left_motor,LOW); // 左电机前进
digitalWrite(Left_motor_pwm,HIGH); //左电机PWM
analogWrite(Left_motor_pwm,90);//PWM比例0~255调速,左右轮差异略增减
//delay(time * 100); //执行时间,可以调整
}

void brake() //刹车,停车
{

digitalWrite(Right_motor_pwm,LOW); // 右电机PWM 调速输出0
analogWrite(Right_motor_pwm,0);//PWM比例0~255调速,左右轮差异略增减

digitalWrite(Left_motor_pwm,LOW); //左电机PWM 调速输出0
analogWrite(Left_motor_pwm,0);//PWM比例0~255调速,左右轮差异略增减
//delay(time * 100);//执行时间,可以调整
}

void left() //左转(左轮不动,右轮前进)
{
digitalWrite(Right_motor,LOW); // 右电机前进
digitalWrite(Right_motor_pwm,HIGH); // 右电机前进
analogWrite(Right_motor_pwm,110);//PWM比例0~255调速,左右轮差异略增减


digitalWrite(Left_motor,LOW); // 左电机前进
digitalWrite(Left_motor_pwm,LOW); //左电机PWM
analogWrite(Left_motor_pwm,0);//PWM比例0~255调速,左右轮差异略增减
//delay(time * 100); //执行时间,可以调整
}

void spin_left() //左转(左轮后退,右轮前进)
{
digitalWrite(Right_motor,LOW); // 右电机前进
digitalWrite(Right_motor_pwm,HIGH); // 右电机前进
analogWrite(Right_motor_pwm,90);//PWM比例0~255调速,左右轮差异略增减

digitalWrite(Left_motor,HIGH); // 左电机后退
digitalWrite(Left_motor_pwm,HIGH); //左电机PWM
analogWrite(Left_motor_pwm,90);//PWM比例0~255调速,左右轮差异略增减
//delay(time * 100); //执行时间,可以调整
}

void right() //右转(右轮不动,左轮前进)
{
digitalWrite(Right_motor,LOW); // 右电机不转
digitalWrite(Right_motor_pwm,LOW); // 右电机PWM输出0
analogWrite(Right_motor_pwm,0);//PWM比例0~255调速,左右轮差异略增减


digitalWrite(Left_motor,LOW); // 左电机前进
digitalWrite(Left_motor_pwm,HIGH); //左电机PWM
analogWrite(Left_motor_pwm,110);//PWM比例0~255调速,左右轮差异略增减
//delay(time * 100); //执行时间,可以调整
}

void spin_right() //右转(右轮后退,左轮前进)
{
digitalWrite(Right_motor,HIGH); // 右电机后退
digitalWrite(Right_motor_pwm,HIGH); // 右电机PWM输出1
analogWrite(Right_motor_pwm,50);//PWM比例0~255调速,左右轮差异略增减


digitalWrite(Left_motor,LOW); // 左电机前进
digitalWrite(Left_motor_pwm,HIGH); //左电机PWM
analogWrite(Left_motor_pwm,50);//PWM比例0~255调速,左右轮差异略增减
//delay(time * 100); //执行时间,可以调整
}

void back() //后退
{
digitalWrite(Right_motor,HIGH); // 右电机后退
digitalWrite(Right_motor_pwm,HIGH); // 右电机前进
analogWrite(Right_motor_pwm,90);//PWM比例0~255调速,左右轮差异略增减


digitalWrite(Left_motor,HIGH); // 左电机后退
digitalWrite(Left_motor_pwm,HIGH); //左电机PWM
analogWrite(Left_motor_pwm,90);//PWM比例0~255调速,左右轮差异略增减
//delay(time * 100); //执行时间,可以调整
}

void loop()
{
if(Serial.available() > 0) //Serial.available()返回串口收到的字节数
{
int index = 0;
delay(100); //延时等待串口收完数据,否则刚收到1个字节时就会执行后续程序
int numChar = Serial.available();
if(numChar > 15) //确认数据不会溢出,应当小于缓冲大小
{
numChar = 15;
}
while(numChar--)
{
buffer[index++] = Serial.read(); //将串口数据一字一字的存入缓冲
}
Serial.println(buffer);
splitString(buffer); //字符串分割
}

}

void splitString(char *data)
{
Serial.print("Data entered:");
Serial.println(data);
char *parameter;
parameter = strtok(data, " ,");//string token,将data按照空格或者,进行分割并截取
Serial.println(parameter);
while(parameter != NULL)
{
setMove(parameter);
parameter = strtok(NULL, " ,"); //string token,再次分割并截取,直至截取后的字符为空
Serial.println(parameter);
}
for(int x = 0; x < 16; x++) //清空缓冲
{
buffer[x] = '\0';
}
Serial.flush();
}

void setMove(char *data)
{
if((data[0] == 'O') && (data[1] == 'N')&& (data[2] == 'A'))
{
Serial.println("go forward!");
run();
}
if((data[0] == 'O') && (data[1] == 'N')&& (data[2] == 'B'))
{
Serial.println("go back!");
back();
}
if((data[0] == 'O') && (data[1] == 'N')&& (data[2] == 'C'))
{
Serial.println("go left!");
left();
}
if((data[0] == 'O') && (data[1] == 'N')&& (data[2] == 'D'))
{
Serial.println("go right!");
right();
}
if((data[0] == 'O') && (data[1] == 'N')&& (data[2] == 'E'))
{
Serial.println("Stop!");
brake();
}
if((data[0] == 'O') && (data[1] == 'N')&& (data[2] == 'F'))
{
Serial.println("Stop!");
brake();
}

}

远程连接部分(烧制在ESP-12E开发板)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#define BLINKER_AT_MQTT
#include <Blinker.h>

// 新建组件对象
BlinkerButton ButtonRun("btn-run");
BlinkerButton ButtonBack("btn-back");
BlinkerButton ButtonLeft("btn-left");
BlinkerButton ButtonRight("btn-right");
BlinkerButton ButtonStop("btn-stop");
BlinkerButton ButtonOn("btn-on");
BlinkerButton ButtonOff("btn-off");
int counter = 0;

// 按键回调函数
void buttonRun_callback(const String & state)
{
if (state == BLINKER_CMD_BUTTON_PRESSED){
Serial.write("ONA");
}
else if (state == BLINKER_CMD_BUTTON_RELEASED){
Serial.write("ONE");
}
else{
Serial.write("ONA");
delay(1000);
Serial.write("ONE");
}
}
void buttonBack_callback(const String & state)
{
if (state == BLINKER_CMD_BUTTON_PRESSED){
Serial.write("ONB");
}
else if (state == BLINKER_CMD_BUTTON_RELEASED){
Serial.write("ONE");
}
else{
Serial.write("ONB");
delay(1000);
Serial.write("ONE");
}
}
void buttonLeft_callback(const String & state)
{
if (state == BLINKER_CMD_BUTTON_PRESSED){
Serial.write("ONC");
}
else if (state == BLINKER_CMD_BUTTON_RELEASED){
Serial.write("ONE");
}
else{
Serial.write("ONC");
delay(1000);
Serial.write("ONE");
}
}
void buttonRight_callback(const String & state)
{
if (state == BLINKER_CMD_BUTTON_PRESSED){
Serial.write("OND");
}
else if (state == BLINKER_CMD_BUTTON_RELEASED){
Serial.write("ONE");
}
else{
Serial.write("OND");
delay(1000);
Serial.write("ONE");
}
}
void buttonStop_callback(const String & state)
{
Serial.write("ONE");
}
void buttonOn_callback(const String & state)
{
digitalWrite(2,LOW);
Serial.write("ONF");
}
void buttonOff_callback(const String & state)
{
digitalWrite(2,HIGH);
}
void setup()
{
Blinker.begin();

pinMode(2,OUTPUT);

ButtonRun.attach(buttonRun_callback);
ButtonBack.attach(buttonBack_callback);
ButtonLeft.attach(buttonLeft_callback);
ButtonRight.attach(buttonRight_callback);
ButtonStop.attach(buttonStop_callback);

ButtonOn.attach(buttonOn_callback);
ButtonOff.attach(buttonOff_callback);

}

void loop() {
// char ch=Serial.read(); // 读取输入的信息
//Serial.print(ch); //输出信息
//delay(1000);
Blinker.run();
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!