当前位置: 首页 > news >正文

Linux学习笔记--IIC子系统

一、一些概念

APP:只需要调用设备驱动程序提供的接口

IIC设备驱动:知道设备地址、数据格式

IIC控制器驱动:根据IIC协议发出各种信号,IIC设备地址、IIC存储地址、数据

        对于Linux来说APP可以通过两类驱动程序访问设备:

1、I2C设备自己的驱动程序

2、内核自带的i2c-dev.c驱动程序,它是i2c控制器驱动程序暴露给用户空间的驱动程序(i2c-dev.c)

         对于I2C Device Driver

1、I2C设备自己的驱动程序

2、内核自带的i2c-dev.c驱动程序,它是i2c控制器驱动程序暴露给用户空间的驱动程序(i2c-dev.c)

         I2C Controller Driver

1、芯片I2C控制器的驱动程序(称为adapter)

2、使用GPIO模拟的I2C控制器驱动程序(i2c-gpio.c)

二、一些结构体和函数

1、struct i2c_adapter

在Linux中:

  • 怎么表示I2C Controller

    • 一个芯片里可能有多个I2C Controller,比如第0个、第1个、……

    • 对于使用者,只要确定是第几个I2C Controller即可

    • 使用i2c_adapter表示一个I2C BUS,或称为I2C Controller

    • 里面有2个重要的成员:

      • nr:第几个I2C BUS(I2C Controller)

      • i2c_algorithm,里面有该I2C BUS的传输函数,用来收发I2C数据

    • i2c_adapter

/** i2c_adapter is the structure used to identify a physical i2c bus along* with the access algorithms necessary to access it.*/
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、struct i2c_algorithm

struct i2c_algorithm {/* If an adapter algorithm can't do I2C-level access, set master_xferto NULL. If an adapter algorithm can do SMBus access, setsmbus_xfer. If set to NULL, the SMBus protocol is simulatedusing common I2C messages *//* master_xfer should return the number of messages successfullyprocessed, 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
};

3、struct i2c_client

怎么表示I2C Device

  • 一个I2C Device,有设备地址

  • 它连接在哪个I2C Controller上,即对应的i2c_adapter是什么

  • 使用i2c_client来表示一个I2C Device

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
};

4、struct i2c_msg

怎么表示要传输的数据

  • 在上面的i2c_algorithm结构体中可以看到要传输的数据被称为:i2c_msg

  • i2c_msg

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_msg中的flags用来表示传输方向:bit 0等于I2C_M_RD表示读,bit 0等于0表示写

  • 一个i2c_msg要么是读,要么是写

  • 举例:设备地址为0x50的EEPROM,要读取它里面存储地址为0x10的一个字节,应该构造几个i2c_msg?

    • 要构造2个i2c_msg

    • 第一个i2c_msg表示写操作,把要访问的存储地址0x10发给设备

    • 第二个i2c_msg表示读操作

5、i2c_transfer()函数

i2c_transfer() 是Linux I2C子系统中的核心枢纽函数,它负责:

  • 承上:接收来自设备驱动(如AT24C02驱动)的传输请求

  • 启下:通过I2C控制器驱动在物理线上产生标准的I2C波形

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

参数解析:

参数解析:
struct i2c_adapter *adapI2C控制器适配器,代表具体的I2C硬件控制器包含硬件操作函数指针(算法)struct i2c_msg *msgsI2C消息数组,描述要传输的数据每个消息代表一个独立的I2C起停序列int num消息数组中消息的数量

函数内部执行流程

1. 参数验证与准备
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{// 检查适配器和消息有效性if (adap->algo->master_xfer) {// 硬件特定的传输函数ret = adap->algo->master_xfer(adap, msgs, num);}return ret;
}
2. 硬件控制器执行传输(以模拟GPIO为例)
// 这是I2C控制器的具体实现
static int i2c_gpio_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{// 遍历所有消息for (int i = 0; i < num; i++) {struct i2c_msg *msg = &msgs[i];// 生成START信号i2c_start(adap);// 发送设备地址 + R/W位u8 addr_byte = (msg->addr << 1) | (msg->flags & I2C_M_RD ? 1 : 0);if (!i2c_send_byte(adap, addr_byte)) {// 无ACK,传输失败i2c_stop(adap);return -EIO;}// 数据传输if (msg->flags & I2C_M_RD) {// 读操作for (int j = 0; j < msg->len; j++) {msg->buf[j] = i2c_recv_byte(adap);// 发送ACK(非最后一个字节)或NACK(最后一个字节)i2c_send_ack(adap, (j == msg->len - 1) ? 1 : 0);}} else {// 写操作for (int j = 0; j < msg->len; j++) {if (!i2c_send_byte(adap, msg->buf[j])) {i2c_stop(adap);return -EIO;}}}// 生成STOP信号if (msg->flags & I2C_M_STOP) {i2c_stop(adap);}}return num;
}

三、I2C-Tools

 APP通过I2C Controller与I2C Device传输数据。 所以使用I2C-Tools时也需要指定:

  • 哪个I2C控制器(或称为I2C BUS、I2C Adapter)

  • 哪个I2C设备(设备地址)

  • 数据:读还是写、数据本身

i2cdetect:I2C检测

// 列出当前的I2C Adapter(或称为I2C Bus、I2C Controller)
i2cdetect -l// 打印某个I2C Adapter的Functionalities, I2CBUS为0、1、2等整数
i2cdetect -F I2CBUS// 看看有哪些I2C设备, I2CBUS为0、1、2等整数
i2cdetect -y -a I2CBUS// 效果如下
# i2cdetect -l
i2c-1   i2c             STM32F7 I2C(0x40013000)                 I2C adapter
i2c-2   i2c             STM32F7 I2C(0x5c002000)                 I2C adapter
i2c-0   i2c             STM32F7 I2C(0x40012000)                 I2C adapter# i2cdetect -F 0
Functionalities implemented by /dev/i2c-0:
I2C                              yes
SMBus Quick Command              yes
SMBus Send Byte                  yes
SMBus Receive Byte               yes
SMBus Write Byte                 yes
SMBus Read Byte                  yes
SMBus Write Word                 yes
SMBus Read Word                  yes
SMBus Process Call               yes
SMBus Block Write                yes
SMBus Block Read                 yes
SMBus Block Process Call         yes
SMBus PEC                        yes
I2C Block Write                  yes
I2C Block Read                   yes// --表示没有该地址对应的设备, UU表示有该设备并且它已经有驱动程序,
// 数值表示有该设备但是没有对应的设备驱动
# i2cdetect -y -a 0  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00: 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- UU -- -- -- 1e --
20: -- -- UU -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

i2cget:I2C读

# i2cget
Usage: i2cget [-f] [-y] [-a] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]I2CBUS is an integer or an I2C bus nameADDRESS is an integer (0x03 - 0x77, or 0x00 - 0x7f if -a is given)MODE is one of:b (read byte data, default)w (read word data)c (write byte/read byte)Append p for SMBus PEC// 写一个字节: I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址//           DATA-ADDRESS就是要写的数据i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS// 给address写1个字节(address, value)://           I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址//           DATA-ADDRESS: 8位芯片寄存器地址; //           VALUE: 8位数值//           MODE: 可以省略,也可以写为bi2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE [b]// 给address写2个字节(address, value)://           I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址//           DATA-ADDRESS: 8位芯片寄存器地址; //           VALUE: 16位数值//           MODE: wi2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE w// SMBus Block Write:给address写N个字节的数据//   发送的数据有:address, N, value1, value2, ..., valueN//   跟`I2C Block Write`相比, 需要发送长度N//           I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址//           DATA-ADDRESS: 8位芯片寄存器地址; //           VALUE1~N: N个8位数值//           MODE: si2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE1 ... VALUEN s// I2C Block Write:给address写N个字节的数据//   发送的数据有:address, value1, value2, ..., valueN//   跟`SMBus Block Write`相比, 不需要发送长度N//           I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址//           DATA-ADDRESS: 8位芯片寄存器地址; //           VALUE1~N: N个8位数值//           MODE: ii2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE1 ... VALUEN i

i2ctransfer:I2C传输(不是基于SMBus)

# i2ctransfer
Usage: i2ctransfer [-f] [-y] [-v] [-V] [-a] I2CBUS DESC [DATA] [DESC [DATA]]...I2CBUS is an integer or an I2C bus nameDESC describes the transfer in the form: {r|w}LENGTH[@address]1) read/write-flag 2) LENGTH (range 0-65535) 3) I2C address (use last one if omitted)DATA are LENGTH bytes for a write message. They can be shortened by a suffix:= (keep value constant until LENGTH)+ (increase value by 1 until LENGTH)- (decrease value by 1 until LENGTH)p (use pseudo random generator until LENGTH with value as seed)Example (bus 0, read 8 byte at offset 0x64 from EEPROM at 0x50):# i2ctransfer 0 w1@0x50 0x64 r8
Example (same EEPROM, at offset 0x42 write 0xff 0xfe ... 0xf0):# i2ctransfer 0 w17@0x50 0x42 0xff-// Example (bus 0, read 8 byte at offset 0x64 from EEPROM at 0x50):
# i2ctransfer -f -y 0 w1@0x50 0x64 r8// Example (bus 0, write 3 byte at offset 0x64 from EEPROM at 0x50):
# i2ctransfer -f -y 0 w9@0x50 0x64 val1 val2 val3// Example 
// first: (bus 0, write 3 byte at offset 0x64 from EEPROM at 0x50)
// and then: (bus 0, read 3 byte at offset 0x64 from EEPROM at 0x50)
# i2ctransfer -f -y 0 w9@0x50 0x64 val1 val2 val3 r3@0x50  
# i2ctransfer -f -y 0 w9@0x50 0x64 val1 val2 val3 r3 //如果设备地址不变,后面的设备地址可省略

访问I2C设备

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)(i2ctransfer.c )
  • SMBus方式:ioctl(file, I2C_SMBUS, &args)(i2cget.c、i2cset.c )

使用I2C-Tools源码

#include <i2c/smbus.h>      // SMBus协议支持
#include "i2cbusses.h"      // I2C总线工具函数

 I2C设备初始化

// 打开I2C设备
file = open_i2c_dev(argv[1][0]-'0', filename, sizeof(filename), 0);
if (file < 0)
{printf("can't open %s\n", filename);return -1;
}// 设置从设备地址(AT24C02的地址是0x50)
if (set_slave_addr(file, dev_addr, 1))
{printf("can't set_slave_addr\n");return -1;
}

关键点

  • argv[1][0]-'0':将字符数字转换为整数(如'1'→1)

  • dev_addr = 0x50:AT24C02的固定I2C地址

  • 第三个参数1表示强制设置从设备地址

 写入操作实现

if (argv[2][0] == 'w')
{str = argv[3];  // 要写入的字符串// 设置延时20ms(AT24C02写入需要时间)req.tv_sec  = 0;req.tv_nsec = 20000000; /* 20ms */// 逐个字符写入while (*str){// 使用SMBus协议写入单字节数据ret = i2c_smbus_write_byte_data(file, mem_addr, *str);if (ret){printf("i2c_smbus_write_byte_data err\n");return -1;}// 等待写入完成(重要!)nanosleep(&req, NULL);mem_addr++;  // 地址递增str++;       // 指向下一个字符}// 写入字符串结束符ret = i2c_smbus_write_byte_data(file, mem_addr, 0);if (ret){printf("i2c_smbus_write_byte_data err\n");return -1;}
}

读取操作实现

else
{// 读取整个块数据ret = i2c_smbus_read_i2c_block_data(file, mem_addr, sizeof(buf), buf);if (ret < 0){printf("i2c_smbus_read_i2c_block_data err\n");return -1;}buf[31] = '\0';  // 确保字符串终止printf("get data: %s\n", buf);
}

关键函数说明

i2c_smbus_write_byte_data(int file, __u8 command, __u8 value)
  • 功能:向指定寄存器地址写入一个字节

  • 参数:

    • file:I2C设备文件描述符

    • command:设备内部地址(mem_addr)

    • value:要写入的数据(*str)

i2c_smbus_read_i2c_block_data(int file, __u8 command, __u8 length, __u8 *values)
  • 功能:从指定地址开始读取多个字节

  • 参数:

    • file:I2C设备文件描述符

    • command:起始地址(mem_addr)

    • length:要读取的字节数

    • values:存储读取数据的缓冲区

四、通用i2c-dev

Linux驱动程序: drivers/i2c/i2c-dev.c

核心:

static const struct file_operations i2cdev_fops = {.owner		= THIS_MODULE,.llseek		= no_llseek,.read		= i2cdev_read,.write		= i2cdev_write,.unlocked_ioctl	= i2cdev_ioctl,.compat_ioctl	= compat_i2cdev_ioctl,.open		= i2cdev_open,.release	= i2cdev_release,
};

五、I2C系统驱动程序模型

I2C Core就是I2C核心层,它的作用:

  • 提供统一的访问函数,比如i2c_transfer、i2c_smbus_xfer等

  • 实现I2C总线-设备-驱动模型,管理:I2C设备(i2c_client)、I2C设备驱动(i2c_driver)、I2C控制器(i2c_adapter)

i2c_driver表明能支持哪些设备:

  • 使用of_match_table来判断

    • 设备树中,某个I2C控制器节点下可以创建I2C设备的节点

      • 如果I2C设备节点的compatible属性跟of_match_table的某项兼容,则匹配成功

    • i2c_client.name跟某个of_match_table[i].compatible值相同,则匹配成功

  • 使用id_table来判断

    • i2c_client.name跟某个id_table[i].name值相同,则匹配成功

i2c_driver跟i2c_client匹配成功后,就调用i2c_driver.probe函数。

创建i2c_client

1、通过I2C bus number来创建

int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len);

通过设备树来创建

	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_client:

i2c_new_device

  static struct i2c_board_info sfe4001_hwmon_info = {I2C_BOARD_INFO("max6647", 0x4e),};int sfe4001_init(struct efx_nic *efx){(...)efx->board_info.hwmon_client =i2c_new_device(&efx->i2c_adap, &sfe4001_hwmon_info);(...)}

i2c_new_probed_device

  static const unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END };static int usb_hcd_nxp_probe(struct platform_device *pdev){(...)struct i2c_adapter *i2c_adap;struct i2c_board_info i2c_info;(...)i2c_adap = i2c_get_adapter(2);memset(&i2c_info, 0, sizeof(struct i2c_board_info));strscpy(i2c_info.type, "isp1301_nxp", sizeof(i2c_info.type));isp1301_i2c_client = i2c_new_probed_device(i2c_adap, &i2c_info,normal_i2c, NULL);i2c_put_adapter(i2c_adap);(...)}
  • i2c_new_device:会创建i2c_client,即使该设备并不存在

  • i2c_new_probed_device:

    • 它成功的话,会创建i2c_client,并且表示这个设备肯定存在

    • I2C设备的地址可能发生变化,比如AT24C02的引脚A2A1A0电平不一样时,设备地址就不一样

    • 可以罗列出可能的地址

    • i2c_new_probed_device使用这些地址判断设备是否存在

         通过用户空间(user-space)生成 调试时、或者不方便通过代码明确地生成i2c_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

六、设备驱动i2c_driver

设备树匹配表

static const struct of_device_id of_match_ids_example[] = {{ .compatible = "com_name,chip_name", .data = NULL },{ /* END OF LIST */ },
};

作用:通过设备树的compatible属性匹配设备

  • "com_name,chip_name":对应设备树中的compatible = "com_name,chip_name"

  • .data:可传递驱动私有数据

I2C设备ID表

作用:非设备树方式匹配设备

  • "chip_name":设备名称,用于模块参数或旧式匹配

 驱动核心函数

static int i2c_driver_example_probe(struct i2c_client *client)

调用时机:当内核发现匹配的设备时自动调用
参数i2c_client结构体,包含设备信息(地址、适配器等)
作用:初始化设备,分配资源,注册设备操作

I2C驱动结构体

static struct i2c_driver i2c_example_driver = {.driver = {.name = "example",                    // 驱动名称.of_match_table = of_match_ids_example, // 设备树匹配表},.probe_new = i2c_driver_example_probe,    // 探测函数.remove = i2c_driver_example_remove,      // 移除函数  .id_table = example_ids,                  // 设备ID表
};

驱动加载和匹配流程

1. 模块加载

insmod example_driver.ko
  • 调用i2c_driver_example_init()

  • 注册驱动到I2C子系统

2. 设备匹配

设备树方式

&i2c1 {example_device: example@50 {compatible = "com_name,chip_name";reg = <0x50>;};
};

传统方式

echo chip_name 0x50 > /sys/bus/i2c/devices/i2c-1/new_device

当匹配成功时:

  1. 内核创建i2c_client结构体

  2. 调用i2c_driver_example_probe()

  3. 驱动完成设备初始化

http://www.dtcms.com/a/446559.html

相关文章:

  • 网站公网安备链接怎么做百度上推广一个网站该怎么做
  • 狗头网网站营销运营平台
  • LeetCode 236. 二叉树的最近公共祖先
  • 理解 Python 装饰器:@ 的强大功能
  • C++进阶(7)——包装器
  • Redis应用场景(黑马点评快速复习)
  • 泉州建站模板搭建深圳工业设计有限公司
  • 外贸出口工艺品怎么做外贸网站想自学做网站
  • 【Docker项目实战】使用Docker部署Dokuwiki个人知识库
  • 建设实验中心网站c2c网站价格
  • arp broadcast enable 概念及题目
  • 在搜狐快站上做网站怎么跳转做商品网站需要营业执照
  • 为什么多智能体系统需要记忆工程
  • C++:string 类
  • [crackme]019-CrackMe3
  • 宠物寄养网站毕业设计营销网站建设专业团队在线服务
  • C++11学习笔记
  • 搜狐快速建站郴州市做网站
  • 在Linux中重定向,复制cp,硬链接的区别,Linux底层存储数据原理。
  • 软考~系统规划与管理师考试—知识篇—V2.0—考试科目2:系统规划与管理案例分析—题型分类—第七章 IT 服务持续改进—20192021
  • NopGraphQL 的设计创新:从 API 协议到通用信息操作引擎
  • 概率论:分布与检验
  • 网站后台视频教程视频号怎么付费推广
  • 浦江网站建设微信开发wordpress 浏览计数
  • 嵌入式开发学习日志35——stm32之超声波测距
  • 山东建设厅官方网站一级建造师搜索引擎平台
  • MATLAB计算有效干旱指数(Effective drought index, EDI)
  • 网站推广如何收费现在建一个网站一年费用只要几百元
  • 如何自己做游戏网站如何建设万网网站
  • 江苏省建设厅八大员考试报名网站石家庄有哪些公司可以做网站