I2C子系统详解
I2C子系统
I2C协议
串行、半双工总线,主要用于低速、近距离的芯片之间的通信
I2C信号
**开始信号(S):**SCL为高电平时,SDA由高电平向低电平跳变
**结束信号(P):**SCL为高电平时,SDA由低电平向高电平跳变
**响应信号(ACK):**接收器在接收到8位数据后,在第九个时钟周期拉低SDA
SDA上传输的数据必须在SCL为高电平期间保持稳定,SDA上的数据只能在SCL为低电平期间变化
数据格式
写操作
白色:主→从 灰色:从→主
方向:读/写(0:写 1读)
读操作
具体实现
芯片可以通过SDA线来传输数据,也可以通过它读取数据,那么主设备与从设备肯定不能同时控制SDA线
在9个时钟里(数据+回应):
- 前8个时钟由主设备发送数据的话,第9个时钟就由从设备发送数据
- 前8个时钟由从设备发送数据的话,第9个时钟就由主设备发送数据
如何才能做到互不影响?
两端使用三极管来实现,如下图所示
A驱动cmos管,接地,SDA输出低电平
A、B同时不驱动,由于上拉电阻的存在,SDA输出高电平
只要有一方驱动三极管,那么SDA都会输出低电平
SMBus协议
IIC (二) – SMBus协议和基础知识介绍-CSDN博客
SMBus:系统管理总线
SMBus是基于I2C协议的,是I2C协议的子集,但是相较于I2C协议要求更加的严格
例如:(重复发送S信号)
读取EEPROM时,涉及两个操作:
- 把存储地址发送给设备
- 读数据
这两步之间的衔接原本需要发送一个P信号停止,再发送一个S信号重新开始
而SMBus对此进行了改进:两步之间的衔接可以直接发送S信号
除此之外,
对于I2C协议,它只定义了怎么传输数据,但是并没有定义数据的格式,这完全由设备来定义
对于SMBus协议,它定义了几种数据格式
I2C子系统的框架
软件框架
应用程序可以通过自己编写的设备驱动访问I2C控制器,从而访问I2C设备,也可以通过内核自带的i2c-dev,c驱动程序访问I2C控制器(I2C_Tools),从而访问I2C设备
设备驱动框架
**I2C适配层:**设备树的I2C控制器节点会被转换为platform_device结构体,与I2C控制器驱动进行匹配,若匹配成功则调用控制器驱动的probe函数,对I2C控制器进行初始化,生成i2c_adapter结构体,再调用i2c_add_adapter,将其添加到I2C控制器链表中
**I2C总线核心层:**遍历adapter对象的子节点并生成i2c_client对象,将i2c_client对象挂载到adapter的client链表中,如果有i2c_driver注册,内核会遍历adapter链表中每一项下的client链表进行匹配,若匹配成功则调用i2c_driver的probe函数,并返回client对象
**I2C从机设备驱动层:**定义驱动程序并注册
要编写设备驱动程序:我们需要做的仅仅只是定义并注册驱动程序
重要的结构体
I2C总线
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.pm = &i2c_device_pm_ops,
};
i2c_device_match:I2C设备与I2C驱动是否匹配
i2c_device_probe:如果匹配则调用,进而调用I2C驱动的probe函数
I2C驱动
struct i2c_driver {
int (*probe)(struct i2c_client *, const struct i2c_device_id *); //probe函数
struct device_driver driver; //表明这是一个驱动
const struct i2c_device_id *id_table; //要匹配的从设备信息(名称)
int (*detect)(struct i2c_client *, struct i2c_board_info *); //设备探测函数
const unsigned short *address_list; //设备地址
struct list_head clients; //设备链表
};
对应I2C驱动程序
I2C设备
struct i2c_client {
unsigned short addr; //设备地址
char name[I2C_NAME_SIZE]; //设备名称
struct i2c_adapter *adapter; //I2C控制器
struct i2c_driver *driver; //设备对应的驱动
struct device dev; //表明这是一个设备
int irq; //中断号
struct list_head detected; //节点
};
对应I2C设备
adapter是这个设备所属的I2C控制器结构体
I2C控制器
通过I2C控制器,I2C驱动可以与I2C设备进行通信
struct i2c_adapter {
unsigned int id; //设备器的编号
const struct i2c_algorithm *algo; //算法,传输函数
struct device dev; //表明这是一个设备
...
int nr; //哪一条I2C总线
...
};
数据
在上面的i2c_algorithm结构体中可以看到要传输的数据被称为:i2c_msg
i2c_msg中的flags用来表示传输方向:bit 0等于I2C_M_RD表示读,bit 0等于0表示写
举例:设备地址为0x50的EEPROM,要读取它里面存储地址为0x10的一个字节,应该构造几个i2c_msg?
u8 data_addr = 0x10;
i8 data;
struct i2c_msg msgs[2];
msgs[0].addr = 0x50;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = &data_addr;
msgs[1].addr = 0x50;
msgs[1].flags = I2C_M_RD;
msgs[1].len = 1;
msgs[1].buf = &data;
内核里的传输函数:
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
// num:几个i2c_msg
I2C-Tools
对于I2C设备,内核提供了驱动程序drivers/i2c/i2c-dev.c
,通过它可以直接使用下面的I2C控制器驱动程序来访问I2C设备
使用一句话概括I2C传输:APP通过I2C Controller与I2C Device传输数据
所以使用I2C-Tolls时也需要指定:
I2C-Tools访问I2C设备的两种方式
I2C方式:i2ctransfer.c
SMBus方式
终端直接控制
-
i2cdetect(检测I2C器件工具) :
i2cdetect 0 :确定I2C0这条总线上有多少个I2C设备(UU代表这个设备已经存在驱动程序,其他值代表着设备地址) i2cdetect -y:查询这个板子上有多少条I2C总线
-
i2cdump(查看寄存器值工具) :
i2cdump -f -y 0 0x68 //读取 I2C 总线 0 上地址为 0x68 的设备寄存器内容
-
i2cget(读取寄存器值工具)
i2cget -f -y 0 0x68 0x06 //读地址0x06的寄存器值
-
i2cset(设置寄存器值工具)
i2cset -f -y 0 0x68 0x06 0x18 //设置为18
-
i2ctransfer(数据传输,但是基于最基本的I2C协议,而不是SMBus协议)
i2ctransfer -f -y 0 w2@0x1e 0 0x4 i2ctransfer -f -y 0 w1@0x1e 0xc r2
使用示例(AP3216C传感器):
使用SMBus协议:
i2cset -f -y 0 0x1e 0 0x4
i2cset -f -y 0 0x1e 0 0x3
i2cget -f -y 0 0x1e 0xc w
i2cget -f -y 0 0x1e 0xe w
使用I2C协议:
i2ctransfer -f -y 0 w2@0x1e 0 0x4
i2ctransfer -f -y 0 w2@0x1e 0 0x3
i2ctransfer -f -y 0 w1@0x1e 0xc r2
i2ctransfer -f -y 0 w1@0x1e 0xe r2
编写APP控制
函数接口:
open_i2c_dev(int i2cbus, char * filename, size_t size, int quit)
// 第几条总线 argv[1]-'0' 数组 数组大小 填0可以打印错误信息
set_slave_addr(file, dev_addr, 1)
// open_i2c_dev的返回值 设备地址 强制设置
unsigned char *str = argv[3];
while(*str)
{
i2c_smbus_write_byte_data(file, mem_addr, *str);
// 文件索引 寄存器地址 要写入的一字节数据
// 写完一个字节后需要等待10ms
// struct timespec req;
// req.tv_sec = 0; // 多少秒
// req.tv_nsec = 20000000; // 多少纳秒
naonsleep(&req, NULL);
mem_addr++;
str++;
}
// 字符串结束符
i2c_smbus_write_byte_data(file, mem_addr, *str);
i2c_smbus_read_i2c_block_data(file, mem_addr, sizeof(buf), buf);
buf[32] = '\0';
printf("get data:%s", buf );
//读出32字节数据 文件索引 寄存器地址 数组大小 数据存放的数组
设备驱动
设备驱动模型
i2c_driver
匹配
//用于和设备树匹配
static const struct of_device_id ap3216_dt_match[] = {
{ .compatible = "alientek,ap3216c", },
{ },
};
//用于和一般的i2c设备匹配,不管i2c设备来自设备树还是手工创建
static const struct i2c_device_id ap3216_i2c_id[] = {
{ "ap3216c", },
{ }
};
static struct i2c_driver ap3216_i2c_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "ap3216",
.of_match_table = ap3216_dt_match,
},
.probe = ap3216_i2c_probe,
.remove = ap3216_i2c_remove,
.id_table = ap3216_i2c_id,
};
i2c_driver表明能支持哪些设备:
i2c_driver跟i2c_client匹配成功后,就调用i2c_driver.probe函数
常用函数接口
i2c_add_driver(driver)
i2c_register_driver(THIS_MODULE, driver)
// 注册i2c驱动
void i2c_del_driver(struct i2c_driver *driver);
// 删除i2c驱动
i2c_client
设备树
在对应的i2c控制器节点下创建子节点,并将引脚复用为i2c功能
i2c1: i2c@400a0000 {
/* ... master properties skipped ... */
clock-frequency = <100000>;
flash@50 {
compatible = "atmel,24c256";
reg = <0x50>;
};
pca9532: gpio@60 {
compatible = "nxp,pca9532";
gpio-controller;
#gpio-cells = <2>;
reg = <0x60>;
};
};
函数
i2c_new_device
使用示例:
// 使用 i2c_new_device 函数创建 i2c_client 结构体
static int __init ap3216_i2c_clien_init(void)
{
int bus = 0;
struct i2c_adapter *adapter;
struct i2c_board_info ap3216_info = {
I2C_BOARD_INFO("ap3216c", 0x1e), // 名字 地址
};
/* get i2c adapter bus*/
adapter = i2c_get_adapter(bus);
if (!adapter) {
pr_err("%s failed to get i2c adapter %d.\n", __func__, bus);
return -1;
}
/* register i2c device */
client = i2c_new_device(adapter, &ap3216_info);
/* release i2c adapter */
i2c_put_adapter(adapter);
return 0;
}
static void __exit ap3216_i2c_clien_exit(void)
{
i2c_unregister_device(client);
}
用户空间
// 创建一个i2c_client, .name = "eeprom", .addr=0x50, .adapter是i2c-3
# echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device
// 删除一个i2c_client
# echo 0x50 > /sys/bus/i2c/devices/i2c-3/delete_device
作者:ad_l