介绍I2C的功能和使用方法。
模块介绍
I2C 总线是一种两线式串行总线,用于连接微控制器及其外围设备,多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短。每个设备都有自己的唯一地址,且I2C为半双工,任意时刻只能有一个主机并进 行单行通信。
功能介绍
Linux 中 I2C 体系结构上图所示,共分成了三个层次:
- 用户空间,包括所有使用I2C 设备的应用程序;
- 内核,也就是驱动部分;
- 硬件,指实际物理设备,包括了 I2C 控制器和 I2C 外设。
其中,Linux 内核中的 I2C 驱动程序从逻辑上主要实现:
- I2C framework 提供一种 “访问 I2C slave devices” 的方法。由于这些 slave devices 由 I2C controller 控制,因而主要由 I2C controller 驱动实现这一目标。
- 在 I2C framework 内部,有 I2C core、I2C busses、I2C algos 和 I2C muxes 四个模 块。
- I2C core 使用 I2C adapter 和 I2C algorithm 两个子模块抽象 I2C controller 的功能。
- I2C busses 是各个 I2C controller drivers 的集合,位于 drivers/i2c/busses/目录下, i2c-k1.c。
- I2C algos 包含了一些通用的 I2C algorithm,所谓的 algorithm,是指 I2C 协议的通信方 法,用于实现 I2C 的 read/write 指令。
源码结构介绍
控制器驱动代码在drivers/i2c/目录下:
drivers/i2c/
|-- i2c-core-of.c #I2C子系统核心文件,提供相关的接口函数
|-- i2c-boardinfo.c
|-- i2c-core-base.c
|-- i2c-core-slave.c
|-- i2c-core-smbus.c
|-- i2c-dev.c #I2C子系统的设备相关文件,用于注册相关的设备文件
|-- busses/i2c-k1x.c #k1平台的I2C控制器驱动代码
关键特性
特性
特性 | 特性说明 |
---|---|
支持9组I2C | 支持9组I2C接口 |
支持DMA | 主机模式下支持DMA数据传输 |
支持100k / 400k/ 1.5M三种速度模式 | 支持三种速度模式,可通过dts配置 |
支持总线仲裁 | 多主机模式下支持总线仲裁 |
配置介绍
主要包括驱动使能配置和dts配置
CONFIG配置
CONFIG_I2C为I2C总线协议提供支持,默认情况,此选项为Y
Device Drivers
I2C support
I2C support (I2C [=y])
CONFIG_I2C_SPACEMIT_K1X为K1 I2C控制器驱动,默认情况下,此选型为Y
Device Drivers
I2C support
I2C support (I2C [=y])
I2C Hardware Bus support
Spacemit k1x I2C adapter (I2C_SPACEMIT_K1X [=y])
CONFIG_I2C_SLAVE为I2C的从设备模式,比如支持
Device Drivers
I2C support
I2C support (I2C [=y])
I2C slave support (I2C_SLAVE [=y])
CONFIG_I2C_CHARDEV为I2C字符设备
Device Drivers
I2C support
I2C support (I2C [=y])
I2C device interface (I2C_CHARDEV [=y])
dts配置
i2c总线配置如下:
配置 | 说明 |
---|---|
spacemit,i2c-fast-mode | 400K速度模式 |
spacemit,i2c-high-mode | 1.5M速度模式 |
spacemit,dma-disable | 关闭DMA传输 |
总线默认速率为 100K,如需切换速度模式,在 dts 中加入对应配置即可;
如切换为 fast mode,配置如下:
i2c6: i2c@d4018800 {
spacemit,i2c-fast-mode;
};
“spacemit,dma-disable”配置表示不使用 DMA 传输,在 dts 中去掉该配置即可打开 DMA 传输;
以 i2c6 为例,关闭 DMA 传输,k1-x.dtsi 中配置如下:
i2c6: i2c@d4018800 {
spacemit,dma-disable;
};
pinctrl
查看开发板原理图,找到 i2c 控制器使用的 pin 组。
以 i2c6 为例,假设原理图中分别使用 gpio56/57 作为 SCL/SDA,且配置可使用 k1-x_pinctrl.dtsi 中已定义的 pinctrl_i2c6_2 组,则方案 dts 配置如下。
&i2c6 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c6_2>;
};
i2c 设备配置
以 touchscreen 设备为例分析 i2c 设备的 dts 如何配置。
设备类型
确认设备类型以及使用的驱动。
选择 touchscreen 设备的 compatible 配置为“goodix,gt9xx”。
设备地址
确认设备的 i2c 通信地址(7 bit)。
查询原理图得到 touchscreen 设备地址为 0x5d,配置如下。
gt9xx@5d {
compatible = "goodix,gt9xx";
reg = <0x5d>;
}
通信频率
确认设备通信频率。
touchscreen 通信频率支持 100K,选择挂载在 i2c bus6 下,去掉“spacemit,i2c-fast-mode”,“spacemit,i2c-high-mode”配置,使用默认速率 100K。
设备控制信号
在方案原理图中查询设备所使用的控制信号。
touchscreen 的 reset / irq 信号分别为 gpio 114/58,irq-flags 配置为所需的中断触发方式(2 为下降沿触发),配置如下。
gt9xx@5d {
reset-gpios = <&gpio 114 GPIO_ACTIVE_HIGH>;
irq-gpios = <&gpio 58 GPIO_ACTIVE_HIGH>;
irq-flags = <2>;
};
设备 dts
touchscreen 设备地址为 0x5d,gpio 114/58 分别为 reset/irq 信号,下降沿触发中断,通信频率为 100K,配置触摸屏相应参数。
设备 dts 配置如下:
gt9xx@5d {
compatible = "goodix,gt9xx";
reg = <0x5d>;
reset-gpios = <&gpio 114 GPIO_ACTIVE_HIGH>;
irq-gpios = <&gpio 58 GPIO_ACTIVE_HIGH>;
irq-flags = <2>;
touchscreen-max-id = <11>;
touchscreen-size-x = <1200>;
touchscreen-size-y = <1920>;
touchscreen-max-w = <512>;
touchscreen-max-p = <512>;
};
dts
综合上述信息,i2c6 连接 i2c touchscreen 设备,方案 dts 配置如下。
&i2c6 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c6_2>;
status = "okay";
gt9xx@5d {
compatible = "goodix,gt9xx";
reg = <0x5d>;
reset-gpios = <&gpio 114 GPIO_ACTIVE_HIGH>;
irq-gpios = <&gpio 58 GPIO_ACTIVE_HIGH>;
irq-flags = <2>;
touchscreen-max-id = <11>;
touchscreen-size-x = <1200>;
touchscreen-size-y = <1920>;
touchscreen-max-w = <512>;
touchscreen-max-p = <512>;
};
};
接口描述
测试介绍
I2C tool是一个开源工具,可自行下载进行交叉编译,代码下载地址: https://mirrors.edge.kernel.org/pub/software/utils/i2c-tools/ 编译后会生成i2cdetect,i2cdump,i2cset,i2cget等工具,可以直接用命令行调试使用:
- i2cdetect:用来列举i2c bus和上面所有的设备
- i2cdump:显示i2c设备上所有register的值
- i2cset:读取i2c设备某个register的值
- i2cget:写入i2c设备某个register的值
API介绍
内核态:I2C的读写通信都是使用linux标准接口,请参考kernel目录下Documentation/i2c/writing-clients.rst文档说明,关于发送和接收部分有详细的介绍。
用户态:从用户态可以通过"/dev/i2c-%d"节点访问总线上所有的设备。demo示例是一个简单的访问i2c设备读写,具体可以参考Documentation/i2c/dev-interface.rst文档
demo示例
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#define I2C_DEV_FILE "/dev/i2c-1" // I2C设备文件路径,通常为/dev/i2c-1
#define DEVICE_ADDR 0x68 // I2C设备的地址,示例为0x68(根据实际设备修改)
// 写数据到I2C设备
int write_to_i2c(int file, uint8_t reg, uint8_t value) {
uint8_t buffer[2];
buffer[0] = reg; // 寄存器地址
buffer[1] = value; // 写入的值
if (write(file, buffer, 2) != 2) {
perror("I2C write failed");
return -1;
}
return 0;
}
// 从I2C设备读取数据
int read_from_i2c(int file, uint8_t reg, uint8_t *data, size_t len) {
if (write(file, ®, 1) != 1) {
perror("I2C write register failed");
return -1;
}
if (read(file, data, len) != len) {
perror("I2C read failed");
return -1;
}
return 0;
}
int main() {
int file;
uint8_t data[2]; // 用于存储读取到的数据
// 打开I2C设备文件
if ((file = open(I2C_DEV_FILE, O_RDWR)) < 0) {
perror("Failed to open I2C bus");
exit(1);
}
// 设置I2C从设备的地址
if (ioctl(file, I2C_SLAVE, DEVICE_ADDR) < 0) {
perror("Failed to acquire bus access and/or talk to slave");
close(file);
exit(1);
}
if (write_to_i2c(file, 0x6B, 0x00) < 0) {
close(file);
exit(1);
}
printf("Write 0x00 to register 0x6B\n");
if (read_from_i2c(file, 0x75, data, 1) < 0) {
close(file);
exit(1);
}
printf("Read data 0x%02x from register 0x75\n", data[0]);
// 关闭I2C设备文件
close(file);
return 0;
}