【花雕学编程】Arduino BLDC 之通过setThrottle()函数控制电机的转速和方向
摘要:Arduino的setThrottle()函数是控制BLDC电机的关键接口,主要用于PWM信号调制和电机调速。该函数通过参数映射实现转速与方向控制,支持标准ESC通信协议(1000-2000μs脉宽)。应用场景包括遥控模型、机器人驱动等,使用时需注意硬件兼容性、信号校准和电源隔离。文中提供了三种实现方案:基于H桥驱动板的双信号控制、ESC单线脉冲控制以及带软启动的增强版,涵盖不同应用需求。关

在 Arduino BLDC 控制编程中,setThrottle() 并非 Arduino 官方核心库的原生函数,它通常出现在特定的第三方库(如用于模拟舵机信号控制电调的库,或某些电机控制封装库)中,或者是开发者自定义的函数。其核心作用是通过输出 PWM 信号来控制电机的转速和方向。
⚙️ 主要特点
PWM 信号调制的核心封装
setThrottle() 函数本质上是对底层 PWM 信号生成逻辑的高级封装,简化了用户对脉冲宽度的直接操作。
参数映射: 该函数通常接收一个数值参数(如 throttle),并将其映射到特定的脉冲宽度范围。例如,在模拟舵机信号控制电子调速器(ESC)的场景下,参数 0 可能映射为 1000μs 的脉宽(全反向),参数 90 映射为 1500μs(停止/中立),参数 180 映射为 2000μs(全正向)。
方向与转速的统一控制: 通过一个函数接口同时实现了转速调节和方向切换,逻辑简洁。函数内部会根据输入值的大小,自动计算并设置相应的 PWM 占空比和频率(通常为 50Hz)。
基于电子调速器(ESC)的标准通信
这种控制方式是 Arduino 与 BLDC 电机之间进行“对话”的通用语言。
标准化接口: 大多数 BLDC 电子调速器(ESC)都设计为接收标准的 PWM 信号(周期约 20ms,脉宽 1000μs-2000μs)。setThrottle() 函数使得 Arduino 能够完美模拟遥控接收机的输出信号,从而通用地控制各类 ESC。
内部换相处理: 使用此方法时,Arduino 无需关心电机的换相逻辑(如六步换相或 FOC 算法),这些复杂的任务完全由 ESC 内部的微控制器处理。
开环控制与动态响应
开环特性: setThrottle() 本身是一种开环控制指令。它向 ESC 发送“希望”的转速指令,但不直接监测电机的实际转速是否达到目标。
动态性能: 电机的实际动态响应(加减速平滑度、线性度)很大程度上取决于 ESC 内部的控制算法和 PID 参数。高质量的 ESC 能够快速、平稳地响应 setThrottle() 指令的变化。
🏭 应用场景
该控制方法因其简单、通用和可靠,被广泛应用于各类模型和自动化设备中:
遥控模型(R/C Models): 在遥控车、船、飞机中,Arduino 通过 setThrottle() 类函数根据遥控信号控制电机油门,实现前进、后退和速度调节。
智能小车与机器人: 作为双电机差速驱动底盘的底层控制指令,通过分别调用左右电机的 setThrottle() 函数,实现机器人的直线行驶、转弯和原地掉头。
自动化测试平台: 在需要模拟人工操作或进行电机性能测试的场景下,Arduino 可以编程生成精确的 throttle 指令序列,自动测试电机在不同油门下的电流、转速等参数。
互动装置: 在艺术装置或科教展品中,通过传感器(如超声波、红外)获取用户距离或动作信息,并将其映射为 throttle 值,实现电机转速随用户互动而变化的效果。
⚠️ 注意事项
使用 setThrottle() 控制 BLDC 电机时,需特别注意以下几个关键点:
库文件与硬件的兼容性
确认来源: 首先要明确 setThrottle() 所属的库文件(如 Servo.h 的变种或特定电机库)。不同库对该函数的参数定义(如取值范围是 0-180 还是 -100~100)可能不同。
引脚限制: 并非所有 Arduino 引脚都支持硬件 PWM。必须将信号线连接到标有 ~ 符号的 PWM 引脚,并在代码中正确配置。
信号范围与校准
范围限制: 必须使用 constrain() 等函数确保传入 setThrottle() 的参数在有效范围内(如 0-180),防止发送无效的脉宽信号导致 ESC 保护或电机失控。
行程校准: 不同品牌或型号的 ESC 对 PWM 信号的解析可能存在微小差异。在使用前,通常需要对 ESC 进行油门行程校准,以确保 setThrottle() 的最大值和最小值能被 ESC 正确识别。
电源管理与电气隔离
独立供电: BLDC 电机工作电流大,噪声严重。必须使用独立的电源为电机和 ESC 供电,并通过稳压模块(BEC)为 Arduino 供电,或使用隔离电源,防止电机反冲电压导致 Arduino 复位。
共地连接: 切记将 Arduino 与 ESC 的地线(GND)连接在一起,否则信号没有参考电平,无法正确传输。
安全操作规范
上电顺序: 在程序启动或系统上电时,应确保 setThrottle() 的初始值为停止值(如 90 或 1500μs),避免电机意外启动。
物理检查: 在首次运行代码前,务必先断开螺旋桨或负载,进行空载测试,确认电机旋转方向正确。若方向错误,可通过交换电机与 ESC 之间的三根相线中的任意两根来纠正。

1、基于标准 H 桥驱动板(L298N / TB6612)
场景:电机驱动板需要两个信号:PWM(速度)和 DIR(方向)。
// 定义引脚
const int pwmPin = 9; // PWM 速度控制引脚
const int dirPin = 8; // 方向控制引脚
// 电机对象类
class DCMotor {
private:
int _pwmPin;
int _dirPin;
public:
DCMotor(int pwm, int dir) {
_pwmPin = pwm;
_dirPin = dir;
pinMode(_pwmPin, OUTPUT);
pinMode(_dirPin, OUTPUT);
}
// setThrottle 函数: 范围 -255 到 255
void setThrottle(int throttle) {
// 限制范围
throttle = constrain(throttle, -255, 255);
if (throttle > 0) {
// 正转
digitalWrite(_dirPin, LOW);
analogWrite(_pwmPin, throttle);
} else if (throttle < 0) {
// 反转
digitalWrite(_dirPin, HIGH);
analogWrite(_pwmPin, abs(throttle));
} else {
// 停止
analogWrite(_pwmPin, 0);
}
}
};
DCMotor motor(pwmPin, dirPin);
void setup() {
Serial.begin(9600);
Serial.println("H-Bridge Throttle Control Ready.");
// 初始化停止
motor.setThrottle(0);
delay(1000);
}
void loop() {
// 示例:加速正转
for (int i=0; i<=255; i+=10) {
motor.setThrottle(i);
Serial.print("Throttle: "); Serial.println(i);
delay(200);
}
// 停止
motor.setThrottle(0);
delay(1000);
// 加速反转
for (int i=0; i>=-255; i-=10) {
motor.setThrottle(i);
Serial.print("Throttle: "); Serial.println(i);
delay(200);
}
delay(2000);
}
2、基于航模电调(ESC)控制
场景:电调(ESC)通常只接一根信号线,通过脉冲宽度(1000us-2000us)控制速度和方向。
#include <Servo.h> // 使用 Servo 库生成精确脉冲
Servo esc; // 创建 Servo 对象
// 电调参数
const int escPin = 9;
const int minPulse = 1000; // 反转最大
const int maxPulse = 2000; // 正转最大
const int stopPulse = 1500; // 停止
void setup() {
Serial.begin(9600);
esc.attach(escPin); // 连接电调信号线
// 电调初始化:必须先发送停止信号
esc.writeMicroseconds(stopPulse);
delay(3000); // 等待电调自检完成
Serial.println("ESC Initialized.");
}
// setThrottle 函数: 范围 -100 到 100 (百分比)
void setThrottle(int percent) {
percent = constrain(percent, -100, 100);
// 映射到脉冲宽度
int pulseWidth;
if (percent >= 0) {
// 正转区域: 1500us -> 2000us
pulseWidth = map(percent, 0, 100, stopPulse, maxPulse);
} else {
// 反转区域: 1000us -> 1500us
pulseWidth = map(percent, -100, 0, minPulse, stopPulse);
}
esc.writeMicroseconds(pulseWidth);
Serial.print("Throttle: "); Serial.print(percent);
Serial.print("% | Pulse: "); Serial.println(pulseWidth);
}
void loop() {
// 缓慢加速到 50%
for (int i=0; i<=50; i+=5) {
setThrottle(i);
delay(500);
}
setThrottle(0);
delay(2000);
// 缓慢加速到 -30% (反转)
for (int i=0; i>=-30; i-=5) {
setThrottle(i);
delay(500);
}
delay(3000);
}
3、带软启动和限流的增强版
场景:在基础控制上增加软启动(Ramp)和电流限制,保护电机和电源。
// 使用案例一的 H桥引脚定义
const int pwmPin = 9, dirPin = 8;
int currentThrottle = 0; // 当前实际输出
int targetThrottle = 0; // 目标设定值
const int rampStep = 5; // 软启动步长
const int currentLimit = 200; // 电流限制 (PWM值)
void setup() {
pinMode(pwmPin, OUTPUT);
pinMode(dirPin, OUTPUT);
Serial.begin(9600);
analogWrite(pwmPin, 0);
}
// 增强版 setThrottle: 设置目标值,由 loop 负责平滑达到
void setThrottle(int throttle) {
throttle = constrain(throttle, -255, 255);
// 应用电流限制
if (throttle > currentLimit) throttle = currentLimit;
if (throttle < -currentLimit) throttle = -currentLimit;
targetThrottle = throttle;
Serial.print("Target Set to: "); Serial.println(targetThrottle);
}
void loop() {
// 软启动逻辑:逐步逼近目标值
if (currentThrottle != targetThrottle) {
if (currentThrottle < targetThrottle) {
currentThrottle += rampStep;
if (currentThrottle > targetThrottle) currentThrottle = targetThrottle;
} else {
currentThrottle -= rampStep;
if (currentThrottle < targetThrottle) currentThrottle = targetThrottle;
}
// 调用底层输出
writeToMotor(currentThrottle);
Serial.print("Ramping: "); Serial.println(currentThrottle);
}
delay(50); // 控制斜坡斜率
}
// 底层电机输出函数
void writeToMotor(int throttle) {
if (throttle > 0) {
digitalWrite(dirPin, LOW);
analogWrite(pwmPin, throttle);
} else if (throttle < 0) {
digitalWrite(dirPin, HIGH);
analogWrite(pwmPin, abs(throttle));
} else {
analogWrite(pwmPin, 0);
}
}
// 测试代码
void testSequence() {
static unsigned long lastChange = 0;
static int testState = 0;
if (millis() - lastChange > 3000) {
lastChange = millis();
switch(testState) {
case 0: setThrottle(150); break; // 低速正转
case 1: setThrottle(-200); break; // 中速反转
case 2: setThrottle(0); break; // 停止
case 3: setThrottle(255); break; // 试图全速,但会被限流在200
}
testState = (testState + 1) % 4;
}
}
要点解读
硬件抽象层 (Hardware Abstraction Layer) 的价值
统一接口:无论底层是 H 桥、电调还是其他驱动芯片,上层代码(如 loop())只需调用 setThrottle(100)或 setThrottle(-50)。
易于移植:如果更换驱动板(如从 L298N 换成 TB6612),只需修改 setThrottle()内部的实现,而不需要修改调用它的业务逻辑代码。
电调 (ESC) 的特殊初始化要求
安全协议:如案例二所示,大多数航模电调上电时必须先收到“中位信号”(通常 1500us),听到“嘀嘀”声确认后,才能接收油门信号。
信号标准:电调通常要求 50Hz 的 PWM 信号,而 Arduino 的 analogWrite()频率通常是 490Hz 或 980Hz。因此必须使用 Servo库 或手动 pulseIn()来生成正确的低频信号。
软启动 (Soft Start / Ramp) 的重要性
防止冲击:案例三展示了软启动。如果直接从 0 跳到 255,电机会产生巨大的浪涌电流(Inrush Current),可能导致电源电压跌落、Arduino 复位,或烧毁驱动芯片。
实现方式:使用状态变量(currentThrottle和 targetThrottle),在 loop()中逐步改变输出值,而不是瞬间跳变。
参数范围与映射
直观性:使用 -255 到 255 或 -100% 到 100% 比直接使用 PWM 数值(0-255)更符合直觉,且便于计算(如差速转向时,左轮=100,右轮=100)。
非线性补偿:在某些应用中,可以在 setThrottle()内部加入指数曲线映射,使低速控制更精细(例如,输入 10% 对应 PWM 5%,输入 20% 对应 PWM 15%)。
安全限制与保护
电流限制:案例三中的 currentLimit可以防止电机过载。
范围约束:始终使用 constrain()函数限制输入参数,防止越界(如用户输入 300,会被自动截断为 255)。
看门狗:在实际产品中,应加入软件看门狗。如果长时间未收到新指令,自动调用 setThrottle(0)进入安全停止状态。

4、基于SimpleFOC库的双向速度控制(带编码器反馈)
#include <SimpleFOC.h>
// 电机引脚定义
#define MOTOR_A 9
#define MOTOR_B 10
#define MOTOR_C 11
#define ENCODER_A 2 // 编码器A相(中断引脚)
#define ENCODER_B 3 // 编码器B相
BLDCMotor motor = BLDCMotor(7); // 7极对数
BLDCDriver3PWM driver = BLDCDriver3PWM(MOTOR_A, MOTOR_B, MOTOR_C);
Encoder encoder = Encoder(ENCODER_A, ENCODER_B, 2048); // 2048 CPR编码器
void setup() {
Serial.begin(115200);
motor.linkDriver(&driver);
motor.linkSensor(&encoder);
// 电机参数配置
motor.voltage_sensor_align = 3; // 对齐电压
motor.controller = MotionControlType::velocity; // 速度闭环控制
motor.PID_velocity.P = 0.2; // PID参数
motor.PID_velocity.I = 10;
motor.limit_voltage = 12.0; // 电压限制
driver.init();
motor.init();
motor.initFOC();
Serial.println("Motor ready for throttle control.");
}
void loop() {
// 模拟输入:电位器读取目标速度(-100%到100%)
int potValue = analogRead(A0);
float throttle = map(potValue, 0, 1023, -100, 100) / 100.0; // 归一化到[-1,1]
motor.move(throttle * motor.velocity_limit); // 设置目标速度(带方向)
Serial.print("Throttle: "); Serial.print(throttle * 100);
Serial.print("% | Speed: "); Serial.println(motor.shaft_velocity);
delay(50);
}
5、基于ESC(电子调速器)的遥控车差速转向
#include <Servo.h>
Servo escLeft; // 左轮ESC
Servo escRight; // 右轮ESC
void setup() {
escLeft.attach(9); // 左轮PWM引脚
escRight.attach(10); // 右轮PWM引脚
Serial.begin(9600);
// ESC初始化(需根据ESC协议调整)
escLeft.writeMicroseconds(1000); // 停止信号
escRight.writeMicroseconds(1000);
delay(2000);
}
void loop() {
// 模拟输入:摇杆X轴控制转向,Y轴控制速度
int joyX = analogRead(A0); // 转向(-512到512)
int joyY = analogRead(A1); // 速度(0到1023)
// 映射到ESC脉宽(1000-2000μs)
int baseSpeed = map(joyY, 0, 1023, 1000, 2000); // 基础速度
int turnOffset = map(joyX, -512, 512, -200, 200); // 转向偏移
// 差速计算(限制在ESC范围内)
int leftSpeed = constrain(baseSpeed + turnOffset, 1000, 2000);
int rightSpeed = constrain(baseSpeed - turnOffset, 1000, 2000);
escLeft.writeMicroseconds(leftSpeed);
escRight.writeMicroseconds(rightSpeed);
Serial.print("Left: "); Serial.print(leftSpeed);
Serial.print(" | Right: "); Serial.println(rightSpeed);
delay(50);
}
6、无传感器六步换相的正反转控制(低成本方案)
// 电机引脚定义
#define PHASE_A 5
#define PHASE_B 6
#define PHASE_C 7
// 换相顺序表(正转和反转)
const int commutationTable[2][6][3] = {
// 正转顺序
{
{1, 0, 0}, {1, 1, 0}, {0, 1, 0},
{0, 1, 1}, {0, 0, 1}, {1, 0, 1}
},
// 反转顺序
{
{1, 0, 1}, {0, 0, 1}, {0, 1, 1},
{0, 1, 0}, {1, 1, 0}, {1, 0, 0}
}
};
int stepIndex = 0;
int direction = 0; // 0:正转, 1:反转
float speed = 0.5; // 占空比(0-1)
void setup() {
pinMode(PHASE_A, OUTPUT);
pinMode(PHASE_B, OUTPUT);
pinMode(PHASE_C, OUTPUT);
Serial.begin(9600);
}
void loop() {
// 模拟输入:电位器控制方向和速度
int potValue = analogRead(A0);
direction = (potValue < 512) ? 0 : 1; // 中位切换方向
speed = map(abs(potValue - 512), 0, 512, 0, 100) / 100.0;
// 六步换相
for (int i = 0; i < 3; i++) {
digitalWrite(
(i == 0) ? PHASE_A : (i == 1) ? PHASE_B : PHASE_C,
commutationTable[direction][stepIndex][i]
);
}
// 更新步进索引(循环)
stepIndex = (stepIndex + 1) % 6;
// PWM调速(通过延迟模拟占空比)
unsigned long cycleTime = 10000; // 10ms周期
unsigned long onTime = cycleTime * speed;
delayMicroseconds(onTime);
digitalWrite(PHASE_A, LOW);
digitalWrite(PHASE_B, LOW);
digitalWrite(PHASE_C, LOW);
delayMicroseconds(cycleTime - onTime);
Serial.print("Dir: "); Serial.print(direction);
Serial.print(" | Speed: "); Serial.println(speed * 100);
}
要点解读
控制接口选择
ESC协议:案例5使用PWM脉宽(1000-2000μs)控制ESC,兼容多数遥控设备,但需注意启动校准。
SimpleFOC库:案例4通过setThrottle()的抽象接口实现闭环控制,适合高精度场景,需编码器反馈。
六步换相:案例6直接操作GPIO,成本最低,但需手动处理换相时序和死区时间。
方向控制实现
正反转逻辑:通过反转换相顺序表(如案例6)或反向设置目标速度(如案例4的throttle * velocity_limit)。
差速转向:案例5中左右轮速度差实现转向,需独立控制两个ESC。
速度调节方法
PWM占空比:案例6通过延迟模拟占空比,适用于无硬件PWM的引脚。
闭环PID:案例4中PID_velocity参数调整响应速度和稳定性。
ESC脉宽映射:案例5将模拟输入线性映射到ESC的1000-2000μs范围。
硬件关键设计
电源隔离:案例4建议逻辑电路和电机电源隔离,防止干扰。
散热管理:高负载时需加散热片或风扇(尤其案例6的MOSFET驱动)。
信号滤波:案例5中摇杆输入需加RC滤波电路,避免抖动。
安全与容错机制
死区处理:案例5中摇杆中位附近设置死区(如±50),防止电机蠕动。
限幅保护:案例4中constrain()确保ESC输入不超限。
急停功能:建议通过外部中断或硬件开关强制拉低PWM信号(未在案例中体现,但至关重要)。
注意,以上案例只是为了拓展思路,仅供参考。它们可能有错误、不适用或者无法编译。您的硬件平台、使用场景和Arduino版本可能影响使用方法的选择。实际编程时,您要根据自己的硬件配置、使用场景和具体需求进行调整,并多次实际测试。您还要正确连接硬件,了解所用传感器和设备的规范和特性。涉及硬件操作的代码,您要在使用前确认引脚和电平等参数的正确性和安全性。

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)