
这段时间在做一块控制板的BSP适配硬件原理图里躺着一颗TI的温度传感器型号是TMP75AIDR挂在某条I2C总线上用来做板级温度监控。任务本身不复杂让Linux系统起来之后能从这颗芯片读到实时温度并且接入标准的hwmon体系。但真正动手后才发现TMP75AIDR驱动移植这件事卡点往往不在驱动代码而在设备树怎么写、内核配置怎么开、I2C地址怎么确认这些外围环节。这篇笔记就按我实际的移植顺序来写把原理、步骤和踩过的坑都整理出来给准备做同类hwmon传感器移植的读者做个参考。如果你是第一次接触Linux下的温度传感器驱动建议先明确一个判断TMP75AIDR不需要自己从零写驱动Linux内核里早就有一套现成的机制可以复用它。我们真正要做的是“对接”不是“发明”。但这套“对接”流程里藏着不少细节比如compatible字符串的选择、I2C从机地址的由来、hwmon目录下温度值的单位换算每一个都值得展开看一下。1. TMP75AIDR到底是什么芯片寄存器模型与内核现成弹药1.1 一颗小芯片怎么上报温度TMP75AIDR是TI出品的一款I2C接口数字温度传感器芯片内部集成温度感温和12位ADC温度分辨率能做到0.0625°C/LSB测量范围覆盖-55°C到125°C典型精度在±1°C这个级别。对绝大多数板级温度监控来说这个精度完全够用。芯片对外提供标准的SMBus/I2C接口只需要四根线就能工作SDA、SCL、GND、VCC。真正让我留意的是它的寄存器模型。TMP75AIDR内部主要有四组寄存器需要关注寄存器地址功能温度寄存器0x00存放当前温度测量结果12位有效数据配置寄存器0x01控制工作模式、比较器/中断模式、极性、分辨率等上限温度寄存器0x02存储上限报警阈值下限温度寄存器0x03存储迟滞报警阈值其中0x00温度寄存器是最常用的我们不间断读取它就能得到芯片当前的温度值。这个寄存器的高8位和低8位拼接后低4位数据在默认12位分辨率下可能为空或保留位实际有效数据需要根据数据手册做符号扩展处理。芯片还引出A0、A1、A2三个地址引脚允许同一根I2C总线上最多挂8颗TMP75AIDR。地址默认值取决于这三个引脚的电平状态后面讲设备树时我会单独算一遍。1.2 内核里的lm75驱动和hwmon机制很多人在拿到TMP75AIDR之后第一反应是去TI官网找Linux驱动其实完全没必要。Linux内核的drivers/hwmon/lm75.c驱动早就覆盖了TMP75系列。名字虽然叫lm75但它兼容的芯片远不止NXP的LM75还包括TI的TMP75、TMP75B、TMP75C以及TMP275、TMP105等一批寄存器兼容的温度传感器。TMP75AIDR能被lm75驱动直接驱动核心原因是它和LM75在寄存器层面高度兼容。温度寄存器、配置寄存器、阈值寄存器的布局和I2C读写时序基本一致驱动只需要按同一套协议解析数据就能得到正确的温度值。然后是hwmon这个子系统。它是Linux内核里硬件监控的标准框架温度传感器、风扇转速、电压监测这类设备都会注册到hwmon体系。用户空间不用关心底层是I2C还是SPI统一通过sysfs接口读取暴露出来的文件就是/sys/class/hwmon/hwmonX/temp1_input这类路径。这种抽象非常好用外部程序只需要cat这个文件就能拿到温度值。1.3 移植的本质不是造轮子而是对齐接口lm75驱动承担的职责可以拆成三块一是通过I2C协议与芯片通信二是解析温度寄存器的原始数据并转换成标准单位三是在hwmon框架下注册设备提供sysfs入口。这三块对TMP75AIDR来说内核早就写好了。所以移植的本质其实是把“内核里现成的驱动”和“板子上具体的硬件实例”对接起来。对接方式就是设备树告诉内核这颗芯片接在哪条I2C总线上、从机地址是多少、兼容哪个驱动。再加上内核配置阶段把lm75驱动编进去整个移植就算完成了。理解了这条主线后面的步骤就顺理成章。2. 方案选型走设备树这条路之前先看清三条岔路2.1 三条路的对比在动手之前我把能想到的实现方案摆在一起比较了一遍因为“驱动移植”这个词在不同人心里可能指向完全不同的做法。方案做法适用场景维护成本设备树对接在内核dts/dtsi中声明I2C设备和compatible现代ARM/MIPS/PPC平台内核支持设备树低标准做法板级文件注册在board文件中用i2c_board_info注册设备老内核、不支持设备树的平台高已被淘汰自己写驱动基于I2C子系统从零编码芯片特殊、需要访问非标准寄存器高一般不推荐从当前内核生态看设备树已经是绝对主流。ARM平台在进入Device Tree时代之后板级文件方式基本只在老代码里见到。而自己写驱动这件事除非芯片有lm75驱动无法覆盖的特殊行为否则完全没有必要。2.2 为什么设备树是当前主流设备树的核心作用是把“硬件长什么样”从内核代码里剥离出来变成一份独立描述文件。这样同一份内核镜像可以支持多种硬件配置只要在启动时更换设备树就好。对驱动移植来说最大的好处是不用动C代码就能把一颗芯片挂到任意一条I2C总线上。比如同样一颗TMP75AIDR在A板上挂在I2C0在B板上挂在I2C1设备树里改一行reg所在节点就能搞定驱动源码一行都不用改。这种灵活性在板级文件时代是很难想象的当时每加一个设备都要改板文件、重编内核。2.3 提前确认内核版本对lm75驱动的支持力度虽然lm75驱动存在已久但不同内核版本对TMP75系列的compatible名称支持有差异。老版本内核里可能只认national,lm75新版本才补全了ti,tmp75、ti,tmp75b、ti,tmp75c这些字符串。所以在写设备树之前先到内核源码里确认一下匹配表是最稳妥的grep -n tmp75 drivers/hwmon/lm75.c如果看到{ .compatible ti,tmp75 }这类条目说明当前内核认识这个型号。找不到也不用慌可以把compatible写成内核里确实存在的通用字符串比如ti,tmp75通常在内核5.x版本都有或者也可以用national,lm75这个老标识符兼容。具体以源码实际内容为准。3. 内核配置使能把lm75驱动真正编进你的内核3.1 找到配置开关设备树写好了内核不知道这颗芯片还需要对应驱动所以首先要确认lm75驱动是否已经编译进内核。配置入口在menuconfig里make menuconfig然后按菜单路径进入Device Drivers - Hardware Monitoring support - National Semiconductor LM75 and compatibles这个配置项对应宏是CONFIG_SENSORS_LM75。hardware monitoring这一层就是hwmon框架lm75驱动挂在它下面。如果没进入这个菜单多半是上层CONFIG_HWMON没打开需要先打开硬件监控总开关。一般建议先编成*内置也就是直接进内核镜像。原因很简单温度监控模块通常在系统启动早期就要用内置可以减少启动阶段的模块加载依赖也不会出现根文件系统还没挂好、模块却放在文件系统里的尴尬。3.2 确认编译目标的常用命令配置完之后可以用grep确认一下配置确实被写入了grep CONFIG_SENSORS_LM75 .config正常的输出应该是CONFIG_SENSORS_LM75y如果输出为空说明刚才的菜单勾选没有生效需要回去看是不是保存配置的文件路径不对。3.3 驱动源码结构简析lm75.c这个驱动源码其实很短主要函数就那么几个lm75_probe()读取芯片配置寄存器识别设备注册hwmon设备lm75_read_temp()读取温度寄存器并定标成毫摄氏度lm75_write_temp()写阈值寄存器lm75_is_visible()决定哪些sysfs属性可见一个很实用的细节是lm75_probe()里会尝试读取配置寄存器来确认芯片是否存在所以如果I2C地址写错probe阶段就会失败并在内核日志里留下对应的错误信息。这可以作为移植成功与否的第一个判断依据。3.4 编译时的常见报错配置内核时最常遇到的编译问题有两个。一个是编译时提示找不到linux/hwmon.h之类的头文件这通常是因为内核源码目录没配对或者执行编译的环境变量有问题。另一个是提示I2C相关符号未定义比如i2c_smbus_read_word_swapped找不到这时要去确认CONFIG_I2C确实打开了lm75驱动依赖I2C子系统没有I2C核心代码则无法链接。编译完成后把新内核或新模块烧到板子上。如果编成模块启动后还要记得手动modprobe lm75或者把模块名写进自动加载配置。用内置方式就没有这个麻烦。4. 设备树节点与地址确认让内核找到这颗传感器4.1 从原理图确定I2C控制器和从机地址设备树里最难确认的不是节点怎么写而是两个看似基础但容易搞错的信息芯片接在哪条I2C总线上以及从机地址是多少。TMP75AIDR的7位I2C地址格式是固定的高4位1001低3位由A2、A1、A0三个引脚的电平决定。如果三个引脚全部接地地址是1001 000 0x48如果A0引脚被拉到高电平地址就变成0x49以此类推。很多人在这一步翻车是因为原理图上A0、A1、A2标注为NC不连接这时引脚内部下拉或浮空状态并不确定实际地址可能不是默认的0x48需要上电后实测。确定总线号比较直接看原理图里TMP75AIDR的SDA、SCL接到哪个I2C控制器。比如接到I2C1控制器那在内核里对应设备树节点可能就是i2c1在用户空间用i2c-tools查到的总线号可能是1。这两个编号不一定一致先跑一遍i2cdetect -l这个命令会列出系统里所有I2C总线核对名字和地址范围至少能区分是哪一条控制器。4.2 上电后先扫一遍总线硬件上电后第一步不是改设备树而是在用户空间用i2cdetect扫描目标总线确认芯片地址i2cdetect -y 1这里的1是上一步查到的总线号。正常扫到TMP75AIDR时输出会有一个地址对应的格子显示48或实际地址。如果扫不到先别急着改代码优先怀疑硬件连接和上拉电阻具体排查思路我在第6章单独说。4.3 设备树节点的标准写法确认了总线和地址后设备树节点就很好写了。这里给一份实际可用的参考i2c1 { status okay; clock-frequency 100000; tmp7548 { compatible ti,tmp75; reg 0x48; status okay; }; };几个字段的说明tmp7548是节点名格式为“设备名I2C地址”这个地址必须与reg一致compatible是关键内核靠它匹配到lm75驱动字符串必须能在drivers/hwmon/lm75.c的of_match_table里找到reg填入芯片实际的7位I2C地址status okay确认该节点使能父节点也必须是okay状态还有一个容易忽略的地方I2C控制器本身的时钟频率。TMP75AIDR支持标准模式100kHz和快速模式400kHz设备树里设置100kHz比较保守如果总线上还挂了其他对时序敏感的器件这样更稳妥。4.4 设备树改动的两种落地方式设备树修改完成后需要让它生效。常见做法有两种。第一种是直接修改内核设备树源文件重新编译设备树blob并随内核烧录。这种方式适合自己掌控整个镜像构建流程的项目改动固化在源码里版本管理清晰。第二种是用设备树overlay机制运行期加载补丁。这种方式适合产品已经量产、不方便替换根设备树的情况。overlay文件里写同样的节点内容通过configfs或u-boot注入。调试阶段用overlay有个好处改错了不用反复烧录加载失败系统还能正常启动。我个人在实际调试时倾向先用overlay快速验证确认没问题后再把改动合入正式设备树源文件。5. 烧录启动后的验证链路日志、sysfs与温度合理性5.1 看内核日志确认设备注册设备树改完、内核烧进去之后第一件事是看启动日志里有没有lm75相关的注册信息dmesg | grep lm75正常情况会看到类似下面的内容lm75 i2c-1:48: hwmon0: registered as lm75这一行说明驱动probe成功hwmon设备已注册。如果内核日志里找不到这行但i2cdetect又能扫到芯片说明compatible匹配或设备树节点有问题可以再检查设备树是否加载成功。5.2 从sysfs读取温度数据接下来真正读取数据。首先列出hwmon设备目录ls /sys/class/hwmon/会看到hwmon0、hwmon1这类目录板子上有多颗温度传感器或多个hwmon设备时编号可能不固定。为了确认哪个目录对应TMP75AIDR可以看目录下的name文件cat /sys/class/hwmon/hwmon0/name输出如果是lm75那这个目录就是它。然后读取温度cat /sys/class/hwmon/hwmon0/temp1_input输出值单位是毫摄氏度。比如输出26250代表当前温度是26.25°C。这个单位约定经常被新手忽略直接把26250当摄氏度去比结果发现温度离谱。5.3 温度合理性检验方法别只看能读出来就觉得完事还要确认读数是否合理。我有两个实用办法。第一个是定性验证。用手捏住芯片外壳等一两秒再读温度应该缓慢上升放开手后温度慢慢回落。如果读数纹丝不动可能是芯片没真正在测量或者读的是寄存器里的旧值。还可以用热风枪调低温档吹一下观察温度变化趋势。注意别直接高温猛吹芯片最高耐温有限。第二个是交叉对比。如果板载还有别的温度传感器比如CPU内部温度、PMIC温度把它们读出来的值和TMP75AIDR对比。正常情况下同一块区域的温度差不会太大如果两者差了十几度要么传感器位置差异巨大要么驱动解析有问题。我在实测中碰到过一次读数为-0.0625°C的情况反复加热也没反应。后来查寄存器数据发现是地址配置把芯片置于关断模式温度寄存器不再更新。这种问题靠看日志看不出来必须回到芯片寄存器层面排查。6. 移植路上的真实踩坑记录与排查思路6.1 坑一i2cdetect扫不到芯片这是最让人头疼的坑因为一旦硬件层面探测不到后面的驱动移植全部无从谈起。我遇到过一次原理图明确写着TMP75AIDR挂在I2C1总线上地址0x48但i2cdetect扫出来列表里一片空白。排查顺序很重要不要一上来就怀疑内核配置。先查I2C总线本身是否正常在总线上挂一个已知好的设备或者直接读出总线上的设备列表排除总线控制器故障。然后查SDA、SCL的上拉电阻I2C总线必须要有上拉电阻否则信号根本拉不到高电平扫描自然失败。接着查地址引脚。前面说过A0、A1、A2的电平决定地址这颗芯片的地址引脚如果悬空实际地址可能漂到一个出乎意料的值。可以考虑用万用表量引脚电平或者用i2cdetect扫描全部地址段看有没有意外收获。最后还有一个很常见的原因SDA和SCL接反了。I2C是异步双向总线两根线没有硬件上的绝对区分接反后通信完全失败。遇到这种问题用示波器或逻辑分析仪看波形是最快的定位手段没有设备的话就检查原理图和实际飞线。6.2 坑二compatible不匹配导致probe不执行还有一次i2cdetect能扫到芯片但dmesg里死活看不到lm95相关信息。这种问题十有八九出在compatible字符串上。前面说过compatible必须能在内核源码的of_match_table里找到。不同内核版本支持的字符串不一样老内核可能只认national,lm75新内核补全了ti,tmp75等。我在调试时遇到过这种组合内核已经更新到5.x设备树里写的却是从老工程拷来的ti,tmp751一批多了一个字符内核完全不认识设备节点没有被绑定到驱动上。排查方法很简单回到内核源码里反复核对grep -n of_match -A 5 drivers/hwmon/lm75.c确认匹配表里的字符串然后保证设备树里写得一字不差。6.3 坑三hwmon节点编号漂移当一个系统里有多个hwmon设备时/sys/class/hwmon/hwmonX的编号并不固定。每次启动顺序不同编号就可能变化写死的监控脚本就会读到错误的设备。解决办法是先通过name文件确认身份再建立稳定的符号链接。很多系统里hwmon目录下方会有与设备名对应的链接或者可以由udev规则根据name属性创建一个固定的软链比如/dev/tmp75_temp指向/sys/class/hwmon/hwmonX。这样应用程序永远读固定路径不受编号影响。这个坑在驱动程序层面不是问题但在实际产品集成时很容易被忽略。移植驱动不只是让芯片能读数还要让上层应用稳定地拿到数所以我在做BSP时会把udev规则一并纳入交付物。6.4 坑四读到的温度比实际偏低有一块板子读出来的温度始终比红外测温枪低3°C到4°C看起来在精度范围内但客户不接受。排查后发现不是驱动问题而是芯片附近有大面积铜皮散热传感器本身的封装又紧贴PCB热传导路径把芯片实际感受到的温度拉低了。这类问题不算驱动移植的技术失误但值得记录软件无论如何写都无法改变传感器封装和布局带来的测量偏移。如果产品对温度精度要求高需要在结构上考虑导热材料填充或者做软件校准偏移量。我在实际项目里会在设备树节点或者驱动里预留一个校准参数在温度定标时加上固定补偿值这样比每次改代码重新编译要灵活得多。最后说点个人体会TMP75AIDR驱动移植这个任务代码层面其实很小难的是把硬件、设备树、内核配置、用户空间工具串起来。我每次做类似的hwmon移植都会先在原理图阶段把I2C地址、总线编号、上拉情况全部确认完再动设备树这样后面能省下大量排查时间。另外建议拿到芯片后先看一下正式版数据手册里的寄存器描述和时序图不要只看博客和驱动注释。lm75驱动虽然兼容但TMP75AIDR在某些寄存器位上的定义可能和通用LM75有细微差别尤其是配置寄存器的比较器模式、极性位这些内容一旦需要配置报警功能绕不开对这些位的精确理解。这次移植完成后我还顺手在应用层写了一个小的温度监控脚本定时读取temp1_input超阈值就往日志里打一条警告。这种扩展做起来非常快因为hwmon接口已经把所有脏活累活都干完了。下个板子如果再换别的温度传感器只要芯片被内核lm75驱动或同类hwmon驱动覆盖移植时间基本可以控制在半天以内。