
简介本资源是一套基于STM32F103微控制器与TMP102高精度数字温度传感器的I²C通信完整实现方案面向嵌入式初学者及STM32开发实践者解决传感器驱动开发、I²C底层协议调试与温度数据读取等典型工程问题。压缩包共146个文件包含38个头文件.h用于接口定义与寄存器配置36个C源文件.c涵盖STM32标准外设库如stm32f10x_i2c.c、stm32f10x_rcc.c及TMP102专用驱动逻辑另有编译中间文件.o、.d、.crf、Keil工程配置.uvprojx、.uvoptx、可执行镜像.axf、.hex及辅助脚本keilkilll.bat整体大小为3.64MB。已有984人学习下载配套简明说明文档代码经实测运行稳定可直接导入Keil MDK环境编译调试便于理解I²C时序控制、寄存器映射、传感器配置流程及软硬件协同调试要点。 写这个项目之前我先说点实话TMP102这颗芯片是我在做一个电池柜温度采集项目时用上的当时手头正好有STM32F103顺手就把IIC驱动写了。后来这个组合我又在好几个项目里复用包括温湿度监测终端、冷链运输记录仪、工业设备预热保护。说实话TMP102虽然不是最便宜的传感器但它的精度、功耗、体积综合起来非常能打配合STM32的IIC接口更是经典搭配网上问的人也特别多。这篇文章我就把从硬件选型到驱动代码再到实际调试的完整过程都翻出来一次性讲透希望能帮你少走几个弯路。1. 先从TMP102这颗传感器说起1.1 TMP102核心参数盘点TMP102是TI推出的一颗低功耗数字温度传感器封装是SOT563只有6个引脚比一粒米还小。供电电压范围是1.4V到3.6V所以3.3V系统直接供电不需要额外的电平转换。温度测量范围是-40℃到125℃在-25℃到85℃这个区间内精度能做到±0.5℃这个精度对于绝大多数环境监测、设备保护场景都是够用的。分辨率上TMP102默认是12位也就是0.0625℃/LSB。如果你想测更精细的温度变化可以把配置寄存器里的扩展模式位打开变成13位分辨率也就是0.03125℃/LSB。不过13位模式下数据格式会稍微有点变化后面驱动代码部分我会专门讲到。功耗是这颗芯片的一个大亮点连续转换模式下典型功耗只有15μA左右如果进入关断模式电流能压到1μA以下。对电池供电的便携设备来说这是很关键的优势。它内部还有一个报警引脚ALERT可以设置高低温阈值超限的时候主动拉警报主控不用一直轮询读温度适合做低功耗唤醒方案这个在后面的低功耗调优部分我再展开。1.2 为什么选IIC接口TMP102的通信接口就是IIC这是它最方便的地方。IIC只需要两根线SCL和SDA加上电源和地总共4根线就能跑起来。对比SPI需要额外的片选线UART需要双方共地并且波特率匹配IIC在传感器这种低速小数据量的场景下是最省引脚的选择。STM32的引脚本来就紧张一个项目里又要接OLED、又要接按键、又要接传感器如果每个外设都占一堆引脚板子布局会很痛苦。IIC总线上可以挂多个设备地址不冲突就行TMP102的地址可以用A0、A1两个引脚配置出四个不同的地址从0x48到0x4B所以一条IIC总线上最多能挂4颗TMP102对多通道温度采集来说非常合适。2. STM32侧IIC方案选型2.1 硬件I2C和软件模拟怎么选这个问题几乎每个用STM32的人都会纠结一遍我直接给结论如果你用的是STM32F1系列项目又是传感器读取这种简单应用老老实实用软件模拟IIC。如果是F0、F4、L4这些新系列硬件I2C是可以用的配合HAL库的阻塞式接口也很稳定。为什么对F1有偏见因为STM32F1的硬件I2C外设在芯片内部设计上有一些历史遗留问题官方勘误表里也承认了。典型的表现是总线繁忙标志清不掉、主模式发送时偶尔卡死、在连续读操作时容易出错。虽然通过合理的代码处理也能绕过去但对项目的开发进度来说没有必要去赌这个概率。软件模拟IIC虽然占用CPU但TMP102的数据量极小一次完整读取也就几十微秒的事对系统性能几乎没影响。另外软件模拟IIC还有一个非常大的好处代码完全透明。你可以准确知道每一根线在哪个时刻拉高拉低出问题的时候用逻辑分析仪对着波形一看就知道是哪一步不对。硬件I2C内部状态机太复杂出问题之后排查难度高不少。F1用户直接选软件模拟相当于提前把潜在的坑填了。2.2 电路连接与上拉电阻取值TMP102的连接电路非常简单SCL和SDA都接到STM32的GPIO然后两根线各接一个上拉电阻到3.3V。A0、A1接GND就是地址0x48接VCC就是对应的高电平地址组合方式我在前面提过。上拉电阻取多大这是新手问得比较多的问题。IIC标准模式下总线上拉电阻的推荐范围是2kΩ到10kΩ具体取值要看总线上的电容。总线电容越大需要越小的上拉电阻才能保证信号的上升沿满足时序要求。STM32引脚加上PCB走线还有TMP102本身总电容通常在几十pF到100pF之间标准模式下用4.7kΩ是稳妥选择。如果板子上走线长、挂的设备多可以把上拉电阻降到2.2kΩ保证上升沿不会太慢。快速模式400kHz下建议用4.7kΩ甚至更小。要注意的是如果STM32的GPIO配置成开漏输出内部上拉其实是有的但STM32内部上拉电阻典型值在30kΩ到50kΩ之间这个阻值太大根本满足不了IIC的上升沿要求所以外部必须加上拉电阻不能偷懒。还有一个小细节TMP102的电源引脚旁边建议放一个0.1μF的去耦电容尽量靠近芯片放置。别小看这个电容温度传感器测量的是自己的芯片温度如果电源纹波太大芯片内部功耗波动会导致测温结果跳变尤其是分辨率到0.0625℃的时候这点影响在数据上能明显看出来。3. TMP102驱动代码实现3.1 寄存器与温度数据格式TMP102内部有4个寄存器最容易搞混的就是温度寄存器的数据格式我先把这块讲透。温度寄存器地址是0x00宽度16位读取的时候IIC连续读两个字节高字节在前。默认12位模式下16位数据中高12位是有效温度数据低4位固定为0。所以拿到两个原始字节后拼成16位整数然后右移4位就得到真正的12位温度值。这个值是二进制补码最高位是符号位负温度也能正确表示。计算方式就是温度 原始12位值 × 0.0625℃。比如原始值是0x0190右移前是0x1900右移后0x0190即400400×0.062525.0℃正好是25度。负温度的例子-0.25℃时12位补码是0xFFC读取的16位数据是0xFFC0右移4位后得到0xFFFC即-4-4×0.0625-0.25℃没问题。配置寄存器地址是0x01两个字节主要关心几个位bit8是SD关断位置1进入关断模式bit11是EM扩展模式位置1变成13位分辨率bit14和bit13组合控制转换速率4种组合分别对应0.25Hz、1Hz、4Hz、8Hz。默认配置值是0x60B0也就是8Hz转换速率、12位模式、连续转换。低限寄存器0x02和高限寄存器0x03是给报警功能用的超过高限或者低于低限ALERT引脚就会按配置的模式产生报警信号平常做普通温度读取可以不用写这两个寄存器。3.2 软件IIC底层实现我用的软件模拟IIC代码结构很简单核心就是SCL和SDA两根线的电平控制。SCL配置成开漏输出SDA需要收发所以要在输出和输入之间切换。下面是我实际项目里用的代码基于STM32标准库或者HAL库都能很容易改过来。#define IIC_SCL_HIGH() GPIOB-BSRR GPIO_PIN_6 #define IIC_SCL_LOW() GPIOB-BRR GPIO_PIN_6 #define IIC_SDA_HIGH() GPIOB-BSRR GPIO_PIN_7 #define IIC_SDA_LOW() GPIOB-BRR GPIO_PIN_7 #define IIC_SDA_READ() ((GPIOB-IDR GPIO_PIN_7) ! 0) #define IIC_SDA_OUT() \ do { \ GPIOB-CRL ~(0xF 28); \ GPIOB-CRL | (0x1 28); \ } while(0) #define IIC_SDA_IN() \ do { \ GPIOB-CRL ~(0xF 28); \ GPIOB-CRL | (0x8 28); \ } while(0)这里用寄存器操作是为了让方向切换更快避免HAL库GPIO_Init里一大堆结构体配置带来的额外开销。PB7这个引脚的CRL寄存器对应的是第7组所以位偏移是7 * 4 28这点初学者容易算错我在这里特意备注一下。延时函数我用的是简单的空循环实测在72MHz主频下一次循环大概几百纳秒IIC时钟频率稳定在100kHz附近完全满足TMP102的要求。如果你用的是别的时钟频率把循环次数稍微调一下就行。static void IIC_Delay(void) { volatile uint32_t i 20; while (i--) {} }起始、停止、发字节、读字节这些基础函数是软件IIC的骨架我直接把它列出来。这几个函数只要逻辑对了任何IIC设备都能用。void IIC_Start(void) { IIC_SDA_OUT(); IIC_SDA_HIGH(); IIC_SCL_HIGH(); IIC_Delay(); IIC_SDA_LOW(); IIC_Delay(); IIC_SCL_LOW(); } void IIC_Stop(void) { IIC_SDA_OUT(); IIC_SDA_LOW(); IIC_SCL_HIGH(); IIC_Delay(); IIC_SDA_HIGH(); IIC_Delay(); } void IIC_SendByte(uint8_t byte) { uint8_t i; IIC_SDA_OUT(); for (i 0; i 8; i) { if (byte 0x80) { IIC_SDA_HIGH(); } else { IIC_SDA_LOW(); } byte 1; IIC_Delay(); IIC_SCL_HIGH(); IIC_Delay(); IIC_SCL_LOW(); } } uint8_t IIC_WaitAck(void) { uint8_t timeout 200; IIC_SDA_IN(); IIC_Delay(); IIC_SCL_HIGH(); IIC_Delay(); while (IIC_SDA_READ()) { if (--timeout 0) { IIC_Stop(); return 1; } IIC_Delay(); } IIC_SCL_LOW(); return 0; } uint8_t IIC_ReadByte(uint8_t ack) { uint8_t i, data 0; IIC_SDA_IN(); for (i 0; i 8; i) { data 1; IIC_SCL_HIGH(); IIC_Delay(); data | IIC_SDA_READ(); IIC_SCL_LOW(); IIC_Delay(); } IIC_SDA_OUT(); if (ack) { IIC_SDA_LOW(); } else { IIC_SDA_HIGH(); } IIC_Delay(); IIC_SCL_HIGH(); IIC_Delay(); IIC_SCL_LOW(); return data; }读字节函数最后的ACK和NACK处理容易写反我每次都要确认一下。主机读最后一个字节的时候要回NACK告诉从机别再发了然后产生停止信号。如果最后一个字节回了ACK从机会继续拉低SDA试图发下一个字节总线时序就会乱掉。3.3 TMP102读写函数TMP102的驱动层建立在IIC底层之上。首先是读温度流程是发送起始信号、发送设备地址加写位、发送寄存器地址0x00、再次发送起始信号也就是重复起始、发送设备地址加读位、连续读两个字节、最后回NACK并停止。这个流程是IIC读寄存器的标准套路几乎所有IIC传感器都长这样。#define TMP102_ADDR 0x48 #define TMP102_WRITE_ADDR ((TMP102_ADDR 1) | 0x00) #define TMP102_READ_ADDR ((TMP102_ADDR 1) | 0x01) float TMP102_ReadTemp(void) { uint8_t buf[2]; int16_t raw; IIC_Start(); IIC_SendByte(TMP102_WRITE_ADDR); if (IIC_WaitAck()) return 0; IIC_SendByte(0x00); if (IIC_WaitAck()) return 0; IIC_Start(); IIC_SendByte(TMP102_READ_ADDR); if (IIC_WaitAck()) return 0; buf[0] IIC_ReadByte(1); buf[1] IIC_ReadByte(0); IIC_Stop(); raw (int16_t)(((uint16_t)buf[0] 8) | buf[1]); raw 4; return (float)raw * 0.0625f; }一定要注意TMP102地址换算之后写地址是0x90读地址是0x91。很多第一次接触IIC的人会把地址直接写成0x48然后发出去那整个通信就完全对不上。IIC总线上传输的设备地址是7位地址左移一位低位表示读写方向这个是基础中的基础但确实高频出错。写配置寄存器的函数也顺手给出来后面低功耗设计会用到void TMP102_WriteConfig(uint16_t config) { IIC_Start(); IIC_SendByte(TMP102_WRITE_ADDR); IIC_WaitAck(); IIC_SendByte(0x01); IIC_WaitAck(); IIC_SendByte((uint8_t)(config 8)); IIC_WaitAck(); IIC_SendByte((uint8_t)(config 0xFF)); IIC_WaitAck(); IIC_Stop(); }3.4 主程序集成示例在主程序里初始化IIC引脚之后一般会先写一次配置确保芯片处于连续转换模式。然后主循环里定时读温度通过串口打印出来这就是最基础的温度采集终端。int main(void) { float temp; HAL_Init(); SystemClock_Config(); UART_Init(); IIC_Init(); // 设置转换速率4Hz12位模式连续转换 TMP102_WriteConfig(0x60A0); while (1) { temp TMP102_ReadTemp(); printf(Temperature: %.2f C\r\n, temp); HAL_Delay(500); } }这个配置值0x60A0对应二进制是0110 0000 1010 0000bit14置1、bit13清零所以转换速率是4Hzbit8为0连续转换模式。默认值0x60B0是8Hz我调到4Hz纯粹是考虑功耗和稳定性平衡实测1Hz其实也够用看产品需求。4. 调试工具与波形分析4.1 使用逻辑分析仪抓IIC波形软件模拟IIC最舒服的地方就是调试方便一个十几块钱的逻辑分析仪就能抓波形。我调试的时候必做的动作是先抓一次启动和停止波形确认起始条件和停止条件正确再看从机回ACK的情况。ACK正常说明地址和寄存器地址都对了ACK异常排查顺序是地址对不对、上拉有没有接、SDA方向切换有没有出问题。抓波形的时候有几个关键点要卡一下SCL高电平期间SDA不能变化才是合法的数据位起始条件是SCL高电平期间SDA从高变低停止条件是SCL高电平期间SDA从低变高。如果抓到的波形在SCL高电平中间SDA发生了多次跳变那多半是代码在时钟高电平期间动了SDA线一步步对着代码查就行。还有个常见问题是读取到的数据全是0x00或者0xFF。全是0x00先怀疑SDA是不是一直在地可能是寄存器方向切换配置错把SDA锁在低电平了。全是0xFF大概率是SDA一直悬空或者被上拉拉高也就是根本没读到从机的数据这时候除了检查接线还要看IIC_ReadByte里有没有在SCL拉高之前正确释放SDA总线。软件模拟IIC还有一个独有的坑如果代码里某个分支提前return没有执行到IIC_Stop总线就会一直处于某种半拉子状态。下一次再Start的时候从机可能还没复位通信就会错乱。所以IIC_WaitAck超时那一类异常分支一定要先Stop再返回这个习惯能帮你省下大量排查时间。4.2 常见故障现象与排查我实际项目中踩过不少坑挑几个典型的列出来故障现象可能原因排查方向读到的温度一直在0℃附近跳动数据拼装错误低字节移位处理不对打印原始两个字节手工换算比对温度值有时候对有时候错IIC时序不稳定延时不足增大IIC_Delay降速到标准模式验证硬件I2C卡死Busy标志清不掉F1系列硬件I2C固有缺陷切换软件模拟IIC最省事ALERT引脚一直拉低配置寄存器未初始化触发报警上电后优先写配置寄存器温度比实际偏高1℃以上芯片自热效应靠近热源检查PCB布局传感器远离发热器件负温度读数不对有符号处理错误确认raw是int16_t右移用算术右移JATG禁用这个问题也顺便说一嘴如果选用的引脚和JTAG复用比如PB3、PB4默认是被调试功能占用的要先把AFIO寄存器里的SWJ配置改掉。这个坑我在复用引脚时踩过一次现象就是程序怎么调IIC都没反应排查半天发现引脚根本不受GPIO控制。5. 实测经验与避坑指南5.1 精度校准注意事项TMP102出厂是校准过的理论上不需要额外校准直接用。但在实际项目中我发现有几个外部因素会让测量结果偏离真实值而且偏离得很稳定这时候可以做一个简单的单点校准。第一个因素是PCB发热。STM32跑起来之后芯片表面温度比环境温度高好几度如果TMP102离STM32太近测出来的温度会明显偏高。我做过一个对比测试传感器放在STM32正下方测得的温度比板子边缘的传感器高3℃左右。所以布局的时候尽量把TMP102放在PCB边缘远离功率器件和MCU。第二个因素是传感器自热。虽然TMP102功耗很低但在静止空气环境里自身发热依然会造成零点几度的误差。这个误差在大多数场景下可以忽略但如果做高精度测量就需要让传感器工作在通风环境或者通过软件做补偿。单点校准的简单做法是把传感器和标准水银温度计放在同一个恒温环境里等稳定后记录差值然后在软件里加上这个补偿量。如果温度跨度大建议做两点校准这样在不同温度点的补偿更准。5.2 低功耗场景调优如果要拿TMP102做电池供电的测温设备功耗优化是绕不开的。这时候TMP102的关断模式就派上用场了。配置寄存器里bit8置1进入关断模式之后需要测量的时候往温度寄存器写一个0x80触发单次转换然后等转换完成再读读完继续关断。这种工作方式下TMP102大部分时间只有1μA的待机电流只在测量的几十毫秒内才会有转换电流。配合ALERT引脚的报警功能可以做到超阈值唤醒平时MCU也进睡眠模式整体系统功耗可以压得非常低。需要注意的是关断模式下读取温度寄存器之前一定要先触发单次转换并等待转换完成直接读的话读到的是上次的旧值。转换时间跟分辨率有关12位模式下典型转换时间是26ms设计睡眠唤醒周期时要留足这个时间。5.3 数据滤波平滑处理温度数据本身是物理量变化不可能像电压跳变那么快所以如果看到读回来的温度在几个LSB之间来回跳优先怀疑是干扰而不是传感器真在抖动。我用的简单滤波方法就是多次采样取平均效果已经很好。float TMP102_ReadTempFiltered(uint8_t samples) { float sum 0.0f; uint8_t i; for (i 0; i samples; i) { sum TMP102_ReadTemp(); HAL_Delay(10); } return sum / samples; }采样间隔不能太短TMP102最高转换速率也就8Hz你读太快读到的都是同一个转换周期的数据平均没有意义。我一般间隔100ms采样5次取平均既平滑又不会让主控等太久。还有一个细节把原始温度值转成float之后再做平均浮点运算在STM32F103这种没有FPU的芯片上会有点耗时但温度采样频率低这个开销完全无所谓。如果追求极致性能可以全程用整数运算先累计12位原始值再统一做除法最后再转成带小数的温度效率更高。最后分享一个小技巧前面讲的都是标准用法最后再补充一个我在项目里用得很顺手的小技巧TMP102芯片温度测量其实有很好的规律性尤其是放在PCB上时它的读数跟环境温度之间存在一个相对固定的热阻关系。如果你的产品结构定型了可以做一个完整的温箱标定标定出不同环境温度下传感器读数和参考温度的对应曲线然后把这个曲线拟合成补偿公式写进固件里精度能控制在±0.3℃以内。这样用TMP102这种便宜传感器也能摸到更高一档精度传感器的门槛性价比拉满。当然如果你的应用场景对精度要求没那么高直接读原始值跑起来就完事了也不用折腾这些。本文还有配套的精品资源点击获取