跳到主要内容位置

I2C基础知识

I2C基础知识#

1.I2C软件框架#

APP 它知道读写什么数据 app


I2C Device Driver 它知道怎么读写数据 some driver(I2C 设备自己的驱动程序) / i2c-dev.c(内核自带的i2c-dev.c驱动程序,它是i2c控制器驱动 程序暴露给用户空间的驱动程序)


I2C Controller Driver 它负责传输数据 adapter driver(芯片 I2C 控制器的驱动程序,称为 adapter) / i2c-gpio.c(使用GPIO模拟的 I2C 控制 器驱动程序)


i2c Device I2C设备

2.I2C传输数据格式#

之前学习I2C都是先介绍各种信号,再组合起来看。但是感觉还是先看读写操作再学信号电平变化细节比较好。

写操作:(白色块为主设备,灰色块为从设备,图片来自《嵌入式Linux应用开发完全手册 – 韦东山》)

读操作

在第一个数据中分为了7bit的地址和1bit的方向。

电平信号

start信号:SCL 为高电平时,SDA 山高电平向低电平跳变

stop信号:SCL 为高电平时,SDA 由低电平向高电平跳变

回应信号:接收器在接收到 8 位数据后,在第 9 个时钟周期,拉低 SDA

高低电平:

在低电平期间变化电平,高电平期间读取电平值。

3.I2C设备硬件#

当1设备的SDA线为高电平,MOS管导通,SDA为0;当1设备的SDA线为低电平,MOS管截止,SDA由外部电路决定。

当2设备的SDA线为高电平,MOS管导通,SDA为0;当2设备的SDA线为低电平,MOS管截止,SDA由外部电路决定。

AoutBoutSDA
00上拉电阻,1
01接地,0
10接地,0
11接地,0

有一个设备的out为1,mos管导通,拉低SDA;所有设备的out为0,所有mos管截止,外部电路拉高SDA。这样的设计防止了一高一低时短路的问题。

SCL也是这样设计的,因为主从设备也都会操作SCL

SMBus协议#

SMBus(System Management Bus,系统管理总线)是I2C协议的一个子集,

I2CSMBus
VDD极限值范围很广,甚至高达12V1.8V-5V
最小时钟频率,最大clock stretching
(可以把SCL拉低占住的最长时间)
都无限制最小值为10KHz,最大值也有限制
地址回应没有强制要求强制要求发出回应信号
数据传输格式只定义了怎么传输数据,但没有定义数据的格式定义了几种数据格式(在《嵌入式Linux应用开发完全手册 – 韦东山》10.3.2 SMBus 协议分析有介绍)
Repeated start Condition重复发出开始信号-在读和写之间,可以不发出P信号,直接发出两次S信号
SMBus Low Power Version-低功耗版本

一个I2C控制器(adaptor)是否支持某一个I2C,SMBus功能,可以从 Functionality flag中得到。比如 Functionality flag: I2C_FUNC_SMBUS_QUICK,表示需

要 I2C 控制器支持 SMBus Quick Command。

I2C系统中的重要结构体#

1.i2c_adapter:

/* i2c */
int nr; //第几个i2c控制器
const struct i2c_algorithm *algo; //含有传输函数
struct i2c_algorithm {
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
};
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;
};

2.i2c_client:

unsigned short addr; //设备地址
struct i2c_adapter *adapter; //设备挂载的i2c总线
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
};

3.i2c_msg

从传输函数原型可以看到,有adapter,msgs,但是没有client,因为i2c_msg里面含有client的信息。

int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
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 */
};

4.i2c_transfer

统一的传输函数,用这个函数就不需要调用i2c_adapter里面的

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

示例:

I2Ctools#

无需编写驱动程序即可访问I2C设备

AP3216C是红外、光强、距离三合一的传感器,以读出光强、距离值为例。步骤如下:

  • 复位:往寄存器0写入0x4
  • 使能:往寄存器0写入0x3
  • 读光强:读寄存器0xC、0xD得到2字节的光强
  • 读距离:读寄存器0xE、0xF得到2字节的距离

AP3216C设备地址为0x1E,假设设备在I2C—BUS0上,操作如下:

  • 使用SMBus协议
i2cset -f -y 0 Ox1e 0 0x4
i2cset -f -y 0 0x1e 0 0x3
i2cget -f -y 0 0x1e 0xc w //读一个word,2个字节
i2cget -f -y 0 0x1e 0xe w
  • 使用I2C协议
i2ctransfer -f -y 0 w2@0x1e 0 0x4 //往设备0x1e写2个字节数据:0 0x4
i2ctransfer -f -y 0 w2@0x1e 0 0x3
i2ctransfer -f -y 0 w1@0x1e 0xc r2 //往设备0x1e写1个字节数据:0xc, 读2个字节
i2ctransfer -f -y 0 w1@0x1e 0xe r2

1.扫描和检测I2C总线上的设备

i2cdetect [-y] [-r] [BUS-NUMBER]

参数说明:

  • -y:使用自动模式,不需要用户确认。
  • -r:以绑定引脚模式运行,适用于特定的硬件配置。
  • BUS-NUMBER:指定要扫描的 I2C 总线号,如果没有指定,默认为 0。

调用 i2cdetect 命令后,它会扫描指定的 I2C 总线上的所有设备,并将它们显示在一个表格中。表格中的每个单元格表示一个设备地址,如果该地址上找到了设备,就会显示地址值,否则显示 “–”。

i2cset
Usage: i2cset [-fy] [-m MASK] BUS CHIP-ADDRESS DATA-ADDRESS [VALUE] ... [MODE]
Set I2C registers
I2CBUS I2C bus number
ADDRESS 0x03-0x77
MODE is:
c Byte, no value
b Byte data (default)
w Word data
i I2C block data
s SMBus block data
Append p for SMBus PEC
-f Force access
-y Disable interactive mode
-r Read back and compare the result
-m MASK Mask specifying which bits to write

3.i2cget

Usage: i2cget [-fy] BUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]
Read from I2C/SMBus chip registers
I2CBUS I2C bus number
ADDRESS 0x03-0x77
MODE is:
b Read byte data (default)
w Read word data
c Write byte/read byte
Append p for SMBus PEC
-f Force access
-y Disable interactive mode

在APP里,有这几个问题:

  • 怎么指定I2C控制器? i2c-dev.c提供为每个I2C控制器(I2CBus、I2C Adapter)都生成个设备节点:/dev/i2c-0、/dev/i2c-1等等 open某个/dev/i2c-X节点,就是去访问该l2C控制器下的设备

  • 怎么指定I2C设备?

    • 通过ioctl指定I2C设备的地址

    • ioctl(file,12C_SLAVE,address)

      如果该设备已经有了对应的设备驱动程序,则返回失败

    • ioctl(file,12C_SLAVE_FORCE,address)

      如果该设备已经有了对应的设备驱动程序,但是还是想通过i2c-dev驱动来访问它,则使用这个ioctl来指定I2C设备地址

  • 怎么传输数据? 两种方式: 一般的I2C方式:ioctl(file,I2C_RDWR,&rdwr) SMBus方式:ioctl(file,I2C_SMBUS,&args)

编写APP直接访问EEPROM#

确定I2C控制器:插上EEPROM后,i2c_detect -y 0

IMX6ull:i2c-0总线

直接使用i2c_tools里面的函数,参考驱动大全-编写APP直接访问EEPROM与完全开发手册


请点击左侧菜单(移动端为右下角)选择要查看的所有笔记吧。

最近更新于