
简介这是围绕 ADT75 数字温度传感器编写的驱动程序源码包主要面向嵌入式软硬件开发者和需要做温度监测的工程师解决系统与传感器之间通过 I2C/SPI 接口通信并准确获取温度数据的问题。ADT75 是 ADI 公司的精密温度传感器被广泛用于工业自动化、环境监测与电子设备散热控制驱动代码具体涵盖初始化配置、数据采集、原始数据转换、通信异常处理以及测量校准等核心环节可为读者理解实际驱动框架和协议实现提供清晰范式。压缩包内仅收录 1 个 C 语言源文件整体体积约 3KB代码量精简、结构清晰适合在 Linux 环境下参考移植与二次开发。目前已有 87 人浏览学习对于正在调试 ADT75 或准备为同类型传感器编写驱动的开发者是一份可直接借鉴的实用样例。1. 一个 rar 包里的 adt75.c拆开前先看芯片三件事做温度采集的人多半遇到过这种情况拿到一个 adt75.rar解压出来只有一个 adt75.c既没有 README 也没有 datasheet第一反应是找规格书还是直接 make我的建议是先看芯片本身。ADT75 是 ADI 出品、LM75 寄存器布局兼容的 12 位数字温度传感器测量范围 -55°C 到 125°CLSB 是 0.0625°CI²C 接口地址由 A0/A1 引脚决定。这颗片子真正难的不是「读温度」这个动作而是三个容易被忽略的细节7 位从机地址怎么拼、12 位温度数据在 SMBus 读回来时字节序是反的、负温度下符号位怎么扩展。这篇就顺着 adt75.c 的源码往下拆把寄存器映射、驱动骨架、编译加载和排错串成一条线适合正在写 Linux I²C 驱动或想把传感器快速接到嵌入式板子上的开发者。2. ADT75 寄存器与 I²C 时序地址、数据格式、配置位2.1 从 A0/A1 引脚推算 7 位从机地址ADT75 的 I²C 从机地址是 7 位高 4 位固定为1001低 2 位由芯片的 A0、A1 引脚电平决定中间的 1 位留空。也就是说地址范围是1001 A1 A0换算成 7 位地址就是 0x48 到 0x4B 四个值对应 A1A0 为 00、01、10、11。很多第一次用的人直接按 0x48 写死十块板子里有三块读到错误地址就是因为 A1/A0 被硬件工程师拉到了不同电平。正确做法是 probe 里用i2c_check_functionality配合i2c_detect去扫或者至少把地址做成设备树/平台数据可配项。datasheet 里写的是 7 位地址但 Linux 内核的struct i2c_client里存的是 7 位左移一位后的 8 位地址即 0x90~0x96 range注意 bit0 是读写位。写驱动的人最容易在这里晕设备树里reg 0x48内核会自动转成 client-addr 0x48I²C 协议层发送时再左移拼上 R/W 位。所以别自己在 probe 里手动左移否则地址会变成 0x90总线上一片 NoAck。引脚组合 (A1, A0)7 位地址SMBus 读写地址8 位0, 00x480x90 / 0x910, 10x490x92 / 0x931, 00x4A0x94 / 0x951, 10x4B0x96 / 0x972.2 0x00 温度寄存器与 12 位补码格式ADT75 的核心寄存器是 0x00只读存放当前温度。数据是 12 位二进制补码左对齐存放在两个字节里高字节是完整 8 位低字节只用了高 4 位低 4 位读出时恒为 0。换算关系是温度 原始 12 位值 × 0.0625°C。比如原始值 0x1A4十进制 420温度就是 420 × 0.0625 26.25°C负温度如 -2°C原始值则是 0xFE0。这里有个常见误读直接把读回来的两个字节拼成一个 u16然后右移 4 位得到「温度码」忽略符号位扩展。12 位补码的符号位是 bit11当 bit11 为 1 时必须先把数据当成有符号数做符号扩展再右移 4 位。否则 -2°C 会算成 4062 × 0.0625 ≈ 253.9°C直接爆表。后面第 3 章会给出具体换算代码。除 0x00 外还有 0x01 配置寄存器、0x02 低阈值 THYST、0x03 高阈值 TOS、0x04 one-shot 寄存器。0x02 和 0x03 都是可读写的比较器阈值中断模式下用到普通轮询采集可以不理。2.3 配置寄存器 0x01 与 one-shot 的工作机制0x01 配置寄存器是 8 位重点管两个事bit7 是 shutdown 位置 1 时 ADC 停止连续转换芯片进入低功耗模式此时读 0x00 返回的是最后一次转换结果bit6 和 bit5 决定比较器/中断模式及输出极性。如果需要按一下采一次温就把 bit7 置 1然后写 0x04 寄存器触发一次单次转换。转换完成后再读 0x00就能拿到本次采样值。驱动初始化最常见的写法如下/* 读取当前配置寄存器 */ s32 cfg i2c_smbus_read_byte_data(client, 0x01); if (cfg 0) return cfg; /* 开启连续转换模式清除 bit7 (shutdown) */ cfg ~0x80; i2c_smbus_write_byte_data(client, 0x01, cfg);这里i2c_smbus_read_byte_data的第一个参数是struct i2c_client *第二个参数是寄存器地址返回 0~255 的寄存器值负值表示通信错误。连续转换模式下ADT75 默认大约每 100ms 更新一次温度寄存器不需要外部触发。如果板子对功耗敏感可以改成 shutdown one-shot 组合shutdown 置 1 后待机电流在微安级需要读数时再写 0x04读完后继续睡。3. 从 adt75.c 源码看温度采集寄存器读写与负温度换算3.1 驱动入口与 probe设备树匹配和设备注册一个完整的 adt75.c 驱动骨架入口通常长这样static const struct i2c_device_id adt75_id[] { { adt75, 0 }, { } }; MODULE_DEVICE_TABLE(i2c, adt75_id); static const struct of_device_id adt75_of_match[] { { .compatible adi,adt75 }, { } }; MODULE_DEVICE_TABLE(of, adt75_of_match); static struct i2c_driver adt75_driver { .driver { .name adt75, .of_match_table adt75_of_match, }, .probe adt75_probe, .id_table adt75_id, }; module_i2c_driver(adt75_driver);module_i2c_driver展开后就是module_initmodule_exit省去手动登记i2c_add_driver的样板代码。of_match_table负责和设备树节点匹配i2c_device_id是给板级代码i2c_board_info方式用的两张表都维护兼容新老两种注册路径。注意 ADT75 与 LM75 寄存器布局高度一致主线内核的 lm75 驱动并没有收录adi,adt75这个 compatible所以要么在自己的驱动里维护这张匹配表要么直接借用nxp,lm75a这类已存在的 compatible 让 lm75 驱动接管。自己写驱动时我一般会同时保留两张表并用MODULE_DEVICE_TABLE导出方便 udev 自动加载模块。probe 里做的核心事情是初始化配置并注册 hwmon 设备static int adt75_probe(struct i2c_client *client) { struct device *hwmon_dev; s32 cfg; cfg i2c_smbus_read_byte_data(client, ADT75_REG_CFG); if (cfg 0) return cfg; cfg ~ADT75_CFG_SHUTDOWN; /* 清 bit7进入连续转换 */ cfg ~ADT75_CFG_COMPARATOR; /* 默认中断模式不用比较器 */ i2c_smbus_write_byte_data(client, ADT75_REG_CFG, cfg); hwmon_dev devm_hwmon_device_register_with_groups( client-dev, adt75, client, adt75_groups); return PTR_ERR_OR_ZERO(hwmon_dev); }devm_hwmon_device_register_with_groups是内核推荐的注册方式设备移除时自动释放不需要手动hwmon_device_unregister。这里的adt75_groups指向sysfs属性组用户态通过/sys/class/hwmon/hwmonX/temp1_input读到的温度值就是由这组属性文件背后的回调函数提供的。3.2 SMBus 读 word 的字节序陷阱读取温度寄存器最直接的方法是i2c_smbus_read_word_data(client, 0x00)但这里埋着一个大坑SMBus 的 word 传输是 little-endian先读低字节再读高字节组合出来的 u16 是「低字节在前」。而 ADT75 的 0x00 寄存器是标准的 big-endian 数据第一个字节是高 8 位第二个字节才是低 4 位。直接用返回值会被颠倒。所以我一般不用 SMBus word 接口而是手动发一个 2 字节读请求把字节序在自己手里控制住static int adt75_read_temp(struct i2c_client *client, long *temp_milli) { u8 reg ADT75_REG_TEMP; /* 0x00 */ u8 buf[2]; struct i2c_msg msg[2] { { .addr client-addr, .flags 0, .len 1, .buf reg }, { .addr client-addr, .flags I2C_M_RD, .len 2, .buf buf }, }; int ret, raw12; ret i2c_transfer(client-adapter, msg, 2); if (ret ! 2) return ret 0 ? ret : -EIO; /* buf[0] 是高字节buf[1] 高位才是有效数据 */ raw12 ((s16)((buf[0] 8) | buf[1])) 4; /* 0.0625°C 用整数表示为 625/10000 °C */ *temp_milli raw12 * 625; return 0; }这里的关键是((s16)((buf[0] 8) | buf[1])) 4先拼成一个有符号 16 位数此时符号位已经在 bit15 上再算术右移 4 位得到 12 位有符号温度码同时完成了符号扩展。如果写成(u16)… 4负温度就会变成大正数。这也是 adt75.c 里最容易改错的一行。3.3 温度定标为什么乘以 625 而不是除以 16内核 hwmon 的约定是温度以毫摄氏度为单位输出即 25°C 应该上报为 25000。ADT75 每个 LSB 是 0.0625°C即 62.5 m°C。直接用浮点会拖慢驱动且引入内核浮点依赖所以用定点运算raw12 × 625 raw12 × 0.0625 × 10000得到的就是毫摄氏度。举例验证12 位原始值温度码有符号换算结果0x1A4420420 × 625 262500 m°C即 26.25°C0xFE0-32-32 × 625 -20000 m°C即 -2.0°C0x00000 m°C即 0°Craw12 * 625的乘法在 32 位整数范围内完全安全温度码上限 4095乘积最大约 255 万不会溢出 int。temp_milli上报给 hwmon 后用户态sensors命令或直接cat temp1_input读到的就是毫摄氏度值。4. 编译加载与 i2c-tools 验证把驱动跑起来并核对数据4.1 解压 rar 与搭建外部模块 Makefile拿到 adt75.rar 先解压Linux 下用unrar x adt75.rar或者7z x adt75.rar都行。解开后如果只有 adt75.c需要自己补一个外部模块的 Makefileobj-m adt75.o KERNELDIR ? /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) all: $(MAKE) -C $(KERNELDIR) M$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M$(PWD) cleanKERNELDIR指向当前内核的 build 目录外部模块编译就是进入内核源码树、以M$(PWD)的方式只编当前目录下的目标文件。编译前确认系统装了linux-headers-$(uname -r)包否则/lib/modules/…/build是个空链接一 make 就报找不到 Makefile。编完生成 adt75.koinsmod adt75.ko加载dmesg | tail看有没有 probe 成功的日志。4.2 设备树节点与模块加载顺序驱动加载只是把i2c_driver注册进内核真正触发 probe 还要总线上有对应地址的设备。设备树里节点写成这样i2c2 { status okay; adt7548 { compatible adi,adt75; reg 0x48; }; };reg 0x48告诉内核这个从设备的 7 位地址是 0x48compatible用于和adt75_of_match里的字符串匹配。如果模块没有自动加载需要确认CONFIG_I2C、CONFIG_HWMON都开着并且 i2c 控制器节点本身状态正常。板级代码老平台没有设备树就用i2c_board_infostatic struct i2c_board_info __initdata adt75_i2c_info { I2C_BOARD_INFO(adt75, 0x48), }; i2c_new_client_device(adapter, adt75_i2c_info);I2C_BOARD_INFO第一个参数必须和adt75_id表里的字符串一致第二个参数是 7 位地址。相较设备树这种方式更直观但每块板子都要改 C 代码不方便维护新平台首选设备树。4.3 用 i2c-tools 和 16 进制视角核对数据加载驱动前先用 i2c-tools 确认芯片真的在总线上i2cdetect -y 2 i2cdump -y 2 0x48i2cdetect -y 2扫描 2 号 i2c 总线-y跳过交互确认如果 0x48 处显示编号或UU说明设备应答正常。i2cdump -y 2 0x48会以 16 进制形式把寄存器全部导出来第一列是寄存器地址后面是值。看 0x00 和 0x01 两个字节能直接验证驱动里的配置是否写进去了。用i2cget单独读温度寄存器更能说明字节序问题i2cget -y 2 0x48 0x00 w这里w表示 SMBus read word。假设返回0x401a注意这是小端组合实际寄存器内容是0x1a40即 26.25°C。如果直接拿0x401a去套 12 位换算结果完全对不上。这也是为什么我推荐用i2c_transfer手动读两个字节而不是用 word 接口。想再看细一点可以把i2cdump的输出重定向到文件用xxd或任意 16 进制编辑器打开对着数据手册逐字节核对。4.4 sysfs 接口验证驱动 probe 成功后hwmon 子系统会自动创建属性文件ls /sys/class/hwmon/ cat /sys/class/hwmon/hwmon0/temp1_inputtemp1_input输出的是毫摄氏度比如26250表示 26.25°C。如果读到的是 0 或一个巨大的正数优先怀疑符号扩展和字节序而不是芯片坏了。用dmesg看内核日志如果出现Failed to read temperature之类的 error先检查i2cdetect是否还能看到设备。5. 排错清单与一个调试技巧先扫地址再看字节序把 adt75.c 跑通的过程中最常踩的就四类问题。第一i2cdetect扫不到地址十有八九是 A0/A1 引脚电平和你预期不一致或者总线上拉电阻没焊用示波器量 SCL/SDA 波形最直接第二probe 失败但设备存在检查compatible字符串和设备树 reg 是否匹配寄存器地址字节是否传成了client-addr第三温度读数正负异常、数值翻倍或减半先怀疑字节序再怀疑换算系数ADT75 是 12 位数据不是 16 位右移 4 位这步不能省第四读出来一直是同一个值多数是配置寄存器 bit7 被置 1 进入了 shutdown 模式此时 0x00 只返回最后一次转换结果。最后一个实用技巧分两步验证驱动逻辑。第一步不做任何换算直接用i2cget -y 2 0x48 0x00 b连续读 5 次记录高字节变化第二步把手放到芯片封装上等温度明显上升后再读一次如果高字节单调递增说明 I²C 通路和芯片都正常问题只剩换算。验证脚本随手就能写for i in $(seq 1 5); do i2cget -y 2 0x48 0x00 b sleep 1 doneb表示读单字节返回的是 0x00 寄存器高字节。初始读数大约在 0x1A 附近对应 26°C 上下手指按压后变成 0x1C、0x1D 这类更大值说明采集链路没问题。此时再回头看 adt75.c 里的换算代码逐个字节比对通常一眼就能找出符号扩展写错的地方。本文还有配套的精品资源点击获取