本代码已在Github开源
开源协议为GPL-3.0
点击此处进入项目页面

ESP32 Smart Car Control System

Introduction

The ESP32 Smart Car Control System is an innovative project that realizes hardware-software interaction between a computer and a microcontroller based on the C++ serial port module and the ESP32 microprocessor chip. This system mediates the transmission of string commands or corresponding data frames recognizable by the microcontroller through serial communication, enabling the car to move forward, backward, turn quickly, and adjust speed via PWM (Pulse Width Modulation). The system is easy to operate with a high fault tolerance rate, and it is equipped with an XOR bit self-identification, self-calculation, and self-verification module for data frames, reducing the probability of misidentifying commands. Additionally, this system can efficiently receive status data transmitted back from the ESP32 Smart Car, simplify the processing of the returned data, and clearly display it on the console, achieving precise control of the ESP32 Smart Car.

Code

main.cpp

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
#include <iostream>
#include <cstdio>
#include <iomanip>
#include <Windows.h>
#include <sstream>
#include "serial.h"
using namespace std;
extern string receivedData;
void displayMenuAndStatus(const string& carStatus, double PWM_L, double PWM_R, double Speed_L, double Speed_R) {
//显示菜单和小车状态
system("cls");
cout << "选择操作:\n";
cout << "1. 前进\n";
cout << "2. 后退\n";
cout << "3. 左转\n";
cout << "4. 右转\n";
cout << "5. 快速左转\n";
cout << "6. 快速右转\n";
cout << "7. 设置速度(单轮/PWM)\n";
cout << "8. 停止\n";
cout << "9. 刷新状态\n";
cout << "0. 退出\n\n";
cout << "状态: " << carStatus << endl;
cout << "左轮PWM: " << PWM_L << endl;
cout << "左轮速度: " << Speed_L << endl;
cout << "右轮PWM: " << PWM_R << endl;
cout << "右轮速度: " << Speed_R << endl;
cout << "接收数据:" << receivedData <<endl;
cout << "请输入选项: ";
}
int calculateChecksum(int* hexArray, int length) {
// 计算异或校验和
int checksum = 0;
for (int i = 0; i < length; ++i) {
checksum ^= hexArray[i];
}
stringstream ss;
//使用字符串流操作数据帧
ss << hex << checksum;
int hexChecksum;
ss >> hex >> hexChecksum;
return hexChecksum;
}
void decimalToHexArray(int decimal, int* hexArray, int index) {
// 进制转换,十进制转为十六进制,并添加前导0
stringstream ss;
ss << hex << setfill('0') << setw(2) << decimal;
int hexValue;
ss >> hex >> hexValue;
hexArray[index] = hexValue;
}
int main() {
wstring comPort;
cout << "请输入COM端口(如COM3):";
getline(wcin, comPort);

int baudRate;
cout << "请输入波特率(如9600):";
cin >> baudRate;

if (!initSerialPort(comPort, baudRate)) {
cerr << "初始化串口失败!\n";
return 1;
}
string carStatus = "未知";

double PWM_L = 0.0, PWM_R = 0.0, Speed_L = 0.0, Speed_R = 0.0;
//初始化小车状态
displayMenuAndStatus(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
while (true) {
res:
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
//处理串口回传数据
displayMenuAndStatus(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
//菜单与小车状态显示
int choice;
cin >> choice;
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
switch (choice) {
case 1:
sendCommandToESP32("FORWARD");
break;
case 2:
sendCommandToESP32("BACKWARD");
break;
case 3:
sendCommandToESP32("LEFT");
break;
case 4:
sendCommandToESP32("RIGHT");
break;
case 5: {
int hexValue[] = {0xAA, 0x01, 0xFF, 0x00, 0xFF, 0xAB};
sendHexCommandToESP32(hexValue);
break;
}
case 6: {
int hexValue[] = {0xAA, 0x00, 0xFF, 0x01, 0xFF, 0xAB};
sendHexCommandToESP32(hexValue);
//向串口发送数据帧
break;
}
case 7: {
//调速二级菜单
int leftPWM, rightPWM, flag = 1, inp;
int leftStatus, rightStatus;
while(flag) {
system("cls");
cout << "调速菜单" <<endl;
cout << "1. pwm双轮调速\n";
cout << "2. 数据帧单轮调速\n";
cout << "3. 返回上一级\n请选择序号:";
cin >> inp;
if(inp == 2){
cout << "请输入左轮方向 1:正,0:反:";
cin >> leftStatus;
cout << "请输入左轮速度 (0-255): ";
cin >> leftPWM;
cout << "请输入右轮方向 1:正,0:反:";
cin >> rightStatus;
cout << "请输入右轮速度 (0-255): ";
cin >> rightPWM;
//使用数据帧对左右轮分别调速
int hexValue[6];
hexValue[0] = 0XAA;
hexValue[1] = (leftStatus == 1)? 0x01 : 0x00; //左轮转动状态
decimalToHexArray(leftPWM, hexValue, 2); //左轮速度
hexValue[3] = (rightStatus == 1)? 0x01 : 0x00; //右轮转动状态
decimalToHexArray(rightPWM, hexValue, 4); //右轮速度
hexValue[5] = calculateChecksum(hexValue, 5); //异或校验位
sendHexCommandToESP32(hexValue);
}
else if(inp == 1){
int pwm;
cout << "请输入速度:";
cin >> pwm;
if (pwm >= 0 && pwm <= 255) {
char command[50];
sprintf_s(command, sizeof(command), "PWM %d", pwm);
sendCommandToESP32(command);
} else {
cout << "速度超出范围,请重新输入。\n";
}
}
else if(inp == 3) flag = 0;
}
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
processSerialData(carStatus, PWM_L, PWM_R, Speed_L, Speed_R);
break;
}
case 8:
sendCommandToESP32("STOP");
break;
case 9: goto res; break;
//刷新小车状态
case 0:
closeSerialPort();
// 在退出前关闭串口
return 0;
default:
cout << "无效的选项,请重新选择。\n";
break;
}
}

return 0;
}

serial.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifndef SERIAL_H
#define SERIAL_H

#include <windows.h>
#include <string>

// 初始化指定的串口并返回成功与否
bool initSerialPort(const std::wstring& portName, DWORD baudRate);

// 发送命令到ESP32
void sendCommandToESP32(const char* command);
void sendHexCommandToESP32(const int* command);
// 接受命令
bool receiveDataFromESP32(std::string& receivedData);
//
void processSerialData(std::string& carStatus,double& PWM_L,double& PWM_R,double& Speed_L,double& Speed_R);
// 关闭串口
void closeSerialPort();

#endif // SERIAL_H

serial.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifndef SERIAL_H
#define SERIAL_H

#include <windows.h>
#include <string>

// 初始化指定的串口并返回成功与否
bool initSerialPort(const std::wstring& portName, DWORD baudRate);

// 发送命令到ESP32
void sendCommandToESP32(const char* command);
void sendHexCommandToESP32(const int* command);
// 接受命令
bool receiveDataFromESP32(std::string& receivedData);
//
void processSerialData(std::string& carStatus,double& PWM_L,double& PWM_R,double& Speed_L,double& Speed_R);
// 关闭串口
void closeSerialPort();

#endif // SERIAL_H

Features

The design results of the ESP32 Smart Car Control System basically meet the expected requirements. The commands are sent accurately and successfully achieve operations such as moving forward, backward, turning, and PWM speed adjustment. The XOR checksum calculation for data frames is precise and efficient. The parsing of the car’s status is basically correct, and it can clearly display the car’s speed and direction on the console. The system exhibits excellent robustness, promptly reporting error messages for out-of-bound data, and possesses high memory safety. However, the system still faces occasional errors in car status discrimination and delays in receiving data transmitted back from the car, which are currently difficult to resolve. In the future, we will further improve the system’s functionality by delving deeper into hardware logic and software algorithm knowledge to rewrite and refactor the code.

Issues and Solutions

The single-digit numbers in the data frame lack a leading “0” placeholder, making it unrecognizable by the car.

Solution: By consulting literature, it was decided to use the stringstream class from the C++ standard library, analogous to the usage of the setfill() function in the iostream stream for homework assignments. Specifically, a string stream was used to add a leading “0” before single digits to achieve precise placeholder formatting.
Specific implementation code:

1
2
3
4
ss << hex << setfill('0') << setw(2) << decimal;
int hexValue;
ss >> hex >> hexValue;
hexArray[index] = hexValue;

The original serial port sending function could not send integer data frames.

Solution: A new serial port sending function was written to send an integer array to the ESP32 microcontroller. This function accepts the base address of an integer array and removes carriage returns when sending information.
Specific implementation code:

1
2
3
4
5
6
7
8
9
10
void sendHexCommandToESP32(const int* command) {
if (hSerial == INVALID_HANDLE_VALUE) {
std::cerr << "Serial port not initialized." << std::endl;
return;
}
DWORD bytesWritten;
for (int i = 0; i < 6; i++) {
WriteFile(hSerial, command + i, 1, &bytesWritten, NULL);
}
}

Incorrect data transmitted back from the smart car simulator.

The smart car simulator transmits data like this: PWM_L = 255, Speed_L = 38, STOP; PWM_R = 255, Speed_R = 38, STOP. The status is STOP, but the speed is not zero, indicating incorrect data transmission.
Solution: In the car status judgment part, recognition of “STOP” was added to improve the accuracy of status discrimination.

1
2
else if ((PWM_L == 0 && PWM_R == 0) || receivedData.find("STOP") != std::string::npos)
carStatus = "Stopped";

Delay in receiving and judging the car’s status.

The serial port information receiving function receiveDataFromESP32() has significant delay in receiving information.
Solution: A “Refresh Status” option was added to the menu to improve the timeliness of status recognition by multiple acquisitions of data transmitted back from the microcontroller. However, there is currently no feasible solution for the delay itself, and the system functionality needs further improvement.

Reflection

During the design process of the ESP32 smart car control system, I deeply experienced the charm of combining hardware and software. By programming to control the behavior of hardware, realizing functions such as the car’s forward, backward, turning, and PWM speed adjustment, I felt the infinite possibilities of technology. However, in the actual design process, I also encountered many challenges, such as how to accurately read the car’s status data, how to accurately calculate the XOR checksum, and how to stably control the motors. Through consulting materials, debugging code, and algorithm optimization, I gradually overcame these difficulties. During the design process, I also constantly tried out new ideas in my mind, cultivated innovation and exploration spirit, and improved my problem-solving abilities and practical skills in C++ programming.

Suggestions

  • More practical sessions should be added to the course, giving students more opportunities to hands-on operate hardware, write code, and debug systems. Only through practice can students gain a deeper understanding of theoretical knowledge and improve their innovative practical abilities.
  • Some team collaboration projects should be set up in the course, giving students opportunities to exercise their team collaboration and communication skills in practice. Teachers can also provide some team collaboration tips and methods to help students better cooperate with others.
  • Some application case analyses of smart car control systems can be added to the course, allowing students to understand practical application scenarios. Through discussion and analysis, students can gain a deeper understanding of the design principles and implementation methods of the system, and also stimulate their innovative thinking and improve their problem-solving abilities.
This project is for the curriculum design of Programming Logic and Methodology of ZhiXing College, DUT. The above code is for academic reference only. Any consequences arising from improper use of the code are the sole responsibility of the user.