简介:基于STC8A8K16S4A12单片机的四轴飞控开源项目,是一份适合航模爱好者与电子DIY初学者的入门级资料包。整套设计聚焦姿态飞行控制,包含完整原理图与C语言源码,可在250mm至750mm轴距多类机架上通过PID调参适配,帮助读者理解陀螺仪/加速度计数据处理、PPM遥控信号解析和电机PWM输出等飞控核心逻辑。资源共29个文件,主要包括C源码与H头文件、工程配置文件、SCH原理图及PDF技术文档,压缩包仅795KB,轻量易下载。目前已有2287人学习,适合对照硬件实物边看边练。通过研读源码可掌握姿态解算、PID控制与PWM驱动等关键实现,原理图则便于自行焊接调试;用户还可按需外接GPS、气压计、光流等传感器,向定点悬停等高级功能扩展。
1. STC51跑四轴飞控,性能不足但路径完整
第一次听说有人用 STC51 做四轴飞控,多数工程师的第一反应是“8 位机带得动?”带不动的说法成立一半:STC89C52RC 只有 512 字节 RAM、主频 11.0592MHz,现代开源飞控那套惯性导航解算确实跑不动;但“跑不动高端的”不等于“做不了入门级 DIY”。这类开源项目压缩包里的原理图与源码,恰好把四轴飞控拆成了传感器读取、姿态融合、PID 控制、PWM 输出四段,每一段都能在 51 上用最朴素的方式验证。适合的人群很明确:会 STC51 点灯、想搞懂俯仰翻滚偏航如何从 MPU6050 变成电机油门的人。目标不是飞得稳,而是完整跑通“读取→解算→输出”的链路,为后续切 STM32 和真正的飞控固件铺路。
2. 原理图怎么读:STC89C52RC 最小系统、电源轨与模拟 I2C 引脚分配
我拿到这类“原理图+源码”的开源包,一般先看三样东西:晶振值、LDO 型号、PWM 输出脚。这三样能直接告诉我作者用的主频、供电策略和电机驱动方式,比先翻源码快得多。标题虽然写“STC51”,但实物原理图常见两种核心:STC89C52RC 和 STC12C5A60S2。前者没有 ADC,电池电压检测得靠外部比较器或者干脆不做;后者自带 10 位 ADC,做低压报警省事。两种型号的最小系统区别不大,传感器与混控代码通用,下面按 STC89C52RC 讲,因为这是网上流传最广、引脚图最好找的型号。
2.1 最小系统就三样:晶振、复位、LDO
STC89C52RC 用 11.0592MHz 晶振是大多数开源工程的选择,不是因为它快,而是因为这个频率能被 9600、115200 这些常见串口波特率整除,STC 单片机靠串口下载程序,晶振选错会让下载和调试一起出问题。两个 30pF 负载电容接晶振两端到地,这是标准接法,直接抄即可。复位电路用 10uF 电解电容加 10kΩ 电阻组成上电复位,按键并联在电容两端,按下时拉低 RST。下载时 STC 需要冷启动,也就是先点下载再给板上电,原理图上那个串口座旁边通常还会画一个电源开关,就是这个原因。
电源轨是原理图里最容易被新手跳过、实机最容易烧的部分。常见思路是 2S 锂电池 7.4V 进来,先经过 AMS1117-5.0 降到 5V 给逻辑电路,再由 ME6211C33 或 HT7333 降到 3.3V 给传感器和无线模块。AMS1117 静态电流大,但胜在便宜好买;HT7333 静态电流只有微安级,电池供电下更合适。三路电源的关系可以整理成一张表,检查原理图时对着看就行:
| 电源轨 | 电压 | 来源 | 去向 |
|---|---|---|---|
| VBAT | 7.4V | 2S 锂电池 | 电调正极、第一级 LDO 输入 |
| 5V | 5.0V | AMS1117-5.0 | 电调信号高电平、逻辑电路 |
| 3V3 | 3.3V | ME6211C33 / HT7333 | MPU6050、nRF24L01、I2C 上拉电阻 |
注意 3V3 这一轨不要和 5V 共用一颗 LDO,电调信号虽然不耗多少电流,但电调上电瞬间的地弹会顺着 5V 轨串进传感器供电,表现出来就是陀螺仪数据跳变。我一般习惯在 3V3 输出端多加一个 100uF 钽电容,成本几分钱,抗电机启动压降效果明显。
2.2 引脚分配:I2C 用 P3.6/P3.7,PWM 独占 P1 口
MPU6050 是 3.3V 器件,I2C 接口,SCL 和 SDA 各接一根信号线到单片机。开源原理图里最常见的接法是占用 P3.6 和 P3.7,原因是这两个引脚在 STC89C52RC 上不带第二功能,不会和串口、外部中断冲突。模块上通常已经焊好 4.7kΩ 上拉电阻,直接接线就能用;如果画的是裸片封装,需要在 SCL、SDA 上各加一个 4.7kΩ 上拉到 3V3。芯片地址由 AD0 引脚决定,接地时是 0x68,读出来的 WHO_AM_I 寄存器应该返回 0x68,这是后面源码调试第一个要验证的点。
四路电机 PWM 输出一般独占 P1 口的低四位。一个重要的选型原因是 P1 口在部分 STC89 系列上内部带上拉,输出电平更稳;另一个原因是 P2 口常被复用为地址总线,P3 口被串口和 I2C 占用,P1 是最干净的 8 位端口。无线模块 nRF24L01 的 SPI 信号线可以挂在 P1 口剩余的高四位上,这样引脚分配表一目了然:
| 功能 | 信号 | MCU 引脚 | 说明 |
|---|---|---|---|
| I2C 时钟 | SCL | P3.6 | 模拟 I2C,软件翻转 |
| I2C 数据 | SDA | P3.7 | 模拟 I2C,双向 |
| 姿态打印 | TXD | P3.1 | 串口 1 输出 |
| 遥控数据 | CE | P1.2 | nRF24L01 使能 |
| 遥控数据 | CSN | P1.3 | nRF24L01 片选 |
| 遥控数据 | SCK | P1.5 | SPI 时钟 |
| 遥控数据 | MOSI | P1.6 | SPI 主机输出 |
| 遥控数据 | MISO | P1.7 | SPI 主机输入 |
| 电机信号 | M1~M4 | P1.0~P1.3 | 定时器中断模拟 PWM |
这里注意 P1.0~P1.3 和 P1.5~P1.7 会有引脚重叠问题,实际工程里通常把电机信号放在 P1.0~P1.3,nRF24L01 的 SPI 放到 P2 口或者 P0 口。原理图分析时先找到这几组信号,再顺着网络标签把对应的单片机引脚画出来,整张图的逻辑就通了。
2.3 无刷电调信号:50Hz、1ms~2ms 脉宽,需要定时器模拟
无刷电机方案里,飞控输出给电调的信号不是占空比 PPM,而是周期 20ms、脉宽 1ms 到 2ms 的方波。这个信号不能用硬件 PWM 直接出,因为你要的是脉宽变化而非占空比变化——虽然物理上一样,但电调固件只认脉宽。STC89C52RC 没有硬件 PWM 单元,常见做法是用定时器 0 做 1us 时基,每 20ms 更新一次四路脉宽值。这样做的最大好处是电调与飞控之间的协议完全兼容,后续换 STM32 也不用改电调。
原理图上看电调接口,一般是三个针:电源、地、信号。电源接 VBAT,地和飞控共地,信号线接 P1 口。为了减小电机开关噪声对 MCU 的干扰,信号线上串一个 100Ω 电阻,靠近电调端再并一个 10nF 电容到地,这是一个成本极低但非常有效的滤波结构。
如果是空心杯有刷电机方案,原理图会多一组 NMOS 驱动电路:AO3400 栅极串 10Ω 电阻接 P1 口,栅极再下拉 10kΩ 保证上电瞬间不误开通,源极接地,漏极接电机负极,电机两端反向并联 1N4148 续流二极管。注意这里不是 H 桥,四轴所有电机永远单向转,一个 NMOS 就够了,不需要 H 桥驱动电路原理图里那套复杂的上下桥臂逻辑。
2.4 低压检测与无线链路
STC89C52RC 没有 ADC,低压检测只能外接比较器方案;如果换 STC12C5A60S2,原理图里会多一条分压电阻链:电池正极经过 100kΩ 和 10kΩ 分压,中点接 ADC 引脚,采样值乘上分压比就是电池电压。这个功能在入门级 DIY 里不是必须的,但有它你能在电池电压掉到 6.6V 以下时拒绝解锁电机,省下好几块电池的钱。无线链路 nRF24L01 需要 3V3 供电,八个引脚里信号占六根,和单片机的连线在 2.2 节的表格里已经有了。SPI 同样用软件模拟,SCK 频率做到 100kHz 左右就够传摇杆数据了,51 的主频限制在这里反而不算问题。
多页原理图导出到 PCB 前,先检查页码和网络标签是否一致。用 OrCAD 打开这类开源工程,如果两张或以上原理图页面的 page number 都设成了 1,软件会直接报 orcap-11010 页码重复;AD 里也会有类似提示。这个报错不影响仿真,但会影响 BOM 输出和多人评审时的定位效率,我一般会统一改成连续页码再跑一遍 DRC。电源符号、GND 符号跨页重复最常见,检查顺序按“电源轨→地→时钟→复位→总线”走,比随机点开一页图有效率得多。
3. 源码主路径:模拟 I2C、MPU6050 读取与互补滤波定点化
源码部分的关键不在代码量,而在于理解了 51 的资源边界之后,知道每一步在为什么服务。STC89C52RC 的 512 字节 RAM,去掉栈和全局变量后大概能用的不到 400 字节,这意味着姿态解算里不能出现大的浮点数组,更不能直接移植 STM32 的 DMP 库。开源项目的源码通常从三个文件开始读:i2c_soft.c 负责 GPIO 模拟时序,mpu6050.c 负责传感器寄存器操作,attitude.c 负责把原始数据变成姿态角。下面按这个顺序拆开讲。
3.1 模拟 I2C:四条基础函数,先读 WHO_AM_I 验证时序
I2C 通信的本质是两根线的时序配合,STC89C52RC 没有硬件 I2C 外设,所以全部靠延时函数模拟。每条指令之间的延时参数要按晶振频率调,11.0592MHz 下目标 SCL 频率做到 100kHz 即可,MPU6050 最快支持 400kHz,但软件模拟稳一点更优先。基础函数就四个:起始、停止、写字节、读字节:
// I2C 起始:SCL 高电平期间,SDA 由高到低 void I2C_Start(void) { SDA = 1; SCL = 1; delay_us(4); SDA = 0; delay_us(4); SCL = 0; } // I2C 停止:SCL 高电平期间,SDA 由低到高 void I2C_Stop(void) { SDA = 0; SCL = 1; delay_us(4); SDA = 1; delay_us(4); } // 写一个字节,MSB 先出,每位数据在 SCL 高电平期间保持稳定 void I2C_WriteByte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { SDA = (dat & 0x80) ? 1 : 0; dat <<= 1; SCL = 1; delay_us(2); SCL = 0; delay_us(2); } SCL = 1; delay_us(2); // 第 9 个时钟:读从机 ACK SCL = 0; }逻辑说明:起始和停止条件都是 SCL 为高时改变 SDA 的电平方向,从设备靠这个来识别通信开始和结束。写字节时先把数据位放到 SDA 上,再拉高 SCL 让从机采样,每 8 bit 后第 9 个时钟由主机释放 SDA,看从机是否拉低作为应答。参数说明:delay_us(4) 对应 100kHz 的半个时钟周期约 5us,留下一点余量;如果晶振换了,改这一个延时函数的数值就能整体适配。验证 I2C 是否通,直接读 MPU6050 的 WHO_AM_I 寄存器,地址 0x75,返回 0x68 即正常,这个测试在写任何解算代码之前先做。
3.2 连续读 14 字节:加速度和陀螺仪一次拿完
MPU6050 的加速度计和陀螺仪原始数据存放在寄存器 0x3B 到 0x48,共 14 字节,连续读比分开读 6 次少一半以上通信时间。先写寄存器地址,再发起重复起始信号,改成读模式逐字节取出。最后一个字节要回 NACK,否则从机会继续输出数据导致总线异常:
// 连续读取 6 轴原始数据,acc 和 gyro 各 3 个 int16 // 寄存器地址 0x3B 起:ACC_X_H, ACC_X_L, ..., GYRO_Z_L void MPU6050_Read_Raw(int16_t *acc, int16_t *gyro) { unsigned char buf[14]; unsigned char i; I2C_Start(); I2C_WriteByte(0xD0); // 0x68 左移 1 位,写方向 I2C_WriteByte(0x3B); // 起始寄存器地址 I2C_Start(); I2C_WriteByte(0xD1); // 读方向 for (i = 0; i < 13; i++) { buf[i] = I2C_ReadByte(1); // 中间字节发 ACK } buf[13] = I2C_ReadByte(0); // 最后字节发 NACK I2C_Stop(); acc[0] = (int16_t)((buf[0] << 8) | buf[1]); // AX acc[1] = (int16_t)((buf[2] << 8) | buf[3]); // AY acc[2] = (int16_t)((buf[4] << 8) | buf[5]); // AZ gyro[0] = (int16_t)((buf[8] << 8) | buf[9]); // GX gyro[1] = (int16_t)((buf[10] << 8) | buf[11]);// GY gyro[2] = (int16_t)((buf[12] << 8) | buf[13]);// GZ }逻辑说明:MPU6050 寄存器是 16 位数据,高字节在前,所以 buf[0] 左移 8 位再或上 buf[1]。ACK 参数 1 表示主机读完后拉低 SDA 表示还要继续读,0 表示不再需要数据。整个读函数在 100kHz 下耗时约 1.2ms,对 5ms 的姿态控制周期来说占比不小,所以源码里一般只在主循环的节拍点调用,不会放进中断。参数说明:I2C_ReadByte(1) 里的 1 和 0 是 ACK 位,没有硬件 I2C 的工程里这个参数由软件在时序里生成——读到第 8 个 bit 后根据参数决定第 9 个时钟 SDA 是拉低还是释放。
3.3 姿态解算用互补滤波:定点化,不碰浮点库
加速度计能测出重力矢量,因此能算出俯仰滚转角,但震动大时噪声明显;陀螺仪短时间精度高,但积分会漂移。互补滤波的思路是:高频段信陀螺仪,低频段信加速度计,一个系数把两者融合成一个角度。数学形式是 angle = 0.95 × (angle + gyro × dt) + 0.05 × acc_angle。0.95 和 0.05 不是拍脑袋定的,它们对应滤波截止频率。更新周期 5ms 时,约等于在 1.6Hz 处做了一次一阶低通/高通拆分。
关键问题来了:C51 的浮点库虽然在 Keil 里开箱即用,但一个 float 乘法会被展开成几十条指令,加上库函数调用开销,5ms 内做 6 组浮点运算会挤占主循环余量。入门开源源码常见的做法是定点化:角度单位固定为 0.01°,系数放大 1024 倍,运算全部用 int32_t,最后右移还原:
// 姿态角单位:0.01°;角速度单位:0.01°/s // 更新周期 dt = 5ms,则陀螺仪积分增量 = gx * dt = gx * 5 / 1000 = gx / 200 #define COEF_ACC 51 // 0.05 * 1024 #define COEF_GYRO 973 // 0.95 * 1024 // 小角度近似解算:acc_roll = ay / az,比例 100 放大成 0.01° 单位 void Attitude_Update(int16_t ax, int16_t ay, int16_t az, int16_t gx, int16_t gy, int16_t gz, int16_t *roll_p, int16_t *pitch_p) { int32_t acc_roll, acc_pitch; int32_t gyro_roll, gyro_pitch; // az 必须足够大才更新,防止地面撞击瞬间除零 if (az < 8000) return; acc_roll = (int32_t)ay * 100 / az; acc_pitch = (int32_t)ax * 100 / az; gyro_roll = *roll_p + gx / 200; gyro_pitch = *pitch_p + gy / 200; *roll_p = (int16_t)((gyro_roll * COEF_GYRO + acc_roll * COEF_ACC) >> 10); *pitch_p = (int16_t)((gyro_pitch * COEF_GYRO + acc_pitch * COEF_ACC) >> 10); }逻辑说明:acc_roll 用 ay/az 的比值近似 tan 角度,乘以 100 变成 0.01° 单位,这是小角度线性化。350° 时会出错,但自稳模式下俯仰滚转通常限制在 ±30° 以内,够用。gx/200 这个系数来自单位换算:gx 是 0.01°/s,乘 dt=0.005s 后变成 0.00005°,也就是 0.005 个 0.01°,正好等价于 gx 除以 200。参数说明:COEF_ACC 和 COEF_GYRO 加起来等于 1024,对应权值还原时用右移 10 位;如果更新周期改成 2ms,积分钟数 gx/200 要改成 gx/500,两个滤波系数也要重新推。
3.4 主循环时间基准:5ms 节拍,中断里只置标志
源码的调度结构决定了姿态解算是否稳定。常见做法是定时器 0 配置为 5ms 溢出中断,中断服务函数里只把一个全局标志位置 1,不做任何实质工作;主循环检测到标志后,依次执行读取传感器、姿态更新、PID 控制、混控输出。这样做的原因很实际:I2C 读取需要持续拉高拉低 SCL,如果在中断里做,一旦被打断就会拖垮时序,而 51 的中断嵌套能力有限,不如干脆不让中断做重活。
void Timer0_ISR(void) interrupt 1 { tick_5ms = 1; // 中断里只置标志 } void main(void) { // 系统初始化:时钟、I2C、MPU6050、定时器 while (1) { if (tick_5ms) { tick_5ms = 0; MPU6050_Read_Raw(acc_raw, gyro_raw); // I2C 读取,约 1.2ms Attitude_Update(acc_raw[0], acc_raw[1], acc_raw[2], gyro_raw[0], gyro_raw[1], gyro_raw[2], &angle_roll, &angle_pitch); PID_and_Mixer(); // 控制与输出 } } }设计含义:5ms 节拍是所有控制算法的时基,PID 里的积分项和微分项都依赖这个固定周期,所以不要在循环里加任何阻塞式延时。MPU6050_Read_Raw 执行期间,PWM 输出依赖定时器中断维持,如果读操作超过 5ms 就会丢节拍。115200 波特率下串口打印一帧姿态数据约需 1ms,正好卡在两次更新之间,这也是选 115200 而不是 9600 的原因——9600 打一帧要 10ms,直接拖垮控制周期。
3.5 DMP 为什么不上:不是不想,是 RAM 不够
看过 STM32 飞控代码的人会问:MPU6050 不是自带 DMP 数字运动处理器吗,直接读四元数不就行了?问题在于 DMP 固件加载需要把约 4KB 的固件表和运行缓冲区写到 MPU6050 内部,配置代码本身也要几百字节 RAM。STC89C52RC 的 512 字节 RAM 连一棵 DMP 初始化函数都装不下,更别提剩下的控制逻辑。所以入门级 STC51 开源项目几乎不用 DMP,全部是裸寄存器加互补滤波。等到你换成 STM32F103C8T6 最小系统板,RAM 从 512 字节涨到 20KB,再上 DMP 就是顺理成章的事,此时上面的 I2C 时序和互补滤波代码照样能跑,只是姿态源换了一个而已。
4. 从姿态到油门:定点 PID、角速度内环与四路混控
姿态角算出来后,下一步是把角度偏差变成四个电机的油门量。这一步的复杂度和飞控手感直接相关,也是原理图之后源码里最值得读的部分。入门工程里控制结构有两种:单环 PD 和串级 PID。单环 PD 直接对角度误差做比例微分控制,参数少、好调,抗风差一些;串级 PID 内环控制角速度、外环控制角度,动态响应更好,但参数增加到六七个,51 的算力下要特别注意执行时间。入门级 DIY 的源码里单级 PD 占多数,这里按单级讲清楚,再给串级的扩展思路。
4.1 定点 PID:放大 100 倍,积分限幅,输出限幅
PID 计算放在 5ms 节拍里,每次跑一遍俯仰、滚转、偏航三路。由于角度单位是 0.01°,误差范围最大也就几百,所以比例、积分、微分系数全部放大 100 倍后存入整型变量,计算过程用 int32_t 做中间量,避免 16 位乘法溢出。输出限幅到 ±500 对应 ±5° 的等效油门修正,防止大角度误差时输出饱和导致电机瞬间顶满。
// 系数放大 100 倍,kp=200 表示真实值 2.00 // 输出限幅 +-500,对应 0.01° 输出域 typedef struct { int16_t kp; int16_t ki; int16_t kd; int16_t error_last; int32_t integral; } PID_T; int16_t PID_Calc(PID_T *pid, int16_t target, int16_t measure) { int16_t err = target - measure; int32_t p, i, d, out; pid->integral += err; if (pid->integral > 3000) pid->integral = 3000; // 积分限幅 if (pid->integral < -3000) pid->integral = -3000; p = (int32_t)err * pid->kp; i = pid->integral * pid->ki; d = (int32_t)(err - pid->error_last) * pid->kd; pid->error_last = err; out = p + i + d; if (out > 500) out = 500; if (out < -500) out = -500; return (int16_t)out; }逻辑说明:积分项累加所有历史误差,因此必须先做积分限幅,否则电机持续推油积分会冲到几万,导致撤油时系统长时间不响应。微分项用“本次误差减上次误差”,而不是对测量值求导,这样设定值突变时不会产生剧烈的微分冲击。参数说明:kp=200 表示真实增益 2.00;积分限幅 3000 乘以 ki 后占输出域的比例决定了积分在稳态修正中的权重,常见做法是让积分最大贡献不超过输出限幅的三分之一。三个 PID 实例分别存放角度误差、积分和上次误差,初始化时清零即可。
4.2 混控矩阵:俯仰、滚转、偏航如何映射到四个电机
姿态控制算出来的是三个轴的控制量:pitch、roll、yaw。这四个电机的油门由两大部分合成:基础油门 thr 加上控制修正量。以“+”字形机架为例,M1 在前、M2 在右、M3 在后、M4 在左,那么俯仰修正作用在前后电机上,滚转修正作用在左右电机上,偏航修正用对角电机反扭矩差实现。Mixer 代码就是四个简单的加减法:
// 输入:thr 基础油门,pitch 升降,roll 横滚,yaw 偏航 // + 字机架:M1 前 / M2 右 / M3 后 / M4 左 // 电机旋转方向:M1 M3 逆时针,M2 M4 顺时针 void Mixer_Output(int16_t thr, int16_t pitch, int16_t roll, int16_t yaw) { int16_t m[4]; int16_t i; m[0] = thr + pitch - yaw; // 前:抬前要升 m[1] = thr - roll + yaw; // 右:右倾要左电机制动 m[2] = thr - pitch - yaw; // 后 m[3] = thr + roll + yaw; // 左 for (i = 0; i < 4; i++) { if (m[i] < 1000) m[i] = 1000; // 电调最小脉宽 1ms if (m[i] > 2000) m[i] = 2000; // 电调最大脉宽 2ms PWM_SetDuty(i, m[i]); // 更新第 i 路 PWM } }逻辑说明:pitch 为正表示要抬头,M1 在前所以要加大、M3 在后要减小,信号相反才有低头抬头的力矩。roll 同理作用在 M2、M4 上。yaw 的作用方式不同:四个电机顺时针与逆时针各占对角,m[0] 和 m[2] 是逆时针组,m[1] 和 m[3] 是顺时针组,yaw 项在这两组之间形成差速,机身就被反扭矩带着旋转。参数说明:1000 和 2000 对应电调信号的 1ms 和 2ms 脉宽,在 50Hz 周期里正好是 5% 和 10% 占空比;thr 中途要确保四个输出经过限幅后仍然保持差值,否则飞机会出现偏航自转。
4.3 电调校准与解锁逻辑
电调不是插上就能用的,第一次上电要校准油门行程。校准流程一般在源码的 README 里写着:遥控器油门推到最高,给电调上电,电调会响两短音,然后油门拉到最低,电调响应确认音,行程就记住了。这个操作等价于告诉电调“1ms 是最小值,2ms 是最大值”。源码里解锁逻辑通常是这样:上电后 P1 口四个 PWM 全部输出 1000us,持续 2 秒;检测到遥控器油门在最低位,同时解锁通道有效,才把控制权交出去。这段保护代码写在 mixer 之前,可以在电池刚接上时防止电机因为 IO 口初始化未完成而猛转。
混控参数表在调试时也很有用:
| 控制量 | M1 前 | M2 右 | M3 后 | M4 左 |
|---|---|---|---|---|
| pitch | +1 | 0 | -1 | 0 |
| roll | 0 | -1 | 0 | +1 |
| yaw | -1 | +1 | -1 | +1 |
| thr | +1 | +1 | +1 | +1 |
检查混控方向时不用起飞,把四轴拿在手里,用手往前倾,观察前方两个电机输出是否增大。如果增大的是后方电机,说明 pitch 符号反了,把混控代码里的符号整体取反即可,不需要动 PID 参数。
5. 实机验证:从串口波形到无桨试车
5.1 串口把姿态打出来,再用 Python 画成曲线
PID 调参靠肉眼读串口数字效率太低,我一般的做法是飞控通过 P3.1 以 115200 波特率输出一帧文本,比如p=123 r=-45 m[0]=1150 m[1]=1150,电脑端用串口工具存成日志文件,再用脚本画曲线。做法很简单:Python 用 pyserial 读串口,按行解析出角度和油门值,matplotlib 画两条曲线,俯仰和滚转各一张。这类完全开源的免费 Python 串口绘图源码在网上非常多,稍改端口号和解析规则就能直接跑,比自己去装商业地面站软件省事得多。
5.2 上电前逐项检查
检查项按顺序走一遍,能解决绝大多数试车问题:
| 检查项 | 方法 | 正常表现 |
|---|---|---|
| 传感器通信 | 读 WHO_AM_I | 固定返回 0x68 |
| 加速度计静态值 | 静止放置读 AZ | ≈ 16384(±2g 量程) |
| 陀螺仪漂移 | 静止 1 分钟看积分 | 漂移小于 ±3°/min |
| 电调校准 | 最高油门上电 | 两声短音提示 |
| 电机方向 | 逐个油门测试 | 方向与混控矩阵一致 |
加速度计 AZ 值偏离 16384 超过 500,优先查 3V3 电压是否被电机启动拉低;陀螺仪漂移大,查模块是否贴在主控附近被 PWM 信号干扰,常见处理是把 MPU6050 的地与功率地分开走线。电调和电机方向这一项,在无桨状态下用手捏住电机座,逐个通道小油门测试,不要四路一起推。
5.3 PID 调参顺序:先内环后外环
无桨状态下,把机身拿在手里快速绕俯仰轴转动,观察串口曲线里角度输出和设定值的偏差。第一步只加 P,从 1.0 开始,直到手转机身时能感到明显的回中阻力;有持续低频振荡就把 P 减半。第二步加 D,用来消除高频振颤,表现是电机发出尖锐的“嗡”声,D 加过头会把这声音放大成啸叫。参数记录每次改动的效果,不要一次改两个参数。实机手感仍然抖,把油门基础值降低,而不是继续调 P——入门级 51 飞控的油门响应本来就有延迟,靠 PID 硬吃抖动很容易进入振荡区。
5.4 安全习惯与升级方向
第一次实机试飞场地选软草地,机身绑一根两米长的线缆,遥控器持在手里保持最低油门。解锁后先小幅推油,让飞机在地面上蹦跳但不离地,确认四轴没有往一个方向猛偏的趋势,再尝试小幅离地。后续想升级,优先换成 STM32F103C8T6 最小系统板,原理图上电源轨、电调 PWM 输出、传感器接线完全可以沿用,只需要重画 MCU 最小系统部分,控制代码也可以先跑原有的定点 PID,再逐步换到浮点姿态解算。
本文还有配套的精品资源,点击获取