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
>;
};
};
```
编译与加载
- 将驱动代码放入内核源码
drivers/misc/ 目录,并添加Makefile条目:obj-$(CONFIG_CAM_I2C_INIT) += cam_i2c_init.o
- 内核配置中启用
CONFIG_CAM_I2C_INIT=y(或=m)
- 编译内核或模块:
make zImage 或 make modules
- 若编译为模块,加载:
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 扫描总线确认设备地址。