
简介这是ADT75数字温度传感器的C语言驱动程序源码面向嵌入式开发者、Linux驱动学习者以及需要做温度采集与监控的硬件工程师。ADT75由ADI公司生产测量精度高常用于工业自动化、环境监测、电子设备散热控制等场景因此这份驱动源码的重点在于解决传感器初始化、寄存器配置、I²C数据读取和温度值转换等基础问题对没有接触过数字温度传感器的开发者同样友好。压缩包内共有1个文件即C语言源文件整个包体仅3KB结构非常精简无需复杂工程配置适合直接阅读、二次移植或嵌入现有项目。源码中涵盖了传感器初始化、温度数据采集、二进制数据到摄氏度的换算、通信错误处理等核心逻辑并可根据实际硬件进行校准调整能够帮助开发者快速理解数字温度传感器的驱动框架。目前已有87人学习对于希望掌握传感器驱动编写思路或需要在项目中集成温度监控功能的开发者具有清晰的参考价值。1. ADT75驱动源码能落地吗这份C代码替你把最多的坑踩完了做嵌入式温度监控的第一反应多半是挂一颗DS18B20或者NTC热敏电阻。但工业板卡上留给传感器的往往只有一组I²C总线这时候ADT75这类12位数字温度传感器反而是更省事的选择。问题是驱动不好找内核主线未必覆盖这一颗很多BSP会直接丢给你一份adt75.c让你自己消化。这份压缩包的核心就是这份C语言驱动源码它把I²C探测、寄存器初始化、温度读取、数据换算、报警阈值比较都写在一个文件里。对做BSP、驱动移植和板级验证的工程师来说它能省掉你对着几百页datasheet抠寄存器时间的功夫至少能当一份可编译、可裁剪的参考工程。如果你压根不想碰内核从它里面剥离用户空间读温度的逻辑也很顺手。下面我按源码逻辑、装载方法到排查顺序把这颗传感器和你手头的实际需求串起来讲。2. 拆包adt75.c寄存器映射、温度换算与三块必看逻辑2.1 驱动骨架init、读取、换算三件套拿到adt75.c先不要急着编译第一步是把文件里的函数列表扫一遍。正常这份源码会包含三个部分一个是i2c_driver结构体负责和I²C总线core对接一个是probe回调负责在检测到器件后做初始化再一个就是温度读取函数这是全部逻辑的核心。我一般会先用grep把函数名列出来确认它到底走的是smbus接口还是普通的i2c_transfer。static const struct i2c_device_id adt75_id[] { { adt75, 0 }, { } }; MODULE_DEVICE_TABLE(i2c, adt75_id); static struct i2c_driver adt75_driver { .driver { .name adt75, }, .probe adt75_probe, .id_table adt75_id, }; module_i2c_driver(adt75_driver);这段是驱动和外层I²C core的接口.probe在系统枚举到地址0x48附近的设备时被调用.id_table用于和device tree或board info里的名字匹配。module_i2c_driver宏把注册和注销都封装好了内核模块的入口出口不用自己写。如果你拿到的源码还是旧的module_initmodule_exit写法功能上没差但新版内核更推荐直接用这个宏。2.2 温度换算0.0625°C/LSB与符号位才是真正的坑ADT75的输出是12位补码分辨率0.0625°C/LSB。很多第一次写驱动的同学会在这里翻车因为寄存器读回来的原始值本身已经是“温度 × 16”的补码形式并不需要你再去移位提取高12位。正确做法是把大端序转成主机序再用带符号类型做乘法。static int adt75_read_temp(struct i2c_client *client, long *val) { s32 raw; s16 t; raw i2c_smbus_read_word_data(client, ADT75_REG_TEMP); /* 0x00 */ if (raw 0) return -EIO; /* SMBus读word返回的是低字节在前温度寄存器是大端需要调换 */ t (s16)swab16((u16)raw); /* 1 LSB 0.0625°C用毫摄氏度输出避免浮点 */ *val t * 625 / 10; return 0; }逻辑说明第一步用i2c_smbus_read_word_data读0x00温度寄存器第二步用swab16做字节序转换这一步缺失会让正数温度对不上负数直接错乱第三步是整数定标625/10表示62.5毫摄氏度也就是0.0625°C。参数上要注意t必须是s16如果用了u160°C以下的读数会被当成65535附近的巨大正数换算出来就是两百多度这是最常见的“负数读成255°C”事故根源。还要警惕有人自作聪明先raw 4再乘0.0625表面上是在提取12位数据实际上把-47°C算成了-2.9°C因为寄存器值本身就是带尺度的补码移位会改变量纲。2.3 配置寄存器与报警阈值probe里该写什么probe函数里除了读取温度还要初始化配置寄存器和两个阈值寄存器。ADT75的配置寄存器在0x01THYST在0x02TOS在0x03。上电默认是连续转换模式比较器输出这些对大多数场景够用但如果你用到了OS/INT引脚就必须把模式配成中断输出否则过热标志会一直锁存。寄存器偏移作用常见配置温度值0x00只读12位补码不需要写配置0x01转换模式、中断/比较模式、Fault queue0x00连续转换0x40低功耗THYST0x02迟滞温度阈值按产品需求写单位同温度寄存器TOS0x03过热报警阈值按产品需求写单位同温度寄存器static int adt75_probe(struct i2c_client *client) { s32 ret; /* 先确认器件在线读温度寄存器0x00不应该返回错误 */ ret i2c_smbus_read_word_data(client, ADT75_REG_TEMP); if (ret 0) return -ENODEV; /* 配置连续转换、比较输出、Fault queue默认0 */ ret i2c_smbus_write_byte_data(client, ADT75_REG_CONFIG, 0x00); if (ret 0) return ret; return 0; }这段probe做的事很朴素先读一次温度寄存器探测器件是否存在然后写配置寄存器。这里有个脏活有些板子在低温环境下第一次上电读回来的温度会异常如果驱动里只有一次探测判定就容易误报ENODEV。我一般会在probe里连读三次连续失败才返回错误能把上电瞬间总线不稳的误判压下去。阈值寄存器要不要在probe里写取决于产品逻辑如果系统里没有thermal zone管理建议至少在probe里把TOS写成125°C、THYST写成120°C的默认值避免OS/INT引脚在默认阈值0°C下被误触发。0°C时INT持续拉低板卡上电就触发中断这是低速排查时最容易忽略的默认行为。3. 跑通I²C链路从地址探测到sysfs读数3.1 上电先确认总线与地址驱动写得再好器件不在总线上也是白搭。ADT75的I²C地址由A0、A1、A2三个引脚的电平决定默认全接地时是0x48全接高是0x4F。拿到板子先量这三个引脚的电压再去看原理图连线很多时候“驱动加载失败”根本不是驱动问题而是A0被上拉电阻拉高了实际地址成了0x49。另外确认一下板卡I²C控制器编号Linux下不同总线的编号与硬件不完全对应i2c-0未必是你要用的那路。SDA和SCL必须有上拉电阻常见取值2.2k到4.7k具体看总线负载。如果总线上还挂了其他器件比如PMIC、EEPROM地址冲突也要提前查清楚。我习惯先拿万用表量一下SCL/SDA静态电平都应该在接近VDD的高电平如果量出来是0.4V这种中间值先修上拉别急着跑驱动。3.2 i2c-tools快速探测在烧驱动之前先用i2c-tools确认器件活着这步成本最低能帮你把硬件问题和软件问题切分干净。# 列举系统中的I²C总线 i2cdetect -l # 扫描总线1上的所有地址 i2cdetect -y 1 # 直接读0x48的温度寄存器w表示word i2cget -y 1 0x48 0x00 w输出里如果0x48位置显示48说明器件在总线上且地址匹配。如果显示UU说明该地址已经被内核驱动占用了。读word的返回值需要注意字节序i2cget输出的是raw数据别拿它直接算温度先对照datasheet确认字节序再换算。i2cdetect -y会向所有地址发探测报文有些从设备对探测报文敏感量产板卡上别乱扫。3.3 内核hwmon路线把adt75.c编译进内核确认器件在线后把adt75.c放进内核或者作为外部模块编译都行。我常用的方案是放到drivers/hwmon/目录下加进Kconfig和Makefile。obj-$(CONFIG_ADT75) adt75.o# 编译外部模块M$(pwd)表示当前目录 make -C /lib/modules/$(uname -r)/build M$(pwd) modules # 装载模块 insmod adt75.ko # 确认驱动是否认到设备 dmesg | tail -20 ls /sys/class/hwmon/如果用设备树节点写法也很固定i2c控制器节点下挂一个子节点。i2c2 { adt7548 { compatible adi,adt75; reg 0x48; }; };说明compatible里的adi,adt75必须和驱动里的of_match_table对应如果内核还没收录这个compatible就用i2c_board_info注册这也是很常见的落地方式。装载成功后驱动会在/sys/class/hwmon/下生成hwmonX目录温度文件是temp1_input单位毫摄氏度。内核hwmon框架的好处是thermal子系统可以直接消费这个温度节点省掉自研用户态轮询。3.4 用户空间路线不编内核直接读不是所有场景都值得编译内核模块尤其做产测或者临时验证时用户空间直读更利索。系统里要有i2c-dev模块设备节点通常是/dev/i2c-1。我倾向用Python的smbus库快速验证脚本短改起来快。import smbus bus smbus.SMBus(1) # 对应 /dev/i2c-1 addr 0x48 # 读温度寄存器返回的是低字节在前 val bus.read_word_data(addr, 0x00) # 手动转成大端 raw ((val 0xFF) 8) | (val 8) # 符号扩展bit15为1表示负数 if raw 0x8000: raw - 0x10000 # 0.0625°C每LSB转换成毫摄氏度 temp_mc raw * 625 // 10 print(temp_mc)这段脚本把内核驱动里的换算逻辑完整复刻了一遍适合在没有交叉编译环境的时候临时验证传感器。注意smbus库的read_word_data返回的字节序在不同板子上可能不一致稳妥做法是先读一次然后对照i2cget的raw输出判断高低字节顺序。用户空间路线只适合单点读取要做高频率连续采样还是走内核驱动更可靠用户态读i2c-dev每次都要经过系统调用延迟和抖动都大。3.5 验证读数是否可信驱动跑通后第一步先确认读数值在合理范围。室温25°C左右读回来应该在24000到26000毫摄氏度之间。然后做两个动作用手指捏住传感器外壳温度应该明显上升用酒精棉球擦拭温度应该下降。如果读数纹丝不动回到3.2用i2cget原语命令直接读看是不是驱动换算的问题。还有一个常见做法是把温度读数和整板功耗变化对应起来比如让CPU满载跑一会儿传感器温度应该跟着涨几度这能确认传感器安装位置和PCB热路没有大问题。4. 避坑排查ADT75驱动调试中五个具体问题4.1 地址对不上i2cdetect看不到0x48现象i2cdetect扫描结果里没有0x48或者出现的是0x49、0x4A这类偏移地址。原因A0/A1/A2引脚电平不是默认的低电平要么被原理图上拉下拉电阻改了默认值要么引脚悬空导致电平不稳。另一个可能是器件供电没起来传感器VDD没电时I²C引脚不会应答。解决先量VDD对地电压确认在datasheet允许范围。然后量A0、A1、A2三个引脚的直流电平对照地址表换算真实地址。最后把i2cdetect换成i2cdetect -y -r 1再扫一次有些器件对quick write探测不响应但接受read byte探测。4.2 温度读数固定不变或者一直是一个奇怪的值现象不管环境温度怎么变读回来的原始值都是同一个数比如稳定在0x0000或者某个固定组合。原因最常见是器件进入了shutdown模式ADC不启动转换温度寄存器停在上一次的值。如果probe里写配置寄存器时把模式位配错就会踩中这个问题。还有个原因是寄存器偏移搞错了读的寄存器根本不是温度寄存器。解决用i2cget直接读0x00、0x01、0x02、0x03四个寄存器的值确认0x01配置寄存器内容。如果配置寄存器显示shutdown位被置位重新写0x00恢复连续转换。再验证一下I²C总线上的波形用示波器抓SCL上的ACK/NACK如果地址阶段一直NACK说明器件地址确实不对。4.3 偶发NACK温度读取会报i2c_transfer失败现象驱动跑一段时间后偶发报错dmesg里出现i2c transfer失败或者read_word_data返回负数重新初始化后又正常。原因I²C时钟频率太高加上总线电容大、上拉电阻不合适导致时序裕量不足。ADT75虽然支持400kHz快速模式但很多主板走线过长400kHz下的建立时间不够。还有一个隐蔽原因是总线上的其他设备在做时钟拉伸主控没等够时间。解决把I²C频率降到100kHz这个改动在设备树里通常是clock-frequency 100000。再把上拉电阻从2.2k换成4.7k降低驱动能力需求。如果问题还在检查是不是有设备在总线空闲时主动拉低SDA多主场景要处理总线仲裁。4.4 OS/INT引脚一上电就拉低中断触发不断现象OS/INT引脚上电后一直为低中断请求一直有效驱动里读取的事件标志永远处于触发状态。原因上电默认TOS和THYST寄存器都是0也就是0°C。如果环境温度高于0°C比较模式下OS/INT输出立即满足超温条件引脚被拉低。这是正常行为不是硬件故障。解决在probe里显式写入合理阈值。比如TOS写入125°C对应的原始值THYST写入120°C对应的值然后再使能中断输出。如果产品里用不到这个引脚最好在配置寄存器里把比较器模式改成关断避免无谓的中断唤醒。检查这个问题时顺便看一眼fault queue设置连续几次超温才触发能滤掉瞬态干扰。4.5 零下温度读成两百多摄氏度现象环境温度到0°C以下读数变成255°C附近或者很大的正数。原因温度原始值是补码如果换算代码用了无符号类型负数就被解释成了65535附近的巨大正数乘完系数就是两百多度。这属于C语言基本功问题在驱动代码里最常见。解决严格使用s16或s32承接原始数据符号扩展必须在乘法之前完成。我还会在换算函数里加一道防御如果结果落在-60°C到150°C之外直接返回错误码比输出一个离谱的温度值更容易排查。产测脚本里也建议加同样范围的合法性校验不然测温异常会被当成环境高温触发错误的保护逻辑。5. 还能再准一点差值表加二分搜索把ADT75校准到±0.5°C5.1 线性校准的局限ADT75的精度标称典型±1°C多数场景够用但如果你做的是医疗设备、精密恒温控制±1°C的系统偏差不可接受。最简单的两点校准是测一个低温点和一个高温点拟合ykxb把增益和偏置校正掉。问题是传感器在整个量程内并非完美线性两点校准在校准点中间的误差可能还很大。更稳妥的做法是取多个温度点建差值表然后用二分搜索加线性插值对任意输入温度做逐段补偿。5.2 差值表与二分插值实现先采集标准温度源和传感器读数值的偏差然后以原始读数为横坐标偏差为纵坐标建表。补偿时先二分定位区间再线性插值算偏差最终输出补偿后的温度。def compensation_table(): # 原始读数毫摄氏度来自 ADT75 xs [0, 20000, 45000, 70000, 100000] # 标准温度源实测值毫摄氏度 refs [0, 20500, 45550, 70300, 102000] # 偏差 标准值 - 原始读数 ds [ref - x for x, ref in zip(xs, refs)] return xs, ds def compensate(x, xs, ds): lo, hi 0, len(xs) - 1 # 二分搜索找到 x 所在的区间 while lo 1 hi: mid (lo hi) // 2 if xs[mid] x: lo mid else: hi mid x0, x1 xs[lo], xs[hi] d0, d1 ds[lo], ds[hi] if x1 x0: return x d0 # 线性插值偏差 d d0 (d1 - d0) * (x - x0) / (x1 - x0) return int(x d)二分搜索的复杂度是O(logN)差值表再大也只是多几次比较几微秒就出结果放在用户态循环里完全没压力。校准表生成后先保存成数组运行时再加载不要在每次读取时重复计算。注意边界如果输入温度超出表的最小或最大值直接取端点偏差不做外推外推在非线性段会引入更大误差。校准表本身也是一种可追溯的记录产测时把表导出来存档比贴一张手写校准标签靠谱。5.3 上板验证把补偿函数接到读数链路里然后重新用标准温度源从低温到高温扫一遍记录补偿后的偏差。如果校准点足够密补偿后的偏差能压进±0.5°C以内。验证时要注意温度源必须稳定后再读数恒温槽每换一个温度点至少等五分钟传感器热容和PCB热阻都会拉长稳定时间。等不及的后果就是校准表本身带误差越补偿越偏。从那以后我每批板子都强制把这三个步骤走完整温度源稳定、采集读数、生成差值表并回归验证宁可多花半小时也不赌运气。希望帮到你。本文还有配套的精品资源点击获取