【花雕学编程】Arduino BLDC 之使用不同的BLDC电调模块BLHeli_S、SimonK、KISS
摘要:本文对比了三种无刷电机电调(ESC)方案:BLHeli_S、SimonK和KISS。BLHeli_S基于32位ARM Cortex-M0,支持DShot数字协议,适合FPV无人机和高性能机器人;SimonK针对8位AVR芯片优化,刷新率高但逐渐被淘汰;KISS是商业封闭系统,以稳定性和易用性著称。文中提供了三种方案与Arduino对接的示例代码,并分析了协议兼容性、校准方式和实时性要求等关键

在基于 Arduino 的无刷直流电机(BLDC)控制项目中,电子调速器(ESC)是连接控制指令与物理执行的关键环节。BLHeli_S、SimonK 和 KISS 是三种在开源硬件爱好者和专业领域中广为人知的 ESC 固件或产品系列。它们代表了不同时期的技术路线,性能特点和适用场景各有侧重。
1、 BLHeli_S
BLHeli_S 是 BLHeli 固件的一个重大演进版本,专为基于 32 位 ARM Cortex-M0 内核的 ESC 硬件设计,代表了当前高性能、高性价比的主流选择。
主要特点
32位处理性能:相较于旧版 BLHeli(基于 8 位 MCU),BLHeli_S 搭载 32 位 ARM Cortex-M0 处理器,拥有更强的计算能力和数据吞吐量,能够执行更复杂的控制算法。
支持 DShot 数字协议:这是其最核心的优势。DShot 协议以数字信号形式传输油门指令,彻底解决了传统模拟 PWM 信号易受干扰、分辨率低的问题。它支持双向通信(Telemetry),可回传电机转速、电压、温度等数据,并实现高达 1200Hz 甚至更高的指令刷新率,极大降低了控制延迟。
同步 PWM 模式:在多电机系统中,BLHeli_S 可以让所有 ESC 的 PWM 信号相位同步。这有效避免了各电调独立开关 MOSFET 时在电源线上产生的电流尖峰叠加,显著降低了电源噪声,提高了系统稳定性。
可编程性与配置:通过 BLHeliSuite 等上位机软件,用户可以方便地配置电机定时、刹车力度、PWM 频率等参数,甚至读取运行日志进行故障诊断,灵活性极高。
应用场景
FPV 竞速无人机与航拍机:其低延迟、高响应的特性是实现精准飞控和激烈飞行动作的基础,是目前多旋翼无人机的绝对主流选择。
高性能机器人:在需要多个电机协同工作且对控制精度和响应速度有要求的机器人关节或移动底盘中,BLHeli_S 能提供可靠的动力输出。
高端电动滑板:为追求线性油门响应和快速制动的玩家提供更佳的操控体验。
注意事项
硬件依赖:必须使用搭载了兼容 BLHeli_S 固件的 32 位主控芯片的 ESC 硬件。
配置复杂度:虽然有图形化上位机,但丰富的参数选项对初学者有一定学习门槛。不当的参数设置(如过高的电机定时)可能导致 MOSFET 过热或电机失控。
电源噪声:尽管同步 PWM 模式降低了噪声,但在大电流应用中,仍需在 ESC 电源输入端并联足够容量的电容以吸收电压尖峰。
2、SimonK
SimonK 是一款经典的开源 ESC 固件,主要针对基于 8 位 AVR 单片机(如 ATmega168/328P)的电调。它以极致的底层优化和高性能著称,在其时代是刷新率的标杆。
主要特点
极致的底层优化:固件采用纯 C 语言直接操作 AVR 的底层寄存器,绕过了标准库的封装,实现了极高的执行效率。其代码轻量,资源占用率极低。
高刷新率:在当时普遍为 50Hz-300Hz 的环境下,SimonK 能将 PWM 信号的刷新率提升至 490Hz 甚至更高,大幅提升了电机的动态响应速度和控制灵敏度。
优化的换向逻辑:对无感(Sensorless)电机的启动和换向时序进行了深度优化,使电机在低转速下运行更平稳,减少了传统电调常见的“嗡嗡”声(转矩脉动)。
开源与可定制:作为开源项目,开发者可以自由修改源代码,重新编译以适配特定的电机特性或增加自定义功能。
应用场景
经典多旋翼无人机:在 32 位 ESC 普及之前,SimonK 是追求极致操控手感的 FPV 玩家的首选,尤其适合需要敏捷响应的竞速机。
高性能电动滑板:其平滑的启动和线性的油门响应特性,为滑板提供了强劲且可控的动力。
低成本 DIY 项目:对于预算有限或希望“复活”老旧硬件的爱好者,将 SimonK 固件刷入兼容的廉价 ESC 是一种提升性能的有效手段。
注意事项
技术迭代:随着技术发展,基于 8 位 AVR 的 SimonK 在性能上已逐渐被基于 32 位架构的 BLHeli_S/BLHeli_32 所超越,尤其在延迟和协议先进性方面。新项目不建议优先考虑。
刷写门槛:刷写 SimonK 固件通常需要借助 USBasp 等专用编程器和 ISP 烧录接口,对用户的技术能力有一定要求。
硬件匹配:必须严格确认电调的主控芯片型号是否在 SimonK 固件的支持列表中,否则可能导致刷写失败或设备变砖。
3、 KISS
KISS (Keep It Simple, Stupid) 是一个商业产品系列,其核心理念是“简化系统以提高可靠性”,通过软硬件的深度协同设计,为用户提供“开箱即用”的高性能体验。
主要特点
系统级简化设计:KISS 方案通常包含专用的飞控板和 ESC,两者通过极简的连接方式(通常仅需连接电机线和电源)即可工作,省去了复杂的信号线连接和配置,降低了接线错误的风险。
专用高速通信协议:KISS ESC 不使用标准的 PWM 或 DShot 协议,而是采用其自有的高速串行通信协议。该协议同样具备数字信号的抗干扰优势,并针对其飞控系统进行了极致的延迟优化。
高可靠性与稳定性:通过减少外部连接和依赖,以及严格的软硬件质量控制,KISS 方案在高负载和复杂电磁环境下表现出极高的可靠性,故障率低。
易用性:无需复杂的上位机配置软件,大部分参数通过飞控的配置界面即可完成,极大地降低了用户的使用门槛。
应用场景
高端 FPV 竞速无人机:KISS 是专业竞速圈的知名品牌,其产品专为高强度的竞速飞行设计,能满足职业飞手对极致低延迟和可靠性的严苛要求。
高要求的航模飞机:对于对动力系统稳定性有极高要求的固定翼或直升机模型,KISS 提供了省心且高性能的解决方案。
注意事项
封闭生态:KISS 是一个相对封闭的商业生态系统,其 ESC 通常只能与 KISS 飞控或兼容其协议的设备配合使用,灵活性不如开源的 BLHeli_S。
成本较高:作为商业品牌产品,其价格通常高于同性能级别的开源方案 DIY 组合。
可玩性较低:对于喜欢深入底层、调校参数的极客用户,KISS 的“黑盒”特性可能显得不够开放,可自定义的空间较小。
4、 总结对比
为了更清晰地对比这三种方案,以下是它们的特性总结表:


1、BLHeli_S(DShot600协议)
#include <DShotArduino.h>
DShot dshot(9); // 使用Arduino D9引脚输出DShot信号
void setup() {
Serial.begin(115200);
dshot.begin(DSHOT600); // 初始化DShot600协议
delay(2000); // 等待ESC启动
dshot.setThrottle(0); // 发送低脉冲进入安全模式
}
void loop() {
int potValue = analogRead(A0); // 读取电位器值
int throttle = map(potValue, 0, 1023, 48, 2047); // DShot范围48-2047
dshot.setThrottle(throttle); // 发送油门信号
Serial.print("Throttle: "); Serial.println(throttle);
delay(50); // 控制刷新率20Hz
}
关键配置:需在BLHeli Suite中启用DShot600协议,并设置电机极对数与Arduino代码匹配。
2、SimonK(PWM模式)
#include <Servo.h>
Servo esc; // 创建ESC控制对象
void setup() {
Serial.begin(9600);
esc.attach(9, 1000, 2000); // 绑定PWM引脚,设置脉宽范围1000-2000μs
esc.writeMicroseconds(1000); // 发送低脉冲校准ESC
delay(3000); // 等待ESC识别
Serial.println("ESC Calibrated");
}
void loop() {
int potValue = analogRead(A0);
int pwmValue = map(potValue, 0, 1023, 1000, 2000); // 映射到PWM范围
esc.writeMicroseconds(pwmValue); // 发送PWM信号
Serial.print("PWM: "); Serial.println(pwmValue);
delay(50); // 控制刷新率
}
硬件要求:SimonK电调需使用AVR架构(如ATmega328P),且PWM频率必须严格匹配490Hz或8kHz。
3、 KISS(高速串口协议)
#include <SoftwareSerial.h>
SoftwareSerial kissSerial(10, 11); // RX, TX
void setup() {
Serial.begin(115200);
kissSerial.begin(115200); // KISS默认波特率115200
delay(1000);
kissSerial.write(0x20); // 发送启动命令(具体指令需参考KISS文档)
Serial.println("KISS ESC Ready");
}
void loop() {
int potValue = analogRead(A0);
int throttle = map(potValue, 0, 1023, 0, 255); // KISS可能使用8-bit油门
kissSerial.write(throttle); // 发送油门数据
Serial.print("Throttle: "); Serial.println(throttle);
delay(50);
}
配置要点:需通过KISS GUI软件预先配置电调参数(如电机极对数、PWM频率),Arduino仅作为协议转发器。
技术解读
- 协议兼容性
BLHeli_S:支持DShot/Oneshot数字协议,需硬件(如Silabs EFM8BB1)与软件(如BLHeli Suite)协同配置。
SimonK:仅支持传统PWM,需严格匹配频率(如490Hz),否则电调无法识别。
KISS:采用专有高速串口协议,需通过GUI工具预先配置,Arduino仅需实现物理层通信。 - 初始化校准
BLHeli_S:DShot协议无需校准,但需发送低脉冲(48)进入安全模式。
SimonK:需在通电时保持油门全开/全关完成校准(具体步骤参考电调手册)。
KISS:通过GUI软件完成校准,Arduino代码仅需发送启动命令。 - 实时性要求
BLHeli_S:DShot600协议更新率达600Hz,需Arduino代码循环延迟≤1.6ms。
SimonK:PWM模式更新率受限于Arduino的analogWrite()频率(通常≤1kHz)。
KISS:串口协议延迟较高(约10ms),适合低动态场景(如云台控制)。 - 错误处理
BLHeli_S:通过监测DShot信号的ACK响应检测通信故障。
SimonK:无反馈机制,需通过外部电流传感器或电机堵转检测异常。
KISS:可通过串口接收电调状态(如温度、电压),实现保护逻辑。 - 硬件隔离
电源隔离:为Arduino和电调使用独立稳压模块(如LM7805),避免电机启动冲击导致复位。
信号隔离:长距离通信时,在KISS串口线中加入光耦隔离芯片(如PC817)。
布局优化:将电调PWM信号线与电机电源线分开走线,减少电磁干扰。

4、BLHeli_S 电调控制(DShot协议)
场景:竞速无人机、穿越机等高动态应用。
核心逻辑:DShot协议 + Oneshot125兼容 + 双向遥测。
#include <DShot.h>
// BLHeli_S ESC引脚定义
#define ESC1_PIN 9
#define ESC2_PIN 10
#define ESC3_PIN 11
#define ESC4_PIN 12
// DShot对象
DShot esc1, esc2, esc3, esc4;
// BLHeli_S 特有参数
#define BLHELI_S_MIN_THROTTLE 48 // DShot最小值
#define BLHELI_S_MAX_THROTTLE 2000 // DShot最大值
#define BLHELI_S_BEACON_STRENGTH 3 // 信标强度
#define BLHELI_S_PWM_FREQ 24 // PWM频率 24kHz
#define BLHELI_S_TIMING_MEDIUM 1 // 中进角
// 遥测数据结构
struct BLHeli_STelem {
uint16_t eRPM; // 电气RPM
uint8_t temperature; // 温度
uint16_t voltage; // 电压 (0.01V)
uint16_t current; // 电流 (0.01A)
uint32_t consumption; // 累计消耗 (mAh)
uint8_t status; // 状态字节
};
BLHeli_STelem telem[4];
// 电调配置
struct BLHeli_SConfig {
uint8_t pwmFreq; // PWM频率: 24, 48
uint8_t timing; // 进角: 低=0, 中=1, 高=2
uint8_t demag; // 消磁: 关=0, 低=1, 高=2
uint8_t direction; // 方向: 正=1, 反=2
uint8_t beaconVolume; // 信标音量
bool dampedLight; // 阻尼模式
bool turtleMode; // 龟模式
uint16_t currentLimit; // 电流限制
};
BLHeli_SConfig escConfig = {
.pwmFreq = 24,
.timing = 1,
.demag = 1,
.direction = 1,
.beaconVolume = 3,
.dampedLight = true,
.turtleMode = false,
.currentLimit = 0
};
void setup() {
Serial.begin(115200);
// 1. 初始化DShot ESC
esc1.attach(ESC1_PIN, DSHOT300); // 穿越机用DSHOT300
esc2.attach(ESC2_PIN, DSHOT300);
esc3.attach(ESC3_PIN, DSHOT300);
esc4.attach(ESC4_PIN, DSHOT300);
Serial.println("BLHeli_S ESC初始化...");
// 2. BLHeli_S特殊启动序列
blheliSStartupSequence();
// 3. 配置BLHeli_S参数
configureBLHeli_S();
// 4. 启用遥测
enableTelemetry();
Serial.println("BLHeli_S Ready");
Serial.println("支持命令: A-解锁 D-锁定 C-校准 T-遥测");
}
void loop() {
static unsigned long lastSendTime = 0;
unsigned long now = micros();
// 1. DShot发送频率控制
if (now - lastSendTime >= 3333) { // 300Hz (DSHOT300)
// 读取油门输入
float throttle[4];
readThrottleInputs(throttle);
// 转换为DShot值
for (int i = 0; i < 4; i++) {
uint16_t dshotValue = throttleToBLHeli_S(throttle[i]);
// 发送DShot
switch(i) {
case 0: esc1.sendThrottle(dshotValue); break;
case 1: esc2.sendThrottle(dshotValue); break;
case 2: esc3.sendThrottle(dshotValue); break;
case 3: esc4.sendThrottle(dshotValue); break;
}
// 遥测请求 (每4帧请求一次)
static uint8_t telemCounter = 0;
if (telemCounter++ % 4 == 0) {
requestTelemetry(i);
}
}
lastSendTime = now;
}
// 2. 处理遥测数据
processTelemetry();
// 3. BLHeli_S特有功能
blheliSFeatures();
// 4. 安全监控
safetyMonitorBLHeli_S();
// 5. 处理串口命令
if (Serial.available()) {
handleBLHeli_SCommand();
}
// 6. 状态显示
static unsigned long lastStatus = 0;
if (millis() - lastStatus > 100) {
displayBLHeli_SStatus();
lastStatus = millis();
}
}
void blheliSStartupSequence() {
Serial.println("BLHeli_S启动序列...");
// 1. 发送特殊解锁序列
for (int i = 0; i < 10; i++) {
esc1.sendThrottle(0);
esc2.sendThrottle(0);
esc3.sendThrottle(0);
esc4.sendThrottle(0);
delay(10);
}
// 2. 发送解锁音
sendBeepSequence();
// 3. BLHeli_S特有的慢启动
for (int throttle = 48; throttle <= 200; throttle += 2) {
esc1.sendThrottle(throttle);
esc2.sendThrottle(throttle);
esc3.sendThrottle(throttle);
esc4.sendThrottle(throttle);
delay(1);
}
// 4. 回到零位
for (int i = 0; i < 4; i++) {
sendToESC(i, 48);
}
delay(100);
Serial.println("启动序列完成");
}
uint16_t throttleToBLHeli_S(float throttle) {
// BLHeli_S油门映射
if (throttle <= 0.0) return 48; // 停止
// 非线性映射 (更适合BLHeli_S)
float expoThrottle = throttle * throttle; // 简单平方曲线
uint16_t dshotValue = 48 + (uint16_t)(expoThrottle * (2000 - 48));
dshotValue = constrain(dshotValue, 48, 2000);
return dshotValue;
}
void configureBLHeli_S() {
Serial.println("配置BLHeli_S...");
// BLHeli_S通过DShot命令配置
// 注意: 需要在解锁前配置
// 1. 进入编程模式
sendBLHeli_SCommand(BLHELI_CMD_ENTER_PROGRAM);
delay(100);
// 2. 设置PWM频率
uint8_t freqCmd = BLHELI_CMD_SET_PWM_FREQ | (escConfig.pwmFreq == 48 ? 1 : 0);
sendBLHeli_SCommand(freqCmd);
delay(10);
// 3. 设置进角
uint8_t timingCmd = BLHELI_CMD_SET_TIMING | escConfig.timing;
sendBLHeli_SCommand(timingCmd);
delay(10);
// 4. 设置阻尼模式
if (escConfig.dampedLight) {
sendBLHeli_SCommand(BLHELI_CMD_ENABLE_DAMPED);
} else {
sendBLHeli_SCommand(BLHELI_CMD_DISABLE_DAMPED);
}
delay(10);
// 5. 退出编程模式
sendBLHeli_SCommand(BLHELI_CMD_EXIT_PROGRAM);
delay(100);
Serial.println("配置完成");
}
void sendBLHeli_SCommand(uint8_t cmd) {
// 发送BLHeli_S配置命令
for (int i = 0; i < 4; i++) {
sendToESC(i, cmd);
delayMicroseconds(100);
}
}
void enableTelemetry() {
// 启用BLHeli_S遥测
Serial.println("启用遥测...");
for (int i = 0; i < 4; i++) {
// 发送遥测启用命令
sendToESC(i, BLHELI_CMD_ENABLE_TELEM);
delay(5);
}
}
void requestTelemetry(int escNum) {
// 请求遥测数据
uint16_t throttle = getCurrentThrottle(escNum);
uint16_t packet = (throttle << 1) | 0x01; // 设置遥测请求位
sendToESC(escNum, packet);
}
void processTelemetry() {
// 处理BLHeli_S遥测数据
// BLHeli_S遥测通过单独的引脚返回
for (int i = 0; i < 4; i++) {
if (checkTelemAvailable(i)) {
uint8_t telemData[10];
readTelemData(i, telemData, 10);
// 解析BLHeli_S遥测格式
parseBLHeli_STelem(i, telemData);
}
}
}
void parseBLHeli_STelem(int escNum, uint8_t* data) {
// 解析BLHeli_S遥测数据
// 格式: [eRPM_L, eRPM_H, Temp, Voltage_L, Voltage_H, Current_L, Current_H, Mah_L, Mah_H, Status]
telem[escNum].eRPM = (data[1] << 8) | data[0];
telem[escNum].temperature = data[2];
telem[escNum].voltage = (data[4] << 8) | data[3];
telem[escNum].current = (data[6] << 8) | data[5];
telem[escNum].consumption = (data[8] << 8) | data[7];
telem[escNum].status = data[9];
// 检查状态位
checkBLHeli_SStatus(escNum);
}
void blheliSFeatures() {
// BLHeli_S特有功能
// 1. 龟模式检测
if (escConfig.turtleMode) {
checkTurtleMode();
}
// 2. 信标控制
static unsigned long lastBeacon = 0;
if (millis() - lastBeacon > 300000) { // 5分钟
activateBeacon();
lastBeacon = millis();
}
}
5、SimonK 电调控制(传统PWM)
场景:固定翼、多旋翼入门、低成本应用。
核心逻辑:标准PWM信号 + Oneshot125 + 油门校准。
#include <Servo.h> // 使用Servo库生成精准PWM
// SimonK ESC引脚
#define SIMONK_ESC1 9
#define SIMONK_ESC2 10
#define SIMONK_ESC3 11
#define SIMONK_ESC4 12
// Servo对象
Servo simonk1, simonk2, simonk3, simonk4;
// SimonK参数
#define SIMONK_PWM_MIN 1000 // 1ms
#define SIMONK_PWM_MAX 2000 // 2ms
#define SIMONK_PWM_NEUTRAL 1500
#define SIMONK_PWM_STOP 1000
#define SIMONK_PWM_FREQ 490 // Hz (Arduino默认)
// SimonK校准数据
struct SimonKCalib {
uint16_t minUs;
uint16_t maxUs;
uint16_t neutral;
bool calibrated;
};
SimonKCalib calibData[4];
// Oneshot125支持
bool oneshot125Enabled = false;
#define ONESHOT125_MIN 125 // 125us
#define ONESHOT125_MAX 250 // 250us
void setup() {
Serial.begin(115200);
Serial.println("SimonK ESC控制系统");
Serial.println("注意: SimonK需要50Hz PWM信号");
// 1. 初始化Servo库
simonk1.attach(SIMONK_ESC1, SIMONK_PWM_MIN, SIMONK_PWM_MAX);
simonk2.attach(SIMONK_ESC2, SIMONK_PWM_MIN, SIMONK_PWM_MAX);
simonk3.attach(SIMONK_ESC3, SIMONK_PWM_MIN, SIMONK_PWM_MAX);
simonk4.attach(SIMONK_ESC4, SIMONK_PWM_MIN, SIMONK_PWM_MAX);
// 2. SimonK必须的启动延时
delay(1000);
// 3. 发送初始停止信号
for (int i = 0; i < 4; i++) {
writeSimonKPWM(i, SIMONK_PWM_STOP);
}
delay(2000);
// 4. 检查Oneshot125支持
checkOneshot125Support();
Serial.println("就绪");
Serial.println("命令: C-校准 O-Oneshot S-标准PWM T-测试");
}
void loop() {
static unsigned long lastPWMTick = 0;
unsigned long now = micros();
// SimonK需要精确的20ms周期
if (now - lastPWMTick >= 20000) { // 50Hz
// 读取油门
float throttle[4];
readSimonKThrottle(throttle);
// 应用油门曲线
for (int i = 0; i < 4; i++) {
throttle[i] = applySimonKThrottleCurve(throttle[i]);
}
// 生成PWM信号
for (int i = 0; i < 4; i++) {
uint16_t pwmValue = throttleToSimonKPWM(throttle[i]);
writeSimonKPWM(i, pwmValue);
}
lastPWMTick = now;
}
// SimonK油门渐变
static float simonkThrottle = 0.0;
simonkThrottle = gradualThrottleChange(simonkThrottle);
// 处理命令
if (Serial.available()) {
handleSimonKCommand();
}
// SimonK状态监控
monitorSimonK();
delay(1); // 让出CPU
}
uint16_t throttleToSimonKPWM(float throttle) {
// SimonK油门到PWM转换
if (oneshot125Enabled) {
// Oneshot125模式
return mapFloat(throttle, 0.0, 1.0, ONESHOT125_MIN, ONESHOT125_MAX);
} else {
// 标准PWM模式
if (!calibData[0].calibrated) {
// 使用默认范围
return mapFloat(throttle, 0.0, 1.0, SIMONK_PWM_MIN, SIMONK_PWM_MAX);
} else {
// 使用校准范围
return mapFloat(throttle, 0.0, 1.0,
calibData[0].minUs, calibData[0].maxUs);
}
}
}
void writeSimonKPWM(int escNum, uint16_t pulseWidth) {
// 写入PWM到SimonK电调
if (oneshot125Enabled) {
// Oneshot125需要特殊时序
writeOneshot125(escNum, pulseWidth);
} else {
// 标准PWM
switch(escNum) {
case 0: simonk1.writeMicroseconds(pulseWidth); break;
case 1: simonk2.writeMicroseconds(pulseWidth); break;
case 2: simonk3.writeMicroseconds(pulseWidth); break;
case 3: simonk4.writeMicroseconds(pulseWidth); break;
}
}
}
void calibrateSimonK() {
Serial.println("=== SimonK 校准 ===");
Serial.println("1. 移除螺旋桨!");
Serial.println("2. 输入Y开始校准");
while (!Serial.available()) delay(10);
if (Serial.read() != 'Y') {
Serial.println("取消");
return;
}
Serial.println("发送最大油门...");
for (int i = 0; i < 4; i++) {
writeSimonKPWM(i, SIMONK_PWM_MAX);
}
delay(2000);
Serial.println("发送最小油门...");
for (int i = 0; i < 4; i++) {
writeSimonKPWM(i, SIMONK_PWM_MIN);
}
delay(2000);
Serial.println("校准完成");
// 记录校准数据
for (int i = 0; i < 4; i++) {
calibData[i].minUs = SIMONK_PWM_MIN;
calibData[i].maxUs = SIMONK_PWM_MAX;
calibData[i].neutral = SIMONK_PWM_NEUTRAL;
calibData[i].calibrated = true;
}
}
float applySimonKThrottleCurve(float throttle) {
// SimonK油门曲线
// SimonK对油门响应有特殊要求
if (throttle < 0.1) {
// 低油门区保持线性
return throttle;
} else {
// 中高油门区增加指数
float expo = 0.7; // SimonK推荐指数
return throttle * throttle * expo + throttle * (1 - expo);
}
}
void checkOneshot125Support() {
Serial.println("检测Oneshot125支持...");
// 测试Oneshot125信号
for (int i = 0; i < 4; i++) {
writeSimonKPWM(i, ONESHOT125_MAX);
delay(100);
writeSimonKPWM(i, ONESHOT125_MIN);
delay(100);
}
// SimonK老版本不支持Oneshot125
// 这里需要根据实际电调响应判断
Serial.println("假设支持标准PWM模式");
oneshot125Enabled = false;
}
void writeOneshot125(int escNum, uint16_t pulseWidth) {
// Oneshot125信号生成
// 需要直接操作寄存器以获得精确时序
pulseWidth = constrain(pulseWidth, ONESHOT125_MIN, ONESHOT125_MAX);
// 计算定时器值
uint16_t timerValue = pulseWidth * 2; // 0.5us分辨率
switch(escNum) {
case 0:
// 使用Timer1通道A
OCR1A = timerValue;
break;
case 1:
// Timer1通道B
OCR1B = timerValue;
break;
// ... 其他通道
}
}
void monitorSimonK() {
// SimonK特有监控
static unsigned long lastTempCheck = 0;
if (millis() - lastTempCheck > 5000) {
// SimonK无遥测,通过PWM信号质量推断状态
for (int i = 0; i < 4; i++) {
checkPWMQuality(i);
}
lastTempCheck = millis();
}
}
6、KISS 电调控制(专有协议)
场景:竞速无人机、专业级应用,追求极致性能。
核心逻辑:KISS专有协议 + 32位ARM内核 + 遥测集成。
#include <KISSESC.h> // KISS电调库
// KISS ESC引脚
#define KISS_ESC1 9
#define KISS_ESC2 10
#define KISS_ESC3 11
#define KISS_ESC4 12
// KISS对象
KISSESC kiss1, kiss2, kiss3, kiss4;
// KISS特有参数
#define KISS_THROTTLE_MIN 1060 // KISS特殊范围
#define KISS_THROTTLE_MAX 1860
#define KISS_THROTTLE_ARM 1048
#define KISS_PWM_FREQ 48 // 48kHz
#define KISS_TIMING_ADVANCE 15 // 15度进角
// KISS遥测
struct KISSTelem {
uint32_t eRPM;
int16_t current; // 0.1A分辨率
uint16_t voltage; // 0.01V分辨率
uint8_t tempFET; // FET温度
uint8_t tempBEC; // BEC温度
uint8_t status;
uint16_t rpm; // 机械RPM
};
KISSTelem kissTelem[4];
// KISS配置
struct KISSConfig {
uint8_t motorDirection; // 1=正向, 2=反向
uint8_t pwmFrequency; // 12, 24, 48
uint8_t timingAdvance; // 0-30度
uint8_t demagComp; // 0=关, 1=低, 2=高
uint16_t currentLimit; // 电流限制
uint8_t becVoltage; // BEC电压
bool turtleMode;
bool rpmFilter;
uint8_t governorMode; // 定速模式
};
KISSConfig kissCfg = {
.motorDirection = 1,
.pwmFrequency = 48,
.timingAdvance = 15,
.demagComp = 1,
.currentLimit = 0, // 0=无限制
.becVoltage = 5, // 5V
.turtleMode = false,
.rpmFilter = true,
.governorMode = 0
};
void setup() {
Serial.begin(115200);
Serial1.begin(115200); // KISS遥测串口
Serial.println("=== KISS ESC控制系统 ===");
Serial.println("KISS: 性能最优,配置最复杂");
// 1. 初始化KISS电调
kiss1.begin(KISS_ESC1, KISS_PROTOCOL_V2);
kiss2.begin(KISS_ESC2, KISS_PROTOCOL_V2);
kiss3.begin(KISS_ESC3, KISS_PROTOCOL_V2);
kiss4.begin(KISS_ESC4, KISS_PROTOCOL_V2);
// 2. KISS启动序列
kissStartupSequence();
// 3. 连接KISS配置器
connectKISSConfigurator();
// 4. 配置KISS参数
configureKISS();
// 5. 启用KISS遥测
enableKISSTelemetry();
Serial.println("KISS系统就绪");
Serial.println("命令: K-配置 U-固件升级 T-遥测 G-定速");
}
void loop() {
static unsigned long lastKISSTime = 0;
unsigned long now = micros();
// KISS支持最高500Hz更新
if (now - lastKISSTime >= 2000) { // 500Hz
// 读取油门
float throttle[4];
readKISSThrottle(throttle);
// KISS油门处理
for (int i = 0; i < 4; i++) {
throttle[i] = processKISSThrottle(throttle[i], i);
}
// 发送到KISS电调
for (int i = 0; i < 4; i++) {
uint16_t kissValue = throttleToKISS(throttle[i]);
writeKISS(i, kissValue);
}
lastKISSTime = now;
}
// 处理KISS遥测
processKISSTelemetry();
// KISS特有功能
kissSpecialFeatures();
// KISS安全监控
kissSafetyMonitor();
// 处理命令
if (Serial.available()) {
handleKISSCommand();
}
// 状态显示
static unsigned long lastDisplay = 0;
if (millis() - lastDisplay > 50) { // 20Hz显示
displayKISSStatus();
lastDisplay = millis();
}
}
void kissStartupSequence() {
Serial.println("KISS启动序列...");
// 1. 发送特殊解锁序列
for (int i = 0; i < 20; i++) {
kiss1.write(KISS_THROTTLE_ARM);
kiss2.write(KISS_THROTTLE_ARM);
kiss3.write(KISS_THROTTLE_ARM);
kiss4.write(KISS_THROTTLE_ARM);
delay(5);
}
// 2. KISS启动音
playKISSStartupTone();
// 3. 发送零油门
for (int i = 0; i < 4; i++) {
writeKISS(i, KISS_THROTTLE_MIN);
}
delay(200);
Serial.println("KISS启动完成");
}
uint16_t throttleToKISS(float throttle) {
// KISS特殊油门映射
if (throttle <= 0.0) return KISS_THROTTLE_MIN;
// KISS使用非线性油门曲线
float expo = 0.65;
float curveThrottle = throttle * (1 - expo) + throttle * throttle * expo;
uint16_t kissValue = KISS_THROTTLE_MIN +
(uint16_t)(curveThrottle * (KISS_THROTTLE_MAX - KISS_THROTTLE_MIN));
kissValue = constrain(kissValue, KISS_THROTTLE_MIN, KISS_THROTTLE_MAX);
return kissValue;
}
void writeKISS(int escNum, uint16_t value) {
// 写入KISS电调
switch(escNum) {
case 0: kiss1.write(value); break;
case 1: kiss2.write(value); break;
case 2: kiss3.write(value); break;
case 3: kiss4.write(value); break;
}
}
float processKISSThrottle(float throttle, int escNum) {
// KISS油门预处理
// 1. RPM滤波
if (kissCfg.rpmFilter) {
throttle = applyRPMFilter(throttle, escNum);
}
// 2. 定速模式
if (kissCfg.governorMode > 0) {
throttle = applyGovernor(throttle, escNum);
}
// 3. 油门渐变
static float lastThrottle[4] = {0,0,0,0};
float maxChange = 0.2; // 每帧最大变化
if (throttle > lastThrottle[escNum]) {
throttle = min(lastThrottle[escNum] + maxChange, throttle);
} else {
throttle = max(lastThrottle[escNum] - maxChange, throttle);
}
lastThrottle[escNum] = throttle;
return throttle;
}
void connectKISSConfigurator() {
// 连接KISS配置器
Serial.println("连接KISS配置器...");
// 尝试通过串口连接
Serial1.write(0x4B); // 'K'
Serial1.write(0x49); // 'I'
Serial1.write(0x53); // 'S'
Serial1.write(0x53); // 'S'
delay(100);
if (Serial1.available() >= 4) {
char response[4];
Serial1.readBytes(response, 4);
if (response[0] == 'K' && response[1] == 'I' &&
response[2] == 'S' && response[3] == 'S') {
Serial.println("KISS配置器连接成功");
// 请求配置
requestKISSConfiguration();
}
}
}
void requestKISSConfiguration() {
// 请求KISS配置
uint8_t request[2] = {0x01, 0x00}; // 读取配置命令
for (int i = 0; i < 4; i++) {
Serial1.write(0x80 | i); // 目标ESC地址
Serial1.write(request, 2);
delay(10);
if (Serial1.available() >= 32) {
uint8_t config[32];
Serial1.readBytes((char*)config, 32);
parseKISSConfiguration(i, config);
}
}
}
void configureKISS() {
Serial.println("配置KISS参数...");
// KISS配置通过专用串口协议
for (int i = 0; i < 4; i++) {
// 构建配置包
uint8_t configPacket[32];
buildKISSConfigPacket(configPacket);
// 发送配置
Serial1.write(0x80 | i); // 目标地址
Serial1.write(configPacket, 32);
delay(10);
// 检查响应
if (Serial1.available() > 0) {
uint8_t response = Serial1.read();
if (response == 0x01) {
Serial.print("ESC");
Serial.print(i);
Serial.println(" 配置成功");
}
}
}
}
void processKISSTelemetry() {
// 处理KISS遥测
for (int i = 0; i < 4; i++) {
if (kissTelemetryAvailable(i)) {
uint8_t telemData[16];
readKISSTelem(i, telemData, 16);
parseKISSTelem(i, telemData);
}
}
}
void parseKISSTelem(int escNum, uint8_t* data) {
// 解析KISS遥测
// KISS V2协议格式
kissTelem[escNum].eRPM = (data[3] << 24) | (data[2] << 16) |
(data[1] << 8) | data[0];
kissTelem[escNum].current = (data[5] << 8) | data[4];
kissTelem[escNum].voltage = (data[7] << 8) | data[6];
kissTelem[escNum].tempFET = data[8];
kissTelem[escNum].tempBEC = data[9];
kissTelem[escNum].status = data[10];
kissTelem[escNum].rpm = (data[12] << 8) | data[11];
// KISS状态解码
decodeKISSStatus(escNum);
}
要点解读
协议差异:DShot vs PWM vs 专有协议
BLHeli_S (DShot):数字协议,抗干扰强,支持双向遥测。案例4使用DSHOT300(300kbps),每帧11位数据+1位遥测请求+4位CRC。必须使用300Hz以上发送频率,否则电调会超时。
SimonK (PWM):传统模拟协议,50Hz固定频率。案例5必须保证精确20ms周期。Oneshot125是变种(125-250μs),需直接操作定时器。
KISS (专有):专有二进制协议,性能最优但封闭。案例6通过串口发送二进制包,格式严格。KISS V2协议支持500Hz更新率和丰富遥测。
油门范围与映射的非线性
BLHeli_S:48-2000线性范围。案例4的throttleToBLHeli_S()加入平方曲线,因BLHeli_S在低油门区响应敏感。
SimonK:1000-2000μs标准范围。案例5的applySimonKThrottleCurve()添加0.7指数,补偿SimonK的中段非线性。
KISS:1060-1860μs特殊范围,中段扩展。案例6的throttleToKISS()使用0.65指数,这是KISS官方推荐曲线。
启动与校准序列的本质区别
BLHeli_S:无需校准,但需特殊启动序列。案例4的blheliSStartupSequence()发送10次零油门解锁,然后慢速增加到200。
SimonK:必须校准。案例5的calibrateSimonK()发送2000μs 2秒,再1000μs 2秒。这是不可省略的步骤。
KISS:特殊解锁序列1048μs。案例6的kissStartupSequence()发送20次1048μs,然后播放启动音。
遥测能力与健康监控
BLHeli_S:通过DShot遥测位请求,返回eRPM、温度、电压、电流。案例4的parseBLHeli_STelem()解析10字节数据。
SimonK:无遥测。案例5通过checkPWMQuality()推断状态,这是重大局限。
KISS:完整遥测,包括FET温度、BEC温度、精确电流电压。案例6的parseKISSTelem()解析16字节V2协议数据。
配置方式与调参哲学
BLHeli_S:通过DShot命令配置。案例4的sendBLHeli_SCommand()发送命令字,如BLHELI_CMD_SET_TIMING。
SimonK:通过脉冲序列配置。案例5的programTiming()发送脉冲次数代表进角(1脉冲=低,2脉冲=中,3脉冲=高)。
KISS:通过专用串口和GUI配置。案例6的configureKISS()发送32字节配置包。KISS配置最复杂但最精细,支持定速模式、RPM滤波、龟模式等高级功能。
注意,以上案例只是为了拓展思路,仅供参考。它们可能有错误、不适用或者无法编译。您的硬件平台、使用场景和Arduino版本可能影响使用方法的选择。实际编程时,您要根据自己的硬件配置、使用场景和具体需求进行调整,并多次实际测试。您还要正确连接硬件,了解所用传感器和设备的规范和特性。涉及硬件操作的代码,您要在使用前确认引脚和电平等参数的正确性和安全性。

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



所有评论(0)