I2C子系统-应用视角
2024年7月 · 预计阅读时间: 7 分钟
I2C子系统层次#
LINUX-I2C关键结构体与函数#
i2c.h
1. i2c控制器 - i2c_adapter
#
主要的成员包括
nr
- 表示是哪个i2c控制器(i2c总线)const struct i2c_algorithm *algo
- i2c的算法,含有传输函数,用来收发I2C数据
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* data fields that are valid for all devices */
const struct i2c_lock_operations *lock_ops;
struct rt_mutex bus_lock;
struct rt_mutex mux_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
int nr;
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
struct i2c_bus_recovery_info *bus_recovery_info;
const struct i2c_adapter_quirks *quirks;
};
struct i2c_algorithm {
/* If an adapter algorithm can't do I2C-level access, set master_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, or a negative value on error */
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
int (*reg_slave)(struct i2c_client *client);
int (*unreg_slave)(struct i2c_client *client);
#endif
};
2. i2c设备 - i2c_client
#
主要成员:
addr
- i2c的设备地址struct i2c_adapter *adapter
- 指向i2c控制器的指针,表示连接在哪个I2C Controller上
LINUX 4.9.88
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* the adapter we sit on */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
i2c_slave_cb_t slave_cb; /* callback for slave mode */
#endif
};
LINUX 6.9
struct i2c_client {
unsigned short flags; /* div., see below */
#define I2C_CLIENT_PEC 0x04 /* Use Packet Error Checking */
#define I2C_CLIENT_TEN 0x10 /* we have a ten bit chip address */
/* Must equal I2C_M_TEN below */
#define I2C_CLIENT_SLAVE 0x20 /* we are the slave */
#define I2C_CLIENT_HOST_NOTIFY 0x40 /* We want to use I2C host notify */
#define I2C_CLIENT_WAKE 0x80 /* for board_info; true iff can wake */
#define I2C_CLIENT_SCCB 0x9000 /* Use Omnivision SCCB protocol */
/* Must match I2C_M_STOP|IGNORE_NAK */
unsigned short c; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* the adapter we sit on */
struct device dev; /* the device structure */
int init_irq; /* irq set at initialization */
int irq; /* irq issued by device */
struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
i2c_slave_cb_t slave_cb; /* callback for slave mode */
#endif
void *devres_group_id; /* ID of probe devres group */
};
3. i2c消息 - i2c_msg#
在上面的i2c_algorithm
结构体中可以看到要传输的数据被称为:i2c_msg
- flags用来表示传输方向:bit 0等于I2C_M_RD表示读,bit 0等于0表示写。其余bit可以使用下面定义的宏来设置一些特殊配置。
struct i2c_msg {
__u16 addr; //设备地址
__u16 flags; //读写标志
#define I2C_M_RD 0x0001 /* guaranteed to be 0x0001! */
#define I2C_M_TEN 0x0010 /* use only if I2C_FUNC_10BIT_ADDR */
#define I2C_M_DMA_SAFE 0x0200 /* use only in kernel space */
#define I2C_M_RECV_LEN 0x0400 /* use only if I2C_FUNC_SMBUS_READ_BLOCK_DATA */
#define I2C_M_NO_RD_ACK 0x0800 /* use only if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* use only if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* use only if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* use only if I2C_FUNC_NOSTART */
#define I2C_M_STOP 0x8000 /* use only if I2C_FUNC_PROTOCOL_MANGLING */
__u16 len; //消息长度
__u8 *buf; //消息
};
示例:设备地址为0x50的EEPROM,要读取它里面存储地址为0x10的一个字节,应该构造几个i2c_msg?
要构造2个i2c_msg
第一个i2c_msg表示写操作,把要访问的存储地址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;
4. i2c传输数据概括#
APP通过I2C Controller与I2C Device传输数据
APP通过i2c_adapter与i2c_client传输i2c_msg
内核函数i2c_transfer
i2c_msg里含有addr,所以这个函数里不需要i2c_client
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
5. i2c读写函数#
i2c-core-base.c
(LINUX 6)
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
int ret;
/* REVISIT the fault reporting model here is weak:
*
* - When we get an error after receiving N bytes from a slave,
* there is no way to report "N".
*
* - When we get a NAK after transmitting N bytes to a slave,
* there is no way to report "N" ... or to let the master
* continue executing the rest of this combined message, if
* that's the appropriate response.
*
* - When for example "num" is two and we successfully complete
* the first message but get an error part way through the
* second, it's unclear whether that should be reported as
* one (discarding status on the second message) or errno
* (discarding status on the first one).
*/
ret = __i2c_lock_bus_helper(adap);
if (ret)
return ret;
ret = __i2c_transfer(adap, msgs, num);
i2c_unlock_bus(adap, I2C_LOCK_SEGMENT);
return ret;
}
EXPORT_SYMBOL(i2c_transfer);
i2c-core.c
(LINUX 4.9)
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
int ret;
/* REVISIT the fault reporting model here is weak:
*
* - When we get an error after receiving N bytes from a slave,
* there is no way to report "N".
*
* - When we get a NAK after transmitting N bytes to a slave,
* there is no way to report "N" ... or to let the master
* continue executing the rest of this combined message, if
* that's the appropriate response.
*
* - When for example "num" is two and we successfully complete
* the first message but get an error part way through the
* second, it's unclear whether that should be reported as
* one (discarding status on the second message) or errno
* (discarding status on the first one).
*/
if (adap->algo->master_xfer) {
#ifdef DEBUG
for (ret = 0; ret < num; ret++) {
dev_dbg(&adap->dev,
"master_xfer[%d] %c, addr=0x%02x, len=%d%s\n",
ret, (msgs[ret].flags & I2C_M_RD) ? 'R' : 'W',
msgs[ret].addr, msgs[ret].len,
(msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
}
#endif
if (in_atomic() || irqs_disabled()) {
ret = i2c_trylock_bus(adap, I2C_LOCK_SEGMENT);
if (!ret)
/* I2C activity is ongoing. */
return -EAGAIN;
} else {
i2c_lock_bus(adap, I2C_LOCK_SEGMENT);
}
ret = __i2c_transfer(adap, msgs, num);
i2c_unlock_bus(adap, I2C_LOCK_SEGMENT);
return ret;
} else {
dev_dbg(&adap->dev, "I2C level transfers not supported\n");
return -EOPNOTSUPP;
}
}
EXPORT_SYMBOL(i2c_transfer);
APP使用i2ctools访问EEPROM#
一句话概括:APP通过I2C Controller与I2C Device传输数据
在APP里,有这三个问题:
(1) 怎么指定I2C控制器?
- i2c-dev.c提供为每个I2C控制器(I2C Bus、I2C Adapter)都生成一个设备节点:/dev/i2c-0、/dev/i2c-1等待
- open某个/dev/i2c-X节点,就是去访问该I2C控制器下的设备
(2) 怎么指定I2C设备?
- 通过ioctl指定I2C设备的地址
- ioctl(file, I2C_SLAVE, address)
- 如果该设备已经有了对应的设备驱动程序,则返回失败
- ioctl(file, I2C_SLAVE_FORCE, address)
- 如果该设备已经有了对应的设备驱动程序
- 但是还是想通过i2c-dev驱动来访问它
- 则使用这个ioctl来指定I2C设备地址
(3) 怎么传输数据?
- 两种方式
- 一般的I2C方式:ioctl(file, I2C_RDWR, &rdwr)
- SMBus方式:ioctl(file, I2C_SMBUS, &args)
编写程序:
- file = open_i2c_dev(i2c总线编号, 返回的文件名, 文件名大小, 0 - 不打印信息)
- set_slave_addr(file, dev_addr, 1 - 强制) - 设置从机设备地址(AT24C02的设备地址是0x50)
- 写:i2c_smbus_write_byte_data(file, command - 寄存器地址,value) - 按字节写入
- 读:i2c_smbus_read_i2c_block_data(file, mem_addr, sizeof(data), data) - I2C连续读
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <i2c/smbus.h>
#include <time.h>
#include "i2cbusses.h"
/**
* Usage:
* ./at24c02 <bus-num> w "100ask.taobao.com"
* ./at24c02 <bus-num> r
*/
int main(int argc, char const *argv[])
{
unsigned char dev_addr = 0x50;
unsigned char mem_addr = 0;
unsigned char data[32];
int file, ret;
char filename[20];
unsigned char *pstr;
struct timespec req;
if (argc != 3 && argc != 4)
{
printf("Usage:\n");
printf("\t%s <bus> <w|r> [write data]\n", argv[0]);
return -1;
}
// 打开i2c设备
file = open_i2c_dev(argv[1][0] - '0', filename, sizeof(filename), 0);
if (file < 0)
{
printf("Unable to open %s\n", filename);
return -1;
}
// 设置i2c设备地址
if (set_slave_addr(file, dev_addr, 1))
{
printf("Error: Could not set address to 0x%02x\n", dev_addr);
return -1;
}
// 读or写
if (strcmp(argv[2], "w") == 0)
{
req.tv_sec = 0;
req.tv_nsec = 20000000; // 20ms
// write
pstr = (char *)argv[3];
while (*pstr)
{
// 写入mem_addr和*pstr指向要写入的数据
ret = i2c_smbus_write_byte_data(file, mem_addr++, *pstr++);
if (ret != 0)
{
printf("Error: i2c_smbus_write_byte_data failed\n");
return -1;
}
// wait TWR 20ms
nanosleep(&req, NULL);
}
ret = i2c_smbus_write_byte_data(file, mem_addr, 0); // end char
if (ret != 0)
{
printf("Error: i2c_smbus_write_byte_data failed\n");
return -1;
}
}
else if (strcmp(argv[2], "r") == 0)
{
ret = i2c_smbus_read_i2c_block_data(file, mem_addr, sizeof(data), data);
if (ret < 0)
{
printf("Error: i2c_smbus_read_i2c_block_data failed\n");
return -1;
}
data[31] = '\0';
printf("get data: %s\n", data);
}
// 关闭i2c设备
close(file);
return 0;
}