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。
另外,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的温度数据,流程是这样的:
- 主机发送起始条件
- 主机发送器件地址(0x48左移1位,加上写位0)
- LM75应答
- 主机发送寄存器地址(0x00,指向温度寄存器)
- LM75应答
- 主机发送重复起始条件
- 主机发送器件地址(0x48左移1位,加上读位1)
- LM75应答
- 主机读取2个字节(高字节在前,低字节在后)
- 主机发送非应答信号
- 主机发送停止条件
你看,这就是标准的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;
}
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的退耦电容。
3.7 扩展思考
读LM75只是I2C实战的第一步。你想想看,如果我要挂多个LM75,怎么办?很简单,把它们的A0、A1、A2设成不同的电平组合,地址就不同了。比如第一个设成0x48,第二个设成0x49,以此类推。
另外,LM75还有一个很实用的功能——过温报警。你可以设置TOS和THYST寄存器,当温度超过阈值时,OS引脚会拉低。这个功能在工业控制中很常用,比如电机过热保护。
好了,今天的实战就到这里。你按照这个流程走一遍,应该能顺利读到温度数据。如果遇到问题,别急,先检查硬件连接,再用逻辑分析仪看波形,基本都能解决。下一章我们讲更复杂的传感器——用I2C读取加速度计的数据,那个更有意思。