1. 初始化I2C通信接口

任务说明

本案例目标是在嵌入式Linux环境中初始化I2C总线,完成时钟使能与引脚复用配置,为后续与Camera Sensor通信建立可靠通道。核心交付物为一段可直接编译运行的I2C初始化代码,以及对应的验证方法。

环境与依赖

  • 硬件:任意带I2C控制器的SoC(示例以i.MX6ULL为参考,代码使用Linux内核I2C子系统接口)
  • 软件:Linux内核源码树(版本4.x或5.x),已配置I2C驱动框架
  • 依赖头文件:linux/i2c.h、linux/i2c-dev.h、linux/pinctrl/consumer.h、linux/clk.h

关键完整代码

以下代码实现I2C适配器初始化、时钟使能、引脚获取与复用配置,并注册一个虚拟Camera Sensor客户端用于验证通信链路:

```c #include #include #include #include #include #include #include #define CAM_SENSOR_I2C_ADDR 0x36 #define I2C_BUS_NUM 1 static struct i2c_board_info cam_sensor_info = { I2C_BOARD_INFO("cam_sensor", CAM_SENSOR_I2C_ADDR), }; static struct i2c_client *cam_sensor_client; static int i2c_init_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct clk *i2c_clk; struct pinctrl *pinctrl; int ret; /* 1. 使能I2C时钟 */ i2c_clk = devm_clk_get(dev, "i2c"); if (IS_ERR(i2c_clk)) { dev_err(dev, "failed to get i2c clock\n"); return PTR_ERR(i2c_clk); } ret = clk_prepare_enable(i2c_clk); if (ret) { dev_err(dev, "failed to enable i2c clock: %d\n", ret); return ret; } /* 2. 配置引脚复用为I2C功能 */ pinctrl = devm_pinctrl_get(dev); if (IS_ERR(pinctrl)) { dev_err(dev, "failed to get pinctrl\n"); return PTR_ERR(pinctrl); } ret = pinctrl_select_state(pinctrl, pinctrl_lookup_state(pinctrl, "i2c_active")); if (ret) { dev_err(dev, "failed to select i2c pinctrl state: %d\n", ret); return ret; } /* 3. 注册I2C客户端(Camera Sensor) */ cam_sensor_client = i2c_new_client_device(i2c_get_adapter(I2C_BUS_NUM), &cam_sensor_info); if (IS_ERR(cam_sensor_client)) { dev_err(dev, "failed to register camera sensor client\n"); return PTR_ERR(cam_sensor_client); } /* 4. 验证通信:向Sensor写入并读取寄存器0x00 */ { u8 reg = 0x00; u8 val = 0; struct i2c_msg msgs[2] = { { .addr = CAM_SENSOR_I2C_ADDR, .flags = 0, .len = 1, .buf = ® }, { .addr = CAM_SENSOR_I2C_ADDR, .flags = I2C_M_RD, .len = 1, .buf = &val }, }; ret = i2c_transfer(cam_sensor_client->adapter, msgs, 2); if (ret == 2) { dev_info(dev, "I2C communication OK, reg0x00=0x%02x\n", val); } else { dev_err(dev, "I2C transfer failed: %d\n", ret); return -EIO; } } return 0; } static int i2c_init_remove(struct platform_device *pdev) { if (cam_sensor_client) i2c_unregister_device(cam_sensor_client); return 0; } static const struct of_device_id i2c_init_of_match[] = { { .compatible = "example,cam-i2c-init" }, { } }; MODULE_DEVICE_TABLE(of, i2c_init_of_match); static struct platform_driver i2c_init_driver = { .probe = i2c_init_probe, .remove = i2c_init_remove, .driver = { .name = "cam_i2c_init", .of_match_table = i2c_init_of_match, }, }; module_platform_driver(i2c_init_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("I2C bus initialization for Camera Sensor"); ```

设备树配置

在设备树中需声明I2C控制器时钟、引脚状态及本驱动节点:

```dts &i2c1 { clock-frequency = <100000>; pinctrl-names = "default", "i2c_active"; pinctrl-0 = <&pinctrl_i2c1_default>; pinctrl-1 = <&pinctrl_i2c1_active>; status = "okay"; cam_i2c_init { compatible = "example,cam-i2c-init"; clocks = <&clks IMX6UL_CLK_I2C1>; }; }; &iomuxc { pinctrl_i2c1_default: i2c1grp_default { fsl,pins = < MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0 MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0 >; }; pinctrl_i2c1_active: i2c1grp_active { fsl,pins = < MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b1 MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b1 >; }; }; ```

编译与加载

  1. 将驱动代码放入内核源码 drivers/misc/ 目录,并添加Makefile条目:obj-$(CONFIG_CAM_I2C_INIT) += cam_i2c_init.o
  2. 内核配置中启用 CONFIG_CAM_I2C_INIT=y(或=m)
  3. 编译内核或模块:make zImage 或 make modules
  4. 若编译为模块,加载:insmod cam_i2c_init.ko

验证方法与判定标准

  • 时钟验证:加载驱动后执行 cat /sys/kernel/debug/clk/clk_summary | grep i2c,观察I2C时钟状态为 enabled。
  • 引脚验证:使用 cat /sys/kernel/debug/pinctrl/pinctrl-handles 查看引脚申请状态,确认I2C引脚已处于 i2c_active 状态。
  • 通信验证:驱动probe中已执行I2C读操作,若dmesg输出 I2C communication OK, reg0x00=0x...,说明总线初始化成功且与Sensor通信正常。
  • 失败判定:若dmesg出现 I2C transfer failed,检查硬件连接、Sensor供电、地址是否正确(0x36是否匹配实际Sensor)。

常见问题

  • 时钟获取失败:确认设备树中 clocks 属性与SoC时钟定义匹配。
  • 引脚状态切换失败:确认设备树中 pinctrl-1 引用的节点存在且引脚号正确。
  • I2C传输NACK:Sensor未上电或地址错误,用 i2cdetect -y 1 扫描总线确认设备地址。