Skip to content

AP3216C I2C 驱动 —— 架构概览

前言:从裸机思维,到 Linux 的"放权"

我们先把丑话说在前头:如果你手上那份 I2C 驱动教程还是 4.1.15 内核时代的写法,那它基本只能当历史文物看。probe 后面拖着个 const struct i2c_device_id *idremove 还在 return 0class_create(THIS_MODULE, ...) 双参数——这些代码搬到我们手里的 6.12.49 或 7.1.0 上,class_create 那行直接给你一个编译错误,其余的也都是 incompatible pointer type 警告。这一篇教程的任务,就是用现代内核的 API,把 AP3216C 这颗传感器的驱动从头到尾、干净利落地重写一遍。

但比"换 API"更要紧的,是换脑子。回想你在裸机篇是怎么写 I2C 的:bsp_i2c.c 直接戳 I.MX6U 的寄存器、配 GPIO、算波特率,bsp_ap3216c.c 把芯片手册的时序硬编码进去,两坨代码焊得死死的。在裸机里这没毛病,因为你是硬件唯一的操控者。可一到 Linux 里,你就得学会一件别扭的事——放权。你得承认:你并不拥有这条 I2C 总线,你只是借用它。总线怎么把比特发出去,那是内核和半导体厂商早就铺好的路;你只管告诉它"我要发什么"。这种"控制器操作"和"设备业务逻辑"的分离,就是 Linux I2C 框架的全部哲学,也是这一章反复要敲打的那根弦。

学习目标

module_i2c_driver() + 单参数 probe + void remove 写出符合 6.12 / 7.1 规范的 I2C 设备驱动;通过 i2c_transfer / i2c_smbus_* 完成寄存器读写;最终把 AP3216C 的 IR / ALS / PS 三路真实数据读到用户空间。

AP3216C:一颗芯片,三副面孔

AP3216C 是 Lite-On(立錡)的一颗三合一传感器,把红外(IR)、环境光(ALS)和接近距离(PS)塞进了一颗芯片,对外只走一根 I2C。在我们这块 I.MX6U-ALPHA 板上,它挂在 I2C1 总线上,7 位从机地址是 0x1e——这个数字你接下来会反复看到,写错了通信直接 NACK。

它的寄存器布局很简单,我们挑后面要用到的几个先认个脸熟:0x00 系统配置、0x01 中断状态、0x0A/0x0B 是 IR 的低/高字节、0x0C/0x0D 是 ALS 的低/高字节、0x0E/0x0F 是 PS 的低/高字节。注意这几组数据寄存器是地址连续的,所以我们可以一口气连读六个字节,再按位拼装出三路数据——这个细节在驱动实现那一节会用到。完整的寄存器定义,我们会单独放进 ap3216creg.h,免得代码里全是魔术数字。

先认认环境

我们这次跑在两套内核上,驱动代码两边通用,这里感谢AI,下面的内容是它基于我们仓库编写的:

  • 板子:I.MX6U-ALPHA,AP3216C 挂 I2C1,地址 0x1e
  • 内核linux-imx 6.12.49(NXP BSP,主开发环境)/ mainline 7.1.0(进阶验证)
  • 源码:仓库 third_party/linux-imxthird_party/linux_mainline
  • 交叉工具链arm-linux-gnueabihf-gcc(编译测试程序)

老教程 vs 新内核:到底差在哪

在动手之前,我们先把"老写法"和"新写法"的核心差异摆到台面上,心里有个谱,后面看到代码才不会困惑。最关键的几处:probe 从双参数变成了单参数,老的 const struct i2c_device_id *id 被砍掉;remove 的返回值从 int 变成了 void,函数体里那个 return 0 得删;class_create 从双参数(THIS_MODULE + 名字)变成了单参数(只留名字);驱动注册不再手写 module_init/module_exit,一行 module_i2c_driver() 宏搞定。这几处变化不是内核作者心血来潮,每一条背后都有理由,我们会在后面的章节里一条条讲清楚。

配套文件

本教程涉及的源码文件如下,源码由你自己建立、照着各节的代码段敲进去,我们不替你生成:

  • ap3216c.c —— 驱动主体(I2C 框架 + 字符设备)
  • ap3216creg.h —— AP3216C 寄存器地址定义
  • ap3216c_app.c —— 用户空间测试程序
  • 设备树片段 —— 在 imx6ull-aes.dtsii2c1 节点下挂 ap3216c@1e

小结

这一节我们理清了为什么要重写、AP3216C 是个什么器件、环境长什么样,以及新老写法差在哪。接下来我们先钻进 I2C 框架的内部,把适配器、设备、驱动这三方的结构体关系彻底搞明白。


Built with VitePress