3. I2C实战:使用STM32读取温度传感器(如LM75)数据

好,咱们今天来点真家伙。前面讲了那么多I2C的理论,什么起始条件、停止条件、应答信号,说实话,光看波形图确实有点枯燥。我个人习惯是,学一个协议,必须上手调一个实际器件。今天我们就拿最常见的LM75温度传感器,配合STM32,把I2C通信完整走一遍。

LM75这个芯片,我估计做嵌入式的朋友都不陌生。它便宜、简单、皮实,非常适合用来做I2C的入门实战。你想想看,一个温度传感器,读出来的数据就是温度值,对不对?验证起来特别方便——你哈一口气,温度就变了,多直观。

3.1 LM75芯片简介

先简单介绍一下LM75。这是一个数字温度传感器,精度是0.125°C,分辨率9位。它通过I2C接口输出温度数据。默认的I2C地址是0x48(7位地址),但你可以通过A0、A1、A2三个引脚来配置,最多可以挂8个器件到同一条I2C总线上。

它的寄存器结构也很简单,主要就三个:

寄存器名称 地址 说明
温度寄存器 0x00 只读,存储当前温度值
配置寄存器 0x01 可读写,设置工作模式
THYST寄存器 0x02 设置迟滞温度
TOS寄存器 0x03 设置过温阈值

嗯,这里要注意,温度寄存器是16位的,但LM75只用了高9位。所以读出来的数据需要右移7位,才能得到真正的温度值。这个坑我当年踩过,读出来数据怎么都不对,后来才发现是移位没处理好。

3.2 硬件连接

硬件连接其实很简单。STM32的I2C外设,一般用PB6和PB7作为SCL和SDA。LM75这边,VCC接3.3V,GND接地,SCL和SDA分别上拉到3.3V(4.7kΩ电阻就行)。A0、A1、A2全部接地,这样地址就是0x48。

注意: 上拉电阻一定要加!我曾经见过有人偷懒没加上拉电阻,结果I2C总线一直拉低,通信完全失败。I2C是开漏输出,没有上拉电阻,总线电平就是不确定的。

另外,LM75的OS引脚是开漏输出,可以接一个LED做温度报警指示。不过咱们这次只是读温度,不搞那么复杂。

3.3 STM32 I2C初始化

好,接下来看代码。我用的是STM32F103系列,HAL库。初始化I2C外设的步骤,说白了就是配置时钟、引脚、模式这些。

// I2C初始化函数
void MX_I2C1_Init(void)
{
    hi2c1.Instance = I2C1;
    hi2c1.Init.ClockSpeed = 100000;          // 100kHz标准模式
    hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
    hi2c1.Init.OwnAddress1 = 0;              // 主机模式,不需要自身地址
    hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    hi2c1.Init.OwnAddress2 = 0;
    hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
    
    if (HAL_I2C_Init(&hi2c1) != HAL_OK)
    {
        Error_Handler();
    }
}

我个人习惯把时钟速度设成100kHz,也就是标准模式。虽然LM75支持400kHz快速模式,但100kHz更稳定,尤其当你的走线比较长的时候。我在一个项目里试过400kHz,结果因为线缆太长,波形都变形了,后来老老实实改回100kHz。

3.4 读取温度数据

读取LM75的温度数据,流程是这样的:

  1. 主机发送起始条件
  2. 主机发送器件地址(0x48左移1位,加上写位0)
  3. LM75应答
  4. 主机发送寄存器地址(0x00,指向温度寄存器)
  5. LM75应答
  6. 主机发送重复起始条件
  7. 主机发送器件地址(0x48左移1位,加上读位1)
  8. LM75应答
  9. 主机读取2个字节(高字节在前,低字节在后)
  10. 主机发送非应答信号
  11. 主机发送停止条件

你看,这就是标准的I2C复合操作。HAL库封装了一个函数,叫HAL_I2C_Mem_Read,专门干这个事。

// 读取LM75温度
float LM75_ReadTemperature(void)
{
    uint8_t buf[2];
    int16_t raw;
    float temp;
    
    // 从LM75的0x00寄存器读取2个字节
    if (HAL_I2C_Mem_Read(&hi2c1, 0x48 << 1, 0x00, 
                         I2C_MEMADD_SIZE_8BIT, buf, 2, 100) != HAL_OK)
    {
        Error_Handler();
    }
    
    // 组合成16位数据
    raw = (buf[0] << 8) | buf[1];
    
    // 右移7位,得到9位有效数据
    raw = raw >> 7;
    
    // 判断正负(LM75用补码表示负数)
    if (raw & 0x0100)  // 第9位为1,表示负数
    {
        raw = raw - 512;  // 补码转原码
    }
    
    // 温度值 = 原始值 * 0.5°C
    temp = raw * 0.5f;
    
    return temp;
}
关键点: LM75的温度分辨率是0.5°C,所以最后要乘以0.5。另外,负温度的处理很多人会忽略。如果你在北方做项目,冬天测到零下温度,这个判断就很重要了。

3.5 主函数调用

主函数里就很简单了,初始化I2C,然后循环读温度,通过串口打印出来。

int main(void)
{
    float temperature;
    
    HAL_Init();
    SystemClock_Config();
    MX_USART1_UART_Init();
    MX_I2C1_Init();
    
    while (1)
    {
        temperature = LM75_ReadTemperature();
        printf("当前温度: %.2f °C\r\n", temperature);
        HAL_Delay(1000);  // 每秒读一次
    }
}

嗯,这里要注意,printf重定向到串口需要做一些配置,这个我就不展开了。你如果用的是串口调试助手,直接看打印出来的温度值就行。

3.6 常见问题与调试技巧

在实际调试中,我遇到过几个典型问题,分享给大家:

  • 读出来全是0xFF:大概率是地址不对,或者上拉电阻没加。检查一下LM75的A0、A1、A2引脚电平。
  • 读出来温度不变:可能是I2C通信根本没成功,用逻辑分析仪抓一下波形看看。
  • 温度值跳变:可能是电源噪声太大,LM75的VCC和GND之间加一个0.1μF的退耦电容。
调试小技巧: 我建议你手边常备一个逻辑分析仪,几十块钱那种就行。I2C协议是数字协议,用逻辑分析仪一看,什么起始条件、应答信号,一目了然。比用示波器方便多了。

3.7 扩展思考

读LM75只是I2C实战的第一步。你想想看,如果我要挂多个LM75,怎么办?很简单,把它们的A0、A1、A2设成不同的电平组合,地址就不同了。比如第一个设成0x48,第二个设成0x49,以此类推。

另外,LM75还有一个很实用的功能——过温报警。你可以设置TOS和THYST寄存器,当温度超过阈值时,OS引脚会拉低。这个功能在工业控制中很常用,比如电机过热保护。

好了,今天的实战就到这里。你按照这个流程走一遍,应该能顺利读到温度数据。如果遇到问题,别急,先检查硬件连接,再用逻辑分析仪看波形,基本都能解决。下一章我们讲更复杂的传感器——用I2C读取加速度计的数据,那个更有意思。