简介:这份资源是面向计算机、自动化、电子信息及物联网等专业学生与嵌入式初学者的STM32微型无人机课程设计完整工程包,可直接用于课程设计、毕业设计、作业提交或项目初期立项演示。压缩包共117个文件,约707KB,以C语言源码与头文件为主体,包含49个.c与53个.h文件,另附汇编启动文件、工程配置、调试配置及说明文档,覆盖任务调度、定时器、队列、Flash、RCC、ADC、I2C、CAN等底层驱动模块,工程结构完整,便于按模块阅读与二次开发。资源已有78人学习下载,代码经过测试,功能完善且可正常运行,读者可借鉴其任务划分与驱动组织方式,快速理解微型无人机飞控的软件框架,并在此基础上修改扩展实现其他功能,适合作为嵌入式入门进阶与综合实践的参考范例。
1. 从一堆课程设计里翻出这个 STM32 微型无人机:它到底能不能飞
每年到了课程设计季,STM32 相关的题目总是扎堆出现,但真正能把「单片机」和「无人机」这两个词捏在一起、还给出完整可编译源码的包并不多。我拿到这个课程设计-基于STM32单片机的微型无人机设计与实现.zip的第一反应是:先别管它能不能飞,先看它是不是一个能跑通闭环的控制系统。拆开之后确认,这是一套典型的四轴微型无人机课程设计工程,核心是一颗 STM32 主控,用 C 语言写的飞控逻辑,配套电机驱动、姿态解算和遥控接收这几块。它解决的不是「造一架能航拍的大疆」,而是让你在一个学期内把「传感器读取 → 姿态融合 → PID 输出 → 电机调速」这条链路亲手跑一遍。适合正在做单片机课程设计、想找一个比「点灯 + 串口」更有含金量的题目的同学,也适合已经工作、想回头补一补飞控底层逻辑的嵌入式从业者。下面我按「这是什么 → 怎么用 → 坑在哪」的顺序,把这份资源拆开讲透。
2. 飞控最小系统的硬件选型与工程结构:先看懂再动手
2.1 为什么是 STM32 而不是 51 或树莓派
课程设计里选主控,本质上是在「性能够用」和「上手成本」之间找平衡。51 单片机做无人机飞控,最大的问题是主频和硬件资源撑不住姿态解算的实时性——MPU6050 的原始数据要滤波、要积分,还要同时输出四路 PWM,51 跑起来会非常吃力。树莓派这类 Linux 板子倒是性能过剩,但实时性反而没保证,操作系统调度一抖动,电机响应就跟着抖,而且对课程设计来说,跑 Linux 的复杂度容易把重点从「控制」带偏到「配环境」。
STM32 卡在中间正合适。常见做法是选 F103 系列,72MHz 主频,自带多路定时器输出 PWM,I2C 读传感器,串口接遥控接收机,资源刚好够用又不浪费。这份工程用的就是 C 语言标准外设库或 HAL 库的写法,代码结构清晰,没有堆太多抽象层,对课程设计答辩来说,能讲清楚每一行在干什么比用多高级的框架更重要。
2.2 工程目录里各文件在干什么
拿到压缩包解压后,不要急着打开 main.c 从头读到尾,那样很容易迷路。我一般先看目录结构,把文件按职责分三类:硬件驱动层、算法层、应用层。下面是一个典型的工程结构,你对照自己解压出来的文件看:
Drone_STM32/ ├── Core/ │ ├── Src/ │ │ ├── main.c // 主循环:初始化 + 任务调度 │ │ ├── stm32f1xx_it.c // 中断服务:定时器、串口接收 │ │ └── system_stm32f1xx.c // 时钟配置 │ └── Inc/ ├── Drivers/ │ ├── MPU6050/ // 姿态传感器驱动 │ ├── Motor/ // 四路 PWM 电机输出 │ └── Receiver/ // 遥控信号解析 ├── Algorithm/ │ ├── pid.c // PID 控制器 │ └── attitude.c // 姿态解算(互补滤波或卡尔曼) └── App/ └── flight_control.c // 飞行控制主逻辑这个分法的好处是,调试的时候能快速定位问题出在哪一层。电机不转,先看 Motor 层;姿态角乱跳,先看 MPU6050 和 attitude;遥控没反应,先看 Receiver 的中断有没有进。课程设计答辩时,老师问「你这个姿态解算怎么实现的」,你能直接翻到 attitude.c 讲,而不是在一坨 main.c 里找。
2.3 关键外设的初始化顺序
STM32 的初始化顺序有讲究,顺序错了会出现「传感器读不到」「电机上电就转」这类玄学问题。常见做法是按「时钟 → 延时 → 串口 → I2C → 定时器 PWM → 传感器 → 遥控」的顺序来。下面这段是初始化骨架,你可以对照工程里的 main.c 看:
int main(void) { HAL_Init(); // 先初始化 HAL 库 SystemClock_Config(); // 配置 72MHz 主频 MX_GPIO_Init(); // GPIO 先配好,后面外设才能用 MX_USART1_UART_Init(); // 串口,用于调试打印和遥控 MX_I2C1_Init(); // I2C,接 MPU6050 MX_TIM1_Init(); // 定时器 1,输出四路 PWM MPU6050_Init(); // 传感器初始化,要放在 I2C 之后 Receiver_Init(); // 遥控接收初始化 PID_Init(); // PID 参数初始化 while (1) { FlightControl_Loop(); // 主控制循环 } }逻辑说明:HAL_Init()必须在最前面,它配置了 SysTick 和中断优先级分组,后面所有延时和中断都依赖它。SystemClock_Config()决定了定时器 PWM 的频率,如果时钟配错,电机转速会整体偏快或偏慢。MPU6050_Init()放在MX_I2C1_Init()之后是硬性要求,I2C 没初始化就去读传感器,返回值永远是错误。参数方面,PWM 频率一般设在 400Hz 左右,太低电机会有啸叫,太高部分电调识别不了;I2C 速率用 400kHz 快速模式,标准模式 100kHz 读姿态数据会拖慢控制周期。
提示:如果你用的是不同型号的 STM32,比如 F4 系列,时钟配置和定时器寄存器会有差异,但初始化顺序的逻辑不变。
3. 姿态解算与 PID 调参:飞控能不能稳就靠这两块
3.1 MPU6050 读出来的原始数据怎么变成姿态角
MPU6050 给你的是三轴加速度和三轴角速度的原始值,加速度计测的是「重力方向」,陀螺仪测的是「旋转快慢」。单独用加速度计算角度,静态准但动态抖得厉害;单独用陀螺仪积分,短时间平滑但会漂移。所以常见做法是互补滤波,把两者的优点合起来。
互补滤波的核心公式就一行:角度 = α × (角度 + 陀螺仪角速度 × dt) + (1-α) × 加速度计角度。α 一般取 0.95 到 0.98 之间,越大越信任陀螺仪,动态响应好但漂移抑制弱;越小越信任加速度计,静态稳但动态滞后。课程设计里用互补滤波足够了,卡尔曼滤波虽然更优雅,但调参和矩阵运算对新手不友好,答辩时也容易被问住。
// 互补滤波姿态解算,dt 为控制周期,单位秒 float alpha = 0.96f; float accel_angle_x = atan2f(accel_y, accel_z) * 57.2958f; // 弧度转角度 float accel_angle_y = atan2f(-accel_x, accel_z) * 57.2958f; // 陀螺仪积分得到角度增量 angle_x = alpha * (angle_x + gyro_x * dt) + (1 - alpha) * accel_angle_x; angle_y = alpha * (angle_y + gyro_y * dt) + (1 - alpha) * accel_angle_y;逻辑说明:atan2f算的是加速度矢量在对应平面上的倾角,57.2958是 180/π,把弧度转成角度方便观察。gyro_x * dt是角速度对时间的积分,得到这段时间转过的角度。参数alpha是调参重点,如果你发现无人机静止时角度慢慢漂,就把 alpha 调小;如果飞行时角度响应迟钝,就调大。dt必须和你的控制周期一致,控制周期 5ms 就填 0.005,填错角度会整体缩放。
3.2 串级 PID 还是单级 PID:课程设计怎么选
PID 是飞控的灵魂,但很多课程设计只写一个单级 PID,结果飞机要么晃要么翻。四轴无人机常见做法是串级 PID:外环角度环,内环角速度环。外环输入是「目标角度 - 当前角度」,输出作为内环的目标角速度;内环输入是「目标角速度 - 陀螺仪角速度」,输出直接给电机。这样做的原因是角度环响应慢、角速度环响应快,串起来既有稳定性又有抗扰动能力。
单级 PID 也能飞,但参数非常难调,稍微有点风就振荡。课程设计如果时间紧,可以先跑单级把基本逻辑打通,再升级到串级。下面是一个串级 PID 的结构示意:
// 外环:角度 PID float angle_error = target_angle - current_angle; float target_rate = PID_Angle_Calc(&pid_angle, angle_error); // 内环:角速度 PID float rate_error = target_rate - gyro_rate; float output = PID_Rate_Calc(&pid_rate, rate_error); // 输出限幅后分配给四个电机 Motor_SetPWM(output);逻辑说明:PID_Angle_Calc和PID_Rate_Calc内部就是标准的比例、积分、微分三项计算,区别在于参数不同。角度环的 P 一般给 4 到 8,I 给 0 到 0.5,D 给 0;角速度环的 P 给 0.5 到 2,I 给 0.01 到 0.1,D 给 0 到 0.05。这些数值不是绝对的,和你的电机、桨叶、机架重量都有关,必须实机调。
注意:调 PID 一定要在解锁电机之前把桨叶拆掉,用调试软件看波形,确认输出方向正确再装桨。我见过太多上电就翻车的案例,都是方向反了。
3.3 控制周期的定时器配置
控制周期决定了整个飞控的节奏,一般设在 2ms 到 10ms 之间。周期太长,姿态更新慢,飞机反应迟钝;周期太短,CPU 跑不过来,反而丢帧。常见做法是用一个定时器中断,比如 TIM2,每 5ms 触发一次,在中断里做姿态读取和 PID 计算。
// TIM2 中断服务函数,5ms 触发一次 void TIM2_IRQHandler(void) { if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) != RESET) { __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE); MPU6050_ReadData(&accel, &gyro); // 读传感器 Attitude_Update(accel, gyro, 0.005f); // 姿态解算 FlightControl_Update(); // PID 和电机输出 } }逻辑说明:中断里只做最紧急的事,串口打印这种耗时操作放到主循环。0.005f就是 5ms 的 dt,必须和定时器周期严格对应。如果你发现飞机有规律地抖动,先检查这个 dt 有没有写错,这是最常见的翻车点之一。
4. 从源码到实机:编译、烧录、遥控对频的完整链路
4.1 开发环境搭建与编译选项
这份工程大概率是 Keil MDK 或者 STM32CubeIDE 的工程。如果你用 Keil,先确认装了对应芯片的器件包,F103 就装 STM32F1xx_DFP。打开工程后,编译前检查三个地方:目标芯片型号选对没有、外部晶振频率填对没有、下载器配置选对没有。这三个错一个,编译能过但烧进去不跑。
# 如果用命令行 arm-none-eabi 工具链编译 make clean make all # 生成的 .hex 或 .bin 在 build 目录下逻辑说明:make clean清掉旧的目标文件,避免缓存导致的玄学错误。make all按 Makefile 里的规则编译链接。如果你用的是 IDE,直接点 Build 就行,但建议第一次编译前先 clean 一次。参数方面,优化等级建议先用 -O0 调试,确认逻辑没问题再换 -O2 减小体积。
4.2 烧录与串口调试
烧录方式取决于你手上的下载器,常见的是 ST-Link 或串口 ISP。ST-Link 直接 SWD 四根线接上就行,串口 ISP 需要把 BOOT0 拉高、复位、下载、再拉低。烧录成功后,先别急着装桨,用串口助手看调试输出。
// 在姿态解算后加一行打印,确认数据正常 printf("angle_x=%.2f, angle_y=%.2f, gyro_x=%.2f\r\n", angle_x, angle_y, gyro_x);逻辑说明:printf重定向到串口后,能在电脑上看到实时姿态角。静止时角度应该在 0 附近小幅波动,用手转动板子,角度应该跟着变。如果角度一直是 0 或者乱跳,先查 I2C 有没有读到数据,再查互补滤波的 dt 和 alpha。串口波特率一般用 115200,和你的串口助手设置一致。
4.3 遥控接收与电机解锁逻辑
遥控接收机输出的是 PWM 信号,一路对应一个通道。常见做法是用定时器的输入捕获功能测脉宽,1000us 到 2000us 对应摇杆的最低到最高。解锁逻辑一般是「油门最低 + 方向舵最右」保持几秒,防止上电误触。
// 读取遥控通道脉宽,判断是否满足解锁条件 if (throttle < 1050 && yaw > 1900) { unlock_timer++; if (unlock_timer > 100) { // 持续 1 秒左右 armed = 1; } } else { unlock_timer = 0; armed = 0; }逻辑说明:throttle < 1050判断油门是否在最低位,yaw > 1900判断方向舵是否打到最右。unlock_timer累加到 100 才解锁,是为了防止瞬间误触。参数 1050 和 1900 要根据你的遥控器实际脉宽微调,有些遥控器最低是 1100,那就得改。解锁后电机才会响应油门,这个逻辑一定要在代码里写死,不能省。
5. 避坑与排查:那些让课程设计卡到答辩前夜的坑
5.1 电机上电就疯转,油门完全不受控
现象:一上电,四个电机直接全速转,遥控器怎么动都没反应。原因通常是 PWM 输出极性反了,或者电机输出没有做限幅。有些电调识别的是 1000us 到 2000us 的脉宽,如果你初始化时给的默认值就是 2000us,电调会认为油门最大。解决方法是初始化时先把四路 PWM 全部设成 1000us,等解锁后再根据 PID 输出调整,并且输出前一定要做上下限幅。
5.2 MPU6050 读出来全是 0 或者固定值
现象:串口打印加速度和角速度,要么全是 0,要么是一个不变的值。原因一般是 I2C 地址错了,MPU6050 的地址是 0x68 或 0x69,取决于 AD0 引脚电平。另一个可能是 I2C 时序不对,上拉电阻没接。解决方法是先用 I2C 扫描程序确认设备地址,再检查MPU6050_Init()里的寄存器配置有没有写错。如果用的是模拟 I2C,延时函数的时间要够,太快传感器反应不过来。
5.3 姿态角静止时慢慢漂移
现象:飞机放在桌上不动,串口打印的角度从 0 慢慢漂到十几度。原因是陀螺仪有零偏,积分之后误差累积。解决方法是上电时先做陀螺仪零偏校准,采集几百个样本取平均,然后在积分时减掉这个偏置。互补滤波的 alpha 也可以适当调小,增加加速度计的权重来抑制漂移。
5.4 遥控信号时有时无,飞机偶尔失控
现象:遥控器摇杆动作,飞机有时候响应有时候不响应。原因可能是输入捕获的中断优先级太低,被其他中断打断导致丢帧。解决方法是把遥控接收的中断优先级设到最高,或者改用 DMA 接收。另外检查接收机的供电,电压不稳也会导致信号断续。
5.5 编译通过但烧录后不运行
现象:Keil 里编译零错误零警告,烧录也成功,但板子就是没反应。原因通常是启动文件选错了,或者中断向量表偏移没设对。解决方法是确认工程里用的 startup 文件和你芯片的 Flash 大小匹配,比如 F103C8 是 64K,就要用startup_stm32f103xb.s。另外检查system_stm32f1xx.c里的VECT_TAB_OFFSET是不是 0。
6. 进阶玩法:把课程设计改成能拿得出手的作品
课程设计做完能飞只是及格线,如果你想让它成为简历上的亮点,有几个方向可以继续挖。第一个是加超声波定高,用 HC-SR04 测距,把高度作为一个 PID 环加进去,实现「悬停在一米」这种演示效果,答辩时非常加分。第二个是加蓝牙或无线模块,用手机 APP 发指令,把「遥控器控制」升级成「手机控制」,虽然底层逻辑一样,但展示效果好很多。第三个是加 OLED 或 LCD 显示实时姿态角,用 I2C 屏,把串口调试的信息搬到屏幕上,脱离电脑也能看状态。
我重点说一下超声波定高的实现思路。HC-SR04 的 Trig 引脚给 10us 高电平触发,Echo 引脚返回高电平,高电平持续时间就是声波往返时间。高度 = 时间 × 声速 / 2。把这个高度和你的目标高度做 PID,输出叠加到油门通道上。
// 超声波测距,返回厘米 float Ultrasonic_GetDistance(void) { HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_SET); delay_us(10); HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_RESET); while (HAL_GPIO_ReadPin(ECHO_PORT, ECHO_PIN) == GPIO_PIN_RESET); uint32_t start = TIM_GetCounter(TIM3); while (HAL_GPIO_ReadPin(ECHO_PORT, ECHO_PIN) == GPIO_PIN_SET); uint32_t end = TIM_GetCounter(TIM3); float duration = (end - start) / 1000000.0f; // 假设定时器 1MHz return duration * 340.0f * 100.0f / 2.0f; // 声速 340m/s,转厘米 }逻辑说明:delay_us(10)是触发脉冲,必须够 10us 以上。TIM_GetCounter读的是定时器计数,前提是定时器配置成 1MHz 计数,这样每个计数就是 1us。340.0f * 100.0f / 2.0f是把米转厘米再除以 2,因为声波是往返。参数方面,超声波测距范围一般 2cm 到 400cm,超出范围会返回 0 或者固定值,代码里要做有效性判断,无效值直接丢弃,不要进 PID。
验证方法很简单:把飞机固定在一根杆子上,手动改变高度,看串口打印的距离和实际卷尺量的差多少。如果误差在 1cm 以内,就可以进 PID 调参了。定高 PID 的 P 给 0.5 到 1.5,I 给 0.01 到 0.05,D 给 0,先调 P 让高度能稳定,再加 I 消除静差。
从那以后我每次拿到一个课程设计工程,都强制先跑一遍「最小闭环」——只让一个电机转、只读一个传感器、只打印一个变量,确认每一环都通了再往上层搭。这份 STM32 微型无人机的资源,价值不在于它有多复杂,而在于它把飞控的完整链路摊开给你看,让你有机会亲手把每个环节跑通。希望帮到你。
本文还有配套的精品资源,点击获取