I2C子系统

I2C通信

I2C协议

I2C协议运用已经非常广泛,直接看图,一图胜前言。

image

I2C设备驱动数据处理

  • Linux内核代码:drivers\i2c\i2c-core.c

数据处理函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* i2c_transfer - execute a single or combined I2C message
* @adap: Handle to I2C bus
* @msgs: One or more messages to execute before STOP is issued to
* terminate the operation; each message begins with a START.
* @num: Number of messages to be executed.
*
* Returns negative errno, else the number of messages executed.
*
* Note that there is no requirement that each message be sent to
* the same slave address, although that is the most common model.
*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

使用i2c_transfer函数发送数据之前要先构建好 i2c_msg

i2c_msg 结构体定义在文件 include/uapi/linux/i2c.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_RD 0x0001 /* read data, from slave to master */
/* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};

以读取 I2C 设备寄存器数据为例:

(参考正点原子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int ret;
struct i2c_msg msg[2];

/* msg[0],第一条写消息,发送要读取的寄存器地址 */
msg[0].addr = addr; /* I2C 设备地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg 长度 */

/* msg[1],第二条读消息,读取寄存器数据 */
msg[1].addr = addr; /* I2C 设备地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度 */

ret = i2c_transfer(adapter, msg, 2);

向 I2C 设备寄存器写入数据:

(参考正点原子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int ret;
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)
dev->private_data;

b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要发送的数据拷贝到数组 b 里面 */

msg.addr = addr; /* I2C 设备地址 */
msg.flags = 0; /* 标记为写数据 */

msg.buf = b; /* 要发送的数据缓冲区 */
msg.len = len + 1; /* 实际数据长度 + 寄存器地址长度*/

ret = i2c_transfer(client->adapter, &msg, 1);

Linux内核对i2c_transfer进一步封装,形成两个API函数分别用于I2C数据的接收和发送操作 。代码位于:drivers\i2c\i2c-core.c

  • i2c_master_recv
1
2
3
4
5
6
7
8
9
10
/**
* i2c_master_recv - issue a single I2C message in master receive mode
* @client: Handle to slave device
* @buf: Where to store data read from slave
* @count: How many bytes to read, must be less than 64k since msg.len is u16
*
* Returns negative errno, or else the number of bytes read.
*/
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)

  • i2c_master_send
1
2
3
4
5
6
7
8
9
/**
* i2c_master_send - issue a single I2C message in master transmit mode
* @client: Handle to slave device
* @buf: Data that will be written to the slave
* @count: How many bytes to write, must be less than 64k since msg.len is u16
*
* Returns negative errno, or else the number of bytes written.
*/
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)

kernel中i2c驱动

核心 i2c_driver 结构体

分配、设置、注册一个i2c_driver结构体

1
2
3
4
5
6
7
8
9
static struct i2c_driver i2c_example_driver = {
.driver = {
.name = "example",
.of_match_table = of_match_ids_example,
},
.probe = i2c_example_driver_probe,
.remove = i2c_example_driver_remove,
.id_table = example_ids,
};

出入口函数

1
2
3
4
5
6
7
8
9
10
11
12
/*入口函数 注册一个i2c_drvier */
static int __init i2c_driver_init(void)
{
return i2c_add_driver(&i2c_example_driver);
}
module_init(i2c_driver_init);

/**出口函数 del一个i2c_drvier */
static void __exit i2c_driver_exit(void)
{
i2c_del_driver(&i2c_example_driver);
}

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
    5
    static const struct of_device_id of_match_ids_example[] = {
    { .compatible = "com_name,chip_name", .data = NULL},
    { /* END OF LIST */ } /*最后空一项为必须,空闲为end 判断条件*/
    };
    // of_ 开头一般与设备树关联
  • 使用id_table来判断

    • i2c_client.name跟某个id_table[i].name值相同,则匹配成功
    1
    2
    3
    4
    static 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
    16
    static 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
    21
    static 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
    # echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device

    // 删除一个i2c_client
    # echo 0x50 > /sys/bus/i2c/devices/i2c-3/delete_device

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要求更严格。

image

如今很多设备都实现了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
    • 如下图所示
      image
  • SMBus Low Power Version

    • SMBus也有低功耗的版本

SMBus通信

Linux内核上集成了SMBus,可以认为是对I2C的进一步封装,在与外设使用I2C子系统进行数据传输时,建议使用SMBus协议。

以下是常用的SMBus函数,更多资料参考内核源码:drivers\i2c\i2c-core.c

Receive Byte

image

读取一个字节,主机接收到一个字节后不需要回应(上图中N表示不回应)

1
2
3
4
5
6
7
8
/**
* i2c_smbus_read_byte - SMBus "receive byte" protocol
* @client: Handle to slave device
*
* This executes the SMBus "receive byte" protocol, returning negative errno
* else the byte received from the device.
*/
s32 i2c_smbus_read_byte(const struct i2c_client *client)

Send Byte

image

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* i2c_smbus_write_byte - SMBus "send byte" protocol
* @client: Handle to slave device
* @value: Byte to be sent
*
* This executes the SMBus "send byte" protocol, returning negative errno
* else zero on success.
*/
s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value)
{
return i2c_smbus_xfer(client->adapter, client->addr, client->flags,
I2C_SMBUS_WRITE, value, I2C_SMBUS_BYTE, NULL);
}

Read Byte

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* i2c_smbus_read_byte_data - SMBus "read byte" protocol
* @client: Handle to slave device
* @command: Byte interpreted by slave
*
* This executes the SMBus "read byte" protocol, returning negative errno
* else a data byte received from the device.
*/
s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command)
{
union i2c_smbus_data data;
int status;

status = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
I2C_SMBUS_READ, command,
I2C_SMBUS_BYTE_DATA, &data);
return (status < 0) ? status : data.byte;
}

Read Word

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* i2c_smbus_read_word_data - SMBus "read word" protocol
* @client: Handle to slave device
* @command: Byte interpreted by slave
*
* This executes the SMBus "read word" protocol, returning negative errno
* else a 16-bit unsigned "word" received from the device.
*/
s32 i2c_smbus_read_word_data(const struct i2c_client *client, u8 command)
{
union i2c_smbus_data data;
int status;

status = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
I2C_SMBUS_READ, command,
I2C_SMBUS_WORD_DATA, &data);
return (status < 0) ? status : data.word;
}

Write Byte

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* i2c_smbus_write_byte_data - SMBus "write byte" protocol
* @client: Handle to slave device
* @command: Byte interpreted by slave
* @value: Byte being written
*
* This executes the SMBus "write byte" protocol, returning negative errno
* else zero on success.
*/
s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command,
u8 value)
{
union i2c_smbus_data data;
data.byte = value;
return i2c_smbus_xfer(client->adapter, client->addr, client->flags,
I2C_SMBUS_WRITE, command,
I2C_SMBUS_BYTE_DATA, &data);
}

Write Word

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* i2c_smbus_write_word_data - SMBus "write word" protocol
* @client: Handle to slave device
* @command: Byte interpreted by slave
* @value: 16-bit "word" being written
*
* This executes the SMBus "write word" protocol, returning negative errno
* else zero on success.
*/
s32 i2c_smbus_write_word_data(const struct i2c_client *client, u8 command,
u16 value)
{
union i2c_smbus_data data;
data.word = value;
return i2c_smbus_xfer(client->adapter, client->addr, client->flags,
I2C_SMBUS_WRITE, command,
I2C_SMBUS_WORD_DATA, &data);
}