Bootstrap

STM32F4X I2C LM75


I2C协议是飞利浦公司在1982年发明的一种用于芯片之间通信的协议,其特点是电路硬件简单,可连接多个设备。在进行I2C通信时,只需要用到2根信号线,I2C通信可以达到几百K左右,适合低速的通信领域。

I2C协议讲解

I2C接线

I2C设备分为主机和从机,主机负责发送时钟信号,从机不发送时钟信号。I2C通信时只需要两个通信线,分别是时钟线SCL和数据线SDA。其中SCL是单向数据线,是主机发给从机,而SDA是双向数据线。因为I2C协议规定在空闲时候SCL和SDA要保持高电平,所以在电路设计时SCL和SDA要接分别接一个上拉电阻到VCC。
在这里插入图片描述

I2C协议波形

在这里插入图片描述
I2C协议的波形有如下的几个特点

  • 开始数据传输时需要发送一个起始信号
  • 结束数据传输时需要发送一个停止信号
  • I2C每次传输的位数是8位,一个字节的数据
  • 每次传输完一个数据后,在第9位需要有应答信号
  • I2C每次进行数据传输前都需要发送一个从设备地址

I2C起始信号

在I2C开始通信前,主机要先发送一个起始信号,告诉从机开始准备通信。
在这里插入图片描述
首先要先把SCL和SDA拉高,保持一段时间,然后把SDA拉低,这时从机就会检测到一个起始信号。

I2C停止信号

当主机要结束通信时,需要发送一个停止信号,告诉从机,通信结束。
在这里插入图片描述
首先要先把SCL和SDA拉低,保持一段时间,然后把SDA拉高,这时从机就会检测到一个停止信号。

I2C应答信号

I2C协议规定,每传输完一个字节的数据,都需要有一个应答信号,应答信号的作用是确认数据是否已经被对方接受到,应答信号由接收数据的设备产生,可以是主机也可以是从机。
在这里插入图片描述

在数据发送或接收完成后,主机需要把SDA线拉高,然后等待从机或者主机把SDA线拉低,则代表有应答,如果SDA线保持为高电平,则代表没有应答。

I2C寻址

I2C的主机可以连接多个从机,这时候就会出现一个问题,当主机开始通信时,要怎么保证能找到对应的从机与之进行通信?为了解决这个问题,I2C协议引入了从机地址的概念,拿个日常生活的例子来说明。
某一天你的朋友来找你,他住在一家酒店里面,酒店里面有3间房间,房间号分别是101、102和103。在你出发前要先问清楚你朋友住在哪一间房间,这样才能保证不会找错。3个房间就代表3个I2C从机,房间号则代表I2C从机的地址。在I2C主机开始通信前,会先给所有的从机发送一个地址,如果有从机应答,则代表寻址正确,开始通信。这个从机地址在一个I2C总线上是唯一的。
I2C从机可以选择7位和10位,大多数的芯片都是7位地址,7位地址的模式下,一个I2C主机最多可以接128个从机设备,而10位地址模式下,一个I2C主机最多可以接1024个从机设备。

I2C地址格式

我们以7位地址模式为例,说一下I2C从机的地址格式

A6A5A4A3A2A1A0R/W
  • A6~A0:I2C从机的地址
  • R/W:设置主机读写模式,通常0为向I2C从机写数据,1为向I2C从机读数据

I2C数据传输

I2C协议规定,每次传输的数据为8位,在SCL为高电平时,SDA数据才有效,也就是说如果需要改变SDA的数据,则需要在SCL为低电平时进行修改。
在这里插入图片描述

LM75A

LM75A介绍

LM75A是NXP半导体公司推出的一具有I2C接口的数字温度传感器芯片,可广泛运用于系统温度管理、个人计算机、电子设备、工业控制器等方面。
特性:

  • I2C 总线接口,器件地址 7 位从机地址 1001xxx,同一总线上可以外扩 8 个器件;
  • 供电范围:2.8V~5.5V,温度范围:-55℃~+125℃;
  • 11 位 ADC 提供温度分辨率达 0.125℃;
  • 温度精度:±2℃(-25℃~100℃) ±3℃(-55℃~125℃)
  • 可编程温度阈值和滞后设定点;
  • 为了减低功耗,关断模式下消耗的电流仅为 1.0μA;
  • 上电时器件可用作一个独立的温度控制器;
  • 在 JEDEC 标准下(JESD78)所做的闩锁测试可达 100mA;
  • 小型 8 脚封装:SO8、TSSOP8 和超小型封装 XSON8U。

LM75A引脚说明

在这里插入图片描述

管脚号符号功能说明
1SDA数据线
2SCL时钟线
3OS过热关断输出,开漏
4GND地线
5A2用户定义地址2
6A1用户定义地址1
7A0用户定义地址0
8VCC电源线

LM75A地址

LM75A的地址为7位,其地址定义如下

A6A5A4A3A2A1A0R/W
1001A2A1A0

其中A0~A2是用户自定义的,可以接地和接VCC,但是不能悬空,根据其地址定义,同一个I2C总线上最多可以接8个LM75A器件。

LM75A寄存器

LM75内部有4个寄存器,其定义如下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
除了配置寄存器为8位外,其他3个寄存器都是16位。

LM75A I2C协议

写配置寄存器

在这里插入图片描述

  1. 主机发送起始信号
  2. 主机发送从机写地址
  3. 主机等待从机应答
  4. 主机发送配置寄存器地址
  5. 主机等待从机应答
  6. 主机发送配置寄存器数据
  7. 主机等待从机应答
  8. 主机发送结束信号

读配置寄存器

在这里插入图片描述

  1. 主机发送起始信号
  2. 主机发送从机写地址
  3. 主机等待从机应答
  4. 主机发送配置寄存器地址
  5. 主机等待从机应答
  6. 主机重新发送起始信号
  7. 主机发送从机读地址
  8. 主机等待从机应答
  9. 主机读取从机数据
  10. 主机发送不应答信号
  11. 主机发送结束信号

写Tos和Thyst寄存器

在这里插入图片描述

  1. 主机发送起始信号
  2. 主机发送从机写地址
  3. 主机等待从机应答
  4. 主机发送Tos或Thyst寄存器地址
  5. 主机等待从机应答
  6. 主机发送16位数据中高8位数据
  7. 主机等待从机应答
  8. 主机发送16位数据中低8位数据
  9. 主机等待从机应答
  10. 主机发送结束信号

读Tos Thyst Temp寄存器

在这里插入图片描述

  1. 主机发送起始信号
  2. 主机发送从机写地址
  3. 主机等待从机应答
  4. 主机发送Tos、Thyst、Temp寄存器地址
  5. 主机等待从机应答
  6. 主机重新发送起始信号
  7. 主机发送从机读地址
  8. 主机等待从机应答
  9. 主机读取从机数据高8位数据
  10. 主机发送应答信号
  11. 主机读取从机数据低8位数据
  12. 主机发送不应答信号
  13. 主机发送结束信号

LM75A温度计算

LM75A的温度值保存在TEMP寄存器里面,温度寄存器是一个只读寄存器,包含2个8位的数据字节,由一个高数据字节(MS)和一个低数据字节(LS)组成。在这两个字节中只用到 11 位,来存放分辨率为 0.125℃的Temp数据(以二进制补码数据的形式。
在这里插入图片描述
根据 11 位的 Temp 数据来计算 Temp 值的方法:
若 D10=0,温度值(℃)=+(Temp 数据)×0.125℃;
若 D10=1,温度值(℃)=-(Temp 数据的二进制补码)×0.125℃。

LM75A例程

i2c.c

#include "i2c.h"

void cpu_delay(unsigned int tns)
{
    int i = 0;
    while(i < tns)
    {
        __asm("NOP");
        i++;
    }
}

//初始化IIC
void IIC_Init(void)
{			
  GPIO_InitTypeDef  GPIO_InitStructure;

RCC_AHB1PeriphClockCmd(I2C_SCL_CLK,ENABLE);
  RCC_AHB1PeriphClockCmd(I2C_SDA_CLK,ENABLE);
  
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_Pin = I2C_SCL_PIN;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(I2C_SCL_PORT,&GPIO_InitStructure);
  
  
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_Pin = I2C_SDA_PIN;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(I2C_SDA_PORT,&GPIO_InitStructure);
	IIC_SDA_H;
	IIC_SCL_H;
}


static void i2c_sda_out(void)	
{
  GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStruct.GPIO_Pin = I2C_SDA_PIN;
  GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(I2C_SDA_PORT,&GPIO_InitStruct);
}
static void i2c_sda_in(void)	
{
 GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
  GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStruct.GPIO_Pin = I2C_SDA_PIN;
  GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(I2C_SDA_PORT,&GPIO_InitStruct);
}


//产生IIC起始信号
void IIC_Start(void)
{
	i2c_sda_out();     //sda线输出
	IIC_SDA_H;	  	  
	IIC_SCL_H;
	cpu_delay(4);
 	IIC_SDA_L;//START:when CLK is high,DATA change form high to low 
	cpu_delay(4);
	IIC_SCL_L;//钳住I2C总线,准备发送或接收数据 
}	  
//产生IIC停止信号
void IIC_Stop(void)
{
	i2c_sda_out(); //sda线输出
	IIC_SDA_L;	  	  
	IIC_SCL_L;
 	cpu_delay(4);
	IIC_SDA_H;	  	  
	IIC_SCL_H;
	cpu_delay(4);							   	
}
//等待应答信号到来
//返回值:1,接收应答失败
//        0,接收应答成功
u8 IIC_Wait_Ack(void)
{
	u8 ucErrTime=0;
	i2c_sda_in();      //SDA设置为输入  
	IIC_SDA_H;cpu_delay(1);	   
	IIC_SCL_H;cpu_delay(1);	 
	while(IIC_SDA_READ() == SET)
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			IIC_Stop();
			return 1;
		}
	}
	IIC_SCL_L;//时钟输出0 	   
	return 0;  
} 
//产生ACK应答
void IIC_Ack(void)
{
	IIC_SCL_L;
	i2c_sda_out();
	IIC_SDA_L;
	cpu_delay(2);
	IIC_SCL_H;
	cpu_delay(2);
	IIC_SCL_L;
}
//不产生ACK应答		    
void IIC_NAck(void)
{
	IIC_SCL_L;
	i2c_sda_out();
	IIC_SDA_H;
	cpu_delay(2);
	IIC_SCL_H;
	cpu_delay(2);
	IIC_SCL_L;
}					 				     
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答			  
void IIC_Send_Byte(u8 txd)
{                        
    u8 t;   
	i2c_sda_out(); 	    
    IIC_SCL_L;//拉低时钟开始数据传输
    for(t=0;t<8;t++)
    {              
		if((txd&0x80) >> 7)
			IIC_SDA_H;
		else
			IIC_SDA_L;
     	
        txd<<=1; 	  
		delay_ms(1);   
		IIC_SCL_H;
		delay_ms(1); 
		IIC_SCL_L;	
		delay_ms(1);
    }	 
} 	    
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
u8 IIC_Read_Byte(unsigned char ack)
{
	unsigned char i,receive=0;
	i2c_sda_in();//SDA设置为输入
    for(i=0;i<8;i++ )
	{
        IIC_SCL_L; 
        delay_ms(2);
		IIC_SCL_H;
        receive<<=1;
		if(IIC_SDA_READ())
			receive++;   
   
		delay_ms(1); 
    }					 
    if (!ack)
        IIC_NAck();//发送nACK
    else
        IIC_Ack(); //发送ACK   
    return receive;
}

i2c.h

#ifndef __I2C_H
#define __I2C_H

#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"

#define I2C_SCL_CLK RCC_AHB1Periph_GPIOB
#define I2C_SDA_CLK RCC_AHB1Periph_GPIOB

#define I2C_SCL_PORT GPIOB
#define I2C_SDA_PORT GPIOB

#define I2C_SCL_PIN GPIO_Pin_8
#define I2C_SDA_PIN GPIO_Pin_9

                                                                  
#define IIC_SDA_H       GPIO_SetBits(I2C_SDA_PORT, I2C_SDA_PIN)
#define IIC_SDA_L       GPIO_ResetBits(I2C_SDA_PORT, I2C_SDA_PIN)                                                                  
                                                                  
#define IIC_SCL_H       GPIO_SetBits(I2C_SCL_PORT, I2C_SCL_PIN)
#define IIC_SCL_L       GPIO_ResetBits(I2C_SCL_PORT, I2C_SCL_PIN)                                                                   
                                                                  
#define IIC_SDA_READ()  GPIO_ReadInputDataBit(I2C_SDA_PORT, I2C_SDA_PIN)   


void IIC_Init(void);                //初始化IIC的IO口				 
void IIC_Start(void);				//发送IIC开始信号
void IIC_Stop(void);	  			//发送IIC停止信号
void IIC_Send_Byte(u8 txd);			//IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); 				//IIC等待ACK信号
void IIC_Ack(void);					//IIC发送ACK信号
void IIC_NAck(void);				//IIC不发送ACK信号
#endif


lm75.c

#include "lm75.h"

unsigned char lm75_init(void)
{
  IIC_Init();
  
}


void lm75_write_conf(unsigned char data)
{
	IIC_Start();
	IIC_Send_Byte(LM75_ADDRESS);
	
	IIC_Wait_Ack();
	
	IIC_Send_Byte(LM75_CONF_REG);
	IIC_Wait_Ack();
	
	IIC_Send_Byte(data);
	IIC_Wait_Ack();
	
	IIC_Stop();
}


unsigned char lm75_read_conf(void)
{
	unsigned char data;
	IIC_Start();
	IIC_Send_Byte(LM75_ADDRESS);
	IIC_Wait_Ack();
	
	IIC_Send_Byte(LM75_CONF_REG);
	IIC_Wait_Ack();
	
	IIC_Start();
	IIC_Send_Byte(LM75_ADDRESS + 0x1);
	IIC_Wait_Ack();
	
	data = IIC_Read_Byte(0);
	IIC_Stop();
	
	return data;
}

void lm75_write_tos_thyst(unsigned char reg,unsigned short data)
{
	IIC_Start();
	IIC_Send_Byte(LM75_ADDRESS);
	IIC_Wait_Ack();
	
	IIC_Send_Byte(reg);
	IIC_Wait_Ack();
	
	IIC_Send_Byte((data >> 8) & 0xFF);
	IIC_Wait_Ack();
	
	IIC_Send_Byte(data & 0xFF);
	IIC_Wait_Ack();
	
	IIC_Stop();

}
	
unsigned char lm75_read_tos_thyst_temp(unsigned char reg,unsigned short *data)
{
	unsigned char datah,datal;
	float temp;
	
	IIC_Start();
	IIC_Send_Byte(LM75_ADDRESS);
	
	IIC_Wait_Ack();
	
	IIC_Send_Byte(reg);
	IIC_Wait_Ack();
	
	IIC_Start();
	IIC_Send_Byte(LM75_ADDRESS + 0x1);
	IIC_Wait_Ack();
	
	datah = IIC_Read_Byte(1);
	
	datal = IIC_Read_Byte(0);
	
	IIC_Stop();

	*data = datah << 8 | datal;

}
	

lm75.h

#ifndef __LM75_H
#define __LM75_H


#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "i2c.h"




#define LM75_ADDRESS (0x48 << 1)

#define LM75_TEMP_REG (0x0)
#define LM75_CONF_REG (0x1)
#define LM75_THYST_REG (0x2)
#define LM75_TOS_REG   (0x3)

unsigned char  lm75_init(void);
void lm75_read_temp(void);
void lm75_write_conf(unsigned char data);
unsigned char lm75_read_conf(void);

void lm75_write_tos_thyst(unsigned char reg,unsigned short data);
unsigned char lm75_read_tos_thyst_temp(unsigned char reg,unsigned short *data);

#endif

main.c

#include "stm32f4xx.h"
#include "delay.h"
#include "usart.h"
#include "lm75.h"
extern u8 rx_buf[RX_BUF_SIZE];
extern u16 USART_RX_STA;
int main(void)
{
	int i;
	unsigned short temp_data,tempforh;
	float temp;
	NVIC_PriorityGroupConfig(2);
	system_tick_init();
	
	bsp_usart_init(115200);
	
	lm75_init();

  while(1){
		delay_ms(1000);
		lm75_read_tos_thyst_temp(LM75_TEMP_REG,&temp_data); // 读取温度
	  
		if(temp_data & 0x8000) // 温度为负数
		{
			tempforh = (temp_data >> 5 & 0xFFF);
			tempforh = (tempforh ^ 0x7FF) + 1;
			temp= tempforh * -0.125;
		}	
		else // 温度为正数
			temp=(temp_data >> 5 & 0xFFF) * 0.125; 
			
		printf("temp = %f\r\n",temp);
	
	  
	}
  
}

在这里插入图片描述

;