Email: BuddyZhang1 buddy.zhang@aliyun.com

目录


LM75A 简介

LM75A 是一块采用片内带隙温和 σ-δ 数字转模拟的的温度传感器。LM75A 也是一款热检测器,用于过热检测。LM75A 包含了一些数据寄存器: “Conf” 配置寄存器用于存储 LM75A 的工作模式、OS 操作模式、OS 极性、 以及 OS 故障队列; “Temp” 温度寄存器用于存储可读的数字温度; “Tos & Thyst” 探测断点寄存器用于存储过温关断和迟滞阈值。以上 寄存器可以被主控通过 I2C 总线访问。LM75A 还包含了一个开漏输出, 当温度超过了预设值,那么该输出就会被激活。LM75A 支持三个可选的 从设备地址。

LM75A 可以被配置为不同的工作模式。当 LM75A 工作在正常模式,其会 周期性的监控环境温度; 当 LM75A 工作在 shutdown 模式,其以最小 功耗运行。OS 比较模式和 OS 中断模式会激活 OS 输出操作,OS 输出可以 为高电平也可以为低电平; 连续的故障数量会激活 OS 输出操作,其可以 通过编程和操作阈值产生故障。

LM75A 的 “Temp” 温度寄存器一直存储着一个 11 位补码数据,温度的 分辨率是 0.125°C,这种高温分辨率在精确测量热漂移或失控的应用中 特别有用。LM75A 上下电之后工作在正常模式,且 OS 为比较模式,温度 的阈值为 80°C,以及迟滞阈值为 75°C,因此可用作具体预定义温度定点 的独立恒温器。

LM75A Datasheet

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


LM75A 原理


功能描述

LM75A 使用了片内带隙传感的温度传感器,其精度达到 0.125°C, 当前温度 数据通过 11 bit 补码的形式存储。主控可以通过 I2C 总线随时读取温度 寄存器的数值,以此获得当前温度。读取温度的过程中,不会影响正在内部 模拟数值到数字数值的转换。

LM75A 可以设置为正常工作模式获得关电模式进行工作。正常模式下,LM75A 每隔 100ms 进行一次将采集到的温度模拟值转换成数字值,每当转换完毕之后, 新的数值就会更新到温度寄存器里面。在关电模式下,LM75A 处于待机模式, 此时数据转换被禁止,并且温度寄存器一致保持最后一次转换的数值。在 关电模式下,I2C 接口保持可用。LM75A 的工作模式可通过编程控制,其 通过配置寄存器的 B0 位决定。每当上点或者从关电模式变为正常模式,内部 的转换器会被初始化。LM75A 框图如下:

此外,正常工作模式下,每次模拟数值转换数字值之后,温度寄存器的值 都会自动与过温掉电阈值进行对比,该值存储在 Tos 寄存器中,并且也会 自动同 Thyst 寄存器内的数值进行对比,以此根据对比的结果设置 LM75A OS 输出状态。LM75A 的 Tos 和 Thyst 寄存器是可读可写寄存器,并且两个 寄存器的有效数值有 9bit 数字值构成,由于使用了 9bit 数值,因此由两个 字节组成,Tos 和 Thyst 寄存器值采用 11bit 数据结构的最高 9 bit。

OS 引脚的输出有 LM75A 采用的模式决定。在 OS 比较模式中,OS 的输出就像 一个恒温器一样。当温度寄存器的值超过 Tos 寄存器的值,则 OS 输出变得 激活,直到温度寄存器的值小于 Thyst 寄存器的值。读取 LM75A 寄存器和让 LM75A 进入关电模式不会改变 OS 的输出状态,OS 的这一特点可以用于控制 制冷风扇或者热开关.正如上图,当温度寄存器数值超过 Tos 寄存器中的值, 那么此时 OS 输出低电平,并保持低电平输出,直到温度寄存器的值低于 Thyst 寄存器中的值,之后 OS 输出高电平并保持。

在 OS 中断模式中,OS 输出用于热中断。LM75A 上电之后,OS 第一次被激活是 在温度寄存器的值操作 Tos 寄存器的值,之后将保持输出不变,直到读取任何 寄存器的产生一次复位,此时 OS 输出复位。当 OS 输出通过超过 Tos 而激活, 并复位之后,在这种情况下,如果温度低于 Thyst 寄存器数值,此时会再次 激活 OS 输出,并保持电平不变,一直到复位才翻转电平。OS 中断模式将会 连续改变 OS 的输出状态,例如: 温度超过 Tos、复位、温度低于 Thyst,复位、 温度超过 Tos、复位、温度低于 Thyst,复位 ….. 在 OS 中断模式中, LM75A 从正常模式进入关电模式会初始化 OS 输出。

LM75A 上电之进入正常工作模式,Tos 寄存器设置为 80°C,Thyst 寄存器设置 为 75°C,OS 激活之后的电平输出为低电平。温度寄存器一开始不可用,一直到 第一次温度转换完成,耗时 100ms.


寄存器

LM75A 寄存器列表如下表:


Pointer Register

Pointer 寄存器用于主控访问 LM75A 寄存器时使用,Pointer 寄存器 是不可读的,其由 8bit 数据构成,8 bit 的 6bit MSB 必须为 0,因此 只有最低 2 bit 有效。其定义如下图:


配置寄存器 (Conf)

配置寄存器是一个可读可写的寄存器,寄存器的位宽为 8bit,该寄存器 用于配置 LM75A 的工作模式,每个字节的含义如下图:

“B0” 用于配置 LM75A 的工作模式。”B0=1” 时工作在关电模式; “B0=0” 时 工作在正常模式。默认工作在正常工作模式。

“B1” 用于配置 OS 输出的工作模式。”B1=1” OS 工作在中断模式; “B1=0” 时工作在比较模式。默认工作工作在比较模式.

“B2” 用于配置 OS 激活时输出电平. “B2=1” OS 激活输出高电平; “B2=0” 时 OS 激活输出低电平。默认 OS 激活输出低电平。

“B4-B3” 用于配置 OS 错误队列

“B7-B5” 保留。


温度寄存器

温度寄存器用于 LM75A 温度传感器采集的模拟值转换为数字的结果。 该寄存器是一个只读寄存器,寄存器位宽是 16bit,但只有其中的 11bit 有效,温度寄存器采用了 MSB 的 8bit 和 LSB 的高三位。温度寄存器的 精度为 0.125°C, 寄存器结构如下图:

11 为温度寄存器数值与温度的转换关系如下:


过热带变寄存器 Thyst

Thyst 寄存器是一个可读可写寄存器,其工作在 LM75A 的看门狗操作中, 每当新采集温度存储到温度寄存器只有,LM75A 自动将温度寄存器中的 值与 Thyst 寄存器的值做对比,当温度寄存器的值有之前大于 Thyst 寄存器的值 变为小于 Thyst 寄存器的值,那么 OS 输出就被激活。Thyst 寄存器的位宽是 9 bits,其精度是 0.5°C,其数据结果如下图:

Thyst 寄存器数值与温度转换关系如下图:


过热掉电阈值寄存器 Tos

Tos 寄存器是一个可读可写寄存器,其工作在 LM75A 的看门狗操作中, 每当新采集温度存储到温度寄存器只有,LM75A 自动将温度寄存器中的 值与 Tos 寄存器的值做对比,当温度寄存器的值超过 Tos 寄存器的值, 那么 OS 输出就被激活。Tos 寄存器的位宽是 9 bits,其精度是 0.5°C, 其数据结果如下图:

Tos 寄存器数值与温度转换关系如下图:


I2C 数据交互

LM75A 通过 I2C 总线协议与主控进行数据交互,LM75A 提供了灵活 的读写支持,具体如下:


实践准备

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


硬件准备

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


硬件平台

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


LM75A

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


逻辑分析仪

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

DSLogic 逻辑分析仪数据工具:


示波器

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

DSCope 示波器采用样图:


硬件连接

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

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

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


软件准备

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


RaspberryPi 4B 开发环境部署

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


内核启用 I2C 功能

为了在 RaspberryPi 上使用 LM75A,开发者应该基于 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 卡槽里。


开发相关文档

LM75A 驱动

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


LM75A 驱动分析

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

BiscuitOS 提供的完整驱动如下:

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


LM75A 设备注册

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

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

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

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


LM75A I2C 接口

通过 LM75A 的数据手册可以知道,LM75A 提供只提供了一种方式的 读和一种方式的写操作,与大多数的 I2C 从设备不同,LM75A 从设备不能通过 通用的 i2c 工具或函数进行读写,需要对 I2C 的读写过程进行封装。 接下里分别介绍每种操作如何编写代码。


LM75A 配置寄存读操作

上图定义了 LM75A 对配置寄存器读操作的时序,从上图可以知道 主控产生一个 START 信号之后,接着发送 LM75A 从设备地址,W/R bit 为低电平,代表写操作。如果此时 LM75A 发出 ACK 应答信号,那么 主控接着发送配置寄存器的地址,如果此时 LM75A 发出 ACK 应答信号, 那么主控的操作成功,前半步操作与 AT24CX 的读操作类似,也是一种 “Dummy Write” 操作。主控再次产生一个 START 信号,接着发送 LM75A 的从设备地址,此时 R/W bit 设置为高,表示以一次读操作,接着 LM75A 收到信号之后,产生一个 ACK 应答信号,之后 LM75A 将对应 寄存器的值发送到 I2C 总线上,之后主控从 I2C 总线上读取这些数据, 最后主控产生 STOP 信号,介绍此次读操作。

上图即是根据信号协议编写的 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                  */
};

根据 LM75A 寄存器读时序,主控需要发送两个 i2c_msg, 第一个用于写操作, 而第二个用于读操作。第一个 i2c_msg 主要用于发送配置寄存器的地址, 因此 i2c_msg 的 buf 指向配置寄存器的地址. 第二个 i2c_msg 主要是用来 读寄存器的 8bit 值,因此 i2c_msg 的 buf 指向存储 8bit 的地址,并且 将 i2c_msg 的 flags 加上 I2C_M_RD, 表示读操作。

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

从上图的波形图可以看出,抓取的波形符合 LM75A 配置寄存器读的规范 要求。

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


LM75A 数据寄存器读操作

上图定义了 LM75A 对数据寄存器读操作的时序,从上图可以知道 主控产生一个 START 信号之后,接着发送 LM75A 从设备地址,W/R bit 为低电平,代表写操作。如果此时 LM75A 发出 ACK 应答信号,那么 主控接着发送数据存器的地址,如果此时 LM75A 发出 ACK 应答信号, 那么主控的操作成功,前半步操作与 AT24CX 的读操作类似,也是一种 “Dummy Write” 操作。主控再次产生一个 START 信号,接着发送 LM75A 的从设备地址,此时 R/W bit 设置为高,表示以一次读操作,接着 LM75A 收到信号之后,产生一个 ACK 应答信号,之后 LM75A 将对应 寄存器的值发送到 I2C 总线上,由于数据寄存器包含 11 bit 的值,因此 需要使用两个字节发送,之后主控从 I2C 总线上读取这些数据, 最后主控产生 STOP 信号,介绍此次读操作。

上图即是根据信号协议编写的 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                  */
};

根据 LM75A 寄存器读时序,主控需要发送两个 i2c_msg, 第一个用于写操作, 而第二个用于读操作。第一个 i2c_msg 主要用于发送配置寄存器的地址, 因此 i2c_msg 的 buf 指向配置寄存器的地址. 第二个 i2c_msg 主要是用来 读寄存器的 8bit 值,因此 i2c_msg 的 buf 指向存储 8bit 的地址,但是 由于数据寄存器包含在 11bit 的数据中,因此需要两个字节存储数据,因此 并且 将 i2c_msg 的 flags 加上 I2C_M_RD, 表示读操作。

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

从上图的波形图可以看出,抓取的波形符合 LM75A 配置寄存器读的规范 要求。

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


LM75A 当前数据寄存器读操作

上图定义了 LM75A 对当前数据寄存器读操作的时序,从上图可以知道 主控产生一个 START 信号之后,接着发送 LM75A 的从设备地址,此时 R/W bit 设置为高,表示以一次读操作,接着 LM75A 收到信号之后, 产生一个 ACK 应答信号,之后 LM75A 将对应寄存器的值发送到 I2C 总 线上,之后主控从 I2C 总线上读取这些数据,最后主控产生 STOP 信号, 介绍此次读操作。

上图即是根据信号协议编写的 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                  */
};

根据 LM75A 当前数据寄存器读操作,只需一个 i2c_msg 即可完成操作。 由于数据寄存器包含两个字节,因此 i2c_msg buf 只想存储 2 个字节的 地址,并且是一个读操作,因此 i2c_msg flags 需要添加 I2C_M_RD 标志。

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

从上图的波形图可以看出,抓取的波形符合 LM75A 配置寄存器读的规范 要求。

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


LM75A 配置寄存器写操作

上图是 LM75A 配置寄存器写操作,其类似与 I2C 单字节写操作。主控 首先产生一个 START 信号,接着向 I2C 总线上发送 LM75A 的从设备地址, 并将 R/W bit 设置为低,即代表一次写操作。如果此时 LM75A 收到该信息 之后,会向 SDA 总线上发送一个 ACK 应答,主控收到应答之后,接着向 I2C 总线上发送配置寄存器的地址,并等待 LM75A 的应答。当等到 LM75A 应答之后,主控向 I2C 发送要写的内容,长度为 1 个字节,当写完之后, 等待 LM75A 的应答,如果 LM75A 应答,那么主控就产生一个 STOP 信号, 经过上面的操作之后,一次完整的 LM75A 配置寄存器写操作就算完成了.

上图即是根据信号协议编写的 LM75A 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                  */
};

在上面的函数中,由于 LM75A 只包含了一次写操作,因此只需要一个 i2c_msg 就可以完成任务,因此定义一个 i2c_msg 结构,由于写操作的 时候,R/W bit 为 0,因此给 i2c_msg 结构的 flags 添加 I2C_M_WR 标志, 由于需要写 2 个 byte 的数据,因此 len 设置为 2,并且 buf 只想存储 两个字节的地址.

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

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

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


LM75A 数据寄存器写操作

上图是 LM75A 数据寄存器写操作时序,其类似与 I2C 一次写两个 byte 数据 操作。主控首先产生一个 START 信号,接着向 I2C 总线上发送 LM75A 从设备 地址,并且 R/W bit 设置为 0,以此表示一次写操作,接着等待 LM75A 应答。 如果此时 LM75A 应答,那么主控接着向 I2C 总线发送数据寄存器的地址,并 等待 LM75A 的应答,LM75A 应答之后,主控先向 I2C 总线发送第一个字节,该 字节是高字节,并等待 LM75A 应答,LM75A 应答之后,主控继续向 I2C 总线 发送第二个字节,并等待 LM75A 应答。LM75A 应答之后,主控产生一个 STOP 信号,以此表示 LM75A 数据寄存器写操作完成。

上图即是根据信号协议编写的 LM75A 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                  */
};

通过 LM75A 数据寄存器写时序的分析,主控只需要使用一个 i2c_msg 就 可以完成写操作,写操作一共包含三个字节的数据,第一个数据是数据寄存器 的地址,后两个字节为要写的数据,因此使用一个三字节的内存存储内容, 并将 i2c_msg 的 buf 只想三字节的地址。

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

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

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


LM75A 使用

开发者可以参考上面代码在内核驱动中使用 LM75A, 例如在上面的代码中, 读取 LM75A 温度寄存器,读取当前温度.


LM75A BiscuitOS-RaspberryPi 实践部署

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


LM75A 源码获取

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

cd BiscuitOS
make RaspberryPi_4B_defconfig
make menuconfig

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

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

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


LM75A 源码编译

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

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


LM75A 模块安装

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


LM75A DTB 安装

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

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

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

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

        lm75a@48 {
                compatible = "BiscuitOS,lm75a";
                reg = <0x48>;
                BD-gpio = <&gpio 25 GPIO_ACTIVE_HIGH>;
        };
};

修改完 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 更新成功.


LM75A 模块使用

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

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


LM75A 工程实践部署

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


LM75A 源码获取

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

wget https://gitee.com/BiscuitOS_team/HardStack/raw/Gitee/Device-Driver/i2c/Device/LM75A/Base/kernel/Kconfig
wget https://gitee.com/BiscuitOS_team/HardStack/raw/Gitee/Device-Driver/i2c/Device/LM75A/Base/kernel/Makefile
wget https://gitee.com/BiscuitOS_team/HardStack/raw/Gitee/Device-Driver/i2c/Device/LM75A/Base/kernel/default.dts
wget https://gitee.com/BiscuitOS_team/HardStack/raw/Gitee/Device-Driver/i2c/Device/LM75A/Base/kernel/lm75a.c

LM75A 源码编译

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

外部模块独立编译

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

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

make
make install
内核源码树编译

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

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

LM75A 模块安装

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


LM75A DTB 安装

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

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

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

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

        lm75a@38 {
                compatible = "BiscuitOS,lm75a";
                reg = <0x38>;
		BD-gpio = <&gpio 25 GPIO_ACTIVE_HIGH>;
        };
};

修改完 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 更新成功.


LM75A 模块使用

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

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


i2cdetect

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

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


i2cdump

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

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


i2cget

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

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


i2cset

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

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


LM75A 应用程序

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


LM75A 应用程序分析

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

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

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


LM75A I2C 接口

通过 LM75A 的数据手册可以知道,LM75A 提供了一种方式的读,也 只提供了一种方式的写。接下里分别介绍每种操作如何编写代码。


LM75A 配置寄存读操作

上图定义了 LM75A 对配置寄存器读操作的时序,从上图可以知道 主控产生一个 START 信号之后,接着发送 LM75A 从设备地址,W/R bit 为低电平,代表写操作。如果此时 LM75A 发出 ACK 应答信号,那么 主控接着发送配置寄存器的地址,如果此时 LM75A 发出 ACK 应答信号, 那么主控的操作成功,前半步操作与 AT24CX 的读操作类似,也是一种 “Dummy Write” 操作。主控再次产生一个 START 信号,接着发送 LM75A 的从设备地址,此时 R/W bit 设置为高,表示以一次读操作,接着 LM75A 收到信号之后,产生一个 ACK 应答信号,之后 LM75A 将对应 寄存器的值发送到 I2C 总线上,之后主控从 I2C 总线上读取这些数据, 最后主控产生 STOP 信号,介绍此次读操作。

上图即是根据信号协议编写的 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                  */
};

根据 LM75A 寄存器读时序,主控需要发送两个 i2c_msg, 第一个用于写操作, 而第二个用于读操作。第一个 i2c_msg 主要用于发送配置寄存器的地址, 因此 i2c_msg 的 buf 指向配置寄存器的地址. 第二个 i2c_msg 主要是用来 读寄存器的 8bit 值,因此 i2c_msg 的 buf 指向存储 8bit 的地址,并且 将 i2c_msg 的 flags 加上 I2C_M_RD, 表示读操作。

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

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

借助 DSlogic 分析仪,可以快速获得数字波形以及协议解析信息, 上图的波形符合 “LM75A 配置寄存读操作” 的要求。


LM75A 数据寄存器读操作

上图定义了 LM75A 对数据寄存器读操作的时序,从上图可以知道 主控产生一个 START 信号之后,接着发送 LM75A 从设备地址,W/R bit 为低电平,代表写操作。如果此时 LM75A 发出 ACK 应答信号,那么 主控接着发送数据存器的地址,如果此时 LM75A 发出 ACK 应答信号, 那么主控的操作成功,前半步操作与 AT24CX 的读操作类似,也是一种 “Dummy Write” 操作。主控再次产生一个 START 信号,接着发送 LM75A 的从设备地址,此时 R/W bit 设置为高,表示以一次读操作,接着 LM75A 收到信号之后,产生一个 ACK 应答信号,之后 LM75A 将对应 寄存器的值发送到 I2C 总线上,由于数据寄存器包含 11 bit 的值,因此 需要使用两个字节发送,之后主控从 I2C 总线上读取这些数据, 最后主控产生 STOP 信号,介绍此次读操作。

上图即是根据信号协议编写的 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                  */
};

根据 LM75A 寄存器读时序,主控需要发送两个 i2c_msg, 第一个用于写操作, 而第二个用于读操作。第一个 i2c_msg 主要用于发送配置寄存器的地址, 因此 i2c_msg 的 buf 指向配置寄存器的地址. 第二个 i2c_msg 主要是用来 读寄存器的 8bit 值,因此 i2c_msg 的 buf 指向存储 8bit 的地址,但是 由于数据寄存器包含在 11bit 的数据中,因此需要两个字节存储数据,因此 并且将 i2c_msg 的 flags 加上 I2C_M_RD, 表示读操作。

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

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

借助 DSlogic 分析仪,可以快速获得数字波形以及协议解析信息, 上图的波形符合 “LM75A 数据寄存器读操作” 的要求。


LM75A 当前数据寄存器读操作

上图定义了 LM75A 对当前数据寄存器读操作的时序,从上图可以知道 主控产生一个 START 信号之后,接着发送 LM75A 的从设备地址,此时 R/W bit 设置为高,表示以一次读操作,接着 LM75A 收到信号之后, 产生一个 ACK 应答信号,之后 LM75A 将对应寄存器的值发送到 I2C 总 线上,之后主控从 I2C 总线上读取这些数据,最后主控产生 STOP 信号, 介绍此次读操作。

上图即是根据信号协议编写的 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                  */
};

根据 LM75A 当前数据寄存器读操作,只需一个 i2c_msg 即可完成操作。 由于数据寄存器包含两个字节,因此 i2c_msg buf 只想存储 2 个字节的 地址,并且是一个读操作,因此 i2c_msg flags 需要添加 I2C_M_RD 标志。

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

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

借助 DSlogic 分析仪,可以快速获得数字波形以及协议解析信息, 上图的波形符合 “LM75A 当前数据寄存器读操作” 的要求。


LM75A 配置寄存器写操作

上图是 LM75A 配置寄存器写操作,其类似与 I2C 单字节写操作。主控 首先产生一个 START 信号,接着向 I2C 总线上发送 LM75A 的从设备地址, 并将 R/W bit 设置为低,即代表一次写操作。如果此时 LM75A 收到该信息 之后,会向 SDA 总线上发送一个 ACK 应答,主控收到应答之后,接着向 I2C 总线上发送配置寄存器的地址,并等待 LM75A 的应答。当等到 LM75A 应答之后,主控向 I2C 发送要写的内容,长度为 1 个字节,当写完之后, 等待 LM75A 的应答,如果 LM75A 应答,那么主控就产生一个 STOP 信号, 经过上面的操作之后,一次完整的 LM75A 配置寄存器写操作就算完成了.

上图即是根据信号协议编写的 LM75A 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                  */
};

在上面的函数中,由于 LM75A 只包含了一次写操作,因此只需要一个 i2c_msg 就可以完成任务,因此定义一个 i2c_msg 结构,由于写操作的 时候,R/W bit 为 0,因此给 i2c_msg 结构的 flags 添加 I2C_M_WR 标志, 由于需要写 2 个 byte 的数据,因此 len 设置为 2,并且 buf 只想存储 两个字节的地址.

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

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

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


LM75A 数据寄存器写操作

上图是 LM75A 数据寄存器写操作时序,其类似与 I2C 一次写两个 byte 数据 操作。主控首先产生一个 START 信号,接着向 I2C 总线上发送 LM75A 从设备 地址,并且 R/W bit 设置为 0,以此表示一次写操作,接着等待 LM75A 应答。 如果此时 LM75A 应答,那么主控接着向 I2C 总线发送数据寄存器的地址,并 等待 LM75A 的应答,LM75A 应答之后,主控先向 I2C 总线发送第一个字节,该 字节是高字节,并等待 LM75A 应答,LM75A 应答之后,主控继续向 I2C 总线 发送第二个字节,并等待 LM75A 应答。LM75A 应答之后,主控产生一个 STOP 信号,以此表示 LM75A 数据寄存器写操作完成

上图即是根据信号协议编写的 LM75A 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                  */
};

通过 LM75A 数据寄存器写时序的分析,主控只需要使用一个 i2c_msg 就 可以完成写操作,写操作一共包含三个字节的数据,第一个数据是数据寄存器 的地址,后两个字节为要写的数据,因此使用一个三字节的内存存储内容, 并将 i2c_msg 的 buf 只想三字节的地址。

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

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

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


LM75A 使用

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

在上面的代码中,读取 LM75A 提供的温度.


LM75A BiscuitOS-RaspberryPi 中使用

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


LM75A 源码获取

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

cd BiscuitOS
make RaspberryPi_4B_defconfig
make menuconfig

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

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

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


LM75A 源码编译

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

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


LM75A 模块安装

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


LM75A 应用程序的使用

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

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


LM75A 工程实践部署

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


LM75A 源码获取

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

wget https://gitee.com/BiscuitOS_team/HardStack/raw/Gitee/Device-Driver/i2c/Device/LM75A/Base/userland/lm75a.sh
wget https://gitee.com/BiscuitOS_team/HardStack/raw/Gitee/Device-Driver/i2c/Device/LM75A/Base/userland/lm75a.c
wget https://gitee.com/BiscuitOS_team/HardStack/raw/Gitee/Device-Driver/i2c/Device/LM75A/Base/userland/Makefile

LM75A 源码编译

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

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

make
make install

LM75A 应用安装

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


LM75A 应用使用

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

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


LM75A 问题合集


LM75A 进阶研究


LM75A OS 比较模式

课题简介

OS 引脚的输出有 LM75A 采用的模式决定。在 OS 比较模式中,OS 的输出就像 一个恒温器一样。当温度寄存器的值超过 Tos 寄存器的值,则 OS 输出变得 激活,直到温度寄存器的值小于 Thyst 寄存器的值。读取 LM75A 寄存器和让 LM75A 进入关电模式不会改变 OS 的输出状态,OS 的这一特点可以用于控制 制冷风扇或者热开关.正如上图,当温度寄存器数值超过 Tos 寄存器中的值, 那么此时 OS 输出低电平,并保持低电平输出,直到温度寄存器的值低于 Thyst 寄存器中的值,之后 OS 输出高电平并保持。

正如上图,当每次读取温度数值之后,如果温度数值大于 Tos 寄存器中 的值,那么 OS 处于激活状态并保持激活。当温度寄存器中的值由高于 Thyst 寄存器中的值变成小于 Thyst 寄存器中的值,那么 OS 变为未激活 态。基于上述原理,本次实践将 OS 的激活态设置为高电平,即在不激活 的状态下,OS 输出低电平。并将测量 OS 的输出变化。硬件连接如下图:

红线连接 LM75A 的 VCC; 黑线连接 LM75A 的 GND; 黄线连接 LM75A 的 SDA; 绿线连接 LM75A 的 SCL; 棕色线连接 LM75A 的 OS 输出. 为了更好的演示这个实验,可以将 LM75A 的 OS 输出接到一个按钮获得风扇 上面。


课题实践

本例中使用一个完整的应用程序介绍 OS 比较模式的使用,如下:

在应用程序中,首先将 LM75A 的工作模式设置为正常工作模式,OS 的激活 态为低电平,OS 比较模式,接着确保写入成功。设置 Tos 的温度为 50°C, Thyst 的温度为 40°C。接着不断的读取温度的值。

在外部,将 OS 连接到一个风扇上面,上电之后,风扇不转,接着使用打火机 让 LM75A 的温度不断升高并超过 50°C,当风扇转起来之后停止加热,接着 观察温度降到 40°C 时风扇的变化。

实际测量温度变化曲线如下:

风扇在 50°C 的时候开始转,而当温度降低到 40°C 时候,风尚停止转动。 风扇变化符合预期,也间接说明在 OS 比较模式中,OS 被激活之后保持 激活态,指到温度回退小于 Thyst 寄存器值之后,OS 变成不激活态。 该驱动同样也集成到 BiscuitOS 里,开发者可以参考下面章节进行驱动 的使用:

基于上面的章节,驱动程序的选择如下图:

将上面的应用程序放到树莓派上运行,运行结果如下图:


LM75A 工程应用

工程实践中经常使用 LM75A 用于特定的任务,本节用于介绍 LM75A I/O Expander的工程应用:


温度探测器

LM75A 是一个高精度温度传感器,因此可以使用 LM75A 制作一个温度探测器。


实现原理

LM75A 内部包含一个温度寄存器,用于存储当前温度,该温度是 LM75A 将模拟值转换成数字值,其长度为 11bit。主控可以通过 I2C 协议读取 其中的数值。基于这些条件,可以编写一个驱动,驱动定时从温度寄存器 中读取当前温度,并通过 input 子系统,将温度上传到用户空间,供用户 空间使用。


实践部署

BiscuitOS 支持一键部署 “温度探测器”, 开发者请参考如下命令:

cd BiscuitOS
make RaspberryPi_4B_defconfig
make menuconfig

选择并进入 “Package —>”

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

设置 “LM75A: Input Device Driver Application —>” 为 “Y”。设置完毕之后, 最后保存并退出,执行下列命令:

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

对应的驱动已经自动编译并安装,驱动位于位于:

cd BiscuitOS
make
cd BiscuitOS/output/RaspberryPi_4B/package/lm75a_input_module-0.0.1/
ls lm75a.ko

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


使用说明

将应用程序安装到硬件平台后,使用如下命令进行使用:

insmod lm75a.ko
lm75a_traffic-0.0.1


附录

BiscuitOS Home

BiscuitOS Driver

BiscuitOS Kernel Build

Linux Kernel

Bootlin: Elixir Cross Referencer

搭建高效的 Linux 开发环境

捐赠支持一下吧 🙂

MMU