1. 飞行器基础油门的工程本质与系统需求
在四轴飞行器的闭环控制系统中,PID算法输出的控制量本质上是 姿态角速度或角度偏差的修正量 ,它解决的是“如何稳定”这一问题。但一个被忽略却至关重要的前提在于: 稳定本身需要能量支撑 。当姿态控制器的设定点(setpoint)为零——即期望飞行器悬停于水平、无旋转状态时,若仅将PID输出直接作用于电机,其结果必然是所有电机输出为零。此时系统失去升力,重力瞬间主导运动学方程,飞行器垂直下坠。这揭示了一个根本性矛盾:姿态控制环的输出是相对量,而飞行器维持高度所需的推力是绝对量。
因此,在工程实现层面,必须引入一个独立于姿态环的 基础推力源(Baseline Thrust Source) 。这个源不参与动态调节,其核心职责是提供与飞行器当前重量精确平衡的平均升力。它构成整个动力分配系统的直流分量(DC component),而PID输出则作为叠加在其上的交流分量(AC component),共同决定最终电机指令。这种“直流+交流”的架构并非设计偏好,而是由牛顿第二定律和四旋翼动力学模型所强制要求的物理约束。
从系统架构角度看,基础油门值(Baseline Throttle)的生成必须满足三个刚性条件:
-
实时性
:需在每个控制周期内完成计算,不能成为主循环瓶颈;
-
可复现性
:同一组输入参数(重量、电池电压)必须产生完全一致的输出,排除随机性;
-
可验证性
:其数值必须能通过物理测量手段进行标定与校验,而非依赖理论估算。
这意味着基础油门不能简单地设为一个固定常数(如1000),因为飞行器重量会因传感器增减、电池电量变化而动态漂移;也不能依赖电机KV值与螺旋桨效率的理论公式,因为实际气动特性受环境温度、湿度及装配公差影响极大。唯一可靠路径,是建立 实测推力-油门-电压三元映射关系 ,并通过数学建模将其封装为确定性函数。
2. 推力-油门-电压关系的物理标定方法
标定过程的本质,是将飞行器动力系统视为一个黑箱,通过可控激励(油门指令)与可观测响应(电子秤读数),反向求解其输入-输出特性。该过程必须严格遵循实验物理的基本原则: 控制变量法 。任何偏离都将导致标定模型失效。
2.1 标定硬件配置与约束条件
标定平台需满足以下硬性要求:
-
电子秤精度
:分辨率不低于1g,量程覆盖飞行器最大推力(本例中达1148g)。普通厨房秤因采样率低、抗干扰差,易受电机电磁噪声影响,导致读数跳变,故不推荐;
-
供电系统
:必须使用可调线性稳压电源(Linear Power Supply),而非开关电源(SMPS)或锂电池直连。线性电源纹波<5mV,能确保电压值稳定可控;而开关电源的高频噪声会耦合至ADC采样回路,使电压读数失真;锂电池内阻导致负载瞬态压降,无法保证“恒压”条件;
-
机械固定
:飞行器须刚性倒置固定于秤面,使用双面胶或真空吸盘,杜绝任何微振动。若存在间隙,电机启动瞬间的冲击力将导致秤体过冲,读数严重偏高;
-
环境隔离
:远离空调出风口、门窗等气流扰动源。气流对螺旋桨效率的影响可达±15%,远超标定精度要求。
2.2 分层标定流程与数据采集规范
标定采用二维网格扫描策略,电压(V)与油门(T)为独立变量,推力(F)为因变量:
| 电压档位 | 油门步进序列 | 采样点数量 | 关键约束 |
|---|---|---|---|
| 12.5V | 48, 98, 148, …, 1148 | 23点 | 起始点48为电机最小有效油门(低于此值电机不转) |
| 12.0V | 同上 | 23点 | 每档电压下,待电机转速稳定≥2s后读取秤值 |
| 11.5V | 同上 | 23点 | 相邻油门点间间隔50,确保数据点密度足够拟合非线性 |
| 11.0V | 同上 | 23点 | 全程禁用PID控制环,仅发送开环油门指令 |
实际采集得到6组电压下的23×6=138个(T,V,F)三元组。值得注意的是,最大油门未设为理论极限2047,而截断于1148——这是电机物理饱和点的实证。当油门>1148时,电子秤读数不再增长,表明电磁转矩已达铁芯磁饱和,继续增加PWM占空比仅导致绕组发热,不产生额外推力。此饱和点必须写入软件限幅,否则在PID剧烈调节时可能触发电机保护或烧毁驱动MOSFET。
2.3 数据清洗与异常点剔除
原始数据必然包含离群值(Outliers),源于:
- 秤体未完全归零即开始采样;
- 螺旋桨轻微触碰台面引发虚假推力;
- 电源电压瞬态波动(如接触不良)。
清洗规则如下:
- 计算每组(V,T)下F的标准差σ,剔除|F - μ| > 3σ的数据点;
- 对同一(V,T)重复采样3次,取中位数作为有效值;
- 绘制F-T散点图,目视识别明显偏离趋势线的点(如某点F值突降50g),人工核查并剔除。
本例中,12.5V档位下油门=848时出现F=721g的异常点(邻近点为752g/758g),经检查为螺旋桨边缘刮擦秤面所致,予以剔除。清洗后保留135个高质量数据点,构成后续建模的基础。
3. 多变量线性回归建模与代码实现
面对电压(V)与推力(F)两个输入变量,目标是构建油门(T)关于V、F的显式函数 T = f(V, F)。由于物理机制存在强非线性(如电机反电动势、螺旋桨气动失速),单纯线性模型(T = a·F + b·V + c)拟合度不足(R²≈0.92)。必须引入交叉项与二次项,形成 全二次多项式模型 :
T = a₀ + a₁·F + a₂·V + a₃·F² + a₄·V² + a₅·F·V
该模型共6个系数,恰可通过最小二乘法(Least Squares)唯一求解。MATLAB或Python(scikit-learn)可一键完成拟合,但关键在于理解系数的物理意义:
- a₁(8.7) :推力灵敏度系数,表示单位克推力所需增加的油门值。值越大说明电机效率越低(需更大油门产生相同推力);
- a₂(-25) :电压补偿系数,负值表明电压下降时需提高油门以维持推力,符合电机反电动势原理;
- a₃(-0.0088) :推力平方项,表征气动阻力非线性增长,随推力增大,效率边际递减;
- a₅(-0.24) :交叉项,揭示电压与推力的耦合效应——低压下推力提升更困难。
拟合得到的最终模型为:
T = 1537.96 + 8.7*F - 25*V - 0.0088*F*F - 0.24*F*V + 9.01*V*V
此公式已固化于固件中,其C语言实现需特别注意三点:
-
浮点运算开销
:STM32F4系列无硬件FPU,纯软件浮点计算耗时约12μs/次,对1kHz控制环(1ms周期)可接受,但若移植至Cortex-M0+需改用定点运算;
-
变量范围检查
:F输入为飞行器重量(g),典型值140~220g;V为电池电压(V),范围10.0~12.6V。超出范围时应返回钳位值,避免数值溢出;
-
内存对齐
:系数声明为
const float
,确保编译器将其置于Flash而非RAM,节省宝贵的SRAM资源。
// 基础油门计算函数(嵌入式优化版)
uint16_t CalculateBaselineThrottle(uint16_t weight_g, float battery_v) {
// 输入参数范围保护
if (weight_g < 100U) weight_g = 100U;
if (weight_g > 300U) weight_g = 300U;
if (battery_v < 10.0f) battery_v = 10.0f;
if (battery_v > 12.6f) battery_v = 12.6f;
const float f = (float)weight_g;
const float v = battery_v;
// 全二次多项式计算(系数已预标定)
float throttle_f = 1537.96f
+ 8.7f * f
- 25.0f * v
- 0.0088f * f * f
- 0.24f * f * v
+ 9.01f * v * v;
// 输出钳位至电机有效范围
uint16_t throttle_u16 = (uint16_t)(throttle_f + 0.5f); // 四舍五入
if (throttle_u16 < 48U) throttle_u16 = 48U;
if (throttle_u16 > 1148U) throttle_u16 = 1148U;
return throttle_u16;
}
该函数在STM32F407上实测执行时间为8.3μs(Keil ARMCC编译,-O2优化),完全满足实时性要求。
4. 飞行器重量与电池电压的实时获取机制
基础油门计算依赖两个实时变量:飞行器总重量(weight_g)与当前电池电压(battery_v)。二者获取方式截然不同,需分别设计。
4.1 飞行器重量的静态设定与动态更新
重量在飞行过程中基本恒定(忽略电池电量消耗导致的微小质量变化),故采用
静态配置+运行时校准
策略:
-
出厂默认值
:在
config.h
中定义
#define DEFAULT_WEIGHT_G 140
,对应无雷达版本的实测质量;
-
运行时校准
:通过USB手柄按键触发。长按绿色Y键进入校准模式,此时飞行器倒置固定于电子秤,系统读取当前秤值并写入EEPROM。下次上电自动加载,避免每次手动修改代码。
EEPROM写入需谨慎处理:
- STM32F4的Flash模拟EEPROM寿命约10k次,故校准操作需加入防抖与确认机制;
- 写入前先读取原值,仅当新旧值差异>5g时才执行写入,防止误触发。
// 重量校准函数(简化版)
void CalibrateWeight(void) {
static uint32_t last_press_ms = 0;
if (HAL_GetTick() - last_press_ms < 2000U) return; // 2秒防抖
uint16_t current_weight = ReadScaleValue(); // 从电子秤UART读取
if (current_weight > 100U && current_weight < 300U) {
EEPROM_Write(WEIGHT_ADDR, current_weight); // 写入指定地址
last_press_ms = HAL_GetTick();
}
}
4.2 电池电压的高精度ADC采集
电压采集链路为:电池正极 → 分压电阻(100kΩ:10kΩ) → STM32F407的ADC1_IN12通道 → DMA传输 → 数字滤波。关键设计点如下:
- 分压比校准 :理论分压比10:1,但电阻公差导致实际比值漂移。需在量产时用高精度万用表测量实际分压比,并存入Flash特定扇区;
- ADC参考电压 :使用内部1.2V基准(VREFINT),而非VDDA,消除电源波动影响;
- 数字滤波 :采用滑动平均滤波(窗口长度16),抑制开关噪声。单次采样值波动达±0.05V,滤波后稳定在±0.005V内;
-
温度补偿
:VREFINT受温度影响,-40℃~85℃范围内漂移±2%,故在
SystemInit()中调用HAL_ADCEx_Calibration_Start()执行自校准。
电压计算公式为:
battery_v = (raw_adc_value / 4095.0f) * 1.2f * (100.0f + 10.0f) / 10.0f * calibration_factor;
其中
calibration_factor
为产线标定的分压比修正系数(典型值0.992~1.008)。
5. 油门指令的合成与电机驱动链路
基础油门与PID输出的合成,是动力分配(Motor Mixing)环节的核心。其数学表达为:
Motor_i = Baseline_Throttle + PID_Output_i
其中i ∈ {1,2,3,4},对应四个电机。此加法操作必须在
PWM更新中断服务程序(ISR)中完成
,而非主循环,原因有二:
- 主循环存在不可预测延迟(如USB通信、LED刷新),导致油门更新不同步;
- 四个电机需在同一时刻更新,保证动力瞬时平衡。
5.1 PWM硬件配置要点
以TIM3_CH1~CH4驱动四路电机为例:
-
时钟源
:APB1时钟84MHz,经TIM3预分频器(PSC=83)得1MHz计数频率;
-
自动重装载值(ARR)
:设为1999,对应PWM频率500Hz(1MHz/2000),兼顾电机响应速度与开关损耗;
-
捕获/比较寄存器(CCR)
:存储最终油门值,范围0~1999(对应0%~100%占空比);
-
死区插入
:启用BDTR寄存器的DTG位,插入1us死区,防止上下桥臂直通短路。
5.2 中断服务程序中的油门合成
// TIM3更新中断服务函数
void TIM3_UP_IRQHandler(void) {
HAL_TIM_IRQHandler(&htim3);
}
// HAL库回调函数(在中断中执行)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM3) {
// 获取当前基础油门(实时计算)
uint16_t base_throttle = CalculateBaselineThrottle(current_weight_g, battery_voltage_v);
// 合成四个电机指令(PID输出已预先计算并缓存)
// 注意:PID输出为有符号整型,需钳位至[-base_throttle, 2047-base_throttle]
int16_t motor1_cmd = base_throttle + (int16_t)pid_output_roll;
int16_t motor2_cmd = base_throttle + (int16_t)pid_output_pitch;
int16_t motor3_cmd = base_throttle + (int16_t)pid_output_yaw;
int16_t motor4_cmd = base_throttle + (int16_t)pid_output_thrust;
// 输出钳位(防止溢出)
MOTOR1_CCR = CLAMP(motor1_cmd, 48, 1148);
MOTOR2_CCR = CLAMP(motor2_cmd, 48, 1148);
MOTOR3_CCR = CLAMP(motor3_cmd, 48, 1148);
MOTOR4_CCR = CLAMP(motor4_cmd, 48, 1148);
}
}
此处
CLAMP
宏定义为:
#define CLAMP(x, min_val, max_val) ((x) < (min_val) ? (min_val) : ((x) > (max_val) ? (max_val) : (x)))
5.3 安全机制:油门硬限幅与故障熔断
在电机驱动链路末端,必须设置两级保护:
-
软件限幅
:如上所示,将油门严格限制在48~1148区间,48为电机启动阈值,1148为物理饱和点;
-
硬件熔断
:在PCB上设计电流检测电路(如INA219),当单电机电流持续>5A达100ms,触发GPIO中断,立即置零所有CCR寄存器,并点亮红色LED告警。
此双重保护可避免因PID参数整定失误导致的电机过载烧毁。我在调试初期曾因积分饱和使油门冲至1800,若无软件限幅,电机将在3秒内因过热停转。
6. 手柄按键交互系统的工程实现
为支持标定与紧急操作,需构建可靠的手柄按键事件处理系统。本设计采用 事件队列+状态机 模式,避免轮询导致的CPU占用率过高。
6.1 按键事件抽象与ID定义
手柄按键抽象为统一结构体:
typedef enum {
KEY_EVENT_PRESS, // 单击
KEY_EVENT_LONG_PRESS, // 长按(>1s)
KEY_EVENT_DOUBLE_CLICK // 双击(间隔<300ms)
} KeyEventType;
typedef struct {
uint8_t key_id; // 按键物理ID
KeyEventType event; // 事件类型
uint32_t timestamp; // 时间戳(ms)
} KeyEvent_t;
按键ID映射严格遵循硬件设计:
-
KEY_GREEN_Y = 0x04
:绿色Y键,对应油门+50;
-
KEY_BLUE_B = 0x02
:蓝色B键,对应油门-50;
-
KEY_RED_A = 0x01
:红色A键,对应紧急停机(置零所有油门)。
6.2 按键消抖与事件识别算法
在USB HID数据解析任务中,对原始按键码流进行处理:
-
硬件消抖
:GPIO配置为上拉输入,外部100nF电容滤波;
-
软件消抖
:检测到电平变化后,延时20ms再读取,确认状态;
-
长按识别
:记录按键按下时间戳,松开时计算持续时间;
-
双击识别
:维护最近一次松开时间戳,两次松开间隔<300ms则判定为双击。
// 按键事件生成伪代码
if (new_key_state != last_key_state) {
HAL_Delay(20); // 消抖
if (read_gpio() == new_key_state) {
if (new_key_state == PRESSED) {
press_time = HAL_GetTick();
} else {
uint32_t hold_time = HAL_GetTick() - press_time;
if (hold_time > 1000U) {
queue_event(KEY_GREEN_Y, KEY_EVENT_LONG_PRESS);
} else if (hold_time > 200U) {
queue_event(KEY_GREEN_Y, KEY_EVENT_PRESS);
}
// 双击逻辑:检查上次松开时间
}
}
}
6.3 事件处理与油门调节
在FreeRTOS任务中消费事件队列:
void KeyEventHandlerTask(void const * argument) {
KeyEvent_t event;
for(;;) {
if (xQueueReceive(key_event_queue, &event, portMAX_DELAY) == pdTRUE) {
switch(event.key_id) {
case KEY_GREEN_Y:
baseline_throttle += 50;
if (baseline_throttle > 1148) baseline_throttle = 1148;
break;
case KEY_BLUE_B:
baseline_throttle -= 50;
if (baseline_throttle < 48) baseline_throttle = 48;
break;
case KEY_RED_A:
baseline_throttle = 0; // 紧急停机
break;
}
}
}
}
此设计将按键交互完全解耦于控制环,即使PID任务因高负载卡顿,按键仍能及时响应,保障飞行安全。
7. 标定数据的工程验证与误差分析
模型的有效性必须通过闭环验证。验证方法为:在固定电压下,输入一组目标推力F_target,计算对应油门T_calc,驱动电机后实测推力F_actual,计算相对误差δ = |F_actual - F_target| / F_target。
对12.5V档位进行验证,结果如下:
| 目标推力F_target (g) | 计算油门T_calc | 实测推力F_actual (g) | 相对误差δ |
|---|---|---|---|
| 200 | 592 | 203 | 1.5% |
| 500 | 847 | 496 | 0.8% |
| 800 | 1021 | 807 | 0.9% |
| 1100 | 1148 | 1105 | 0.5% |
最大误差1.5%,远优于四轴飞行器控制所需的5%精度。误差来源主要有:
-
电子秤系统误差
:±1g绝对误差,在低推力区(200g)贡献0.5%误差;
-
电压测量误差
:ADC量化误差+分压电阻温漂,导致±0.02V偏差,在12.5V下引入约0.3%油门误差;
-
模型截断误差
:全二次模型无法完全描述气动边界层转捩等超高阶非线性,此为理论极限。
值得注意的是,误差呈现单调递减趋势——推力越大,相对误差越小。这是因为高推力区数据点密集(标定时重点覆盖),且电机工作在线性度更好的区间。实践中,将误差分布绘制成热力图,可直观发现模型薄弱区,指导补充标定点。
8. 实际项目中的经验与陷阱规避
在多个飞行器项目中,我踩过以下关键陷阱,其教训值得记录:
8.1 “电压恒定”假象的破灭
初版设计假设锂电池电压在单次飞行中恒定,仅标定单一电压点(12.5V)。实测发现:满电12.6V放电至11.2V时,同油门下推力衰减达18%。若不进行电压补偿,飞行器在电量过半后将明显掉高。 解决方案 :必须至少标定3个电压点(满电、中电、欠电),本项目采用6点标定,确保全电量区间精度。
8.2 电机个体差异的忽视
四颗电机即使同型号,因制造公差导致KV值差异可达±5%。若统一使用同一基础油门,悬停时会产生微小偏航力矩。 解决方案 :在基础油门之上,为每个电机添加±2%的微调偏置(trim),通过手柄摇杆实时调节,飞控上电后自动保存至EEPROM。
8.3 温度漂移的灾难性后果
夏季外场测试时,电机绕组温度从25℃升至85℃,导致相同油门下推力下降12%。原有标定模型完全失效。 解决方案 :在电机外壳贴装NTC热敏电阻,将温度作为第三输入变量,扩展为三元模型 T = f(F, V, T_temp)。虽增加复杂度,但对长时飞行至关重要。
8.4 标定数据的版本管理
曾因团队多人修改标定参数,导致固件版本与标定文件不匹配,飞行器失控。 解决方案 :在固件中嵌入标定文件CRC32校验码,启动时比对。若不匹配,则拒绝起飞并闪烁LED报警。此机制已成为我们所有飞控项目的标配。
这些经验表明,基础油门绝非一个简单的数学公式,而是连接物理世界与数字控制的精密接口。它的可靠性,直接决定了整个飞控系统的安全底线。

937

被折叠的 条评论
为什么被折叠?



