Arduino BLDC 基于串口指令的远程控制工业巡检机器人
介绍基于 Arduino 和 BLDC 电机的工业巡检机器人系统。涵盖串口通信(UART/Modbus)、PID 闭环控制及 ROS 集成方案。详细解析了基础速度控制、差速转向、安全机制(看门狗/急停)及工业协议设计。提供 C++ 代码示例,适用于电力、轨道交通等场景的自动化巡检需求。

介绍基于 Arduino 和 BLDC 电机的工业巡检机器人系统。涵盖串口通信(UART/Modbus)、PID 闭环控制及 ROS 集成方案。详细解析了基础速度控制、差速转向、安全机制(看门狗/急停)及工业协议设计。提供 C++ 代码示例,适用于电力、轨道交通等场景的自动化巡检需求。

基于 Arduino 的 BLDC 串口指令远程控制工业巡检机器人,是一种将嵌入式控制、高效驱动与可靠通信技术深度融合的工业自动化解决方案。该系统以 Arduino 为核心控制器,驱动 BLDC 电机实现高机动性移动,通过串口通信链路接收上位机或远程终端的指令,实现对机器人的精确操控与状态监控。
串口通信(UART)作为工业控制领域的基石,提供了稳定、低延迟的指令传输通道。
BLDC 电机为巡检机器人提供了适应复杂工业环境的动力保障。
Arduino 的开源生态为系统功能的扩展提供了极大便利。
#include <Servo.h>
Servo esc; // 电调控制对象
String inputCommand;
int currentSpeed = 1000; // 默认低脉冲(停止)
void setup() {
Serial.begin(115200);
esc.attach(9, 1000, 2000); // 信号线接 9 号引脚
esc.writeMicroseconds(currentSpeed); // 初始化电调
Serial.println("BLDC Control Ready (PWM Mode)");
Serial.println("Commands: SPEED:<1000-2000> | STOP");
}
void loop() {
if (Serial.available() > 0) {
inputCommand = Serial.readStringUntil('\n');
inputCommand.trim();
if (inputCommand.startsWith("SPEED:")) {
currentSpeed = inputCommand.substring(6).toInt();
currentSpeed = constrain(currentSpeed, 1000, 2000); // 限制范围
esc.writeMicroseconds(currentSpeed);
Serial.print("Speed set to: ");
Serial.println(currentSpeed);
} else if (inputCommand == "STOP") {
currentSpeed = ;
esc.(currentSpeed);
Serial.();
}
}
}
应用场景:通过上位机(如 Python 脚本)发送串口指令控制机器人电机启停和调速。
#include <Servo.h>
Servo escLeft, escRight; // 左右电机电调
String command;
void setup() {
Serial.begin(115200);
escLeft.attach(9, 1000, 2000);
escRight.attach(10, 1000, 2000);
Serial.println("Differential Drive Ready");
Serial.println("Commands: FORWARD:<speed> | TURN:<left_speed>:<right_speed> | STOP");
}
void loop() {
if (Serial.available()) {
command = Serial.readStringUntil('\n');
command.trim();
if (command.startsWith("FORWARD:")) {
int speed = command.substring(8).toInt();
speed = constrain(speed, 1000, 2000);
escLeft.writeMicroseconds(speed);
escRight.writeMicroseconds(speed);
Serial.print("Moving forward at: ");
Serial.println(speed);
} else if (command.startsWith("TURN:")) {
int colonIndex = command.(, );
leftSpeed = command.(, colonIndex).();
rightSpeed = command.(colonIndex + ).();
escLeft.((leftSpeed, , ));
escRight.((rightSpeed, , ));
Serial.();
Serial.(leftSpeed);
Serial.();
Serial.(rightSpeed);
} (command == ) {
escLeft.();
escRight.();
Serial.();
}
}
}
应用场景:差速驱动机器人(如巡检小车),通过串口指令实现直行、转向和停止。
#include <Servo.h>
#include <PID_v1.h> // 需要安装 PID 库
Servo esc;
int encoderPin = 2; // 编码器信号输入(需中断引脚)
volatile long pulseCount = 0;
double targetRPM = 100, actualRPM = 0;
double outputPWM = 1000;
PID myPID(&actualRPM, &outputPWM, &targetRPM, 0.5, 0.1, 0.01, DIRECT); // PID 参数需调试
void countPulses() {
pulseCount++;
} // 编码器中断函数
void setup() {
Serial.begin(115200);
esc.attach(9, 1000, 2000);
pinMode(encoderPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(encoderPin), countPulses, RISING);
myPID.SetMode(AUTOMATIC);
myPID.SetOutputLimits(1000, 2000); // 限制 PWM 范围
Serial.println("Closed-Loop Control Ready");
}
void loop() {
static lastTime = ;
(() - lastTime >= ) {
();
currentPulses = pulseCount;
pulseCount = ;
();
actualRPM = (currentPulses / ) * ;
myPID.();
esc.(outputPWM);
Serial.();
Serial.(targetRPM);
Serial.();
Serial.(actualRPM);
Serial.();
Serial.(outputPWM);
lastTime = ();
}
(Serial.()) {
String cmd = Serial.();
(cmd.()) {
targetRPM = cmd.().();
Serial.();
Serial.(targetRPM);
}
}
}
应用场景:需要精确控制轮速的巡检机器人(如爬坡时保持恒定速度),通过编码器反馈实现 PID 闭环控制。
场景:室内简单巡检,通过串口接收控制指令。 核心逻辑:ASCII 指令解析 + 差速控制。
#include <SimpleFOC.h>
// BLDC 电机对象
BLDCMotor motorL = BLDCMotor(7);
BLDCMotor motorR = BLDCMotor(7);
// 编码器
Encoder encoderL(2, 3, 2048);
Encoder encoderR(18, 19, 2048);
void doA_L() { encoderL.handleA(); }
void doB_L() { encoderL.handleB(); }
void doA_R() { encoderR.handleA(); }
void doB_R() { encoderR.handleB(); }
// 控制参数
float linearSpeed = 0.0; // 线速度 m/s
float angularSpeed = 0.0; // 角速度 rad/s
float maxSpeed = 1.0; // 最大速度 1m/s
float wheelSeparation = 0.3; // 轮距 0.3m
float wheelRadius = 0.05; // 轮半径 0.05m
// 指令结构
{
type;
param1;
param2;
timestamp;
};
Command currentCmd = {, , , };
newCommand = ;
String serialBuffer = ;
{
posX = , posY = ;
heading = ;
battery = ;
temp = ;
uptime = ;
};
RobotState state;
obstacleDistance = ;
{
Serial.();
Serial();
encoderL.();
encoderR.();
encoderL.(doA_L, doB_L);
encoderR.(doA_R, doB_R);
motorL.(&encoderL);
motorR.(&encoderR);
motorL.();
motorR.();
motorL.();
motorR.();
(SONAR_TRIG, OUTPUT);
(SONAR_ECHO, INPUT);
Serial.();
Serial.();
Serial.();
}
{
();
(newCommand) {
(currentCmd);
newCommand = ;
}
();
();
();
motorL.();
motorR.();
lastReport = ;
(() - lastReport > ) {
();
lastReport = ();
}
}
{
(Serial.()) {
c = Serial.();
(c == || c == ) {
(serialBuffer.() > ) {
(serialBuffer);
serialBuffer = ;
}
} {
serialBuffer += c;
}
}
}
{
buffer.();
buffer.();
Command cmd = {, , , ()};
(buffer.() == ) {
cmd.type = buffer[];
(buffer[]) {
:
cmd.param1 = ;
;
:
cmd.param1 = ;
;
:
cmd.type = ;
cmd.param1 = ;
;
:
cmd.type = ;
cmd.param1 = ;
;
:
cmd.type = ;
;
:
cmd.type = ;
;
}
} (buffer.()) {
cmd.type = ;
buffer.(, );
cmd.param1 = buffer.();
} (buffer.()) {
cmd.type = ;
buffer.(, );
cmd.param1 = buffer.();
} (buffer.()) {
(buffer, cmd);
} (buffer.()) {
cmd.type = ;
(buffer, cmd);
}
currentCmd = cmd;
newCommand = ;
Serial.();
Serial.(buffer);
}
{
(cmd.type) {
:
linearSpeed = (cmd.param1, -maxSpeed, maxSpeed);
angularSpeed = ;
Serial.();
Serial.(linearSpeed, );
;
:
angularSpeed = (cmd.param1, , );
linearSpeed = ;
Serial.();
Serial.(angularSpeed, );
;
:
linearSpeed = ;
angularSpeed = ;
Serial.();
;
:
();
;
:
(cmd.param1, cmd.param2);
;
:
(cmd);
;
}
}
{
leftSpeed = linearSpeed - (angularSpeed * wheelSeparation / );
rightSpeed = linearSpeed + (angularSpeed * wheelSeparation / );
leftMotorSpeed = leftSpeed / wheelRadius;
rightMotorSpeed = rightSpeed / wheelRadius;
leftMotorSpeed = (leftMotorSpeed, , );
rightMotorSpeed = (rightMotorSpeed, , );
motorL.(leftMotorSpeed);
motorR.(rightMotorSpeed);
}
{
lastLeftPos = , lastRightPos = ;
leftPos = motorL.shaft_angle;
rightPos = motorR.shaft_angle;
deltaLeft = leftPos - lastLeftPos;
deltaRight = rightPos - lastRightPos;
linearDelta = (deltaLeft + deltaRight) * wheelRadius / ;
angularDelta = (deltaRight - deltaLeft) * wheelRadius / wheelSeparation;
state.heading += angularDelta;
state.posX += linearDelta * (state.heading);
state.posY += linearDelta * (state.heading);
lastLeftPos = leftPos;
lastRightPos = rightPos;
state.uptime = () / ;
state.battery = + (, ) / ;
state.temp = + (, ) / ;
}
{
(SONAR_TRIG, LOW);
();
(SONAR_TRIG, HIGH);
();
(SONAR_TRIG, LOW);
duration = (SONAR_ECHO, HIGH, );
obstacleDistance = duration * / ;
(obstacleDistance > && obstacleDistance < ) {
(linearSpeed > ) {
linearSpeed = ;
angularSpeed = ;
Serial.();
}
}
}
{
Serial.();
Serial.(state.posX, );
Serial.();
Serial.(state.posY, );
Serial.();
Serial.(state.heading, );
Serial.();
Serial.(state.battery, );
Serial.();
Serial.(state.temp, );
Serial.();
Serial.(obstacleDistance, );
Serial.();
}
场景:工业环境,需要标准 Modbus 协议与 PLC/DCS 集成。 核心逻辑:Modbus RTU 协议 + 寄存器映射。
#include <SimpleFOC.h>
#include <ModbusRTU.h>
// Modbus 库
// BLDC 电机
BLDCMotor motorL, motorR;
// Modbus 从站
ModbusRTU mb;
#define MODBUS_SLAVE_ID 1
#define MODBUS_BAUDRATE 9600
#define MODBUS_SERIAL Serial2 // 硬件串口 2
// Modbus 寄存器映射
enum ModbusRegisters {
// 保持寄存器 (可读可写)
REG_SET_SPEED = 0, // 设置速度
REG_SET_ANGLE, // 设置角度
REG_MAX_SPEED, // 最大速度
REG_ACCELERATION, // 加速度
REG_CONTROL_MODE, // 控制模式
REG_TARGET_X, // 目标 X 坐标
REG_TARGET_Y, // 目标 Y 坐标
// 输入寄存器 (只读)
REG_ACTUAL_SPEED = 100, // 实际速度
REG_ACTUAL_ANGLE, // 实际角度
REG_POSITION_X, // 当前位置 X
REG_POSITION_Y, // 当前位置 Y
REG_BATTERY_VOLTAGE, // 电池电压
REG_MOTOR_TEMP, // 电机温度
REG_OBSTACLE_DIST, // 障碍物距离
REG_ERROR_CODE, // 错误代码
REG_STATUS_WORD, // 状态字
REG_COUNT = 150
};
// 寄存器值
uint16_t mbRegisters[REG_COUNT] = {0};
uint16_t mbInputs[50] = {0};
// 机器人状态
struct {
speedActual = ;
angleActual = ;
posX = , posY = ;
battery = ;
temperature[] = {, };
errorCode = ;
statusWord = ;
emergencyStop = ;
};
IndustrialState iState;
{ SPEED_CTRL, POSITION_CTRL, TRAJECTORY_CTRL };
ControlMode ctrlMode = SPEED_CTRL;
{
Serial.();
MODBUS_SERIAL.(MODBUS_BAUDRATE, SERIAL_8N1);
mb.(&MODBUS_SERIAL);
mb.(MODBUS_BAUDRATE);
mb.(MODBUS_SLAVE_ID);
mb.(REG_SET_SPEED, , REG_COUNT);
mb.((), writeRegisterCallback, REG_COUNT);
mb.((), readRegisterCallback, REG_COUNT);
();
();
Serial.();
Serial.();
}
{
mb.();
();
(ctrlMode) {
SPEED_CTRL:
();
;
POSITION_CTRL:
();
;
TRAJECTORY_CTRL:
();
;
}
();
();
motorL.();
motorR.();
lastDiag = ;
(() - lastDiag > ) {
();
lastDiag = ();
}
}
{
mb.(REG_SET_SPEED, );
mb.(REG_MAX_SPEED, );
mb.(REG_ACCELERATION, );
mb.(REG_TARGET_X, );
mb.(REG_TARGET_Y, );
mb.(REG_CONTROL_MODE, SPEED_CTRL);
( i = ; i < ; i++) {
mb.(i, );
}
}
{
(event == Modbus::EX_SUCCESS) {
Serial.();
Serial.(address);
Serial.();
Serial.(val);
(address) {
REG_SET_SPEED:
(val);
;
REG_CONTROL_MODE:
ctrlMode = (ControlMode)(val, , );
Serial.();
Serial.(ctrlMode);
;
REG_TARGET_X:
REG_TARGET_Y:
(ctrlMode == POSITION_CTRL || ctrlMode == TRAJECTORY_CTRL) {
();
}
;
:
mb.(address, val);
}
;
}
;
}
{
speedSetpoint = speedReg / ;
currentSpeed = ;
maxAccel = mb.(REG_ACCELERATION) / ;
maxSpeed = mb.(REG_MAX_SPEED) / ;
(speedSetpoint > currentSpeed) {
currentSpeed = (currentSpeed + maxAccel * , speedSetpoint);
} {
currentSpeed = (currentSpeed - maxAccel * , speedSetpoint);
}
currentSpeed = (currentSpeed, -maxSpeed, maxSpeed);
iState.speedActual = currentSpeed;
motorSpeed = currentSpeed / ;
motorL.(motorSpeed);
motorR.(motorSpeed);
}
{
targetX = mb.(REG_TARGET_X) / ;
targetY = mb.(REG_TARGET_Y) / ;
dx = targetX - iState.posX;
dy = targetY - iState.posY;
distance = (dx * dx + dy * dy);
(distance > ) {
targetAngle = (dy, dx);
angleError = targetAngle - iState.angleActual;
(angleError > PI) angleError -= * PI;
(angleError < -PI) angleError += * PI;
angularSpeed = angleError * ;
linearSpeed = (distance * , );
wheelSep = ;
leftSpeed = linearSpeed - (angularSpeed * wheelSep / );
rightSpeed = linearSpeed + (angularSpeed * wheelSep / );
motorL.(leftSpeed / );
motorR.(rightSpeed / );
}
}
{
mb.(REG_ACTUAL_SPEED - , ()(iState.speedActual * ));
mb.(REG_POSITION_X - , ()(iState.posX * ));
mb.(REG_POSITION_Y - , ()(iState.posY * ));
mb.(REG_BATTERY_VOLTAGE - , ()(iState.battery * ));
mb.(REG_MOTOR_TEMP - , ()(iState.temperature[] * ));
();
mb.(REG_STATUS_WORD - , iState.statusWord);
mb.(REG_ERROR_CODE - , iState.errorCode);
}
{
iState.statusWord = ;
iState.statusWord |= ;
iState.statusWord |= ;
(iState.errorCode != ) {
iState.statusWord |= ;
}
(iState.emergencyStop) {
iState.statusWord |= ;
}
(iState.battery < ) {
iState.statusWord |= ;
}
iState.statusWord |= ;
((iState.speedActual) < ) {
iState.statusWord |= ;
}
}
{
(iState.battery < ) {
iState.errorCode = ;
();
}
(iState.temperature[] > || iState.temperature[] > ) {
iState.errorCode = ;
();
}
lastComm = ;
(() - lastComm > ) {
iState.errorCode = ;
();
}
}
场景:与 ROS 系统集成,实现高级导航和感知。 核心逻辑:ROS Serial 协议 + Twist 消息。
#include <SimpleFOC.h>
#include <ros.h>
// ROS 库
#include <geometry_msgs/Twist.h>
#include <nav_msgs/Odometry.h>
#include <sensor_msgs/BatteryState.h>
#include <std_msgs/Bool.h>
// ROS 节点句柄
ros::NodeHandle nh;
// BLDC 电机
BLDCMotor motorL, motorR;
// 里程计参数
float wheel_radius = 0.05; // 轮半径
float wheel_base = 0.30; // 轮距
float encoder_resolution = 2048 * 4; // 编码器分辨率
float left_ticks = 0, right_ticks = 0;
float last_left_ticks = 0, last_right_ticks = 0;
// ROS 消息
geometry_msgs::Twist cmd_vel;
nav_msgs::Odometry odom_msg;
sensor_msgs::BatteryState battery_msg;
std_msgs::Bool emergency_msg;
// ROS 发布者
ros::Publisher odom_pub("odom", &odom_msg);
ros::Publisher battery_pub("battery", &battery_msg);
ros::Publisher emergency_pub("emergency_stop", &emergency_msg);
// ROS 订阅者
{
cmd_vel = msg;
}
;
{
(msg.data) {
();
} {
();
}
}
;
max_linear_vel = ;
max_angular_vel = ;
linear_vel = ;
angular_vel = ;
emergency_stop = ;
safe_zone_x_min = , safe_zone_x_max = ;
safe_zone_y_min = , safe_zone_y_max = ;
{
Serial.();
nh.();
nh.(odom_pub);
nh.(battery_pub);
nh.(emergency_pub);
nh.(cmd_vel_sub);
nh.(emergency_sub);
();
();
();
nh.();
odom_msg.header.frame_id = ;
odom_msg.child_frame_id = ;
}
{
nh.();
();
(!emergency_stop) {
();
} {
motorL.();
motorR.();
}
();
();
motorL.();
motorR.();
();
}
{
linear_vel = (cmd_vel.linear.x, -max_linear_vel, max_linear_vel);
angular_vel = (cmd_vel.angular.z, -max_angular_vel, max_angular_vel);
left_speed = (linear_vel - angular_vel * wheel_base / ) / wheel_radius;
right_speed = (linear_vel + angular_vel * wheel_base / ) / wheel_radius;
left_speed = (left_speed, , );
right_speed = (right_speed, , );
motorL.(left_speed);
motorR.(right_speed);
}
{
current_left = motorL.shaft_angle * encoder_resolution / ( * PI);
current_right = motorR.shaft_angle * encoder_resolution / ( * PI);
delta_left = current_left - last_left_ticks;
delta_right = current_right - last_right_ticks;
last_left_ticks = current_left;
last_right_ticks = current_right;
left_ticks += delta_left;
right_ticks += delta_right;
left_distance = delta_left * * PI * wheel_radius / encoder_resolution;
right_distance = delta_right * * PI * wheel_radius / encoder_resolution;
x = , y = , theta = ;
linear = (left_distance + right_distance) / ;
angular = (right_distance - left_distance) / wheel_base;
x += linear * (theta + angular / );
y += linear * (theta + angular / );
theta += angular;
(theta > PI) theta -= * PI;
(theta < -PI) theta += * PI;
odom_msg.header.stamp = nh.();
odom_msg.pose.pose.position.x = x;
odom_msg.pose.pose.position.y = y;
odom_msg.pose.pose.position.z = ;
odom_msg.pose.pose.orientation.x = ;
odom_msg.pose.pose.orientation.y = ;
odom_msg.pose.pose.orientation.z = (theta / );
odom_msg.pose.pose.orientation.w = (theta / );
odom_msg.twist.twist.linear.x = linear_vel;
odom_msg.twist.twist.angular.z = angular_vel;
}
{
last_odom_pub = ;
last_battery_pub = ;
now = ();
(now - last_odom_pub >= ) {
odom_pub.(&odom_msg);
last_odom_pub = now;
}
(now - last_battery_pub >= ) {
();
battery_pub.(&battery_msg);
last_battery_pub = now;
}
emergency_msg.data = emergency_stop;
emergency_pub.(&emergency_msg);
}
{
battery_msg.header.stamp = nh.();
battery_msg.voltage = ();
battery_msg.current = ();
battery_msg.percentage = (battery_msg.voltage);
battery_msg.power_supply_status = sensor_msgs::BatteryState::POWER_SUPPLY_STATUS_DISCHARGING;
battery_msg.power_supply_health = sensor_msgs::BatteryState::POWER_SUPPLY_HEALTH_GOOD;
battery_msg.present = ;
}
{
x = odom_msg.pose.pose.position.x;
y = odom_msg.pose.pose.position.y;
(x < safe_zone_x_min || x > safe_zone_x_max || y < safe_zone_y_min || y > safe_zone_y_max) {
(!emergency_stop) {
nh.();
();
geometry_msgs::Twist return_cmd;
return_cmd.linear.x = ;
return_cmd.angular.z = ;
cmd_vel = return_cmd;
}
}
}
{
emergency_stop = ;
linear_vel = ;
angular_vel = ;
motorL.();
motorR.();
nh.();
}
{
(emergency_stop) {
emergency_stop = ;
nh.();
}
}
{
}
parseCommand() 解析单字符(F/B/L/R)和带参数命令(V0.5/T0.2)。优点是可读性强,缺点是效率低、无校验。writeRegisterCallback() 处理写操作,状态字提供完整状态信息。这是工业部署首选。constrain(linearSpeed, -maxSpeed, maxSpeed) 防止超速。工业环境通常限制在 1m/s 以内。handleSpeedCommand() 实现梯形速度曲线,避免急启急停对机械的冲击。checkSafetyZone() 实现地理围栏,超出预设区域自动急停。safetyMonitor() 检测 5 秒无通讯自动停止,防止失控。emergencyStop() 函数,立即置零速度并发布状态。position += (Δleft + Δright)/2 * cos(heading)。deratePower(0.5)。statusWord,每 bit 表示一种状态(就绪、错误、急停等),便于 PLC 快速读取。checkObstacles() 检测 30cm 内障碍物自动转向。工业环境需考虑超声波多径反射问题。navigateTo(x,y) 实现简单航点导航。工业应用需结合反光板、二维码或 UWB 精确定位。battery < 18.0 欠压保护针对 24V 系统(6S 锂电)。注意,以上案例只是为了拓展思路,仅供参考。它们可能有错误、不适用或者无法编译。您的硬件平台、使用场景和 Arduino 版本可能影响使用方法的选择。实际编程时,您要根据自己的硬件配置、使用场景和具体需求进行调整,并多次实际测试。您还要正确连接硬件,了解所用传感器和设备的规范和特性。涉及硬件操作的代码,您要在使用前确认引脚和电平等参数的正确性和安全性。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online