ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

STM32四轴飞行器DIY:从姿态解算到PID调参全攻略

STM32四轴飞行器DIY:从姿态解算到PID调参全攻略

上周日(05.19)在北京参加的那场“四轴飞行器DIY——基于STM32微控制器”线下活动,我印象挺深的。现场一半人是第一次接触STM32,拿着最小系统板、MPU6050和一堆电调电机,目标就一个:让飞机稳定悬停。其实四轴飞控覆盖的知识点非常典型:姿态解算、控制理论、PWM输出、定时器捕获、传感器通信,每一个都是嵌入式开发里绕不开的基本功。这篇文章我把整个项目的方案选型、原理拆解、实操步骤和排坑经验完整捋了一遍,适合想做四轴却不知道从哪下手的同学,也适合板子烧坏了、调参调到怀疑人生的朋友。

1. 项目设计与方案选型

1.1 主控芯片:为什么是STM32而不是Arduino或树莓派

这个项目的主控毫无疑问选了STM32。原因很直接:四轴飞行器是一个典型的实时控制任务,它在每个控制周期里要完成传感器采样、姿态计算、控制量更新和PWM输出,整个流程必须在固定时间窗口内跑完。STM32是Cortex-M内核的32位微控制器,指令执行时间确定,中断响应快,定时器资源丰富,天然适合这种硬实时的场景。

如果非要用Arduino Uno,也不是完全不行,但实际体验会差很远。ATmega328P主频只有16MHz,内存2KB,跑互补滤波和PID还行,但运算精度和实时性很快就会到瓶颈,尤其是你想加定高、GPS、光流这些模块的时候,性能就不够看了。树莓派更不用提,虽然算力强大,但Linux系统启动慢、实时性差,跑姿态解算还会被后台任务抢占CPU,这是飞行控制里不能接受的。

型号选择上,成本敏感的同学可以用STM32F103C8T6,市面上俗称“Blue Pill”的最小系统板,几块钱一片,72MHz主频,20KB RAM,外设齐全,做基本四轴绰绰有余。想留有升级空间就上STM32F405或F407,Cortex-M4内核带FPU(浮点运算单元),硬件算浮点非常快。很多家用级飞控F4/F7方案用的也是这套思路。活动当天我用的是ST官方Nucleo-F103RB,为了方便现场接口调试,其实核心逻辑和Blue Pill完全一致。

1.2 传感器、电调、电机与机架怎么搭配

飞控的“感觉器官”就是惯性测量单元(IMU),最常用的是MPU6050。它内部集成三轴陀螺仪和三轴加速度计,通过I2C接口输出原始数据,价格便宜到可以忽略,资料也到处都是,作为入门传感器的首选没有任何悬念。进阶玩法是在MPU6050基础上外接HMC5883L磁力计和BMP280气压计,磁力计可以修正航向漂移,气压计用来做定高,但这些都是“有了更好”的加分项,不是起飞的必要条件。

电机和电调是整个动力系统的核心。新手强烈建议选2204或2205级别的无刷电机,KV值1800到2300之间,配上F450机架和8045或者8043正反桨,推力余量充足。电调选30A的就行,兼容3S锂电池,后续就算把机架改大一点,这套电调依然能用。机架真心建议选大不选小,F450这种级别的轴距大、装机空间充裕,炸机维修时换零件的难度也低。很多人上来就想玩五寸穿越机,速度快、响应猛,调试窗口极窄,新手往往还没搞清楚方向就炸了,实在不推荐。

1.3 系统架构:信号是如何从遥控器一路走到螺旋桨的

在动手写代码之前,先把信号链完全想明白。遥控器发射机发出指令,接收机收到后把每一通道的舵量转换成PWM脉宽信号,STM32的定时器输入捕获模块负责测量这些脉宽是多少微秒,解析成油门、副翼、升降、方向四个通道的数值。与此同时,MPU6050通过I2C不断上报当前机体的角速度和加速度,STM32经过姿态解算得到当前的横滚角、俯仰角和偏航角。然后飞控把“期望姿态”和“当前姿态”做差,丢给PID控制器,计算得出的控制量转换为四个电机的PWM占空比,输出给电调,电调驱动无刷电机转动,螺旋桨产生升力。整个回路每一毫秒循环一次。

打个比方:飞控相当于人的小脑,IMU是内耳里的前庭系统,电调和电机就是肌肉,遥控器是大脑下达的意图。小脑每时每刻都在做同一件事——比较“我想怎么样”和“我现在怎么样”,然后让肌肉发力去纠正偏差。四轴没有人在上面平衡,全靠这个回路撑住飞机。

2. 核心技术与原理拆解

2.1 姿态解算:用互补滤波把“现状”算出来

姿态解算最基础的任务,是从MPU6050的原始数据里得到机体的横滚角和俯仰角。这里面有一个核心矛盾:陀螺仪测的是角速度,对角速度积分可以得到角度,但积分过程中误差会不断累积,时间长了以后读数就像跑偏的计步器,越来越离谱;加速度计测的是重力方向,静态或者慢速运动时通过三角函数能直接算出角度,但机体一旦有震动或者加速运动,加速度计的数据里就混入了运动加速度,导致角度剧烈抖动。

互补滤波的思路就是让这两个传感器各干各擅长的事。陀螺仪短时间内非常准确,所以让它主导高频段的姿态变化;加速度计长期不漂移,所以让它把陀螺仪的积分误差慢慢拉回来。简化到代码里,就是一行公式:

angle = 0.98 × (angle + gyro × dt) + 0.02 × accel_angle

这里0.98和0.02就是互补系数。系数越大,姿态越依赖陀螺仪,短期噪声越小,但长期漂移越明显;系数越小,加速度计纠偏越积极,但动态响应会抖动。0.98和0.02是经过大量调试后比较通用的取值,实际做的时候可以根据机架震动情况微调。复杂一些的项目会用四元数加卡尔曼滤波,但新手阶段没必要,互补滤波在稳定悬停这一目标上完全够用。

2.2 串级PID:为什么单环控制压不住四轴

如果你直接拿一个PID去控制角度,实际飞起来会发现飞机非常“神经质”。原因在于四轴是个欠驱动、强耦合的非线性系统,电机推力不仅受控制量影响,还受机体当前角速度影响。等到角度出现偏差再去纠正,反应链路过长,飞机早倒了。

所以工程上普遍采用串级PID结构:外层是角度环,输入期望角度和当前角度,输出期望角速度;内层是角速度环,输入期望角速度和实际角速度,输出电机控制量。为什么外环一般只用PD不加大I?因为角度环的稳态目标就是零度,积分项在这里容易引发超调;内环的I可以用来补偿陀螺仪零偏和电机特性的不对称。通俗理解:外环是“司机”,负责看方向偏移了多少,然后给出一个“该以多大角速度转回来”的指令;内环是“执行者”,时时刻刻通过调整电机转速去追逐这个角速度。两条环串起来,飞行的稳定性和响应速度都好很多。

四轴四个电机之间的油门分配,用的是完全解耦的叠加方式。假设悬停油门基础值是M,横滚控制量为Roll,俯仰控制量为Pitch,偏航控制量为Yaw,X型布局下四个电机的输出大致是:

  • 电机1(右前):M + Roll - Pitch - Yaw
  • 电机2(左后):M - Roll + Pitch - Yaw
  • 电机3(左前):M + Roll + Pitch + Yaw
  • 电机4(右后):M - Roll - Pitch + Yaw

不同布局加减顺序不一样,但原理相同。要注意电机输出不能小于零也不能大于油门最大值,所以M不能取太高也不能太低,否则控制器根本没有余量去改变单个电机的转速,飞机自然就失控了。

2.3 PWM输出与电调控制细节

电调的油门输入信号,标准情况下是频率50Hz、脉宽1ms到2ms的PWM。1ms对应最小油门,2ms对应最大油门。STM32用定时器的PWM输出功能可以非常精准地产生这种信号。配置时重点算两个参数:预分频器PSC和自动重装载值ARR。假设定时器时钟是72MHz,想得到50Hz的PWM,最简单的一组参数是PSC=719、ARR=1999,因为72MHz除以(720×2000)正好等于50Hz。脉宽则通过比较寄存器CCR去设置,1ms脉宽对应CCR=200,2ms对应CCR=400。

电调使用前必须做油门校准。上电前先把信号线拉到最大脉宽(2ms),电调上电后会进入校准模式,听到提示音之后,再把信号线拉到最小脉宽(1ms),电调记住这个范围,后续油门就会线性映射。三个电调建议逐个校准,因为不同电调之间的固件和出厂参数可能有微小差异,统一校准后四个电机才会在相同油门值下有接近一致的转速。

无刷电机有三根相线,如果通电后发现某个电机转向不对,不需要改程序,直接把三根相线中任意两根对调,电机就会反向旋转。

2.4 这个项目涉及的STM32外设覆盖面很广

做四轴飞控最吸引我的一点,是它把STM32的常用外设几乎全用上了。I2C连接MPU6050,定时器输入捕获读取遥控器信号,定时器PWM输出控制电调,ADC采样电池电压,UART连接调试串口或者蓝牙模块,甚至还能挂一个SPI接口的显示屏做小型地面站。这意味着只要你把这个项目完整做下来,STM32开发的核心能力基本就打通了。

很多看起来“不相关”的热门需求,其实都能从四轴项目里迁移过去。比如有人问“stm32 巴法云”怎么搞,本质就是通过ESP8266串口透传加MQTT协议上报数据,你在四轴上先把UART透传调通了,后面接云平台只是换协议栈的事;“stm32鱼缸”“智能台灯”这类物联网项目,也无非是传感器采集、阈值判断、PWM调光、继电器控制这几个套路组合。“print f重定向到USART”更直接,飞控调试时把姿态数据和控制量通过串口打印到电脑上,用串口波形工具观察PID响应,这是调参的必备手段。

3. 实操过程与关键环节实现

3.1 硬件组装清单与接线表

现场实测下来,一份稳定靠谱的硬件清单大致是这样的:

模块推荐型号数量备注
机架F450四轴机架1套含机臂、底板、减震球
飞控板STM32F103C8T6最小系统板1片Blue Pill即可
惯性测量单元MPU6050模块1个I2C接口,带稳压
无刷电机2204/2205,KV1800-23004个两正两反
电调30A无刷电调4个建议带BEC
螺旋桨8045正反桨4支两正两反
遥控器6通道以上1套含接收机
锂电池3S 2200mAh1块XT60接口
降压模块5V BEC / 电调内置BEC1个给飞控板供电
电压检测100kΩ+10kΩ分压电阻1组接ADC引脚

接线原则记住三条:GND必须共地,所有传感器和电调的参考地都接到飞控板的地;MPU6050的SDA和SCL线尽量短,有条件加上4.7kΩ上拉电阻;电调的PWM信号线全部接到同一个定时器的不同通道,方便统一管理。

如果多个电调自带BEC,不要把所有BEC的输出并联起来给飞控板供电,这会导致电压不一致,轻则工作异常,重则烧板。通常情况下只用一个电调的BEC给飞控供电,其余的BEC红线用电工胶带包好不接。

3.2 开发环境搭建:从CubeMX到Keil

开发环境我用的是STM32CubeMX生成初始化代码加Keil MDK编译下载的组合,这套流程对新手最友好。先安装Keil MDK,把STM32F1的器件支持包装上。打开CubeMX,新建工程选择STM32F103C8Tx芯片,按下面的思路配置外设:

  • RCC:选择外部晶振,保证系统时钟72MHz;
  • I2C1:开启,把SDA配到PB7,SCL配到PB6;
  • TIM2:作为遥控信号输入捕获,四个通道对应PA0到PA3;
  • TIM3:作为电机PWM输出,四个通道全部打开;
  • ADC1:独立单通道模式,读取电池电压,引脚选PA1;
  • USART1:波特率115200,调试打印用。

时钟树确认没问题后,Project Manager里选择生成独立的初始化代码,工具链选MDK-ARM。生成后在main.c里写业务逻辑,每次修改引脚配置只需要回CubeMX重新生成,不用手动去改初始化代码,非常省心。用标准库手写初始化也可以,但会白白消耗很多时间在寄存器配置上,新手不推荐。

3.3 飞控核心代码:从传感器读取到电机输出

核心代码可以分成四块,每块都可以独立测试:

第一块是MPU6050的I2C读取。上电先检查WHO_AM_I寄存器,返回值应该是0x68或者0x69,这一步通过才能继续往下走。然后配置加速度计量程为±2g,陀螺仪量程为±250°/s,配置数字低通滤波,把采样率设置在1kHz左右。读取加速度和角速度原始数据时,注意高低字节合并,数据是带符号的16位整数。

第二块是姿态解算。把加速度原始值除以量程灵敏度得到g值,陀螺仪原始值除以灵敏度得到°/s单位的值,然后做归一化,套用前面提到的互补滤波更新横滚角和俯仰角。偏航角如果没有磁力计,可以先放着不管,靠陀螺仪Z轴积分。

第三块是PID控制器。角度环和角速度环串起来,代码上写成两个函数调用就行。积分项一定要加限幅,不然长时间偏航漂移会让积分饱和,电机输出卡死在边界。微分项建议加一个简单的低通滤波,把高频噪声滤掉,否则电机转速会跟着传感器噪声抖个不停。

第四块是输出映射。根据第二节的混控公式算出四个电机的CCR值,然后直接写TIM3的比较寄存器,比如TIM3->CCR1 = motor1Pulse; 这样PWM硬件就自动输出了,不占用CPU时间。

伪代码大致长这样:

// 主循环每秒执行1000次(由定时器中断触发) void controlLoop(void) { readMPU6050(); // 读取IMU原始数据 complementaryFilter(); // 互补滤波更新姿态角 computeYawRate(); // 计算偏航角速度 // 外环:角度环,输出期望角速度 desiredRateRoll = pidAngleRoll(期望横滚角, 实际横滚角); desiredRatePitch = pidAnglePitch(期望俯仰角, 实际俯仰角); // 内环:角速度环,输出控制量 pidOutputRoll = pidRateRoll(desiredRateRoll, 陀螺仪横滚角速度); pidOutputPitch = pidRatePitch(desiredRatePitch, 陀螺仪俯仰角速度); pidOutputYaw = pidRateYaw(期望偏航角速度, 陀螺仪偏航角速度); // 油门混控 motor1 = baseThrottle + pidOutputRoll - pidOutputPitch - pidOutputYaw; motor2 = baseThrottle - pidOutputRoll + pidOutputPitch - pidOutputYaw; motor3 = baseThrottle + pidOutputRoll + pidOutputPitch + pidOutputYaw; motor4 = baseThrottle - pidOutputRoll - pidOutputPitch + pidOutputYaw; clampMotorValues(); // 限幅 updatePwmCompareRegisters(); // 写CCR寄存器 }

另外必须加解锁逻辑:油门最低、方向杆保持某一方向持续3秒,这时候才允许电机响应油门。没有解锁逻辑的飞控,调试时一给电可能就直接全油门起飞,这是大忌。

3.4 首次通电与调参流程

第一次上电,千万别急着装桨。先把螺旋桨全部拆掉,接上电池,做四件事:看IMU数据是否正常,看遥控器各通道脉宽是否合理,看推油门时四路PWM输出是否同步变化,听电调有没有正常触发校准音。一切正常后,再装桨进行低速测试。

调参顺序有讲究,先内环后外环。先只给内环角速度Kp一个较小的值,飞机一般会表现出轻微的高频抖动,此时不用慌,慢慢增大Kp到飞机出现等幅震荡,然后退回70%,这个值就是Kp的初步合适点。接着加Kd,观察抖动有没有被压下去。内环稳定后,再慢慢给外环角度Kp,你会看到飞机从“乱动”变得“能自己回来一点”。整个过程反复微调,一次只改一个参数,改完必须记录,否则很快改乱。

调参时一个非常实用的工具,是把PID的中间变量通过串口实时输出,用串口绘图软件(或者简单的Python脚本)把期望角度、实际角度、控制量画成波形。看波形比肉眼看飞机准得多,能直接看出是比例不足还是微分过多。

3.5 进阶扩展:定高、GPS和无线地面站

基础悬停搞定后,这个项目还有很多延展空间。加BMP280气压计,套一个高度环,就能做定高飞行;加GPS模块,就能实现定点悬停和返航;加nRF24L01或串口数传模块,可以在地面站上实时看姿态和电池电压;挂一个SPI接口的ILI9341屏幕,甚至可以把飞控当成便携式手持地面站,实时绘制姿态球。

热词里那些“stm32 freertos物联网网关”“两轮差速小车”“基于stm32的智能台灯”,说到底都是同一套技术栈的变体。你在四轴上学会了I2C采集、定时器PWM、串口打印、中断驱动和PID控制,做小车无非是把电调换成电机驱动、把混控换成差速模型;做智能台灯就是ADC读环境光、PWM调LED亮度;做网关就是串口接Wi-Fi模块,封装成MQTT消息上报。这也是我一直说四轴是“学一次、用到底”的经典项目的原因。

4. 常见问题与排查技巧实录

4.1 MPU6050读不到数据或者数据全是0

这个坑几乎每个人都踩过。排查顺序先看硬件:I2C地址对不对,默认是0x68,有些模块把AD0引脚拉高后变成0x69;接线有没有虚焊,SDA和SCL是不是接反了;模块供电是不是3.3V。再看软件:读WHO_AM_I寄存器确认通信链路通畅,然后用逻辑分析仪看I2C总线上有没有SCL时钟和SDA应答信号,没有应答基本就是地址或接线问题。

装上螺旋桨之后IMU数据乱跳,一般是电机震动通过机架传到传感器导致的。解决方法是给MPU6050加减震泡棉或者硅胶减震球,别拧死,留一点浮动空间。但减震也不能太软,否则机体自身的姿态剧烈变化会被减震吸收掉一部分,反而让数据失真。

4.2 电调不响应或者电机不转

电调上电后应该有提示音,没有声音先查电调电源和信号线。信号线上传的如果不是标准50Hz的PWM,很多电调会直接不识别,所以调试初期强制把PWM频率配置成50Hz,不要一上来就试oneshot125。油门校准没做或做得不彻底也会导致电机转速阈值不一致,重新再校准一遍,确认电调“听到”了完整的1ms到2ms扫描范围。

还有一类问题出在BEC供电上。多个电调BEC并联供电给飞控板,会造成回流和压差,飞控板5V可能被拉低到4V以下,表现为单片机偶尔重启、传感器数据瞬间丢失。建议只保留一个BEC供电,其余BEC正极悬空。

4.3 起飞就倾斜甚至翻滚

这是安装问题居多。先检查螺旋桨是不是装反了——正桨和反桨的区别不只是外形镜像,旋转方向必须和电机转向匹配。再检查四个电机的转向是否正确,用一张小纸条放在电机上方就能看出气流方向是吹向哪里的。接着核对混控矩阵,不同机架布局(十字型、X型)对应的控制量加减方向是不一样的,如果你是X型布局却用了十字型的混控代码,起飞瞬间就会原地翻。

还有一种情况是起飞时单侧机臂先离地,马上往另一侧翻。这说明那一侧的电机响应偏慢,可能是电调油门校准不一致,最好再做一次统一校准,把四个电调的一致性调到尽量接近。

4.4 遥控器通道映射反了

遥控器和接收机之间的通道方向如果不匹配,飞控会把“油门最低”理解成“油门最高”。上一秒还在解锁,下一秒电机直接全油门,非常危险。所以第一个铁律:试飞之前先把螺旋桨拆掉,在串口监视器里看四个通道的脉宽范围,确认油门最低时脉宽接近1000微秒,最高时接近2000微秒,副翼、升降、方向三个通道在拨动摇杆时方向和数值变化符合预期。有问题的通道就在遥控器上设置反向,不要试图在飞控代码里硬翻译舵量,那是错误的方向。

4.5 电池电压采样不准或者跳变

ADC读取电池电压时,因为电机负载变化,电池电压本身就会波动,再加上ADC多通道切换的毛刺,读数忽高忽低很正常。分压电阻选型时注意阻抗不要太大,100kΩ+10kΩ组合可以接受,但ADC采样时间要相应拉长,必要时开DMA连续采样再做软件滤波。计算电压的公式是:

Vbat = (ADC值 / 4095) × 3.3V × (R1 + R2) / R2

比如R1=100kΩ、R2=10kΩ,3S电池满电12.6V时ADC引脚电压约1.145V,对应ADC读数约1421。低压报警阈值建议设在10.8V左右,这是3S电池比较健康的放电截止点附近,千万别按空载电压去设置,不然负载一上来就误报警。

4.6 其他杂项问题速查

现象可能原因处理办法
串口打印乱码波特率不匹配或时钟配置错误确认串口波特率一致,检查外部晶振频率配置
printf不输出未重定向fputc,未勾选MicroLIB添加fputc重定向代码,MDK中勾选Use MicroLIB
定时器捕获频率不准时钟源选错、捕获边沿设置错误检查定时器时钟树和触发边沿配置
PlatformIO识别不到下载器驱动未装或连接线松动重新安装ST-Link驱动,检查SWD接线
定高时高度波动大气压计受气流和震动影响给气压计加海绵,软件加低通滤波

4.7 热词里的“高热点问题”为什么都和这个项目有关

把热搜词列表扫一遍,会发现很多问题都在这类“STM32+传感器+控制”项目中出现过。比如“stm32 adc切换通道”就是电压采样时解决的;“stm32定时器捕获测频率”就是遥控器输入捕获的应用;“printf to usart stm32”和“stm32串口调试pid”是调参环节的工具链;“stm32 gbk转utf8”和“ili9341读id”是给飞控配屏幕显示中文时遇到的字符编码问题;“stm32 can通信突然连不上”“jtag禁用后无法下载程序”这类问题,则是STM32开发里很常见的配置坑。

所以说,四轴飞行器这个项目就像一棵树的树干,所有外设知识都从树干上长出来。你把树干养结实了,那些日常搜索高频问题自然就有了答案。

5. 试飞安全与个人心得

5.1 安全措施绝不能省

四轴飞行器装好桨之后,就是一个高速旋转的切割工具,必须严肃对待。第一,任何调试阶段都先拆桨再上电,确认逻辑正确后再装桨;第二,试飞场地选空旷、人少、地面平整的区域,远离围观人群,有条件的话用一根绑绳把机架系在地上做“绑系测试”,飞机能离地但不至于飞远;第三,戴护目镜,穿长袖,螺旋桨万一断片飞溅出来,防护装备能挡掉大部分风险;第四,上电前再次确认油门处于最低位置,养成习惯,形成肌肉记忆。

5.2 我自己的踩坑与建议

这次活动的实操环节里,我观察到大家花时间最多的地方反而不是PID调参,而是接线和排查。有人MPU6050地址配错了查了半小时,有人电调校准顺序搞反了导致四电机转速不一致,还有一个人把输入捕获和PWM输出配置在了同一个引脚上,一推油门信号就乱。这些问题的共同根源是:不要跳过验证步骤。每接好一个模块,就先单独测试;每改一段代码,就重新编译下载再观察现象。步步为营,调参的难度会低一个数量级。

我个人在多次做四轴之后的体会是:STM32本身并不难,难的是把传感器、控制逻辑、功率设备放在同一个系统里可靠运行。你会遇到震动干扰、电源噪声、信号冲突这些纯软件调试碰不到的问题,而解决这些问题积累下来的经验,才是比“飞起来”更值钱的东西。这个项目做完以后,你会发现自己看任何带电机、带传感器的硬件项目,脑子里的第一反应不再是“这行不行”,而是“我知道它大概是怎么工作的,我也可以做出来”。这就是做一次四轴DIY最大的收获。

返回列表