3、嵌入式系统基础:嵌入式处理器选型、实时操作系统与传感器数据采集
各位同学,欢迎来到嵌入式系统基础这一章。说实话,很多做农业无人机的新手,一上来就纠结选哪个芯片,或者直接套用别人的方案。我个人的经验是,先把基础打牢,后面才不容易翻车。这一章,我们就聊聊处理器选型、FreeRTOS 的基本概念,以及怎么把传感器数据给“请”进来。
3.1 嵌入式处理器选型:STM32 vs ESP32
选处理器,说白了就是选“脑子”。农业无人机集群里,每个节点既要算得快,又要扛得住田间地头的恶劣环境。我见过不少项目,因为选型时图便宜,结果到现场频繁死机,最后返工成本反而更高。
目前主流的选择,基本就是 STM32 和 ESP32 这两大家族。它们各有各的脾气。
| 特性 | STM32(以F4/H7系列为例) | ESP32 |
|---|---|---|
| 核心架构 | ARM Cortex-M 系列 | Xtensa LX6 双核 |
| 主频 | 通常 72MHz - 480MHz | 最高 240MHz |
| WiFi/蓝牙 | 需外挂模块 | 内置双模蓝牙 + WiFi |
| 实时性 | 极强,硬件定时器精准 | 较强,但受WiFi协议栈影响 |
| 功耗 | 低,深度睡眠可到微安级 | 中等,WiFi开启时功耗较高 |
| 生态与工具 | Keil、STM32CubeIDE 成熟 | Arduino、ESP-IDF 上手快 |
| 典型应用场景 | 飞控核心、传感器融合、电机控制 | 数传链路、图传、地面站通信 |
我的选型建议:
- 如果你要做飞控的核心控制,比如姿态解算、电机PWM输出,我建议优先考虑 STM32F4 或 F7 系列。它的硬件定时器和DSP指令集,做实时控制非常稳。
- 如果你需要跟地面站或者手机通信,或者做集群间的WiFi组网,ESP32 是性价比之王。我去年做的一个集群项目,就用 ESP32 做网关节点,省去了外接WiFi模块的麻烦。
- 当然,也可以两者结合:STM32 负责“干活”,ESP32 负责“说话”。
避坑指南: 我曾经在一个项目中,为了省成本选了低主频的 STM32F1 系列做视觉处理,结果图像数据还没传完,下一帧就来了,导致系统卡死。后来换成了 H7 系列才解决。所以,选型时一定要留出至少 30% 的性能余量。
3.2 实时操作系统(FreeRTOS)简介
为什么农业无人机要用实时操作系统?你想想看,无人机在空中,要同时处理遥控器指令、传感器数据、电机控制、还有跟其他无人机通信。如果还用“超级循环”那种裸机编程,一个任务卡住,整个系统就崩了。
FreeRTOS 就是一个轻量级的实时内核。它帮你把 CPU 的时间切成很多片,分给不同的任务。每个任务看起来就像在“独占”CPU。
核心概念其实就几个:
- 任务(Task): 每个功能模块,比如“读取GPS”、“控制电机”,都可以是一个独立的任务。
- 队列(Queue): 任务之间怎么传数据?用队列。比如GPS任务把位置数据放进队列,飞控任务从队列里取出来用。
- 信号量(Semaphore): 用来同步。比如,只有GPS数据更新了,才允许飞控任务去读取。
下面是一个简单的 FreeRTOS 任务创建示例,我习惯用这个模板来启动所有任务:
#include "FreeRTOS.h"
#include "task.h"
// 任务函数声明
void vTaskGPS(void *pvParameters);
void vTaskControl(void *pvParameters);
int main(void) {
// 硬件初始化
HardwareInit();
// 创建任务
xTaskCreate(
vTaskGPS, // 任务函数
"GPS Task", // 任务名字(调试用)
256, // 栈深度(单位:字)
NULL, // 参数
2, // 优先级(数字越大优先级越高)
NULL // 任务句柄
);
xTaskCreate(vTaskControl, "Control Task", 512, NULL, 3, NULL);
// 启动调度器
vTaskStartScheduler();
// 正常情况下不会运行到这里
while(1);
}
void vTaskGPS(void *pvParameters) {
for(;;) {
// 读取GPS数据
ReadGPSData();
// 延时10ms,让出CPU
vTaskDelay(pdMS_TO_TICKS(10));
}
}
void vTaskControl(void *pvParameters) {
for(;;) {
// 执行控制算法
ExecuteControlLoop();
// 延时5ms
vTaskDelay(pdMS_TO_TICKS(5));
}
}
注意: 任务栈大小一定要算好。我刚开始用 FreeRTOS 时,经常遇到“堆栈溢出”导致系统跑飞。建议每个任务至少分配 256 字(1024字节)的栈空间,复杂任务要更大。调试时可以通过 uxTaskGetStackHighWaterMark() 函数查看剩余栈空间。
3.3 传感器数据采集基础
农业无人机上,传感器就是它的“眼睛”和“耳朵”。常见的传感器包括:
- 惯性测量单元(IMU): 加速度计 + 陀螺仪,用来感知姿态。
- 气压计: 测量高度。
- GPS/RTK: 定位。
- 多光谱相机: 监测作物长势。
传感器数据采集,说白了就是通过 I2C、SPI、UART 这些总线,把传感器里的寄存器数据读出来。嗯,这里要注意,很多传感器上电后需要一段“稳定时间”,不能一上来就读。
我以最常见的 I2C 接口的 IMU 传感器(比如 MPU6050)为例,展示一下初始化流程:
#include "i2c.h"
#define MPU6050_ADDR 0x68 // I2C 设备地址
// 初始化 MPU6050
void MPU6050_Init(void) {
uint8_t data;
// 1. 唤醒传感器(默认是睡眠模式)
data = 0x00;
I2C_WriteRegister(MPU6050_ADDR, 0x6B, &data, 1);
// 2. 设置量程:加速度计 ±2g,陀螺仪 ±250°/s
data = 0x00; // 加速度计配置寄存器
I2C_WriteRegister(MPU6050_ADDR, 0x1C, &data, 1);
data = 0x00; // 陀螺仪配置寄存器
I2C_WriteRegister(MPU6050_ADDR, 0x1B, &data, 1);
// 3. 配置数字低通滤波器(滤除高频噪声)
data = 0x03; // 设置带宽约 44Hz
I2C_WriteRegister(MPU6050_ADDR, 0x1A, &data, 1);
// 等待传感器稳定
vTaskDelay(pdMS_TO_TICKS(100));
}
// 读取加速度计原始数据
void MPU6050_ReadAccel(int16_t *ax, int16_t *ay, int16_t *az) {
uint8_t buf[6];
I2C_ReadRegister(MPU6050_ADDR, 0x3B, buf, 6); // 从0x3B开始读6个字节
*ax = (buf[0] << 8) | buf[1];
*ay = (buf[2] << 8) | buf[3];
*az = (buf[4] << 8) | buf[5];
}
我的经验: 传感器数据采集,最怕的就是“噪声”。我曾经在田里测试,发现IMU数据跳得厉害,查了半天,原来是电机PWM的电磁干扰串到了I2C线上。后来在I2C线上加了磁珠,并且把传感器和电机供电分开,问题就解决了。所以,硬件布局和滤波,跟软件一样重要。
另外,采集到的原始数据,通常需要做“校准”和“单位转换”。比如加速度计,原始值是16位整数,要除以灵敏度系数,才能得到 m/s²。这个转换公式,每个传感器的数据手册里都有,千万别想当然。
好了,这一章的内容就到这里。嵌入式系统基础,是后面所有集群控制算法的基石。下一章,我们会把这些传感器数据用起来,开始做姿态解算。到时候见。