GitHub: I2C

Email: BuddyZhang1 buddy.zhang@aliyun.com

目录

  1. I2C 原理

  2. Uboot 中通过工具访问 I2C

  3. Uboot 中通过源码访问 I2C

  4. Kernel 中通过源码访问 I2C

  5. 用户空间通过工具访问 I2C

  6. 用户空间通过源码访问 I2C

  7. 附录


I2C 协议

Menuconfig1

2条双向串行线,一条数据线SDA,一条时钟线SCL

总线空闲状态

I2C 总线总线的 SDA 和 SCL 两条信号线同时处于高电平时,规定为总线的空闲状态。此 时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电 阻把电平拉高。

启动信号

Menuconfig1

在时钟线 SCL 保持高电平期间,数据线 SDA 上的电平被拉低(即负跳变),定义为 I2C 总线总线的启动信号,它标志着一次数据传输的开始。启动信号是一种电平跳变时序 信号,而不是一个电平信号。启动信号是由主控器主动建立的,在建立该信号之前I2C总 线必须处于空闲状态。

重启动信号

在主控器控制总线期间完成了一次数据通信(发送或接收)之后,如果想继续占用总线再 进行一次数据通信(发送或接收),而又不释放总线,就需要利用重启动Sr信号时序。重 启动信号Sr既作为前一次数据传输的结束,又作为后一次数据传输的开始。利用重启动信 号的优点是,在前后两次通信之间主控器不需要释放总线,这样就不会丢失总线的控制 权,即不让其他主器件节点抢占总线。

停止信号

在时钟线 SCL 保持高电平期间,数据线 SDA 被释放,使得 SDA 返回高电平(即正跳 变),称为 I2C 总线的停止信号,它标志着一次数据传输的终止。停止信号也是一种电 平跳变时序信号,而不是一个电平信号,停止信号也是由主控器主动建立的,建立该信 号之后,I2C 总线将返回空闲状态。不是在数据有效性中规定在 SDA 只能在 SCL 的低电 平的时候变化,为何 STAR,STOP 不一样?首先 STAR 和 STOP 不是数据,所以可以不遵 守数据有效性中的规定,其它数据都遵守,而 STAR 和 STOP“不遵守”导致 STAR 和 STOP 更容易被识别。这样不是不遵守而是更有优势。

起始和停止条件一般由主机产生,总线在起始条件后被认为处于忙的状态,在停止条件的 某段时间后总线被认为再次处于空闲状态。如果产生重复起始 (Sr) 条件而不产生停止条 件,总线会一直处于忙的状态。此时的起始条件 (S) 和重复起始 (Sr) 条件在功能上是 一样的。如果连接到总线的器件合并了必要的接口硬件,那么用它们检测起始和停止条件 十分简便。但是没有这种接口的微控制器在每个时钟周期至少要采样SDA 线两次来判别有 没有发生电平切换。

数据位传送

Menuconfig1

在 I2C 总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在 SCL 串 行时钟的配合下,在 SDA 上逐位地串行传送每一位数据。进行数据传送时,在 SCL 呈现 高电平期间,SDA 上的电平必须保持稳定,低电平为数据 0,高电平为数据 1。只有在 SCL 为低电平期间,才允许 SDA 上的电平改变状态。逻辑 0 的电平为低电压,而逻辑 1 的电平取决于器件本身的正电源电压 VDD(当使用独立电源时)。数据位的传输是边沿 触发。

应答信号

I2C 总线上的所有数据都是以 8 位字节传送的,发送器每发送一个字节,就在时钟脉冲 9 期间释放数据线,由接收器反馈一个应答信号。 应答信号为低电平时,规定为有效应 答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时, 规定为非应答位(NACK),一般表示接收器接收该字节没有成功。 对于反馈有效应答位 ACK 的要求是,接收器在第 9 个时钟脉冲之前的低电平期间将SDA 线拉低,并且确保在 该时钟的高电平期间为稳定的低电平。 如果接收器是主控器,则在它收到最后一个字节 后,发送一个 NACK 信号,以通知被控发送器结束数据发送,并释放 SDA 线,以便主控 接收器发送一个停止信号 P。

插入等待时间

Menuconfig1

如果被控器需要延迟下一个数据字节开始传送的时间,则可以通过把时钟线 SCL 电平拉 低并且保持,使主控器进入等待状态。一旦被控器释放时钟线,数据传输就得以继续下去, 这样就使得被控器得到足够时间转移已经收到的数据字节,或者准备好即将发送的数据字 节。带有 CPU 的被控器在对收到的地址字节做出应答之后,需要一定的时间去执行中断 服务子程序,来分析或比较地址码,其间就把 SCL 线钳位在低电平上,直到处理妥当后 才释放 SCL 线,进而使主控器继续后续数据字节的发送。

总线封锁状态

在特殊情况下,如果需要禁止所有发生在I2C总线上的通信活动,封锁或关闭总线是一种 可行途径,只要挂接于该总线上的任意一个器件将时钟线SCL锁定在低电平上即可。

总线竞争的仲裁

总线上可能挂接有多个器件,有时会发生两个或多个主器件同时想占用总线的情况,这种 情况叫做总线竞争。I2C总线具有多主控能力,可以对发生在SDA线上的总线竞争进行仲裁, 其仲裁原则是这样的:当多个主器件同时想占用总线时,如果某个主器件发送高电平,而 另一个主器件发送低电平,则发送电平与此时SDA总线电平不符的那个器件将自动关闭其 输出级。总线竞争的仲裁是在两个层次上进行的。首先是地址位的比较,如果主器件寻址 同一个从器件,则进入数据位的比较,从而确保了竞争仲裁的可靠性。由于是利用I2C总 线上的信息进行仲裁,因此不会造成信息的丢失。为何识别到“0”将丢失仲裁呢?因为对于 OD门,只能驱动到低电平,释放总线只能通过不驱动总线释放,停止驱动即产生“1”,但 是发现总线还是“0”,这说明还有主机在跟自己竞争总线使用权,自己线驱动到“1”,确检 测到“0”,那代表自己已经失去了仲裁。

主机只能在总线空闲的时侯启动传送。两个或多个主机可能在起始条件的最小持续时间 tHD;STA 内产生一个起始条件,结果在总线上产生一个规定的起始条件。当SCL 线是高电 平时,仲裁在SDA 线发生;这样,在其他主机发送低电平时,发送高电平的主机将断开它 的数据输出级,因为总线上的电平与它自己的电平不相同。然后,进一步获得其的判定条 件:

  1. 仲裁可以持续多位。首先是比较地址位。如果每个主机都试图寻址同一的器件,仲 裁会继续比较数据位(假设主机是发送器),或者比较响应位(假设主机是接收器)。

  2. I2C 总线的地址和数据信息由赢得仲裁的主机决定,在仲裁过程中不会丢失信息。 丢失仲裁的主机可以产生时钟脉冲直到丢失仲裁的该字节末尾。

  3. 在串行传输过程中时,一旦有重复的起始条件或停止条件发送到I2C 总线的时侯, 仲裁过程仍在进行。如果可能产生这样的情况,有关的主机必须在帧格式相同位置 发送这个重复起始条件或停止条件。

  4. 此外,如果主机也结合了从机功能,而且在寻址阶段丢失仲裁,它很可能就是赢得 仲裁的主机在寻址的器件。那么,丢失仲裁的主机必须立即切换到它的从机模式。

  5. I2C 总线的控制只由地址或主机码以及竞争主机发送的数据决定,没有中央主机, 总线也没有任何定制的优先权。

时钟信号的同步

在 I2C 总线上传送信息时的时钟同步信号是由挂接在SCL线上的所有器件的逻辑“与”完成 的。SCL 线上由高电平到低电平的跳变将影响到这些器件,一旦某个器件的时钟信号下跳 为低电平,将使SCL线一直保持低电平,使SCL线上的所有器件开始低电平期。此时,低电 平周期短的器件的时钟由低至高的跳变并不能影响SCL线的状态,于是这些器件将进入高 电平等待的状态。当所有器件的时钟信号都上跳为高电平时,低电平期结束,SCL线被释 放返回高电平,即所有的器件都同时开始它们的高电平期。其后,第一个结束高电平期的 器件又将SCL线拉成低电平。这样就在SCL线上产生一个同步时钟。可见,时钟低电平时间 由时钟低电平期最长的器件确定,而时钟高电平时间由时钟高电平期最短的器件确定。


Uboot 中使用 I2C

Uboot 中使用 I2C 包括两个方面:I2C 工具使用以及源码中使用 I2C。

相关 github 主页

I2C 工具使用

uboot 中提供了一套 i2c 工具,可以通过命令行方式使用这套工具去操作 I2C 总线。为 了使用这套工具,开发者在 uboot 启动之后,按住回车进入 uboot 的命令行模式。进入 命令行模式之后,开发者可以输入如下命令使用工具:

查看 i2c 工具帮助:

ZynqMP> help i2c
i2c - I2C sub-system

Usage:
i2c bus [muxtype:muxaddr:muxchannel] - show I2C bus info
crc32 chip address[.0, .1, .2] count - compute CRC32 checksum
i2c dev [dev] - show or set current I2C bus
i2c loop chip address[.0, .1, .2] [# of objects] - looping read of device
i2c md chip address[.0, .1, .2] [# of objects] - read from I2C device
i2c mm chip address[.0, .1, .2] - write to I2C device (auto-incrementing)
i2c mw chip address[.0, .1, .2] value [count] - write to I2C device (fill)
i2c nm chip address[.0, .1, .2] - write to I2C device (constant address)
i2c probe [address] - test for and show device(s) on the I2C bus
i2c read chip address[.0, .1, .2] length memaddress - read to memory
i2c write memaddress chip address[.0, .1, .2] length [-s] - write memory
          to I2C; the -s option selects bulk write in a single transaction
i2c reset - re-init the I2C Controller
i2c speed [speed] - show or set I2C bus speed

I2C 总线操作

命令格式:

i2c dev [dev]

查看当 I2C 总线

ZynqMP> i2c dev
Current bus is 0
ZynqMP>

设置当前 I2C 总线

ZynqMP> i2c dev 0
Current bus is 0
ZynqMP>

I2C 探测操作

命令模式

i2c probe [address]

探测当前总线上的所有可用 I2C 设备

ZynqMP> i2c probe
Valid chip addresses: 50 51
ZynqMP>

探测指定 I2C 从设备是否可用

ZynqMP> i2c probe 0x50
Valid chip addresses: 50
ZynqMP>

I2C 读操作

命令模式

i2c md chip address[.0, .1, .2] [# of objects]

例如,从 I2C 总线 0 上,slave 地址为 0x50,偏移地址为 0x2 的地方读取一个字节 的数据

ZynqMP> i2c md 0x50 0x2 1                                                       
0001: 30    0                                                                   
ZynqMP>

I2C 写操作

命令模式:

i2c mw chip address[.0, .1, .2] value [count]

例如,向 I2C 总线 0 上,slave 地址为 0x50,偏移地址为 0x1 的地方,写入 0x12.

ZynqMP> i2c md 0x50 0x1                                                         
0001: 00    .                                                                   
ZynqMP> i2c mw 0x50 0x1 0x12                                                    
ZynqMP> i2c md 0x50 0x1                                                         
0001: 12    .

Uboot 源码中使用 I2C

Uboot 阶段源码中要使用 I2C,可以参考如下代码:

/*
* I2C read/write on Uboot
*
* (C) 2018.11.12 BiscuitOS <buddy.zhang@aliyun.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <i2c.h>
#include <errno.h>

/* I2C write */
static int i2c_demo_write(uint i2c_addr, ulong offset, uchar value)
{
    i2c_write(i2c_addr, offset, 1, &value, 1);

    return 0;
}

/* I2C read */
static int i2c_demo_read(uint i2c_addr, ulong offset, uchar &value)
{
    i2c_read(i2c_addr, offset, 1, &value, 1);

    return 0;
}

Kernel 里使用 I2C

Kernel I2C on github

Kernel 往往需要通过 I2C 协议从设备上读取或写入相应的信息,开发者可以使用如下命 令快速实践代码:

cd /tmp/
mkdir I2C
cd I2C
git init
git remote add -f  origin https://github.com/BiscuitOS/HardStack.git
git config core.sparsecheckout true
echo "i2c" >> .git/info/sparse-checkout
git pull origin master
cd bus/i2c/kernel
make
sudo insmod i2c.ko
dmesge | tail -n 10
sudo rmmod i2c.ko

Menuconfig1

通过上面的命令,开发者可以获得一份 I2C 驱动源码,并动态编译加入运行的 Linux 内 核中。其中驱动源码和 Makefile 脚本如下:

/*
* I2C read/write on Kernel
*
* (C) 2018.11.17 BiscuitOS <buddy.zhang@aliyun.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/of.h>

#define DEV_NAME         "i2cdemo"
#define SLAVE_I2C_ADDR   0x50

/*
* Slave information on DTS
*   I2C Bus:    I2C 0
*   Slave addr: 0x50
*
* &i2c0 {
*      status = "okay";
*              i2cdemo@50 {
*                      compatible = "i2cdemo,eeprom";
*                      reg = <0x50>;           
*              };
* };
*/

/*
* Write data into Slave device on I2C Bus.
*
* client: Slave client.
* reg: offset on slave device.
* data: need to write.
* count: number for write.
*/
static int i2c_demo_write(struct i2c_client *client, unsigned char reg,
                                 unsigned char *data, unsigned long count)
{
    int ret;

    struct i2c_msg msgs[] = {
        {
            .addr  = client->addr,
            .flags = client->flags & I2C_M_TEN,
            .len   = 1,
            .buf   = &reg, /* Offset address on slave device */
        },
        {
            .addr  = client->addr,
            .flags = client->flags,
            .len   = count,
            .buf   = data, /* value need to write */
        }
    };

    ret = i2c_transfer(client->adapter, msgs, 2);
    if (2 != ret) {
        printk(KERN_ERR "%s fail\n", __func__);
        return -1;
    }

    return 0;
}

/*
* Read data from slave device on I2C Bus.
*
* client: Slave client
* reg: offset on slave device.
* data: store ready data.
* count: number for read.
*/
static int i2c_demo_read(struct i2c_client *client, unsigned char reg,
                           unsigned char *data, unsigned long count)
{
    int ret;

    struct i2c_msg msgs[] = {
        {
            .addr  = client->addr,
            .flags = client->flags & I2C_M_TEN,
            .len   = 1,
            .buf   = &reg, /* Offset address on slave device */
        },
        {
            .addr  = client->addr,
            .flags = client->flags | I2C_M_RD,
            .len   = count,
            .buf   = data, /* value need to write */
        }
    };

    ret = i2c_transfer(client->adapter, msgs, 2);
    if (2 != ret) {
        printk(KERN_ERR "%s fail\n", __func__);
        return -1;
    }

    return 0;
}

/* probe entence */
static int i2c_demo_probe(struct i2c_client *client,
                            const struct i2c_device_id *id)
{
    unsigned char addr = 0x20;
    unsigned char buf;

    /* Read data from I2C Bus */
    i2c_demo_read(client, addr, &buf, 1);
    printk(KERN_INFO "Origin-Data: %#x\n", (unsigned int)buf);

    buf = 0x68;
    /* Write data into I2C Bus */
    i2c_demo_write(client, addr, &buf, 1);

    /* Read data from I2C Bus */
    i2c_demo_read(client, addr, &buf, 1);
    printk(KERN_INFO "Modify-Data: %#x\n", (unsigned int)buf);
    
    return 0;
}

/* remove entence */
static int i2c_demo_remove(struct i2c_client *client)
{
    return 0;
}

static struct of_device_id i2c_demo_match_table[] = {
    { .compatible = "i2cdemo,eeprom", },
    { },
};

static const struct i2c_device_id i2c_demo_id[] = {
    { DEV_NAME, SLAVE_I2C_ADDR},
    {},
};

static struct i2c_driver i2c_demo_driver = {
    .driver = {
        .name  = DEV_NAME,
        .owner = THIS_MODULE,
        .of_match_table = i2c_demo_match_table,
    },
    .probe    = i2c_demo_probe,
    .remove   = i2c_demo_remove,
    .id_table = i2c_demo_id,
};

module_i2c_driver(i2c_demo_driver);

/* Module information */
MODULE_DESCRIPTION("i2c demo");
MODULE_LICENSE("GPL v2");

Makefile

obj-m += i2c.o

KERNELDIR ?= /lib/modules/$(shell uname -r)/build

PWD       := $(shell pwd)

ROOT := $(dir $(M))
DEMOINCLUDE := -I$(ROOT)../include -I$(ROOT)/include

GCCVERSION = $(shell gcc -dumpversion | sed -e 's/\.\([0-9][0-9]\)/\1/g' -e 's/\.\([0-9]\)/0\1/g' -e 's/^[0-9]\{3,4\}$$/&00/')

GCC49 := $(shell expr $(GCCVERSION) \>= 40900)

all:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

install: all
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
        depmod -a

clean:
        rm -rf *.o *.o.d *~ core .depend .*.cmd *.ko *.ko.unsigned *.mod.c .tmp_versions *.symvers \
        .cache.mk *.save *.bak Modules.* modules.order Module.markers *.bin

CFLAGS_i2c.o := -Wall $(DEMOINCLUDE)

ifeq ($(GCC49),1)
        CFLAGS_i2c.o += -Wno-error=date-time
endif

CFLAGS_i2c.o := $(DEMOINCLUDE)

核心数据分析

ARM 体系在编写 I2C slave 驱动时,需要在 DTS 中加入 slave 设备相关信息,开发者 根据 slave 所在的 I2C 总线添加相应的 DTS 信息。例根据上面实例代码,在 I2C 总线 0 上存在一个 slave 设备,名字叫 i2cdemo,其 i2c 地址是 0x50,所以可以按如下方 法编写 slave 的 DTS 描述:

&i2c0 {
    status = "okay";
        i2cdemo@50 {
            compatible = "i2cdemo,eeprom";
            reg = <0x50>;           
        };
};

为了让 I2C 总线在总线初始化遍历期间能够找到 Slave 设备,开发者必须提供准确的驱 动数据。数据描述如下:

i2c_device_id 结构

static const struct i2c_device_id i2c_demo_id[] = {
    { DEV_NAME, SLAVE_I2C_ADDR},
    {},
};

该结构用于 I2C 总线在 match driver 时使用。

of_device_id 结构

static struct of_device_id i2c_demo_match_table[] = {
    { .compatible = "i2cdemo,eeprom", },
    { },
};

这个结构用于 I2C 总线 match DTS 中的 I2C 设备,其中 compatible 属性必须和 DTS 定义的一致,不然找不到 Slave 设备。

i2c 读操作

i2c 读操作依赖于 i2c_msg 结构和 i2c_transfer()。每次读操作之前,应提供两个 i2c_msg 结构,第一个结构用于发送 i2c 从设备需要读的偏移地址,第二个 i2c_msg 结构主要用于从 slave 设备中读数据的数量以及存储读入数据的缓存。

static int i2c_demo_read(struct i2c_client *client, unsigned char reg,
                           unsigned char *data, unsigned long count)
{
    int ret;

    struct i2c_msg msgs[] = {
        {
            .addr  = client->addr,
            .flags = client->flags & I2C_M_TEN,
            .len   = 1,
            .buf   = &reg, /* Offset address on slave device */
        },
        {
            .addr  = client->addr,
            .flags = client->flags | I2C_M_RD,
            .len   = count,
            .buf   = data, /* value need to write */
        }
    };

    ret = i2c_transfer(client->adapter, msgs, 2);
    if (2 != ret) {
        printk(KERN_ERR "%s fail\n", __func__);
        return -1;
    }

    return 0;
}

i2c 写操作

i2c 写操作也依赖于 i2c_msg 结构和 i2c_transfer()。每次读操作之前,应提供两个 i2c_msg 结构,第一个结构用于发送 i2c 从设备需要写的偏移地址,第二个 i2c_msg 结构主要用于从 slave 设备中写数据的数量以及写入的数据。

static int i2c_demo_write(struct i2c_client *client, unsigned char reg,
                                 unsigned char *data, unsigned long count)
{
    int ret;

    struct i2c_msg msgs[] = {
        {
            .addr  = client->addr,
            .flags = client->flags & I2C_M_TEN,
            .len   = 1,
            .buf   = &reg, /* Offset address on slave device */
        },
        {
            .addr  = client->addr,
            .flags = client->flags,
            .len   = count,
            .buf   = data, /* value need to write */
        }
    };

    ret = i2c_transfer(client->adapter, msgs, 2);
    if (2 != ret) {
        printk(KERN_ERR "%s fail\n", __func__);
        return -1;
    }

    return 0;
}

用户空间使用 I2C

用户空间有两种方法使用 I2C,一种是使用 i2c-tools 工具集,另外一种是在源码中访 问 I2C。开发者可以根据需要自行选择。

命令行方式访问 I2C

Linux 用户空间可以使用 i2c-tools 工具可以用户态访问 I2C,其包含了 i2cdump, i2cdetect, i2cget, i2cset 4 个工具。如果系统中没有这些工具,请使用如下工具进行 安装:

sudo apt install i2c-tools

i2cdetect

i2cdetect 工具用于探测 I2C 总线上所有的 I2C 从设备,从设备地址从 0x3 到 0x77. 这个工具使用格式如下:

Usage: i2cdetect [-F I2CBUS] [-l] [-y] [-a] [-q|-r] I2CBUS [FIRST LAST]

例如,使用 i2cdetect 探测 I2C 0 上所有的 slave 设备

# i2cdetect -y 0                                                                
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f                             
00:                                                                             
10:                                                                             
20:                                                                             
30: -- -- -- -- -- -- -- --                                                     
40:                                                                             
50: 50 51 -- -- -- -- -- -- -- -- -- -- -- -- -- --                             
60:                                                                             
70:

i2cdump

i2cdump 工具由于 dump 指定 I2C 总线上 slave 设备的所有内容,工具使用格式如下:

Usage: i2cdump [-f] [-r FIRST-LAST] [-y] BUS ADDR [MODE]

例如,使用 i2cdump 工具 dump 出 I2C 总线 0 上,slave 地址为 0x50 的所有内容, 如下:

# i2cdump -f -y 0 0x50
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 35 02 32 52 00 02 00 02 ff ff ff ff ff ff ff ff    5?2R.?.?........
10: aa aa aa aa aa aa ff ff ff ff ff ff ff ff ff ff    ??????..........
20: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
30: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
40: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
50: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
60: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
70: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
80: 93 00 73 14 13 05 00 20 00 00 00 00 ff ff ff ff    ?.s???. ........
90: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
a0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
b0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
c0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
d0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
f0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
#

i2cget

i2cget 工具用于在指定 I2C 总线上,读取指定 slave 设备偏移地址上的内容。 i2cget 相当于 i2c read 操作,其使用模式如下:

Usage: i2cget [-f] [-y] BUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]

例如,从 I2C 总线 0 上,读取 slave 设备地址为 0x50,偏移地址为 0x80 上的数据, 如下:

# i2cget -f -y 0 0x50 0x80
0x93

i2cset

i2cset 工具用于在 I2C 总线上,往 slave 设备偏移地址上写数据,其命令使用格式如 下:

Usage: i2cset [-f] [-y] [-m MASK] BUS CHIP-ADDR DATA-ADDR [VALUE] ... [MODE]

例如,在 I2C 总线 0 上,往 slave 地址为 0x50 的设备上,偏移地址为 0x40 的地址 上写入 0x68,如下:

# i2cget -f -y 0 0x50 0x40
0xff
# i2cset -f -y 0 0x50 0x40 0x68
# i2cget -f -y 0 0x50 0x40
0x68

用户空间应用程序中访问 I2C

应用程序有时也要访问 I2C 总线上的设备,开发者可以参考如下代码进行访问:

/*
* I2C demo on userspace
*
* (C) 2018.12.14 <buddy.zhang@aliyun.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#include <linux/i2c.h>
#include <linux/i2c-dev.h>

#define I2C_SLAVE_ADDR      0x50
#define I2C_BUS             "/dev/i2c-0"
#define I2C_M_WR            0x0

/*
* I2C read
* @fd: file handler
* @addr: i2c slave 7-bit address
* @offset: read position.
* @buf: buffer for reading data.
* @len: length for reading.
*
* @return: the number of i2c_msg has read.
*          succeed is 2.
*/
int I2CBus_packetRead(int fd, unsigned char addr, unsigned char offset,
                             unsigned char *buf, unsigned char len)
{
    unsigned char tmpaddr[2];
    struct i2c_msg msgs[2];
    struct i2c_rdwr_ioctl_data msgset;
    int rc;

    tmpaddr[0]     = offset;
    msgs[0].addr   = addr & 0xfe;
    msgs[0].flags  = I2C_M_WR;
    msgs[0].len    = 1;
    msgs[0].buf    = tmpaddr;

    msgs[1].addr   = addr & 0xfe;
    msgs[1].flags  = I2C_M_RD;  ;
    msgs[1].len    = len;
    msgs[1].buf    = buf;

    msgset.msgs    = msgs;
    msgset.nmsgs   = 2;

    rc = ioctl(fd, I2C_RDWR, &msgset);
    return rc;
}

/*
* I2C write
* @fd: file handler.
* @addr: i2c slave 7-bit address
* @offset: write position
* @buf: buffer for writuing data.
* @len: the length for writing
*
* @return: the number of i2c_msg has write.
*          succeed is 1.
*/
int I2CBus_packetWrite(int fd, unsigned char addr, unsigned char offset,
                              unsigned char *buf, unsigned char len)
{
    unsigned char tmpaddr[2];
    struct i2c_msg msgs[2];
    struct i2c_rdwr_ioctl_data msgset;
    int rc;

    tmpaddr[0]     = offset;
    tmpaddr[1]     = buf[0];
    msgs[0].addr   = addr & 0xfe;
    msgs[0].flags  = I2C_M_WR;
    msgs[0].len    = 2;
    msgs[0].buf    = tmpaddr;

    msgset.msgs    = msgs;
    msgset.nmsgs   = 1;

    rc = ioctl(fd, I2C_RDWR, &msgset);
    return rc;
}

int main()
{
    unsigned char value;
    int fd;

    fd = open(I2C_BUS, O_RDWR);
    if (fd < 0) {
        printf("Unable to open I2C Bus.\n");
        return -1;
    }

    /* Read Data from I2C */
    I2CBus_packetRead(fd, I2C_SLAVE_ADDR, 0x20, &value, 1);

    /* Write Dato into I2C */
    I2CBus_packetWrite(fd, I2C_SLAVE_ADDR, 0x20, &value, 1);

    return 0;
}

附录

I2C总线信号时序总结

BiscuitOS Home

BiscuitOS Driver

BiscuitOS Kernel Build

Linux Kernel

Bootlin: Elixir Cross Referencer

赞赏一下吧 🙂

MMU