3. 设备驱动开发基础

飞控设备驱动是飞控软件与传感器、执行机构、通信模块之间的“翻译层”。它把 IMU、磁罗盘、气压计、GPS、CAN 总线、PWM 输出等硬件行为,抽象成内核可调度、可读写、可诊断的对象。理解驱动模型、字符/块设备、设备树、中断、DMA 与内存映射,是后续调试驱动、分析日志、定位丢包与延迟问题的前提。

一、驱动模型:总线、设备与驱动三者匹配

Linux 驱动模型的核心是“总线—设备—驱动”三角关系。总线负责枚举与匹配,设备描述“有什么硬件”,驱动描述“怎么操作硬件”。当设备树或 ACPI 提供的 compatible 属性与驱动 of_match_table 中的字符串一致时,内核调用驱动的 probe 函数完成初始化。飞控中常见的 SPI IMU、I2C 气压计、CAN 控制器都遵循这一模型。

驱动生命周期通常包括:注册、匹配、probe、运行、remove、注销。probe 阶段要完成资源申请(GPIO、中断号、时钟、DMA 通道)、寄存器初始化、字符设备或输入子系统注册。若 probe 失败,日志中会出现 probe of xxx failed with error -517 这类信息,-517 表示 EPROBE_DEFER,说明依赖的资源尚未就绪,驱动会被延迟重试。

调试要点:probe 失败优先看返回值。ENODEV 多为匹配失败,EBUSY 多为资源被占用,EPROBE_DEFER 多为依赖未就绪,EINVAL 多为设备树参数错误。

二、字符设备与块设备:数据形态决定接口

字符设备以字节流方式访问,通常不支持随机读写缓存,适合传感器寄存器、串口、PWM、GPIO 等。块设备以固定大小数据块访问,带页缓存和 I/O 调度,适合存储卡、eMMC、日志分区。飞控中黑匣子日志写入 eMMC 属于块设备路径,而 IMU 采样读取属于字符设备或 IIO 子系统路径。

字符设备注册常用 alloc_chrdev_region、cdev_init、cdev_add,并实现 file_operations 中的 open、read、write、ioctl、release。用户态通过 /dev/xxx 访问。块设备则通过 register_blkdev、blk_mq_alloc_tag_set 等接口注册,最终以 /dev/mmcblk0p1 形式出现。

对比项字符设备块设备
访问单位字节流固定块(如 512B/4KB)
典型对象IMU、串口、GPIO、PWMeMMC、SD 卡、日志分区
缓存机制一般无页缓存有页缓存与 I/O 调度
飞控用途实时采样与控制黑匣子日志持久化

三、设备树:把硬件描述从代码中分离

设备树(Device Tree)用节点和属性描述板级硬件连接,使同一驱动可适配不同飞控板。常见节点包括 SPI、I2C、UART、GPIO、中断控制器、DMA 控制器。以 SPI IMU 为例,设备树需描述片选、SPI 频率、中断引脚、供电与 compatible。

&spi1 {
    status = "okay";
    imu@0 {
        compatible = "vendor,icm42688";
        reg = <0>;
        spi-max-frequency = <8000000>;
        interrupt-parent = <&gpio1>;
        interrupts = <12 IRQ_TYPE_EDGE_RISING>;
        vdd-supply = <&vdd_3v3>;
    };
};

设备树错误是驱动不 probe 的高频原因:status 仍为 disabled、reg 与片选不符、中断类型写错、时钟或供电 phandle 缺失。调试时可查看 /proc/device-tree 与 dmesg 中的 of 相关打印。

四、中断处理:上半部与下半部

飞控对实时性敏感,中断处理必须短小。Linux 将中断分为上半部(硬中断)和下半部(softirq、tasklet、workqueue、threaded IRQ)。上半部只做清中断标志、读取关键状态、唤醒下半部;耗时解析、数据搬运、日志记录放到下半部或线程化中断中。

申请中断使用 request_irq 或 request_threaded_irq,需指定中断号、处理函数、触发方式和设备名。共享中断要检查返回值判断是否为本设备触发。中断日志中若出现 irq 45: nobody cared,说明中断被触发但无人处理,常见于中断号错误、未清标志或设备树触发方式不匹配。

飞控建议:IMU 数据就绪中断使用线程化中断,配合高优先级线程读取 FIFO;避免在硬中断中做 I2C/SPI 长传输。

五、DMA与内存映射:降低 CPU 搬运开销

DMA 允许外设直接与内存交换数据,减少 CPU 参与。飞控中 SPI IMU 批量读取、UART 高速接收、CAN 报文搬运都可能使用 DMA。使用 DMA 需注意:缓冲区必须物理连续或使用 scatter-gather;要正确进行 cache 一致性维护(dma_alloc_coherent 或 dma_map_single);传输完成后通过中断或轮询确认完成。

内存映射分为两类:一是内核虚拟地址到物理地址的映射,驱动通过 ioremap 访问寄存器;二是用户态通过 mmap 映射设备内存或 DMA 缓冲区,便于零拷贝采集。飞控日志诊断中,若发现 CPU 占用高但数据量不大,应检查是否误用 PIO 而非 DMA,或 cache 维护过于频繁。

飞控设备驱动核心结构:设备树 → 驱动匹配 → 中断/DMA → 用户态 设备树 DTS compatible reg / spi-max-freq interrupts dmas / clocks 平台/SPI 驱动 of_match_table probe / remove file_operations request_irq 飞控硬件 IMU / 气压计 CAN / UART eMMC / SD PWM / GPIO 用户态 /dev/imu0 /dev/ttyS1 mmap ioctl 中断与 DMA 数据路径 硬中断上半部 清标志 / 读状态 线程化下半部 解析 / 入队 DMA 搬运 内存 ↔ 外设 环形缓冲 零拷贝 日志诊断关键点 • probe 返回 -517:依赖未就绪,检查设备树 phandle 与驱动加载顺序 • irq nobody cared:中断触发方式或中断号错误,检查 interrupts 属性 • CPU 高但吞吐低:检查是否使用 PIO、cache 维护过频或 DMA 未对齐

六、案例与复盘:IMU 驱动不工作的排查路径

某飞控板 IMU 无数据,用户态 /dev/imu0 不存在。排查顺序如下:先看 dmesg | grep -i imu,发现 probe of imu@0 failed with error -517;再查设备树,发现 vdd-supply 指向的 regulator 尚未注册;调整驱动加载顺序后 probe 成功。随后数据仍不更新,查看中断计数 cat /proc/interrupts,发现中断号 0 增长,说明触发方式写成了电平触发而硬件是边沿触发,改为 IRQ_TYPE_EDGE_RISING 后恢复。

常见误区:把设备树当成“配置文件”随意改;在硬中断中做 SPI 长传输;DMA 缓冲区未做 cache 一致性处理;用轮询代替中断导致 CPU 占用飙升。复盘时应同时核对设备树、驱动 probe 日志、中断计数与 DMA 状态。