Email: BuddyZhang1 buddy.zhang@aliyun.com

目录


AT24C08 简介

AT24C08 是一个提供 8192 bits 的串行可擦除和编程的只读内存 (EEPROM), 其包含了 1024 个 8 bit 的字节。AT24C08 针对许多工业和商业应用进行了 优化,在这些应用中,低功耗和低电压的特定显得特别突出。AT24C08 提供节省 空间的 8-lead PDIP,8-lead JEDEC SOIC,8-ball dBGA2, 8-leap MAP, 8-lead TSSOP, 以及 5-lead SOT23 封装。AT24C08 提供了标准的 I2C 接口, 可以通过两线的 I2C 总线访问 AT24C08. AT24C08 提供了 2.7V-5.5V 的版本, 以及 1.8V 至 5.5V 版本。AT24C08 Datasheet 链接如下:

AT24C08 Datasheet

市面上也提供了很过针对 AT24C08 的模块设计,例如下图:


实践准备

BiscuitOS 已经支持 AT24C08 的硬件实践,但在实践前需要作相应的准备, 必须要的准备有:


硬件准备

在开发 AT24C08 驱动前,开发者应该准备下列硬件.


硬件平台

开发者需要准备一块 RaspberryPi 4B 开发板,并配有 SD 卡,SD 读卡器, TTL 转 USB 串口一个。


AT24C08

开发者可以从网上购买 AT24C08 模块。


逻辑分析仪

逻辑分析仪能够帮助开发者快速分析数据,测试 AT24C08 功能,稳定性, 大量数据采样等。逻辑分析仪不是必须的,这里推荐使用 DreamSourceLab 开发的 DSLogic:

DSLogic 逻辑分析仪数据工具:


示波器

示波器能够帮助开发者对 I2C 总线进行最透彻的分析,示波器测量 的数据具有可靠性高,精度高的特定,是分析 I2C 问题不可或缺的 工具。示波器建议准备,这里推荐使用 DreamSourceLab 开发的 DsCope:

DSCope 示波器采用样图:


硬件连接

在准备好所有的硬件之后,接下来将 AT24C08 与 RaspberryPi 4B 连接, 连接如下图:

红线为 VCC (3.3V) 接 AT24C08 的 VCC 引脚; 黑线为 GND 接 AT24C08 的 GND 引脚; 黄线为 SDA 接 AT24C08 的 SDA 引脚; 绿线为 SCL 接 AT24C08 的 SCL 引脚. RaspberryPi 4B 的引脚定义如下图:

连接过程中要注意杜邦线虚连,连接完毕之后请使用万用表确保每条线 都已经连接上。


软件准备

在进行 AT24C08 软件开发前,开发这应该准备并部署所需的 软件工具及环境。如下表列出的内容:


RaspberryPi 4B 开发环境部署

在驱动开发前,开发者需要准备一个能在 RaspberryPi 4B 上运行的 BiscuitOS,具体制作方法可以参考下列文档:


内核启用 I2C 功能

为了在 RaspberryPi 上使用 AT24C08,开发者应该基于 BiscuitOS RaspberryPi 的开发环境对内核进行配置,开发者可以参考如下命令:

cd BiscuitOS/output/RaspberryPi_4B/linux/linux
make menuconfig ARCH=arm

选择并进入 “Device Driver”

选择并进入 “I2C support —>”

以模块的方式选择 “I2C device interface”

选择并进入 “I2C Hardware Bus support —>”

以模块的形式选择 “Broadcom BCM2835 I2C controller”, 最后保存并退出。 接着是编译内核和模块,并安装模块和内核到新的 SD 卡上,可以 参数如下文档:

准备好以上步骤之后,在系统启动之后,使用串口登录到 RaspberryPi, 使用如下命令安装 I2C 总线模块,如下:

cd /lib/modules/5.0.21/kernel/driver/i2c/bus
insmod busses/i2c-bcm2835.ko
insmod i2c-dev.ko

RaspberryPi 4B 启用 I2C 功能

制作完 BiscuitOS 镜像之后,将 BiscuitOS 镜像烧录到 SD 卡, 烧录完毕之后,重启插拔 SD 读卡器,此时可以获得 SD 卡出现 两个分区,其中一个为 BOOT 分区,如下图:

此时修改 BOOT 分区了的 “config.txt” 文件,RaspberryPi 默认是 关闭 I2C 功能的,此时将下面关于 I2C 的配置打开,如下:

# Uncomment some or all of these to enable the optional hardware interfaces
dtparam=i2c_arm=on
#dtparam=i2s=on
#dtparam=spi=on

确保 “dtparam=i2c_arm=on” 没有被注释掉。修改完毕之后,保存退出,最后 移除 SD 卡,将其重新插入到 RaspberryPi 4B 的 SD 卡槽里。


开发相关文档

AT24C08 驱动

BiscuitOS 为 AT24C08 提供了完整的内核驱动支持,开发者可以参考本节 内容,对 AT24C08 进行实践部署。


AT24C08 驱动分析

BiscuitOS 已经支持最新的 AT24C08 芯片,开发者可以使用 BiscuitOS 提供 的驱动对 AT24C08 进行使用,驱动源码 GithuB 地址如下:

BiscuitOS 提供的完整驱动如下:

驱动分作三个部分,第一个部分是 AT24C08 设备注册到对应总线上;第二部分是 AT24C08 提供 EEPROM 的读写接口;第三部分是驱动对 EEPROM 的使用。接下来的内容将详细分析 每一部分代码的构成。


AT24C08 设备注册

新版本内核中,I2C 子系统提供了一套简单的接口就可以将一个 I2C 设备 注册到 I2C 子系统。本例程通过向 DTS 中添加 AT24C08,并将 AT24C08 注册到 I2C 子系统。首先如下图:

函数首先定义一个 struct i2c_driver 结构体,然后填充该结构体。结构体中提供了 driver 成员,在 AT24C08 中需要提供 name, owner, 以及 of_match_table 三个 成员,其中 of_match_table 是一个 struct of_device_id 结构,该结构用于与 DTS 中的节点 compatible 属性进行对比,如果相同,即从 DTS 中找到驱动对应的节点, 例如在本例程中,compatible 属性的属性值是 “BiscuitOS,at24c08”; name 成员必须 与 struct i2c_device_id 结构的 name 成员相同,即总线上的驱动和设备通过名字进行 匹配,在上面的代码中即 at24c08_id 结果的 name 设置的与 at24c08_driver 结构的 driver 成员的 name 一致;至于 owner 成员,由于驱动以模块的形式添加到内核,那么 owner 设置为 THIS_MODULE 就行。最后提供 I2C 设备需要的 probe 和 remove 接口。

以上就是简单的驱动端程序设置,设置完毕之后调用 module_i2c_driver() 函数 将 at24c08_driver 对应的驱动注册到 I2C 子系统上。提供驱动代码还不能让驱动 正常的工作,还需 DTS 中添加 at24c08 对应的设备节点。开发者可以参考 BiscuitOS 提供的 RaspberryPi 4B 项目的方法进行添加。在 RaspberryPi 4B 中,DTS 位于 内核源码 arch/arm/boot/dts/bcm2711-rpi-4-b.dts, 添加如下:

在上面的 DTS 文件中,节点名字为 at24c08,与驱动中的 struct i2c_device_id name 成员相同。特别值得注意的是节点 reg 属性值必须与节点名字 @ 符号后面的数字 相同,这符合 DTS 语法。reg 属性即 AT24C08 I2C 的从设备地址。节点的 compatible 属性值必须与驱动的 struct of_device_id 一致。通过上面的代码,系统启动后可以 向系统注册一个简单的 AT24C08 模块。


AT24C08 I2C 接口

通过 AT24C08 的数据手册可以知道,AT24C08 提供了三种方式的读,包括随机地址读操作, 连续读操作,以及当前地址读操作。AT24C08 也提供了两种方式的写,包括按字节写,以及 按页写操作。接下里分别介绍每种操作如何编写代码。


AT24C08 随机地址读操作

上图为 AT24C08 定义的 Random Read 操作的 I2C 时序图,分析上图时序图,可以 读出简单的读写规则:在 SDA 信号线上产生一个 START 条件之后,AT24C08 在一个 I2C Message 周期内将将 AT24C08 的从设备地址和读地址写入到 I2C 总线上,在 这个周期中,I2C 控制器向 I2C 总线上按 MSB 到 LSB 的方式,发送从设备地址到 I2C 总线上,占用了 7 个 SCL 周期,接下来是一个 bit 代表 R/W 信号,此时 I2C 主控发送一个低电平,以此代表这次是一个写操作。操作完毕之后,等待从设备的 ACK, 如果 ACK 没有回应,那么主控产生一个 STOP 条件,停止传输;如果从设备 ACK 应答, 那么 I2C 主控继续将读地址按 MSB 到 LSB 的方向写到 I2C 总线上,写完的第九个周期, 如果从设备没有应答,那么操作失败;如果从设备回应,那么从设备会产生一个低电平将 I2C 总线在这个周期内拉低,此时主控收到这个 ACK 之后,主控将独立产生一个 START 条件,紧随其后的从设备地址,按 MSB 到 LSB 的方向写到 I2C 总线上,从地址写完的 下一个周期,主控产生一个高电平,以此表示此时操作是 READ 操作。之后主控等待从 设备的 ACK,从设备如果应答,那么会将 I2C 总线拉低,并将 8-bit 的数据写到总线 上,主控接收到以上信号后,自动将这 8-bit 抓取下来,以此作为读到的数据。最后 从设备不应答,所以主控产生一个 STOP 条件。如果以上操作完成,那么一个 Random Read 操作就算完成。由于第一个 I2C Message 的 R/W 周期为低,为写操作, 因此称该 I2C message 周期为 DUMMY WRITE。

上图即是根据信号协议编写的 Random Read。在内核中,I2C 子系统定义了 struct i2c_msg 结构为一个 I2C 帧,其定义如下:

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_DMA_SAFE          0x0200  /* the buffer of this message is DMA safe */
                                        /* makes only sense in kernelspace */
                                        /* userspace buffers are copied anyway */
#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                  */
};

根据 Random Read 的信号定义,在代码中首先定义两个 i2c_msg 结构体, 第一个 i2c_msg 用于生成 Dummy Write 帧,i2c_msg 的 addr 成员对应 Random Read 信号的 Device Address,其值设置为 client->addr, 即 AT24C08 的从设备地址;接下来设置 i2c_msg 的 flags 成员,此时不 包括 I2C_M_RD 宏,此次产生 Random Read 的 R/D 周期的低电平;i2c_msg 的 len 成员表示 I2C msg 的长度,由 Random Read 的 Dummy Write 定义可以知道,其后只用跟随一个 8-bit 的读地址,因此 i2c_msg 的 len 设置为 1,并且 buf 成员就是指向读地址。

第二个 i2c_msg 用于主控在获得从设备的 ACK 之后从 I2C 总线读取一个 字节的数据。根据 Random Read 的定义可以知道,当 dummy write 帧发送 完毕之后,从设备应答,那么主控会重新生成一个 START 条件,随后跟随 一个 AT24C08 的从设备地址,因此第二个 i2c_msg 的 addr 同样指向 AT24C08 的地址,即 client->addr; 此时向 I2C 总线发送完 Device Address 之后,主控需要产生一个高电平,因此此时需要将 i2c_msg 的 flags 设置为 I2C_M_RD, 接着主控会从总线上获得从设备的应答信号, 那么从总线上将 8bit 的数据存储在 i2c_msg 的 buf 成员里,因此 i2c_msg 的 len 成员设置为 1,并且 buf 指向存储的位置。

以上准备好 i2c_msg 之后,继续调用 i2c_transfer() 函数,将 i2c_msg 传递给 I2C 核心层,最终到达 I2C 控制器的收发器上发送。接下来使用 示波器和逻辑分析仪实际的抓起波形分析,具体如下:

从上图的波形图可以看出,抓取的波形符合 “Random Read” 的规范 要求。

借助 DSlogic 分析仪,可以快速获得数字波形以及协议解析信息, 上图的波形符合 “Random Read” 的要求。


AT24C08 连续读操作

上图为 AT24C08 定义的 Sequential Read 操作的 I2C 时序图,分析上图时序图,可以 读出简单的读写规则:连续读操作的前部操作和 Random Read 一致,只是连续读从 AT24C08 上连续读多个,每读一个数据,主控都会产生一个 ACK,从设备接收到 ACK 之后,就会向总线 上发送一个字节数据。主控在读完数据之后,就不会产生 ACK,那么从设备也不会向总线发送 数据,最终主控产生一个 STOP 条件,此时连续读操作完毕。如果连续读的地址超过了 AT24C08 最大地址,那么 AT24C08 会重新返回到 0 地址开始继续传输数据。

上图即是根据信号协议编写的 Sequential Read。在内核中,I2C 子系统定义了 struct i2c_msg 结构为一个 I2C 帧,其定义如下:

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_DMA_SAFE          0x0200  /* the buffer of this message is DMA safe */
                                        /* makes only sense in kernelspace */
                                        /* userspace buffers are copied anyway */
#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                  */
};

根据 Sequential Read 的信号定义,第一个 i2c_msg 与 Random Read, 或 Current Address Read 一致,都是传递 Device Address 与读地址, 或者只传 Device Address,因此 i2c_msg 只用将 AT24C08 的从设备地址 写入到 i2c_msg addr 成员里,由于 R/D 周期为低电平,因此 i2c_msg 的 flags 不用添加 I2C_M_RD;如果是 Dummy Read,那么需要传递读地址,该 地址占用 8bit,因此 i2c_msg 的 len 成员设置为 1,并且 buf 成员指向 offset。

第二个 i2c_msg 用于主控在获得从设备的 ACK 之后从 I2C 总线读取多个 字节的数据,每个数据占用一个字节。根据 Random Read 的定义可以知道, 当 dummy write 帧发送完毕之后,从设备应答,那么主控会重新生成一个 START 条件,随后跟随一个 AT24C08 的从设备地址,因此第二个 i2c_msg 的 addr 同样指向 AT24C08 的地址,即 client->addr; 由于主控需要读取多个 字节,那么将 i2c_msg 的 len 设置为 n 个字节,并且使用 buf 指向存储 数据的位置。

以上准备好 i2c_msg 之后,继续调用 i2c_transfer() 函数,将 i2c_msg 传递给 I2C 核心层,最终到达 I2C 控制器的收发器上发送。接下来使用 示波器和逻辑分析仪实际的抓起波形分析,具体如下:

上图为连续读的波形,波形符合 AT24C08 协议规范。

上图可以看出逻辑分析仪对连续读波形的解读,符合规范要求。


AT24C08 当前地址读操作

上图为 AT24C08 定义的 Current Address Read 操作的 I2C 时序图,分析上图时序图,可以 读出简单的读写规则:主控在获得 START 条件之后,只需按 MSB 到 LSB 的方向发送从 设备的地址,并且 R/D 周期主控产生一个高电平。如果此时从设备应答,那么主控就从 I2C 总线上读取 8bit 或者更多 (Sequential Read),读完毕之后,主控主动在 ACK 周期产生一个高电平,因此告诉从设备读操作完毕,随后产生一个 STOP 条件。

上图即是根据信号协议编写的 Current Address Read。在内核中,I2C 子系统定义了 struct i2c_msg 结构为一个 I2C 帧,其定义如下:

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_DMA_SAFE          0x0200  /* the buffer of this message is DMA safe */
                                        /* makes only sense in kernelspace */
                                        /* userspace buffers are copied anyway */
#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                  */
};

AT24C08 支持当前地址读,也就是在 AT24C08 内部有一个地址计数器,该 寄存器会锁存上一个次读写地址加上 1 的地址,因此在该模式下,主控只需向 从设备发送需要读的地址就可以读取对应的值。因此一个 i2c_msg 就满足需求。 在该模式下,首先也是向 I2C 总线上传输从设备的地址,因此 i2c_msg 的 addr 值为 client->addr 也就是 AT24C08 的从设备地址,R/D 周期主控 将电平拉低,并在 ACK 周期产生一个低电平,因此 i2c_msg 的 flags 添加 I2C_M_RD 表示。由于只需传递 8bit 的读地址,因此 i2c_msg len 为 1, 且 buf 指向 offset。

以上准备好 i2c_msg 之后,继续调用 i2c_transfer() 函数,将 i2c_msg 传递给 I2C 核心层,最终到达 I2C 控制器的收发器上发送。接下来使用 示波器和逻辑分析仪实际的抓起波形分析,具体如下:

上图为当前地址读的波形,波形符合 AT24C08 协议规范。

上图可以看出逻辑分析仪对当前地址读波形的解读,符合规范要求。


AT24C08 按字节写操作

上图为 AT24C08 定义的 Byte Write 操作,简单的对上图时序进行分析:主控首先产生 一个 START 条件,然后按 MSB 到 LSB 的方向将 AT24C08 的从设备地址发送到 I2C 总线上,并在 R/D 周期,将 SDA 总线拉低,以此表示以此写操作。接下来主控发出一个 应答信号,然后将写地址传送到 I2C 总线上。如果此时 AT24C08 发出一个应答信号,那么 主控将需要写的 8bit 数据写到 I2C 总线上,AT24C08 将数据写到指定位置。

上图即是根据信号协议编写的 Byte Write。在内核中,I2C 子系统定义了 struct i2c_msg 结构为一个 I2C 帧,其定义如下:

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_DMA_SAFE          0x0200  /* the buffer of this message is DMA safe */
                                        /* makes only sense in kernelspace */
                                        /* userspace buffers are copied anyway */
#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                  */
};

AT24C08 支持 Byte Write,从信号图上可知,只需要使用一个 i2c_msg 就可以 完成 Byte Write 操作。首先发送 AT24C08 从设备地址,然后是写地址和数据, 因此 i2c_msg addr 指向 client->addr,即 AT24C08 的从设备地址;i2c_msg len 的值为 2,包含写地址和数据;i2c_msg 的 buf 指向存储写地址和写内存。

以上准备好 i2c_msg 之后,继续调用 i2c_transfer() 函数,将 i2c_msg 传递给 I2C 核心层,最终到达 I2C 控制器的收发器上发送。接下来使用 示波器和逻辑分析仪实际的抓起波形分析,具体如下:

上图为按字节写的波形,波形符合 AT24C08 协议规范。

上图可以看出逻辑分析仪对按字节写的波形的解读,符合规范要求。


AT24C08 按页写操作

上图为 AT24C08 定义的 Page Write 操作,简单的对上图时序进行分析,与 Byte Write 对比会发现,Page Write 之后连续写了多个字节而已,因此可以参考 Byte Write 分析。 主控首先产生 START 条件,并发送 AT24C08 的从设备地址到总线上,在 R/D 周期拉低电平, 接着产生一个 ACK。接着将写地址写到 I2C 总线上。如果此时 AT24C08 应答,每应答一次, 主控就发送一个字节。当发送完指定字节之后,主控注定发送一个 ACK,并接着产生一个 STOP 条件,至此 Page Write 操作完成。

上图即是根据信号协议编写的 Page Write。在内核中,I2C 子系统定义了 struct i2c_msg 结构为一个 I2C 帧,其定义如下:

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_DMA_SAFE          0x0200  /* the buffer of this message is DMA safe */
                                        /* makes only sense in kernelspace */
                                        /* userspace buffers are copied anyway */
#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                  */
};

AT24C08 支持 Page Write,从信号图上可知,只需要使用一个 i2c_msg 就可以 完成 Page Write 操作。首先发送 AT24C08 从设备地址,然后是写地址和数据, 因此 i2c_msg addr 指向 client->addr,即 AT24C08 的从设备地址;i2c_msg len 的值为写入数据长度加 1,包含写地址和数据;i2c_msg 的 buf 指向存储写 地址和写内存。在函数中使用 memcpy 构造存储数据的写地址的有序序列。

以上准备好 i2c_msg 之后,继续调用 i2c_transfer() 函数,将 i2c_msg 传递给 I2C 核心层,最终到达 I2C 控制器的收发器上发送。接下来使用 示波器和逻辑分析仪实际的抓起波形分析,具体如下:

上图为按页写的波形,波形符合 AT24C08 协议规范。

上图可以看出逻辑分析仪对按页写的波形的解读,符合规范要求。


AT24C08 使用

开发者可以参考上面代码在内核驱动中使用 AT24C08。


AT24C08 BiscuitOS-RaspberryPi 实践部署

BiscuitOS 以及完整支持 AT24C08,并基于 Kbuild 编译系统,制作了一套 便捷的 AT24C08 开发环境,开发者可以参考如下步骤进行快速开发。


AT24C08 源码获取

首先开发者应该准备基于 RaspberryPi 4B BiscuitOS 开发环境,并 准备好一个在 RaspberryPi 上运行的 BiscuitOS,然后使用如下命令 获得 AT24C08 所需的开发环境:

cd BiscuitOS
make RaspberryPi_4B_defconfig
make menuconfig

选择 “Package —>” 并进入下一级菜单

选择 “I2C: Inter-Integrated Circuit —>” 并进入下一级菜单

设置 “AT24C08 Device Driver Module —>” 为 “Y”。设置完毕之后, 保存并退出,


AT24C08 源码编译

AT24C08 的编译很简单,只需执行如下命令就可以快速编译:

make
cd BiscuitOS/output/RaspberryPi_4B/package/at24c08_module-0.0.1/
make prepare
make download
make
make install
make pack


AT24C08 模块安装

接下来是将模块更新到 RaspberryPi 4B 上,更新的方式多种方式,可以参考下面 方式:


AT24C08 DTB 安装

由于 AT24C08 驱动的加载需要使用 DTB,因此在编写完驱动之后,应该 修改 DTS 并生成 DTB 更新到 RaspberryPi 4B 上。如果 DTB 已经更新, 不必重复更新 DTB。DTS 的修改可以参考 “at24c08_module-0.0.1” 目录下的 default.dts 文件,使用命令 如下:

cd BiscuitOS/output/RaspberryPi_4B/linux/linux/arch/arm/boot/dts
vi bcm2711-rpi-4-b.dts

将 AT24C08 DTS 节点信息加入到 i2c1 节点下,如下:

&i2c1 {
        pinctrl-names = "default";
        pinctrl-0 = <&i2c1_pins>;
        clock-frequency = <100000>;

        at24c08@50 {
                compatible = "BiscuitOS,at24c08";
                reg = <0x50>;
        };
};

修改完 DTS 之后,开发者使用如下命令编译 DTS:

cd BiscuitOS/output/RaspberryPi_4B/linux/linux/
make ARCH=arm CROSS_COMPILE=BiscuitOS/output/RaspberryPi_4B/arm-linux-gnueabi/arm-linux-gnueabi/bin/arm-linux-gnueabi- dtbs -j4

最后就是将生成的 DTB 更新到 RaspberryPi 4B 上,更新的方式 如下:

如果采用 NFS 方式更新,在 RaspberryPi 4B 上使用如下命令:

mkdir -p /nfs
mount -t nfs 192.168.x.x:BiscuitOS/output/RaspberryPi_4B/ /nfs -o nolock
cp /nfs/linux/RPI_linux_github/arch/arm/boot/dts/bcm2711-rpi-4-b.dtb /boot
sync
reboot

在上面的命令中,192.168.x.x 为主机的 IP 地址。如果采用 SD 拷贝方式, 主机端请参考如下命令:

cp BiscuitOS/output/RaspberryPi_4B/linux/RPI_linux_github/arch/arm/boot/dts/bcm2711-rpi-4-b.dtb /media/XXXX/BOOT/
sync

在上面的命令中,XXXX 表示主机的用户名。至此 DTB 更新成功.


AT24C08 模块使用

最后就是在 BiscuitOS RaspberryPi 4B 平台上加载模块,可以参考如下命令:

cd /lib/modules/5.0.21-v7l\+/extra
ls
insmod i2c-bcm2835.ko
insmod at24c08.ko


AT24C08 工程实践部署

开发者也可以在自己的工程中部署 AT24C08 驱动程序,可以参考如下步骤:


AT24C08 源码获取

首先获得 AT24C08 驱动源码,使用如下命令:

wget https://raw.githubusercontent.com/BiscuitOS/HardStack/master/Device-Driver/i2c/Device/AT24C08/kernel/at24c08.c
wget https://raw.githubusercontent.com/BiscuitOS/HardStack/master/Device-Driver/i2c/Device/AT24C08/kernel/default.dts
wget https://raw.githubusercontent.com/BiscuitOS/HardStack/master/Device-Driver/i2c/Device/AT24C08/kernel/Makefile
wget https://raw.githubusercontent.com/BiscuitOS/HardStack/master/Device-Driver/i2c/Device/AT24C08/kernel/Kconfig

AT24C08 源码编译

获得上面的源码之后,可以采用外部模块独立编译,或者加入内核源码树进行编译, 开发者可以参考如下步骤进行编译。

外部模块独立编译

此时对 Makefile 文件进行修改,其中 KERNELDIR 修改为开发者项目 Linux 源码树所在的目录。CROSS_COMPILE 变量指向交叉编译工具链所在的位置。

设置完毕之后,使用如下命令:

make
make install
内核源码树编译

开发者也可以将驱动源码直接加入内核源码树,当加入到指定目录后,请参考 Kconfig 文件的内容,将 AT24C08 加入到指定目录,修改该目录下的 Kconfig 和 Makefile, 然后配置内核将 AT24C08 启动加入编译,使用如下命令:

make menuconfig ARCH=arm
make modules ARCH=arm CROSS_COMPILE=BiscuitOS/output/RaspberryPi_4B/arm-linux-gnueabi/arm-linux-gnueabi/bin/arm-linux-gnueabi-
make modules_install INSTALL_MOD_PATH=BiscuitOS/output/RaspberryPi_4B/rootfs/rootfs

AT24C08 模块安装

接下来是将模块更新到项目平台上,更新的方式多种方式,可以参考下面 方式:


AT24C08 DTB 安装

由于 AT24C08 驱动的加载需要使用 DTB,因此在编写完驱动之后,应该 修改 DTS 并生成 DTB 更新到 RaspberryPi 4B 上。如果 DTB 已经更新, 不必重复更新 DTB。DTS 的修改可以参考 “at24c08_module-0.0.1” 目录下的 default.dts 文件,使用命令 如下:

cd BiscuitOS/output/RaspberryPi_4B/linux/linux/arch/arm/boot/dts
vi bcm2711-rpi-4-b.dts

将 AT24C08 DTS 节点信息加入到 i2c1 节点下,如下:

&i2c1 {
        pinctrl-names = "default";
        pinctrl-0 = <&i2c1_pins>;
        clock-frequency = <100000>;

        at24c08@50 {
                compatible = "BiscuitOS,at24c08";
                reg = <0x50>;
        };
};

修改完 DTS 之后,开发者使用如下命令编译 DTS:

cd BiscuitOS/output/RaspberryPi_4B/linux/linux/
make ARCH=arm CROSS_COMPILE=BiscuitOS/output/RaspberryPi_4B/arm-linux-gnueabi/arm-linux-gnueabi/bin/arm-linux-gnueabi- dtbs -j4

最后就是将生成的 DTB 更新到 RaspberryPi 4B 上,更新的方式 如下:

如果采用 NFS 方式更新,在 RaspberryPi 4B 上使用如下命令:

mkdir -p /nfs
mount -t nfs 192.168.x.x:BiscuitOS/output/RaspberryPi_4B/ /nfs -o nolock
cp /nfs/linux/RPI_linux_github/arch/arm/boot/dts/bcm2711-rpi-4-b.dtb /boot
sync
reboot

在上面的命令中,192.168.x.x 为主机的 IP 地址。如果采用 SD 拷贝方式, 主机端请参考如下命令:

cp BiscuitOS/output/RaspberryPi_4B/linux/RPI_linux_github/arch/arm/boot/dts/bcm2711-rpi-4-b.dtb /media/XXXX/BOOT/
sync

在上面的命令中,XXXX 表示主机的用户名。至此 DTB 更新成功.


AT24C08 模块使用

最后就是在开发平台上加载模块,可以参考如下命令:

cd /lib/modules/5.0.21-v7l\+/extra
ls
insmod i2c-bcm2835.ko
insmod at24c08.ko


AT24C08 工具

开发者在用户空间使用 AT24C08 的时候,可以使用如下工具辅助调试和 使用:


i2cdetect

i2cdetect 工具用于探测指定 I2C 总线上所有 I2C 从设备,如果 指定 I2C 地址存在 I2C 设备,那么会将 I2C 从设备地址标记在结果 里。i2cdetect 使用如下图:

如上图,i2cdetect 找到了 AT24C08 的多个从设备地址。i2cdetect 工具的 参数中,”-y” 用于指定 I2C 总线的编号.


i2cdump

i2cdump 工具用于打印指定 I2C 总线上指定从设备上的所有数据,该 工具用于 EEPROM 的数据查看,i2cdump 使用如下图:

如上图,使用 i2cdump 工具查看 AT24C08 上的所有数据. 该工具对快速读取 I2C 设备上数据有帮助。


i2cget

i2cget 工具用于从指定 I2C 总线的从设备上读取指定位置的值, 在开发过程中,需要对从设备的某个寄存器进行读取操作,这个 工具再适合过不,其使用如下图:

在使用 i2cget 工具的时候,”-y” 参数后面匹配 I2C 总线编号,如上图 中查找的是 I2C1 总线; 接下来的参数是 I2C 从设备地址和内部偏移地址。


i2cset

与 i2cget 工具相对应的工具就是 i2cset, 该工具用于在指定 I2C 总线 上,给特定的 I2C 从设备的内部地址进行写操作, 其使用如下图:

在使用 i2cset 工具的时候,”-y” 参数后面匹配 I2C 总线编号,如上图 查找的是 I2C1 总线;接下来的参数是 I2C 从设备地址,以及内部要写 的地址,最后是要写的内容。


AT24C08 应用程序

BiscuitOS 为 AT24C08 提供了完整的应用程序支持,开发者可以参考本节 内容,对 AT24C08 应用程序进行实践部署。


AT24C08 应用程序分析

BiscuitOS 已经支持最新的 AT24C08 芯片,开发者可以使用 BiscuitOS 提供 的应用程序 AT24C08 进行使用,驱动源码 GithuB 地址如下:

BiscuitOS 提供的完整应用程序如下:

应用程序分作两个部分,第一部分是 AT24C08 提供 EEPROM 的读写接口; 第二部分是驱动对 EEPROM 的使用。接下来的内容将详细分析每一部分代码的构成。


AT24C08 I2C 接口

通过 AT24C08 的数据手册可以知道,AT24C08 提供了三种方式的读,包括随机 地址读操作,连续读操作,以及当前地址读操作。AT24C08 也提供了两种方式的写, 包括按字节写,以及按页写操作。接下里分别介绍每种操作如何编写代码。


AT24C08 随机地址读操作

上图为 AT24C08 定义的 Random Read 操作的 I2C 时序图,分析上图时序图,可以 读出简单的读写规则:在 SDA 信号线上产生一个 START 条件之后,AT24C08 在一个 I2C Message 周期内将将 AT24C08 的从设备地址和读地址写入到 I2C 总线上,在 这个周期中,I2C 控制器向 I2C 总线上按 MSB 到 LSB 的方式,发送从设备地址到 I2C 总线上,占用了 7 个 SCL 周期,接下来是一个 bit 代表 R/W 信号,此时 I2C 主控发送一个低电平,以此代表这次是一个写操作。操作完毕之后,等待从设备的 ACK, 如果 ACK 没有回应,那么主控产生一个 STOP 条件,停止传输;如果从设备 ACK 应答, 那么 I2C 主控继续将读地址按 MSB 到 LSB 的方向写到 I2C 总线上,写完的第九个周期, 如果从设备没有应答,那么操作失败;如果从设备回应,那么从设备会产生一个低电平将 I2C 总线在这个周期内拉低,此时主控收到这个 ACK 之后,主控将独立产生一个 START 条件,紧随其后的从设备地址,按 MSB 到 LSB 的方向写到 I2C 总线上,从地址写完的 下一个周期,主控产生一个高电平,以此表示此时操作是 READ 操作。之后主控等待从 设备的 ACK,从设备如果应答,那么会将 I2C 总线拉低,并将 8-bit 的数据写到总线 上,主控接收到以上信号后,自动将这 8-bit 抓取下来,以此作为读到的数据。最后 从设备不应答,所以主控产生一个 STOP 条件。如果以上操作完成,那么一个 Random Read 操作就算完成。由于第一个 I2C Message 的 R/W 周期为低,为写操作, 因此称该 I2C message 周期为 DUMMY WRITE。

上图即是根据信号协议编写的 Random Read。在内核中,I2C 子系统定义了 struct i2c_msg 结构为一个 I2C 帧,其定义如下:

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_DMA_SAFE          0x0200  /* the buffer of this message is DMA safe */
                                        /* makes only sense in kernelspace */
                                        /* userspace buffers are copied anyway */
#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                  */
};

/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {
        struct i2c_msg *msgs;   /* pointers to i2c_msgs */
        __u32 nmsgs;                    /* number of i2c_msgs */
};

根据 Random Read 的信号定义,在代码中首先定义两个 i2c_msg 结构体, 第一个 i2c_msg 用于生成 Dummy Write 帧,i2c_msg 的 addr 成员对应 Random Read 信号的 Device Address,其值设置为 AT24C08 的从设备地址;接下来设置 i2c_msg 的 flags 成员,此时不 包括 I2C_M_RD 宏,此次产生 Random Read 的 R/D 周期的低电平;i2c_msg 的 len 成员表示 I2C msg 的长度,由 Random Read 的 Dummy Write 定义可以知道,其后只用跟随一个 8-bit 的读地址,因此 i2c_msg 的 len 设置为 1,并且 buf 成员就是指向读地址。

第二个 i2c_msg 用于主控在获得从设备的 ACK 之后从 I2C 总线读取一个 字节的数据。根据 Random Read 的定义可以知道,当 dummy write 帧发送 完毕之后,从设备应答,那么主控会重新生成一个 START 条件,随后跟随 一个 AT24C08 的从设备地址,因此第二个 i2c_msg 的 addr 同样指向 AT24C08 的地址; 此时向 I2C 总线发送完 Device Address 之后,主控需要产生一个高电平,因此此时需要将 i2c_msg 的 flags 设置为 I2C_M_RD, 接着主控会从总线上获得从设备的应答信号, 那么从总线上将 8bit 的数据存储在 i2c_msg 的 buf 成员里,因此 i2c_msg 的 len 成员设置为 1,并且 buf 指向存储的位置。

以上准备好 i2c_msg 之后,继续调用 ioctl() 函数,将 i2c_msg 传递给 I2C 核心层,最终到达 I2C 控制器的收发器上发送。接下来使用 示波器和逻辑分析仪实际的抓起波形分析,具体如下:

从上图的波形图可以看出,抓取的波形符合 “Random Read” 的规范 要求。

借助 DSlogic 分析仪,可以快速获得数字波形以及协议解析信息, 上图的波形符合 “Random Read” 的要求。


AT24C08 连续读操作

上图为 AT24C08 定义的 Sequential Read 操作的 I2C 时序图,分析上图时序图,可以 读出简单的读写规则:连续读操作的前部操作和 Random Read 一致,只是连续读从 AT24C08 上连续读多个,每读一个数据,主控都会产生一个 ACK,从设备接收到 ACK 之后,就会向总线 上发送一个字节数据。主控在读完数据之后,就不会产生 ACK,那么从设备也不会向总线发送 数据,最终主控产生一个 STOP 条件,此时连续读操作完毕。如果连续读的地址超过了 AT24C08 最大地址,那么 AT24C08 会重新返回到 0 地址开始继续传输数据。

上图即是根据信号协议编写的 Sequential Read。在内核中,I2C 子系统定义了 struct i2c_msg 结构为一个 I2C 帧,其定义如下:

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_DMA_SAFE          0x0200  /* the buffer of this message is DMA safe */
                                        /* makes only sense in kernelspace */
                                        /* userspace buffers are copied anyway */
#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                  */
};

/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {
        struct i2c_msg *msgs;   /* pointers to i2c_msgs */
        __u32 nmsgs;                    /* number of i2c_msgs */
};

根据 Sequential Read 的信号定义,第一个 i2c_msg 与 Random Read, 或 Current Address Read 一致,都是传递 Device Address 与读地址, 或者只传 Device Address,因此 i2c_msg 只用将 AT24C08 的从设备地址 写入到 i2c_msg addr 成员里,由于 R/D 周期为低电平,因此 i2c_msg 的 flags 不用添加 I2C_M_RD;如果是 Dummy Read,那么需要传递读地址,该 地址占用 8bit,因此 i2c_msg 的 len 成员设置为 1,并且 buf 成员指向 offset。

第二个 i2c_msg 用于主控在获得从设备的 ACK 之后从 I2C 总线读取多个 字节的数据,每个数据占用一个字节。根据 Random Read 的定义可以知道, 当 dummy write 帧发送完毕之后,从设备应答,那么主控会重新生成一个 START 条件,随后跟随一个 AT24C08 的从设备地址,因此第二个 i2c_msg 的 addr 同样指向 AT24C08 的地址; 由于主控需要读取多个 字节,那么将 i2c_msg 的 len 设置为 n 个字节,并且使用 buf 指向存储 数据的位置。

以上准备好 i2c_msg 之后,继续调用 ioctl() 函数,将 i2c_msg 传递给 I2C 核心层,最终到达 I2C 控制器的收发器上发送。接下来使用 示波器和逻辑分析仪实际的抓起波形分析,具体如下:

上图为连续读的波形,波形符合 AT24C08 协议规范。

上图可以看出逻辑分析仪对连续读波形的解读,符合规范要求。


AT24C08 当前地址读操作

上图为 AT24C08 定义的 Current Address Read 操作的 I2C 时序图,分析上图时序图,可以 读出简单的读写规则:主控在获得 START 条件之后,只需按 MSB 到 LSB 的方向发送从 设备的地址,并且 R/D 周期主控产生一个高电平。如果此时从设备应答,那么主控就从 I2C 总线上读取 8bit 或者更多 (Sequential Read),读完毕之后,主控主动在 ACK 周期产生一个高电平,因此告诉从设备读操作完毕,随后产生一个 STOP 条件。

上图即是根据信号协议编写的 Current Address Read。在内核中,I2C 子系统定义了 struct i2c_msg 结构为一个 I2C 帧,其定义如下:

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_DMA_SAFE          0x0200  /* the buffer of this message is DMA safe */
                                        /* makes only sense in kernelspace */
                                        /* userspace buffers are copied anyway */
#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                  */
};

/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {
        struct i2c_msg *msgs;   /* pointers to i2c_msgs */
        __u32 nmsgs;                    /* number of i2c_msgs */
};

AT24C08 支持当前地址读,也就是在 AT24C08 内部有一个地址计数器,该 寄存器会锁存上一个次读写地址加上 1 的地址,因此在该模式下,主控只需向 从设备发送需要读的地址就可以读取对应的值。因此一个 i2c_msg 就满足需求。 在该模式下,首先也是向 I2C 总线上传输从设备的地址,因此 i2c_msg 的 addr 值为 AT24C08 的从设备地址,R/D 周期主控 将电平拉低,并在 ACK 周期产生一个低电平,因此 i2c_msg 的 flags 添加 I2C_M_RD 表示。由于只需传递 8bit 的读地址,因此 i2c_msg len 为 1, 且 buf 指向 offset。

以上准备好 i2c_msg 之后,继续调用 ioctl() 函数,将 i2c_msg 传递给 I2C 核心层,最终到达 I2C 控制器的收发器上发送。接下来使用 示波器和逻辑分析仪实际的抓起波形分析,具体如下:

上图为当前地址读的波形,波形符合 AT24C08 协议规范。

上图可以看出逻辑分析仪对当前地址读波形的解读,符合规范要求。


AT24C08 按字节写操作

上图为 AT24C08 定义的 Byte Write 操作,简单的对上图时序进行分析:主控首先产生 一个 START 条件,然后按 MSB 到 LSB 的方向将 AT24C08 的从设备地址发送到 I2C 总线上,并在 R/D 周期,将 SDA 总线拉低,以此表示以此写操作。接下来主控发出一个 应答信号,然后将写地址传送到 I2C 总线上。如果此时 AT24C08 发出一个应答信号,那么 主控将需要写的 8bit 数据写到 I2C 总线上,AT24C08 将数据写到指定位置。

上图即是根据信号协议编写的 Byte Write。在内核中,I2C 子系统定义了 struct i2c_msg 结构为一个 I2C 帧,其定义如下:

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_DMA_SAFE          0x0200  /* the buffer of this message is DMA safe */
                                        /* makes only sense in kernelspace */
                                        /* userspace buffers are copied anyway */
#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                  */
};

/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {
        struct i2c_msg *msgs;   /* pointers to i2c_msgs */
        __u32 nmsgs;                    /* number of i2c_msgs */
};

AT24C08 支持 Byte Write,从信号图上可知,只需要使用一个 i2c_msg 就可以 完成 Byte Write 操作。首先发送 AT24C08 从设备地址,然后是写地址和数据, 因此 i2c_msg addr 指向 AT24C08 的从设备地址;i2c_msg len 的值为 2,包含写地址和数据;i2c_msg 的 buf 指向存储写地址和写内存。

以上准备好 i2c_msg 之后,继续调用 ioctl() 函数,将 i2c_msg 传递给 I2C 核心层,最终到达 I2C 控制器的收发器上发送。接下来使用 示波器和逻辑分析仪实际的抓起波形分析,具体如下:

上图为按字节写的波形,波形符合 AT24C08 协议规范。

上图可以看出逻辑分析仪对按字节写的波形的解读,符合规范要求。


AT24C08 按页写操作

上图为 AT24C08 定义的 Page Write 操作,简单的对上图时序进行分析,与 Byte Write 对比会发现,Page Write 之后连续写了多个字节而已,因此可以参考 Byte Write 分析。 主控首先产生 START 条件,并发送 AT24C08 的从设备地址到总线上,在 R/D 周期拉低电平, 接着产生一个 ACK。接着将写地址写到 I2C 总线上。如果此时 AT24C08 应答,每应答一次, 主控就发送一个字节。当发送完指定字节之后,主控注定发送一个 ACK,并接着产生一个 STOP 条件,至此 Page Write 操作完成。

上图即是根据信号协议编写的 Page Write。在内核中,I2C 子系统定义了 struct i2c_msg 结构为一个 I2C 帧,其定义如下:

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_DMA_SAFE          0x0200  /* the buffer of this message is DMA safe */
                                        /* makes only sense in kernelspace */
                                        /* userspace buffers are copied anyway */
#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                  */
};

/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {
        struct i2c_msg *msgs;   /* pointers to i2c_msgs */
        __u32 nmsgs;                    /* number of i2c_msgs */
};

AT24C08 支持 Page Write,从信号图上可知,只需要使用一个 i2c_msg 就可以 完成 Page Write 操作。首先发送 AT24C08 从设备地址,然后是写地址和数据, 因此 i2c_msg addr 指向 AT24C08 的从设备地址;i2c_msg len 的值为写入数据长度加 1,包含写地址和数据;i2c_msg 的 buf 指向存储写 地址和写内存。在函数中使用 memcpy 构造存储数据的写地址的有序序列。

以上准备好 i2c_msg 之后,继续调用 ioctl() 函数,将 i2c_msg 传递给 I2C 核心层,最终到达 I2C 控制器的收发器上发送。接下来使用 示波器和逻辑分析仪实际的抓起波形分析,具体如下:

上图为按页写的波形,波形符合 AT24C08 协议规范。

上图可以看出逻辑分析仪对按页写的波形的解读,符合规范要求。


AT24C08 使用

有了上面的接口之后,开发者可以在用户空间参考下面代码对 AT24C08 使用:

上图的代码包含了 AT24C08 的基本读写操作方法,开发者可以 在项目中参考上图的用法来实现对 AT24C08 的使用。


AT24C08 BiscuitOS-RaspberryPi 中使用

BiscuitOS 以及完整支持 AT24C08,并基于 Kbuild 编译系统,制作了一套 便捷的 AT24C08 开发环境,开发者可以参考如下步骤进行快速开发。


AT24C08 源码获取

首先开发者应该准备基于 RaspberryPi 4B BiscuitOS 开发环境,并 准备好一个在 RaspberryPi 上运行的 BiscuitOS,然后使用如下命令 获得 AT24C08 所需的开发环境:

cd BiscuitOS
make RaspberryPi_4B_defconfig
make menuconfig

选择 “Package —>” 并进入下一级菜单

选择 “I2C: Inter-Integrated Circuit —>” 并进入下一级菜单

设置 “AT24C08 Application —>” 为 “Y”。设置完毕之后, 保存并退出,


AT24C08 源码编译

AT24C08 的编译很简单,只需执行如下命令就可以快速编译:

make
cd BiscuitOS/output/RaspberryPi_4B/package/at24c08_app-0.0.1/
make prepare
make download
make
make install
make pack


AT24C08 模块安装

接下来是将模块更新到 RaspberryPi 4B 上,更新的方式多种方式,可以参考下面 方式:


AT24C08 应用程序的使用

最后就是在 BiscuitOS RaspberryPi 4B 平台上加载模块,可以参考如下命令:

cd /lib/modules/5.0.21-v7l\+/extra
insmod i2c-bcm2835.ko
insmod i2c-dev.ko
at24c08_app-0.0.1


AT24C08 工程实践部署

开发者也可以在自己的工程中部署 AT24C08 应用程序,可以参考如下步骤:


AT24C08 源码获取

首先获得 AT24C08 应用源码,使用如下命令:

wget https://raw.githubusercontent.com/BiscuitOS/HardStack/master/Device-Driver/i2c/Device/AT24C08/userland/at24c08.c
wget https://raw.githubusercontent.com/BiscuitOS/HardStack/master/Device-Driver/i2c/Device/AT24C08/userland/at24c08.sh
wget https://raw.githubusercontent.com/BiscuitOS/HardStack/master/Device-Driver/i2c/Device/AT24C08/userland/Makefile

AT24C08 源码编译

获得上面的源码之后,开发者只需提供编译工具,就可以直接编译源码, 此时对 Makefile 文件进行修改,其中 CROSS_COMPILE 变量指向交叉编 译工具链所在的位置。

设置完毕之后,使用如下命令:

make
make install

AT24C08 应用安装

接下来是将应用更新到项目平台上,更新的方式多种方式,可以参考下面 方式:


AT24C08 应用使用

最后就是在开发平台上加载模块,可以参考如下命令:

cd /lib/modules/5.0.21-v7l\+/extra
ls
insmod i2c-bcm2835.ko
insmod at24c08.ko
at24c08_app-0.0.1


AT24C08 测试

AT24C08 的测试主要包括读写测试,稳定性测试以及硬件波形测试,接下来 本节重点测试方法:


AT24C08 读压力测试

读压力测试主要测试的点是在特定 I2C 总线频率下,AT24C08 读操作 的极限值,本次测试采用随机地址读的方法。测试通过软件读操作是否 成功为标准,硬件则为实际抓的波形为准。测试在用户空间进行, 按下面几个步骤进行:


测试源码

测试使用的源码位于 GitHub,其使用方法请参照如下:


测试说明

本次采用随机地址读操作,每次从从设备读一个字节的内容,I2C 总线的 频率为测试条件,分别采用 10K、50K、100K、400K 测试读操作的极限值, 在无线循环的条件下,验证读操作是否可以完成。为了能使用不同频率的 I2C 总线,开发者修改修改 DTS,参照如下:

cd BiscuitOS/output/RaspberryPi_4B/linux/linux/arch/arm/boot/dts
vi bcm2711-rpi-4-b.dts

修改 DTS 中 I2C1 的频率,如下将 I2C 的频率改为 10K:

&i2c1 {
        pinctrl-names = "default";
        pinctrl-0 = <&i2c1_pins>;
        clock-frequency = <10000>;

        at24c08@50 {
                compatible = "BiscuitOS,at24c08";
                reg = <0x50>;
        };
};

在上面的代码中,将 clock-frequency 设置为 10000,接着是更新 DTB, 请参考下面文章:

接下来在一个循环中无限次读操作,直到读失败.


测试实施

从 Github 中获得源码后,在 RaspberryPi 4B 上运行测试程序, 如果不知道如何使用测试程序,请参考下列文章:

将 SDA 和 SCL 线连接到示波器或逻辑分析仪上进行分析。 最后运行程序。


测试结果

当 I2C 总线频率是 400K 的时候,将逻辑分析仪的采样频率设置为 1M, 此时抓到的波形如下图:

此时读取的数据均正常,所以在 400K 的 I2C 总线上,AT24C08 可以正常 工作。

当 I2C 总线频率是 100K 的时候,将逻辑分析仪的采样频率设置为 400K, 此时抓到的波形如下图:

此时读取的数据均正常,所以在 100K 的 I2C 总线上,AT24C08 可以正常 工作。

当 I2C 总线频率是 50K 的时候,将逻辑分析仪的采样频率设置为 100K, 此时抓到的波形如下图:

此时读取的数据均正常,所以在 50K 的 I2C 总线上,AT24C08 可以正常 工作。

当 I2C 总线频率是 10K 的时候,将逻辑分析仪的采样频率设置为 50K, 此时抓到的波形如下图:

此时读取的数据均正常,所以在 10K 的 I2C 总线上,AT24C08 可以正常 工作。


测试分析

AT24C08 在 I2C 总线频率为 10K、50K、100K、400K 下均可正常进行读操作。


AT24C08 写压力测试

写压力测试主要测试的点是在特定 I2C 总线频率下,AT24C08 写操作 的极限值,本次测试采用按字节写的方法。测试通过软件写操作是否 成功为标准,硬件则为实际抓的波形为准。测试在用户空间进行, 按下面几个步骤进行:


测试源码

测试使用的源码位于 GitHub,其使用方法请参照如下:


测试说明

本次采用按字节写操作,每次向从设备写一个字节的内容,I2C 总线的 频率为测试条件,分别采用 10K、50K、100K、400K 测试写操作的极限值, 在无线循环的条件下,验证写操作是否可以完成。为了能使用不同频率的 I2C 总线,开发者修改修改 DTS,参照如下:

cd BiscuitOS/output/RaspberryPi_4B/linux/linux/arch/arm/boot/dts
vi bcm2711-rpi-4-b.dts

修改 DTS 中 I2C1 的频率,如下将 I2C 的频率改为 10K:

&i2c1 {
        pinctrl-names = "default";
        pinctrl-0 = <&i2c1_pins>;
        clock-frequency = <10000>;

        at24c08@50 {
                compatible = "BiscuitOS,at24c08";
                reg = <0x50>;
        };
};

在上面的代码中,将 clock-frequency 设置为 10000,接着是更新 DTB, 请参考下面文章:

接下来在一个循环中无限次读操作,直到读失败.


测试实施

从 Github 中获得源码后,在 RaspberryPi 4B 上运行测试程序, 如果不知道如何使用测试程序,请参考下列文章:

将 SDA 和 SCL 线连接到示波器或逻辑分析仪上进行分析。 最后运行程序。


测试结果

当 I2C 总线频率是 400K 的时候,将逻辑分析仪的采样频率设置为 1M, 此时抓到的波形如下图:

此时软件已经报错了,写失败,如下图:

从波形也可以看出,当 I2C 总线频率达到 400K 的时候,连续的写 会让 I2C 总线错误。为了解决这个问题,可以这么解决。此时 I2C 总线频率为 400K,可以初略的计算 I2C 总线发送一个 bit 的周期 为 (1 / 400K) = 0.000002 s, 因此开发者在发送一个完整的 Byte write 操作至少保证有一个帧的间隔,也就是在两次 Byte_write() 调用之间添加一个延时,这个延时正好保证一个 Byte_write 帧可以 发送,因此在代码中添加 usleep() 函数。Byte_write() 一个帧包含 了 29 个 bit,因此 usleep() 延时时长为 “29 * (1 / 400K)”, 如 下图:

基于这样的修改实际抓一下波形,如下图:

测试之后,可以连续向 AT24C08 继续写操作。这里只测试 400K 情况, 更多写测试请看:


测试分析

AT24C08 在 I2C 总线频率为 10K、50K、100K、400K 下执行写操作应该 添加适当的延时,保证 I2C 总线不会忙死。


AT24C08 问题合集


I2C 总线无法找到 AT24C08

将 AT24C08 连接到 RPI-4B 的 I2C-1 总线上,具体连接如下图,AT24C08 模块 VCC 连接到 RPI-4B 1 号脚 VCC;GND 连接到 RPI-4B 9 号脚;SDL 连接到 RPI-4B 5 号脚;SDA 连接到 RPI-4B 3 号脚。

接着在 RPI-4B 上,I2C 总线注册使用的是 /dev/i2c-1, 此时使用 BiscuitOS 系统上自带的工具 i2cdetect 对 I2C-1 总线进行 detect 操作,运行 结果如下:

从结果可以看出 i2cdetect 工具并未在 I2C-1 总线上找到任何从设备。 接着使用测试程序对 AT24C08 进行读操作,测试代码如下:

基于上面的代码,使用 DSCope 示波器对 SDL 和 SDA 信号进行抓取,更多 DSCope 的使用请参阅下面文档:

在抓取波形之前,先分析一下 AT24C08 I2C 读操作的信号定义,AT24C08 数据手册 请参阅:

其中对应读操作,代码中使用的是 AT24C98 的 Random Read,其信号定义如下:

在 AT24C08 的 Random Read 信号中,首先是 START 信号,该信号是 SDA 首先产生 一个下降沿,如果此时 SCL 任处于高电平,那么 I2C 认为其为 START 信号。如下图:

START 信号之后,I2C Master 传递 7 bit 的 Slave 地址,Slave 地址首先传 MSB 部分,即 Slave 地址的高字节,然后传低字节。传递完毕之后是 W/R 标志位,此时 W/R bit 位为低电平,W/R 位为低代表写操作。W/R 信号之后是 ACK 信号,此时如 果指定的 Slave 设备没有应答,那么 ACK 将会是高电平,此时 Master 会重传一次 指定 Slave 设备的地址,如果 Slave 设备还是没有应答,那么 Master 就发出一个 STOP 信号;如果指定的 Slave 设备应答,那么 ACK 信号会将 SDA 拉低。ACK 信号 之后是 8 bit 的读地址,即需要在 AT24C08 上读取数据的地址,其也是先传 MSB 到 LSB 的传信号。至此,之前传输的信号称为 “DUMMY WRITE”,因为在这段信号中, W/R 信号为低,代表一次写,但又不是 I2C 写操作,因此称为假的写操作。接下来 Master 继续产生一个 START 信号,并再次按 MSB 到 LSB 的方式传输从设备的地址。 接下来传递一个 READ 信号,为高电平,代表真正的读操作,此时如果 Slave 设备准备 好数据,那么 Slave 设备就会将 SDA 总线拉低,以此生成一个 ACK 信号,接着 Slave 设备将数据写入到 SDA 信号线上;如果此时 Slave 设备没有应答,那么 Master 会重传 Slave 地址到 SDA 信号线上。在 Slave 设备传输完数据之后,如果 没有其他操作,那么到第 9 个周期 Slave 将不会产生 ACK 信号,SDA 信号线变高, 此时如果 Master 无其他操作,那么 Master 将会产生一个 STOP 信号。至此,一次 I2C 的 Random Read 操作完成。

无 Slave 设备 I2C Read 操作

首先在 I2C 总线上不挂载任何从设备的情况下,进行 I2C 的 Random Read 操作。 搭建好测试环境之后,运行 DSView 程序。点击触发按钮,将右侧 “触发位置” 调整 为 10%,以便能从 I2C 起始信号处开始抓取波形;此时通道 0 连接 SDA 信号线,根据 I2C 信号可知,当 SDA 产生一个下降沿之后,SCL 接着也产生一个下降沿,那么认为 是 I2C 开始传输信号,因此将 SDA 的下降沿作为触发信号。设置好之后点击 “单次” 按钮,对信号进行捕捉。使用上面的测试程序,对 0x50 的 0x00 地址读取 1 个字节 的操作:

从捕获的波形可以看出,绿色为 SCL 信号,黄色为 SDA 信号。在前 8 个周期中, 从设备地址为 0x50,R/W 为 0,ACK 没有应答,因此 Master 在重发一次,此时 ACK 还是没有应答,于是 Master 发送一个 STOP 信号。此次操作预计结果符合预期。

挂载 AT24C08 I2C Read 操作

首先将 AT24C08 连接到 RPI-4B 上,进行 I2C 的 Random Read 操作。 搭建好测试环境之后,运行 DSView 程序。点击触发按钮,将右侧 “触发位置” 调整 为 10%,以便能从 I2C 起始信号处开始抓取波形;此时通道 0 连接 SDA 信号线,根据 I2C 信号可知,当 SDA 产生一个下降沿之后,SCL 接着也产生一个下降沿,那么认为 是 I2C 开始传输信号,因此将 SDA 的下降沿作为触发信号。设置好之后点击 “单次” 按钮,对信号进行捕捉。使用上面的测试程序,对 0x50 的 0x00 地址读取 1 个字节 的操作:

从捕获的波形可以看出,绿色为 SCL 信号,黄色为 SDA 信号。在前 8 个周期中, 从设备地址为 0x50,R/W 为 0,ACK 没有应答,因此 Master 重传了一次,到了 第 9 个周期还是没有应答。因此发现问题 Slave 设备无法识别。

问题排除

首先使用电压表测量 AT24C08 的电压,VCC 为 3.2V,SDA 与 SCL 均为 3.1V: 在这听取社区朋友的建议,对 RPI 4B I2C 的频率进行修改,分别测试了 400K, 100K, 50K, 以及 10K,都无法找到 I2C 从设备。 接着查看 BCM2835 关于 I2C 控制器的文档,以及对应的的驱动程序,在其中发现一个 名为 “Clock Stretching” 的问题,即由于主控在 I2C 总线上向从设备发送数据之后, 从设备回应时间超过了 Master 等待时间,这称为时钟拉升问题。下面是对该问题的讨论:

通过上面的文档,也发现了挂载 AT24C08 的时候,也出现了周期拉升问题,如下图:

因此确定问题就是 Master 正常发送波形,只是 Slave 没有或者没有及时应答, 因此继续查找根本原因。于是我找了一个高速的 I2C 设备 “LM75A” 高精度温度 传感器。使用同样的安装办法,结果还是同样的问题,RPI 4B 无法找到 Slave 设备。

社区朋友建议在 AT24C08 芯片引脚上确认是否有 SDA 和 SCL 信号传输过来, 结果通过示波器发现 SCL 信号并未接收到,于是排查链接问题。最后发现连接 AT24C08 的杜邦线头处出现松动,导致虚接。将线连接好后,RPI 4B 可以正常 找到 Slave 设备。

问题总结

为了处理这个问题,花费了很多时间查找各方面的原因,通过不断的查找,离真相越来 越近。最后发现问题是因为杜邦线连接问题,这里曾经是自己没有实际测量但认为 没有问题的地方,最后这里成为问题的根本原因。通过这次问题的解决,虽然绕了 一大圈,但对 I2C 的多个方面也有了更多的理解,比如 I2C 控制器,I2C 平台总线, I2C 设备方面。但最终问题还是由于自己对硬件连线处理不够谨慎引起的。


AT24C08 多次写失败

在用户空间使用 “Byte Write” 的方式向 AT24C08 指定地址不停的写入指定值, 发现写入失败,此时 I2C 总线的频率是 100K,使用的测试代码如下:

使用上面的代码进行测试,运行测试程序如下图:


问题分析

AT24C08 “Byte Write” 的时序定义如下图:

测试程序对 “Byte Write” 的详细分析请看下面文档:

当发生错误的时候,使用逻辑分析仪抓到的时序信号如下图:

从上面的 I2C 信号图可以看出,主控第一次向 AT24C08 的第一次写 是成功的,但是第二次主控向 I2C 总线写的时候 I2C 总线处于忙状态, 因此可以猜测由于应用程序写的太快,I2C 控制器还没有将前一帧数据 写完,第二帧数据就到来。


问题解决

通过上面的分析,只需在两次写操作或者一次写操作后面加上一定的延时, 让 I2C 主控能将数据帧发送完毕。但问题来了,延时的时长多长?其实 在嵌入式领域,延时的使用是很常见的,其计算可以大概按下列方法:

1. 假设 I2C 总线的频率为 M (M 通常的数值为 400K、100K、50K etc),
   那么 I2C 控制器发送一个 bit 所需的时间计算为:

     T_bit = (1 / M)

2. 应用程序中,使用 "Byte Write" 的方式写操作,一个完整的 "Byte Write"
   帧至少包含 30 个 bit, 因此 I2C 控制器发送一个完整 "Byte Write" 帧
   所需的时间为:

     T_bw = 30 * T_bit

3. 因此,每次 "Byte Write" 之后都应该有 T_bw 的延时,至于使用什么延时
   函数,依实际开发而定。

通过上面的分析,解决问题可以按如下步骤。首先 I2C 总线的频率是 100K, 因此此时:

T_bit = 1 / (100 * 1024)
T_bit = 0.000009766

所以 I2C 控制器发送一个 bit 大概需要 0.009 毫秒,接着计算在这种情况下 一个完整的 “Byte Write” 所需的时间如下:

T_bw = 30 * T_bit
T_bw = 30 * 0.000009766
T_bw = 0.000292969

通过上面的计算,一次完整的 “Byte Write” 所需的时间是 0.3 毫秒,因此 在代码中添加如下代码:

代码中使用 usleep() 函数,usleep() 的延时单位是微妙 (百万分之一秒). 更新新代码并在 RaspberryPi 4B 上进行测试,测试结果如下:

程序已经正常运行,从抓起的波形可以看出 “Byte Write” 已经成功执行.


问题总结

当遇到 I2C 总线忙或者读写失败的问题,可以从延时这里切入。


AT24C08 进阶研究


AT24C08 多从地址问题

课题简介

当使用 i2cdetect 工具对 I2C 总线进行扫描的时候,总线上只挂载了 AT24C08 模块,却发现有 4 个从设备地址,分别是 0x50、0x51、0x52 和 0x53,那么开发者应该如何使用这四个地址呢?

首先查看 AT24C08 手册,其中关于地址介绍的章节 “Device Addressing”, 其描述如下:

上面对于 AT24C08 多从设备地址的解释是这样的,AT24C08 为了能访问 8K 的存储 空间,那么 AT24C08 会将 A0 和 A1 引脚悬空,与设备地址的 8 位地址一起组成一 个 10 位地址,且 A1,A2 作为高地址,如下图:


课题分析

AT24C08 访问 8K EEPROM 空间,那么 P1、P0 则作为设备内部地址, 而 A2 引脚作为片选,也就是同一个 I2C 总线上只能同时接 2 个 AT24C08,并且第一个 AT24C08 的 A2 引脚接地,第二个 AT24C08 的 A2 引脚上拉接 VCC,这样就可以区分两块 AT24C08. 接着每块 AT24C08 的 A1,A2 与设备的 8 位地址一同组成 10 位的设备内部 偏移地址,如下:

设备地址  =  A1:A0:8bit

AT24C08 一共有 4 个从设备地址: 0x50, 0x51, 0x52, 0x53. 每个从设备地址指向 2K 的 EEPROM 空间。例如 AT24C08 从设备地址 0x50 对应第一块 2K bits 的 EEPROM 空间,其使用 10 bit 的设备 地址寻址,这 10 bit 设备地址由 AT24C08 的 A1、A0 和 8bit 设备 地址构成,由于从设备地址为 0x50,则 A1, A0 均为 0,因此只能 使用 8bit 的设备地址访问第一块 2K EEPROM 空间,其寻址范围 是 “0x00000000 - 0x00FFFF”;同理 AT24C08 从设备地址为 0x51, 其 A1 为 0,A0 为 1,那么剩余的 8bit 作为第二块 2K EEPROM 的 内部偏移地址;同理 AT24C08 从设备地址为 0x52, 其 A1 为 1,A2 为 0,那么剩余的 8bit 作为第三块 EEPROM 的内部偏移地址。通过上面的的分析,AT24C08 地址空间 分布与从设备地址的关系如下:

从设备地址             地址范围
0x50                  0x00000000 - 0x0000FFFF
0x51                  0x00010000 - 0x0001FFFF
0x52                  0x00020000 - 0x0002FFFF
0x53                  0x00030000 - 0x0003FFFF

课题实践

通过上面的分析,在用户空间使用程序对上面的代码进行验证,完整代码如下:


AT24C08 回滚问题

课题简介


AT24C08 工程应用

工程实践中经常使用 AT24C08 用于特定的任务,本节用于介绍 AT24C08 EERPOM 的工程应用:


潘多拉魔盒

潘多拉的盒子,来源于希腊神话。潘多拉是一个美女,她有一个盒子, 所以这个盒子就被叫作潘多拉的盒子。她的盒子里装着很多坏东西和很多好东西。 但她只放出了坏东西,没放出好东西,导致人间有了很多灾难。 AT24C08 EERPOM 也经常在工程中用来存储很多重要内容,比如用户密码, 账号,IP、MAC,ID、配置信息等,这些信息对个人隐私有着至关重要的 作用,如果不妥善管理,一但泄露不堪设想。因此本节用于介绍一个完整 的 AT24C08 工程运用。


实现原理

AT24C08 EERPOM 具有掉电不丢失数据,采用 I2C 接口进行数据通信,具有 8Kbit 的存储空间,将 ERRPOM 的存储空间进行规划,使其满足开发者工程 需要。


实践部署

BiscuitOS 支持一键部署 “潘多拉魔盒”, 开发者请参考如下命令:

cd BiscuitOS
make RaspberryPi_4B_defconfig
make menuconfig

选择并进入 “Package —>”

选择 “I2C: Inter-Integrated Circuit —>” 并进入下一级菜单

选择并进入 “AT24C08 Chest —>”

最后保存并退出,执行下列命令:

cd BiscuitOS
make
cd BiscuitOS/output/RaspberryPi_4B/package/at24c08_chest-0.0.1/
make prepare
make download
make
make install
make pack

至此,项目编译已经完成,接下来的将 “潘多拉” 魔盒安装到指定硬件平台, 本文例子运行在 RaspberryPi 4B 上,具体应用程序安装请参考如下内容:


使用说明

将应用程序安装到硬件平台后,可以使用如下命令查看工具的使用方法:

at24c08_chest-0.0.1 -h

例如开发者可以存储一个密码,并读出最新的密码:

at24c08_chest-0.0.1 -w password root

at24c08_chest-0.0.1 -r password

同理开发者可以修改获得更新 IP, MAC, boolean, int, string 等类型 数据,更多使用方法请查看其帮助。


项目融合

开发者在将 “潘多拉魔盒” 移植到自己的硬件平台,需要添加一些新的项, 此时开发者只需修改 “src/user_command.c” 文件,向 cmd_lists[] 数组中添加一个新的成员就可以,例如添加一个项,用于存储账号名字, 于是可以添加代码如下:

struct chest_cmd_struct cmd_lists[] = {

...

    {
         .type        = CMD_TYPE_STRING,
         .eeprom_addr = 0x50,
         .len         = 10,
         .cmdline     = "name",
         .desc        = "User name",
    },

...

};

然后重新编译代码,就可以使用这个项目了。


附录

BiscuitOS Home

BiscuitOS Driver

BiscuitOS Kernel Build

Linux Kernel

Bootlin: Elixir Cross Referencer

搭建高效的 Linux 开发环境

捐赠支持一下吧 🙂

MMU