I2C子系统
I2C通信
I2C协议
I2C协议运用已经非常广泛,直接看图,一图胜前言。
I2C设备驱动数据处理
- Linux内核代码:
drivers\i2c\i2c-core.c
数据处理函数:
1 | /** |
使用i2c_transfer
函数发送数据之前要先构建好 i2c_msg
。
i2c_msg
结构体定义在文件 include/uapi/linux/i2c.h
1 | struct i2c_msg { |
以读取 I2C 设备寄存器数据为例:
(参考正点原子)
1 | int ret; |
向 I2C 设备寄存器写入数据:
(参考正点原子)
1 | int ret; |
Linux内核对i2c_transfer
进一步封装,形成两个API函数分别用于I2C数据的接收和发送操作 。代码位于:drivers\i2c\i2c-core.c
- i2c_master_recv
1 | /** |
- i2c_master_send
1 | /** |
kernel中i2c驱动
核心 i2c_driver 结构体
分配、设置、注册一个i2c_driver结构体
1 | static struct i2c_driver i2c_example_driver = { |
出入口函数
1 | /*入口函数 注册一个i2c_drvier */ |
i2c_driver表明能支持哪些设备
i2c_driver表明能支持哪些设备:
使用of_match_table来判断
- 设备树中,某个I2C控制器节点下可以创建I2C设备的节点
- 如果I2C设备节点的compatible属性跟of_match_table的某项兼容,则匹配成功
- i2c_client.name跟某个of_match_table[i].compatible值相同,则匹配成功
1
2
3
4
5static const struct of_device_id of_match_ids_example[] = {
{ .compatible = "com_name,chip_name", .data = NULL},
{ /* END OF LIST */ } /*最后空一项为必须,空闲为end 判断条件*/
};
// of_ 开头一般与设备树关联- 设备树中,某个I2C控制器节点下可以创建I2C设备的节点
使用id_table来判断
- i2c_client.name跟某个id_table[i].name值相同,则匹配成功
1
2
3
4static const struct i2c_device_id example_ids[] = {
{ "chip_name", (kernel_ulong_t)NULL },
{ /* END OF LIST */ }
};
i2c_driver跟i2c_client匹配成功后,就调用i2c_driver.probe函数。
i2c_client
参考资料 Linux内核文档: 5.0版本内核
Documentation\i2c\instantiating-devices.rst
Documentation\i2c\writing-clients.rst
i2c_client表示一个I2C设备,创建i2c_client的方法有4种:
方法1
- 通过I2C bus number来创建
1
int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len);
- 通过设备树结点
1
2
3
4
5/* 在某个I2C控制器的节点下,添加如下代码: */
ap3216c@1e {
compatible = "lite-on,ap3216c";
reg = <0x1e>;
};方法2
有时候无法知道该设备挂载哪个I2C bus下,无法知道它对应的I2C bus number。
但是可以通过其他方法知道对应的i2c_adapter结构体。
可以使用下面两个函数来创建i2c_client:- i2c_new_device
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16static struct i2c_client *ap3216c_client;
static int __init i2c_client_ap3216c_init(void)
{
struct i2c_adapter *adapter;
struct i2c_board_info board_info = {
I2C_BOARD_INFO("ap3216c", 0x1e),
};
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* register I2C device */
adapter = i2c_get_adapter(0);
ap3216c_client = i2c_new_device(adapter, &board_info);
i2c_put_adapter(adapter);
return 0;
}- i2c_new_probed_device
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21static struct i2c_client *ap3216c_client;
/* 罗列出所有可能的地址,这里只有一个*/
static const unsigned short normal_i2c[] = { 0x1e, I2C_CLIENT_END };
static int __init i2c_client_ap3216c_init(void)
{
struct i2c_adapter *adapter;
struct i2c_board_info i2c_info;
/* 将名字信息写入i2c_info,先清0 */
memset(&i2c_info, 0, sizeof(struct i2c_board_info));
strscpy(i2c_info.type, "ap3216c", sizeof(i2c_info.type));
adapter = i2c_get_adapter(0); /* 第0根i2c总线 */
ap3216c_client = i2c_new_probed_device(adapter, &i2c_info,
normal_i2c, NULL);
i2c_put_adapter(adapter);
return 0;
}差别
i2c_new_device:
会创建i2c_client,即使该设备并不存在
i2c_new_probed_device:
它成功的话,会创建i2c_client,并且表示这个设备肯定存在
I2C设备的地址可能发生变化,比如AT24C02的引脚A2A1A0电平不一样时,设备地址就不一样
可以罗列出可能的地址
i2c_new_probed_device使用这些地址判断设备是否存在
方法3 (不推荐):由i2c_driver.detect函数来判断是否有对应的I2C设备并生成i2c_client
方法4 : 通过用户空间(user-space)生成
调试时、或者不方便通过代码明确地生成i2c_client时,可以通过用户空间来生成。
1
2
3
4
5// 创建一个i2c_client, .name = "eeprom", .addr=0x50, .adapter是i2c-3
// 删除一个i2c_client
SMBus协议
参考资料:
Linux内核文档:
Documentation\i2c\smbus-protocol.rst
Linux内核代码:
drivers\i2c\i2c-core.c
SMBus协议:
SMBus是I2C协议的一个子集。
SMBus: System Management Bus,系统管理总线。
SMBus最初的目的是为智能电池、充电电池、其他微控制器之间的通信链路而定义的。
SMBus也被用来连接各种设备,包括电源相关设备,系统传感器,EEPROM通讯设备等等。
SMBus 为系统和电源管理这样的任务提供了一条控制总线,使用 SMBus 的系统,设备之间发送和接收消息都是通过 SMBus,而不是使用单独的控制线,这样可以节省设备的管脚数。
SMBus是基于I2C协议的,但SMBus要求更严格。
如今很多设备都实现了SMBus,即使I2C控制器没有实现SMBus,软件方面也是可以使用I2C协议来模拟SMBus。
在Linux上建议优先使用SMBus。
SMBus与一般I2C协议的差别
VDD的极限值不一样
- I2C协议:范围很广,甚至讨论了高达12V的情况
- SMBus:1.8V~5V
最小时钟频率、最大的
Clock Stretching
Clock Stretching含义:某个设备需要更多时间进行内部的处理时,它可以把SCL拉低占住I2C总线
I2C协议:时钟频率最小值无限制,Clock Stretching时长也没有限制
SMBus:时钟频率最小值是10KHz,Clock Stretching的最大时间值也有限制
地址回应(Address Acknowledge)
- I2C协议:没有强制要求必须发出回应信号
- SMBus:强制要求必须发出回应信号,这样对方才知道该设备的状态:busy,failed,或是被移除了
SMBus协议明确了数据的传输格式
- I2C协议:它只定义了怎么传输数据,但是并没有定义数据的格式,这完全由设备来定义
- SMBus:定义了几种数据格式
REPEATED START Condition(重复发出S信号)
- 比如读EEPROM时,涉及2个操作:
- 把存储地址发给设备
- 读数据
- 在写、读之间,可以不发出P信号,而是直接发出S信号:这个S信号就是
REPEATED START
- 如下图所示
- 比如读EEPROM时,涉及2个操作:
SMBus Low Power Version
- SMBus也有低功耗的版本
SMBus通信
Linux内核上集成了SMBus,可以认为是对I2C的进一步封装,在与外设使用I2C子系统进行数据传输时,建议使用SMBus协议。
以下是常用的SMBus函数,更多资料参考内核源码:drivers\i2c\i2c-core.c
Receive Byte
读取一个字节,主机接收到一个字节后不需要回应(上图中N表示不回应)
1 | /** |
Send Byte
1 | /** |
Read Byte
1 | /** |
Read Word
1 | /** |
Write Byte
1 | /** |
Write Word
1 | /** |