ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从零用STM32制作无人机飞控:硬件搭建与PID调参实战指南

从零用STM32制作无人机飞控:硬件搭建与PID调参实战指南 从零开始用STM32做无人机控制这条路我走过一遍踩过的坑比飞起来的高度还高。如果你正打算入坑或者已经在学STM32但不知道怎么跟飞控结合这篇内容应该能帮你省掉几个月的摸索时间。我默认的路线是用STM32F103C8T6最小系统板 MPU6050六轴传感器 四路PWM输出到电调 航模遥控器接收机做一台能手动飞行的四旋翼学习飞控。这套组合成本低、资料多、踩坑少是最适合学习无人机控制的硬件平台没有之一。1. 整体设计拆解为什么选这样的方案而不是直接抄开源的1.1 学习型飞控的核心需求很多新手上来就想做“全自主飞控”又是定位、又是路径规划、又是视觉避障结果连IMU数据都读不稳最后全部烂尾。学习无人机控制第一目标应该是让飞机能稳、能飞、能救回来。所以我的需求定义很简单手动遥控飞行 姿态自稳。也就是说遥控器给个姿态目标飞控自己调整电机转速让飞机保持那个姿态人只需要控制油门和方向。做到这一步你就已经掌握了无人机控制的核心传感器读取、姿态解算、PID控制、PWM输出。不做定高、不做GPS、不做光流这些是第二步的事。先把基础打牢后续加什么传感器都是顺手的事。1.2 为什么是STM32F103C8T6这个型号STM32家族很大从F0到H7做飞控最经典的是F103和F405。F405在开源飞控里用到烂性能强、硬件浮点但对新手不友好封装复杂、启动流程长、外设配置繁琐。F103C8T6的优势在于72MHz主频够跑姿态解算和PID控制主循环2ms绰绰有余三个定时器可以输出多路PWM正好覆盖四路电机硬件I2C、硬件SPI、多个串口接MPU6050、接收机、调试口都够用资料极度丰富STM32最经典的教程全围绕F103展开价格便宜炸机换芯片不心疼硬件浮点说实话学习阶段用不到。互补滤波和PID运算用浮点模拟也才几十微秒F103完全扛得住。2. 硬件清单与开发环境搭建2.1 一套能真正升空的入门硬件组合我在实际搭建的时候用了下面这套配置性能和成本比较均衡部件型号/规格备注飞控MCUSTM32F103C8T6最小系统板买带晶振的别图便宜姿态传感器MPU6050模块GY-521六轴自带DMP可选项电调ESC好盈20A或类似刷BLHeli四只支持PWM信号电机2204或22121400KV无刷电机四只带螺旋桨机架F450机架新手首选结实耐炸电池3S 2200mAh锂聚合物电池12.6V满电遥控器FlySky FS-i6 IA6接收机便宜稳定新手神器降压模块5V/3A的BEC模块给飞控供电注意共地调试模块CH340串口转USB模块用于串口调试输出这里有几个关键点要特别强调第一电调和电机必须匹配。2204电机配20A电调是标准组合电调电流不够会烧太大浪费重量和空间。第二飞控供电不能直接接电池。锂电池电压高达12.6VSTM32和MPU6050都是3.3V/5V逻辑必须经过降压。我用的是一个5V/3A的BEC模块输出同时给STM32板子和接收机供电。第三所有模块必须共地。这是新手最常见的玄学问题。电调、电机、飞控、接收机的负极要全部连在一起参考地不一致I2C和PWM信号全乱飞机根本没法控制。2.2 开发环境从零装好Keil与CubeMXSTM32开发有两个主流路线标准外设库和HAL库。我推荐用HAL库原因很简单CubeMX可以图形化配置时钟树、引脚、定时器、串口自动生成初始化代码省去大量枯燥的寄存器配置工作把精力留给飞控逻辑本身。环境三件套Keil MDK编译下载IDE网上安装包很多注意装对版本不要把C51和MDK搞混STM32CubeMX图形化配置工具选择芯片型号后直接生成工程FlyMcu或STM32CubeProgrammer串口下载工具。F103C8T6支持串口ISP下载用CH340模块就能烧程序不用额外买ST-Link在CubeMX里的关键配置项照着下面来就行时钟源外部8MHz晶振倍频到72MHz调试接口SWD如果要用ST-Link调试同时把JTAG禁用否则PB3/PB4之类的引脚不能用定时器TIM1输出四路PWM频率50Hz对应电调标准PWM信号USART1115200波特率接但CH340做调试输出I2C1标准模式100kHz接MPU6050引脚分配参考PA0-3接电调信号线PB6/PB7接MPU6050的SCL/SDAPA9/PA10接串口CubeMX生成工程后还差一个关键步骤在魔术棒Options for Target里勾选Use MicroLIB。这个选项能大幅减小printf等标准库的flash占用不然F103C8T6的64KB Flash很容易装不下程序。2.3 基础代码框架点亮LED是第一步在碰任何传感器之前第一件事永远是点亮板载LED。这不是废话而是检验工具链是否打通的关键步骤。工程能编译、能下载、LED能亮说明环境没问题后面所有的调试都建立在这个基础上。HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); // 点亮LED HAL_Delay(500); HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); // 熄灭LED HAL_Delay(500);把这段代码放到主循环里下载进去看到LED闪烁你的开发环境就彻底通了。这时候再往下走每遇到一个问题就能确定问题出在哪个环节而不是怀疑工具链有问题。3. 四个必须掌握的外设GPIO、定时器、串口、I2C3.1 定时器PWM电机油门信号的核心无人机控制里油门信号本质就是PWM脉宽。电调收到的PWM信号宽度在1000us到2000us之间1000us代表油门最小电机停转2000us代表油门最大。我在CubeMX里用TIM1产生四路PWM工程配置的核心参数如下预分频器PSC 7172MHz除以72得到1MHz的计数频率自动重装载ARR 199991MHz计数20000个脉冲得到50Hz的PWM周期脉冲宽度CCR1000到2000对应1ms到2ms的油门信号这里补充一个关键计算过程方便你自己改频率PWM频率 定时器时钟 / ((PSC1) × (ARR1))。我用的是72MHz时钟PSC71ARR19999所以频率 72,000,000 / (72 × 20000) 50Hz。CCR值直接填1000~2000就是对应1~2ms脉宽。在代码里控制电机的油门只需要一行__HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, throttle_value); // 0-1000范围对应1000-2000us这里throttle_value取值范围是1000到2000初始一定要设为1000。新手最常见的炸机原因之一就是上电时CCR值默认是0相当于PWM占空比为零电调识别成油门最大信号飞机瞬间满油门窜出去。3.2 串口调试的眼睛串口是无人机的“眼睛”。没有串口输出你压根不知道IMU数据是什么样、PID输出对不对、遥控器信号有没有收到。我把USART1在CubeMX里配成115200然后重定向printf到串口。#ifdef __GNUC__ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endif PUTCHAR_PROTOTYPE { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 0xFFFF); return ch; }有了printf之后我一定会把下面这组信息周期性打印出来姿态角Roll/Pitch/Yaw陀螺仪原始数据PID三个环的输出值遥控器油门通道值主循环实际周期用来确认调度是否准时调试时在电脑端用串口助手或者VOFA把数据用波形方式显示出来。看波形比看数字重要得多PID调参要是没有波形图基本就是在瞎调。3.3 I2C接口读取MPU6050姿态数据MPU6050是飞控的核心传感器内部集成三轴陀螺仪和三轴加速度计。I2C接口只需要两根线SCL和SDA。这里我用的是CubeMX生成的硬件I2C配合ST官方封装好的MPU6050驱动初始化加读取数据大概就是这样的流程uint8_t MPU6050_Init(void) { uint8_t check; uint8_t data; HAL_I2C_Mem_Read(hi2c1, MPU6050_ADDR, WHO_AM_I_REG, 1, check, 1, 100); if (check ! 0x68) return 1; // 从设备地址确认0x68是MPU6050的固定地址 data 0x00; HAL_I2C_Mem_Write(hi2c1, MPU6050_ADDR, PWR_MGMT_1_REG, 1, data, 1, 100); // 退出睡眠模式 data 0x07; HAL_I2C_Mem_Write(hi2c1, MPU6050_ADDR, SMPLRT_DIV_REG, 1, data, 1, 100); // 采样率分频 data 0x06; HAL_I2C_Mem_Write(hi2c1, MPU6050_ADDR, CONFIG_REG, 1, data, 1, 100); // 低通滤波5Hz data 0x00; HAL_I2C_Mem_Write(hi2c1, MPU6050_ADDR, GYRO_CONFIG_REG, 1, data, 1, 100); // 陀螺仪量程±250°/s data 0x00; HAL_I2C_Mem_Write(hi2c1, MPU6050_ADDR, ACCEL_CONFIG_REG, 1, data, 1, 100); // 加速度计量程±2g return 0; }用标准库或HAL库的I2C读MPU6050有个老生常谈的坑HAL库的I2C在高速连续读取时容易卡死。我之前用HAL_I2C_Mem_Read连续读六个轴数据跑一会儿程序就挂了。解决办法有两种一是加超时参数二是干脆用软件模拟I2C。为了调试更稳定我后来把MPU6050的I2C改成了软件模拟稳定得一批读取时间也才几十微秒完全够用。读取原始数据后别忘了换算单位。陀螺仪量程设为±250°/s对应16位ADC输出-32768到32767所以原始值乘以250/32768就得到每秒多少度。加速度计量程±2g时原始值乘以2/32768得到g值。4. 姿态解算与控制算法从传感器数据到飞行指令4.1 姿态数据怎么变成角度传感器是最底层提供的是角速度和线加速度但飞控控制需要的是横滚角Roll、俯仰角Pitch和偏航角Yaw。从原始数据到角度中间要经过姿态解算。姿态解算的完整理论是四元数和卡尔曼滤波对新手太劝退。我用的方案是互补滤波代码量不大效果在入门飞控上完全够用。核心思想很简单陀螺仪短时间准确但会随时间漂移加速度计长期稳定但短时间噪声大两者互补短时间信陀螺仪长时间信加速度计互补滤波的简化实现如下// 从加速度计计算横滚和俯仰角 float acc_roll atan2f(acc_y, acc_z) * 180.0f / PI; float acc_pitch atan2f(-acc_x, sqrtf(acc_y*acc_y acc_z*acc_z)) * 180.0f / PI; // 互补滤波融合 roll 0.98f * (roll gyro_x * dt) 0.02f * acc_roll; pitch 0.98f * (pitch gyro_y * dt) 0.02f * acc_pitch;系数0.98和0.02是互补滤波的权重意思是98%相信陀螺仪的积分结果2%相信加速度计的静态测量。这两个系数要根据实际手感微调系数太大响应慢太小又容易被振动污染。偏航角Yaw不能用加速度计修正因为加速度计测不到绕重力轴的旋转。所以偏航角只能靠陀螺仪积分时间长了必然漂移这也是为什么入门飞控的Yaw通道手感一般。不用慌这是正常的不影响正常飞行。4.2 PID控制飞控稳定悬停的数学基础姿态算出来了接下来就是控制。飞控里最核心的算法就是PID没有之一。PID的公式是所有控制入门的基础输出 Kp × 误差 Ki × 积分误差 Kd × 误差变化率用生活类比解释一下你用手掌托着一个盘子想把盘子保持水平。误差就是盘子的倾斜角度。比例项Kp像是你看到盘子歪了立刻施加的反向力歪得越多推得越用力。积分项Ki像是盘子长时间偏向一边时你逐渐加大反向力去纠正这个持续偏差。微分项Kd像是你感觉到盘子正在快速往一个方向歪提前施加阻尼防止它越歪越厉害。在飞控里用的是串级PID外环是角度环内环是角速度环。这是入门飞控最容易忽略的点。外环输入期望角度遥控器给的姿态目标和实际角度的误差外环输出期望角速度作为内环的目标内环输入期望角速度和实际角速度陀螺仪直读的误差内环输出电机油门修正量为什么需要内环因为角度变化是角速度的积分直接控制角度环响应太慢飞机会像喝醉酒一样晃。内环角速度环响应快能先把飞机的“转动趋势”稳住外环再慢慢把角度拉准。用一个形象的比喻外环决定你要去哪内环决定你走路稳不稳。PID参数我给出一个能让飞机悬停的起始值具体还要根据机架大小微调通道KpKiKd外环角度Roll/Pitch4.00.010.1内环角速度Roll/Pitch0.150.0010.05Yaw角速度4.00.050.14.3 手动调参的步骤与判断方法调参是无人机控制里最考验耐心的一环。我的调试步骤是固定的分享给你第一步飞机固定在地面上。拿胶带把机架底座粘在桌子上或者找人按住机架确保螺旋桨不碰东西。这时候给油可以观察电机响应但千万别装桨。我调参的时候是拔掉全部螺旋桨的只观察电机转速变化让风险最小化。第二步一点一点加P。从很小的P开始比如内环Kp0.05给飞机一个姿态扰动观察它是否回正。如果飞机震荡剧烈、发出嗡嗡声说明P大了往回退。目标是“快速回正但不要来回晃”。第三步P调稳后加D抑制震荡。D的作用是阻尼。如果加了P之后飞机还在轻微抖动就加一点内环Kd一个直观的判断标准是用手快速转动飞机时电机应该能立刻给出有力的抵抗但又不会疯狂抖。第四步加I消除稳态误差。如果飞机始终偏向一个方向回不到水平就给内环和外环的Ki加一点值。I不要大I大了会振荡甚至震荡发散。调参这条路上没有捷径。我的经验是先在小P大D的保守区间飞飞起来之后根据手感一点点加P。每次只调一个参数改完参数只做单轴测试等姿态响应急速但不过冲再换上螺旋桨试飞。5. 飞控主程序框架把模块串起来5.1 主循环的调度时序飞控是一个典型的实时系统。我的主循环结构是固定的while (1) { // 1. 读取传感器 MPU6050_Read_All(imu_data); // 2. 姿态解算 attitude_update(imu_data, attitude); // 3. 读取遥控输入 rc_read(rc_data); // 4. 控制器计算 pid_control(attitude, rc_data, motor_output); // 5. 输出PWM set_motor_pwm(motor_output); // 6. 串口调试输出可选 debug_output(attitude, rc_data); HAL_Delay(2); // 2ms主循环对应500Hz控制频率 }主循环周期直接决定控制性能。2ms意味着控制频率500Hz对入门飞控来说是合理的。1ms也行但MCU负载会高我用买1ms反而会给调试带来更多干扰。控制频率的稳定性比大小更重要。如果主循环周期忽长忽短PID积分和微分计算就会出问题。这里有一个细节HAL_Delay(2)并不精确因为中断处理、串口打印都会占用时间。入门阶段可以先容忍但如果你想认真学飞控建议用定时器中断做时基把控制逻辑放在1ms或2ms的定时中断里跑主循环只做通信和数据处理。5.2 遥控输入与安全逻辑遥控器是飞控和操作员之间的桥梁。FS-i6接收机输出PWM信号每个通道一根信号线脉宽也是1000到2000us。在CubeMX里用定时器输入捕获模式来读取这个脉宽或者在F103上用外部中断加定时器计数来测脉宽。遥控器输入不只是给飞机指令它还是最重要的安全开关。我写了自己的“解锁”逻辑坚决不给裸桨上电就转的机会// 解锁条件油门通道最低 偏航通道最大持续2秒 static void safety_check(rc_data_t *rc) { static uint8_t unlock_counter 0; static uint8_t armed 0; if (rc-throttle 1050 rc-yaw 1900) { unlock_counter; if (unlock_counter 100) // 持续约100个控制周期约200ms { armed 1; } } else { unlock_counter 0; // 如果油门不在最低位强制上锁 if (rc-throttle 1100) { armed 0; } } // 未解锁时所有电机输出1000us最小油门 if (!armed) { set_motor_pwm(1000, 1000, 1000, 1000); } }这是我做过的最正确的设计之一。刚开始调试时曾经不小心把遥控器油门推高了如果没有解锁逻辑飞机直接就会窜出去。有了解锁逻辑之后即使误开遥控器电机也只会低速待命不会起飞。另一个必做的安全措施是油门渐变。就算已经解锁目标油门也不能直接跳变到遥控器对应值而是要逐步变化。直白一点说油门信号瞬间变化过大电调会承受极大冲击飞机也会猛窜。下面是油门渐变代码uint16_t target_throttle rc_data.throttle; // 遥控器目标油门 uint16_t current_throttle 1000; // 主循环里逐步逼近目标值 if (current_throttle target_throttle) current_throttle 20; // 每周期增加一定步长相当于缓慢加油 else current_throttle target_throttle; set_motor_pwm(current_throttle, current_throttle, current_throttle, current_throttle);6. 实际飞行中遇到的问题与排查技巧6.1 上电就重启的电源问题这个问题我回忆起来都头疼。上电瞬间MCU不断重启串口输出乱码电机转一下又停。原因九成是电源纹波太大。电机和电调工作时瞬间电流非常大特别是启动瞬间电流可以达到几十安培。这个电流在电源线上产生巨大的压降和电磁干扰直接拉垮了MCU的供电电压。排查步骤有几个用万用表或示波器测飞控供电端电压如果降到4.5V以下肯定有问题确认BEC的额定电流是否够用我一开始用了一个5V/1A的小模块带STM32、MPU6050、接收机就勉强加上电机启动干扰直接崩溃换成5V/3A后问题解决在飞控电源引脚旁边加一个大电容100uF到470uF的电解电容外加一个0.1uF陶瓷电容这个组合能吸收大部分低频和高频干扰信号线和电源线分开走线不要贴着电机线平行布6.2 MPU6050读不到数据的排查这个问题基本每个做飞控的人都会遇到。MPU6050读不到数据的时候不要慌按顺序排查先查接线。这是最高频的坑。SCL接SCLSDA接SDA共地了吗MPU6050模块的VCC是5V还是3.3V供电我用的GY-521模块板载稳压5V供电没问题但很多裸模块必须3.3V再查地址。MPU6050的I2C地址由AD0引脚决定高电平是0x69低电平是0x68。我的模块AD0默认接地所以要读0x68。有的模块在背面有焊接盘焊上跳线后地址就变了然后查上拉电阻。I2C总线需要上拉电阻有些模块已经集成有些没有。没集成的话要自己在SCL和SDA各接一个4.7k电阻到3.3V最后测电平。用逻辑分析仪或者示波器看SCL和SDA上有没有时钟和数据波形。如果波形乱大概率是接线接触不良或模块本身坏了6.3 电机只响不转的油门校准问题电调上电时如果发出一连串提示音但推油门电机就是不转最常见的原因是油门行程不一致。电调需要知道你的飞控输出的油门下限和上限范围才能正确响应。不同品牌的电调出厂默认行程可能不一样。解决办法是给电调做一次油门行程校准遥控器油门推到最大飞控上电电调进入校准模式识别到最大油门信号听到提示音后油门拉到最小电调识别到最小油门信号校准完成断电重启如果用的是自己写的飞控可以在代码里加一个简单的校准模式上电时检测到油门通道在高位就把当前PWM值作为上限然后等待油门归零把归零后的PWM值作为下限。这就是我一直强调“先有安全逻辑再飞高”的原因校准模式这个功能也是我实际试飞后被逼出来的。6.4 飞起来就翻机的常见原因飞机刚离地就侧翻炸机不用怀疑这些原因挨个查电机转向不对。四旋翼有两种旋转方向的对角电机必须反向旋转才能抵消反扭矩。如果电机装反了飞控一给修正量飞机反而更歪。解决办法先不装桨给电机逐个测试转速确认1号和4号电机同一转向2号和3号另一转向。螺旋桨装反。螺旋桨上有R正桨和L反桨标记或者看桨叶边缘的倾斜方向。装反了推油门时飞机原地打转根本起不来。飞控安装方向错误。MPU6050的X轴方向应该和机头方向一致。如果装偏了90度飞控以为飞机在横滚实际飞机在俯仰修正逻辑全错一离地必翻。确认方法把飞机机头朝前拿起看串口输出的Roll/Pitch角度是否跟实际姿态一致。PID参数严重不合适。P太大了飞控会对微小的振动过度反应越来越振直到翻机。新手调参一定要按节4.3的流程来先不装桨在地面测试响应确认姿态环稳定了再上桨试飞。螺旋桨动平衡差。桨本身不平衡的话高速转动会产生强烈振动加速度计数据被污染姿态解算出现巨大偏差飞控会做出错误决策。便宜的桨建议直接多买几个挑振动小的用。最后说点实在的。学STM32做无人机控制本质上不是什么高深的技术竞赛而是硬件、软件、控制理论、动手能力的一次综合训练。你可能会发现真正难的不是读懂MPU6050的数据而是让飞控在0.1秒内做出正确反应真正磨人的不是PID公式而是调参时那几个小时的反复试验。如果你正卡在某个环节不要急着怀疑自己不行先把数据打出来把逻辑拆开一个模块一个模块地验证飞控最终会跟你“对话”的。而我上面的经验和踩坑记录就是你在这条路上最好的同行者。
返回列表