3. 传感器驱动开发基础

传感器驱动开发,说白了就是让Zephyr内核和你的传感器芯片能「对上话」。我刚开始接触Zephyr的Sensor子系统时,第一反应是——这API怎么这么多?但用熟了就会发现,核心就那么几个东西:驱动接口、触发模式、通道属性、数据回调。今天咱们就把这些掰开揉碎了讲清楚。

3.1 Sensor驱动接口:从probe到fetch

每个传感器驱动都必须实现一组标准接口。Zephyr用struct sensor_driver_api来定义这组契约。我习惯把这个结构体叫做「驱动身份证」——内核不关心你底层是I2C还是SPI,它只认这组函数指针。

// 核心驱动接口结构体
struct sensor_driver_api {
    int (*sample_fetch)(const struct device *dev,
                        enum sensor_channel chan);
    int (*channel_get)(const struct device *dev,
                       enum sensor_channel chan,
                       struct sensor_value *val);
    int (*attr_set)(const struct device *dev,
                    enum sensor_channel chan,
                    enum sensor_attribute attr,
                    const struct sensor_value *val);
    int (*attr_get)(const struct device *dev,
                    enum sensor_channel chan,
                    enum sensor_attribute attr,
                    struct sensor_value *val);
    int (*trigger_set)(const struct device *dev,
                       const struct sensor_trigger *trig,
                       sensor_trigger_handler_t handler);
};

这里我重点说三个:

  • sample_fetch:发起一次采样。你可以传SENSOR_CHAN_ALL一次拿所有通道,也可以指定单个通道。我在项目里踩过坑——有些传感器fetch全部通道时耗时太长,后来改成按需fetch,功耗降了30%。
  • channel_get:从驱动内部缓冲区拿数据。注意,它不触发硬件操作,只是把上次fetch的结果转出来。
  • trigger_set:配置中断触发。这个后面单独讲。

关键点:sample_fetch和channel_get是分离的。fetch负责「去硬件拿」,channel_get负责「把数据给应用」。这种分离设计让驱动可以批量采样、按需读取,非常灵活。

3.2 触发模式 vs 轮询模式:什么时候用哪个?

这是传感器开发里最常被问的问题。我直接给结论:

对比维度 轮询模式 触发模式
CPU占用 高(一直问「数据好了没?」) 低(数据好了才通知)
响应延迟 取决于轮询间隔 微秒级(硬件中断)
代码复杂度 简单,while循环搞定 需要配置中断、回调、GPIO
适用场景 温度/湿度等慢变信号 加速度计/陀螺仪等运动检测

轮询模式的代码长这样:

// 轮询模式示例:每100ms读一次温度
while (1) {
    sensor_sample_fetch(dev, SENSOR_CHAN_ALL);
    sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &val);
    printk("温度: %d.%d°C\n", val.val1, val.val2);
    k_sleep(K_MSEC(100));
}

触发模式则需要先注册中断:

// 触发模式示例:加速度计检测到运动时回调
static void accel_trigger_handler(const struct device *dev,
                                  const struct sensor_trigger *trig)
{
    struct sensor_value val;
    sensor_sample_fetch(dev, SENSOR_CHAN_ALL);
    sensor_channel_get(dev, SENSOR_CHAN_ACCEL_XYZ, &val);
    // 处理数据...
}

// 配置触发
struct sensor_trigger trig = {
    .type = SENSOR_TRIG_DATA_READY,
    .chan = SENSOR_CHAN_ACCEL_XYZ,
};
sensor_trigger_set(dev, &trig, accel_trigger_handler);

我的经验:电池供电的设备,能用触发就别用轮询。我曾经在一个穿戴项目里用轮询读加速度计,电池两天就耗光了。换成触发模式后,续航直接拉到两周。

3.3 Sensor通道与属性:数据怎么组织?

Zephyr用enum sensor_channel来定义数据通道。每个传感器可以输出多个通道的数据。比如一个6轴IMU,通常有:

  • SENSOR_CHAN_ACCEL_XYZ:三轴加速度
  • SENSOR_CHAN_GYRO_XYZ:三轴陀螺仪
  • SENSOR_CHAN_DIE_TEMP:芯片内部温度

属性(attribute)则是用来配置传感器工作模式的。比如设置采样率、量程、滤波器等:

// 设置加速度计量程为±4g
struct sensor_value range = { .val1 = 4, .val2 = 0 };
sensor_attr_set(dev, SENSOR_CHAN_ACCEL_XYZ,
                SENSOR_ATTR_FULL_SCALE, &range);

// 设置输出数据速率为100Hz
struct sensor_value odr = { .val1 = 100, .val2 = 0 };
sensor_attr_set(dev, SENSOR_CHAN_ACCEL_XYZ,
                SENSOR_ATTR_SAMPLING_FREQUENCY, &odr);

注意:不是所有传感器都支持所有属性。设置前最好查datasheet,或者先attr_get看看当前值。我遇到过有人给不支持量程设置的传感器设量程,驱动直接返回-ENOTSUP。

3.4 数据回调机制:中断来了怎么办?

触发模式的核心就是回调函数。当传感器硬件产生中断(比如数据准备好、阈值超限),驱动会调用你注册的处理函数。

回调函数的执行上下文很关键:

  • 它运行在中断上下文(ISR)中
  • 不能调用阻塞函数(如k_sleep、printk)
  • 不能做耗时操作(如复杂浮点运算)
  • 最佳实践:回调里只做数据搬运,处理逻辑交给线程

我常用的模式是「回调+工作队列」:

// 定义工作项
static struct k_work sensor_work;

// 工作队列处理函数(线程上下文)
static void sensor_work_handler(struct k_work *work)
{
    // 这里可以做耗时操作
    sensor_sample_fetch(dev, SENSOR_CHAN_ALL);
    // 处理数据、更新状态机...
}

// 中断回调(ISR上下文)
static void sensor_irq_handler(const struct device *dev,
                               const struct sensor_trigger *trig)
{
    // 只提交工作项,不做其他事
    k_work_submit(&sensor_work);
}

核心原则:ISR里只做「最小必要操作」。把数据解析、滤波、融合等逻辑放到线程里。这能避免中断嵌套导致系统不稳定。

3.5 知识结构总览

下面这张图把Sensor Driver的核心API关系梳理清楚了:

Sensor Driver核心API结构图 Sensor Driver核心API关系图 应用层 (Application) sensor_driver_api sample_fetch | channel_get | attr_set | trigger_set 轮询模式 while循环 + k_sleep 触发模式 中断 + 回调函数 属性配置 attr_set / attr_get 硬件层 (I2C/SPI/GPIO) 传感器芯片 | 中断引脚 | 数据寄存器 核心流程 应用调用sample_fetch → 驱动通过总线读取硬件 → 应用调用channel_get获取数据

3.6 避坑指南:我踩过的几个坑

  • 回调里做printk:我曾经在ISR里直接printk调试,结果系统卡死。后来才知道printk可能触发调度,中断里绝对不能干这事。
  • 忘记配置GPIO中断:trigger_set配好了,但GPIO引脚没配成中断模式,传感器死活不触发。检查硬件配置要仔细。
  • 通道号搞混:有些传感器ACCEL_XYZ和GYRO_XYZ的通道号是连续的,但索引方式不同。我建议写驱动时先打印所有通道的值确认一下。
  • attr_set顺序:部分传感器必须先设量程再设采样率,顺序反了会返回错误。看datasheet的初始化序列很重要。

调试小技巧:用sensor_attr_get把传感器当前配置全部读出来打印,能快速定位配置问题。我每次移植新传感器都先做这个「配置快照」。

嗯,Sensor Driver的核心API就这些。说白了就是三件事:怎么拿数据(fetch/get)、怎么配参数(attr)、怎么响应事件(trigger)。把这套接口用熟了,Zephyr上任何传感器驱动你都能快速上手。