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²。这个转换公式,每个传感器的数据手册里都有,千万别想当然。

好了,这一章的内容就到这里。嵌入式系统基础,是后面所有集群控制算法的基石。下一章,我们会把这些传感器数据用起来,开始做姿态解算。到时候见。