IIC实战---》BH1750FVI光照强度传感器

记录一下IIC比较典型应用

传感器相关介绍

IIC实战---》BH1750FVI光照强度传感器

//引脚定义   硬件IC--》复用开漏   普通IO---》通用推挽

#define B_LUX_V20_SCL0_O    {\
															GPIO_InitTypeDef  GPIO_ST; \
															GPIO_ST.GPIO_Pin = GPIO_Pin_1;\
															GPIO_ST.GPIO_Mode = GPIO_Mode_Out_PP; \
															GPIO_ST.GPIO_Speed = GPIO_Speed_50MHz;\
															GPIO_Init(GPIOB, &GPIO_ST); }									//GPIOB10 开漏输出
#define B_LUX_V20_SCL0_H    GPIO_SetBits(GPIOB, GPIO_Pin_1)
#define B_LUX_V20_SCL0_L    GPIO_ResetBits(GPIOB, GPIO_Pin_1)

#define B_LUX_V20_SCL0_I    {\
															GPIO_InitTypeDef  GPIO_ST; \
															GPIO_ST.GPIO_Pin = GPIO_Pin_1;\
															GPIO_ST.GPIO_Mode = GPIO_Mode_IN_FLOATING; \
															GPIO_ST.GPIO_Speed = GPIO_Speed_50MHz;\
															GPIO_Init(GPIOB, &GPIO_ST); }									//GPIOB10 浮空输入
#define B_LUX_V20_SCL0_DAT  GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)

#define B_LUX_V20_SDA0_O    {\
															GPIO_InitTypeDef  GPIO_ST; \
															GPIO_ST.GPIO_Pin = GPIO_Pin_2;\
															GPIO_ST.GPIO_Mode = GPIO_Mode_Out_PP; \
															GPIO_ST.GPIO_Speed = GPIO_Speed_50MHz;\
															GPIO_Init(GPIOB, &GPIO_ST); }    							//GPIOB11 开漏输出
#define B_LUX_V20_SDA0_H    GPIO_SetBits(GPIOB, GPIO_Pin_2)
#define B_LUX_V20_SDA0_L    GPIO_ResetBits(GPIOB, GPIO_Pin_2)

#define B_LUX_V20_SDA0_I    {\
															GPIO_InitTypeDef  GPIO_ST; \
															GPIO_ST.GPIO_Pin = GPIO_Pin_2;\
															GPIO_ST.GPIO_Mode = GPIO_Mode_IN_FLOATING; \
															GPIO_ST.GPIO_Speed = GPIO_Speed_50MHz;\
															GPIO_Init(GPIOB, &GPIO_ST); }    							//GPIOB11 浮空输入
#define B_LUX_V20_SDA0_DAT  GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2)

#define B_LUX_V20_ADDR_O    {\
															GPIO_InitTypeDef  GPIO_ST; \
															GPIO_ST.GPIO_Pin = GPIO_Pin_13;\
															GPIO_ST.GPIO_Mode = GPIO_Mode_Out_PP; \
															GPIO_ST.GPIO_Speed = GPIO_Speed_50MHz;\
															GPIO_Init(GPIOC, &GPIO_ST); }    							//GPIOC13 推免输出
#define B_LUX_V20_ADDR_H    GPIO_SetBits(GPIOC, GPIO_Pin_13)
#define B_LUX_V20_ADDR_L    GPIO_ResetBits(GPIOC, GPIO_Pin_13)

`
这个是引脚定义,这里有一个点是比较奇怪的,可能是第一次遇见,我平常都是用没有硬件集成的IIC引脚,也就是用普通的IO口模拟IIC去通信,这里的代码是用PB10和PB11,这两个引脚复用功能都是有IIC的功能,我移植到普通的IO的时候就直接不行了(虽然代码用的是硬件IIC口,但是没有用32的库,也是软件模拟的)后来检查了一下是IO口的模式设置的问题,我的最后测试结果就是:硬件IC–》复用开漏 普通IO—》通用推挽输出,(模块已经有上拉电阻)。

模块时序

IIC实战---》BH1750FVI光照强度传感器

起始信号代码:

/*---------------------------------------------------------------------
 功能描述: 起始信号
 参数说明: 无	
 函数返回: 无
 ---------------------------------------------------------------------*/
vid B_LUX_V20_Start()
{
  B_LUX_V20_SDA0_H;                         //拉高数据线
  B_LUX_V20_SCL0_H;                         //拉高时钟线
  B_LUX_V20_Delay5us();                     //延时
  B_LUX_V20_SDA0_L;                         //产生下降沿
  B_LUX_V20_Delay5us();                     //延时
  B_LUX_V20_SCL0_L;                         //拉低时钟线
}

起始信号的定义:SCL处于高电平的时候,SDA拉低,出现一个下降沿,这个时候生成一个开始信号
代码一开始就是要把两根线拉高,(一般来说,三个信号我都会将SCL拉高,才开始去做延时),延时的时间一般来说按照100K的速度来延时就可以了,也就是5US。

停止信号代码

/*---------------------------------------------------------------------
 功能描述: 停止信号
 参数说明: 无	
 函数返回: 无
 ---------------------------------------------------------------------*/
vid B_LUX_V20_Stop()
{
  B_LUX_V20_SDA0_L;                         //拉低数据线
  B_LUX_V20_SCL0_H;                         //拉高时钟线
  B_LUX_V20_Delay5us();                     //延时
  B_LUX_V20_SDA0_H;                         //产生上升沿
  B_LUX_V20_Delay5us();                     //延时
  B_LUX_V20_SCL0_L;
  B_LUX_V20_Delay5us();
}

停止信号的定义是SCL处于高电平期间,SDA有一个上升沿的变化(我一开始是分不清楚的,但是后面想想IIC的两根线空闲状态处于高电平状态,而停止信号就是停止通信,所以这下就记清楚了)。

应答信号

/*---------------------------------------------------------------------
 功能描述: 发送应答信号
 参数说明: ack - 应答信号(0:ACK 1:NAK)
 函数返回: 无
 ---------------------------------------------------------------------*/
vid B_LUX_V20_SendACK(uint8 ack)
{
  if (ack&0x01)	B_LUX_V20_SDA0_H;		//写应答信号
  else	B_LUX_V20_SDA0_L;
  
  B_LUX_V20_SCL0_H;                         //拉高时钟线
  B_LUX_V20_Delay5us();                     //延时
  B_LUX_V20_SCL0_L;                         //拉低时钟线
  B_LUX_V20_SDA0_H;
  B_LUX_V20_Delay5us();                     //延时
}

/*---------------------------------------------------------------------
 功能描述: 接收应答信号
 参数说明: 无
 函数返回: 返回应答信号
 ---------------------------------------------------------------------*/
uint8 B_LUX_V20_RecvACK()
{
  uint8 CY = 0x00;
	uint16 vConter = 1000;
  B_LUX_V20_SDA0_H;
  
  B_LUX_V20_SDA0_I;
  
  B_LUX_V20_SCL0_H;                         //拉高时钟线
  B_LUX_V20_Delay5us();                     //延时
  
	while (vConter)
	{
		vConter--;
		CY |= B_LUX_V20_SDA0_DAT;                 //读应答信号
		
		if(!CY)	break;
	}
  
  B_LUX_V20_Delay5us();                     //延时
  
  B_LUX_V20_SCL0_L;      	                   //拉低时钟线
  
  B_LUX_V20_SDA0_O;
  
  return CY;
}

应答信号需要设置SDA的IO口另一个模式:浮空输入,用来检测IIC器件是否向主机发送了一个应答信号,如果发送了主机就可以检测到,

发送一个字节

/*---------------------------------------------------------------------
 功能描述: 向IIC总线发送一个字节数据
 参数说明: dat - 写字节
 函数返回: 无
 ---------------------------------------------------------------------*/
uint8 B_LUX_V20_SendByte(uint8 dat)
{
	uint8 vRval = 0x00;
  uint8 i;
  
  for (i=0; i<8; i++)         			//8位计数器
  {
    if (dat&0x80)	B_LUX_V20_SDA0_H;
    else	B_LUX_V20_SDA0_L;                   //送数据口
    
    B_LUX_V20_Delay5us();             		//延时
    B_LUX_V20_SCL0_H;                		//拉高时钟线
    B_LUX_V20_Delay5us();             		//延时
    B_LUX_V20_SCL0_L;                		//拉低时钟线
    B_LUX_V20_Delay5us();             		//延时
    dat <<= 1;              			//移出数据的最高位
  }
  
  vRval = B_LUX_V20_RecvACK();

	
	return vRval;
}

根据IIC的定义,数据变化必须发生在SCL的低电平时候
解释一下:
if (dat&0x80)
{B_LUX_V20_SDA0_H;}
else
{B_LUX_V20_SDA0_L; }
这个是用位操作进行的,一个字节的数据和0X80相与可以得到最高的位一个二进制数是1还是0,如果是1的话SDA输出1,如果不是(也就是0)SDA输出0。
B_LUX_V20_Delay5us(); //延时
B_LUX_V20_SCL0_H; //拉高时钟线
B_LUX_V20_Delay5us(); //延时
B_LUX_V20_SCL0_L; //拉低时钟线
B_LUX_V20_Delay5us(); //延时
dat <<= 1; //移出数据的最高位

这几行代码比较关键
首先就是上一步已经确定了数据线输出的数据是1或者是0,这个时候必须时SCL保持高电平才能保证数据正确传输,延时之后就开始拉低SCL,这个时候数据右移,下一个送出的数据就是次高位,这个时候SCL是低电平,数据可以变化,依次循环八次,就可以发送完一个字节,最后进行应答部分。

主机从IIC器件读取数据

/*---------------------------------------------------------------------
 功能描述: 从IIC总线接收一个字节数据
 参数说明: 无
 函数返回: 接收字节
 ---------------------------------------------------------------------*/
uint8 B_LUX_V20_RecvByte()
{
  uint8 i;
  uint8 dat = 0;
  B_LUX_V20_SDA0_I;
  
  B_LUX_V20_SDA0_H;                         //使能内部上拉,准备读取数据,
  for (i=0; i<8; i++)         	        //8位计数器
  {
    B_LUX_V20_SCL0_H;                       //拉高时钟线
    B_LUX_V20_Delay5us();             	//延时
    dat |= B_LUX_V20_SDA0_DAT;              //读数据               
    B_LUX_V20_SCL0_L;                       //拉低时钟线
    B_LUX_V20_Delay5us();             	//延时
    
    if (i<7) dat <<= 1;	
  }
  B_LUX_V20_SDA0_O;
  
  return dat;
}

这个接收和上面发送差不多,这里不在累赘。

IIC实战---》BH1750FVI光照强度传感器
上面的是这个模块的通信协议
数据格式:地址+读写位+应答+数据位+应答
地址有两个的7位数据,看模块的ADR接线,如果是往模块写数据(发指令、配置寄存器)就让W置位,不然就是让R置位。然后主机接收一个应答信号,接下来就是一个字节的数据,最后是一个应答
地址一般是7位,加上读写位就是八位,应答信号是独立的,最后的数据也是八位的数据。

/*---------------------------------------------------------------------
 功能描述: 写BH1750
 参数说明: REG_Address - 寄存器地址
 函数返回: 无
 ---------------------------------------------------------------------*/
uint8 B_LUX_V20_Single_Write(uint8 REG_Address)
{
	uint8 vRval = 0;
  B_LUX_V20_Start();                                //起始信号
  vRval += B_LUX_V20_SendByte(B_LUX_V20_SlaveAddress);           //发送设备地址+写信号
  vRval += B_LUX_V20_SendByte(REG_Address);                  //内部寄存器地址,
  //  BH1750_SendByte(REG_data);                //内部寄存器数据,
  B_LUX_V20_Stop();                                 //发送停止信号
  
	return vRval;
}

/*---------------------------------------------------------------------
 功能描述: 连续读出BH1750内部数据
 参数说明: 无
 函数返回: 无
 ---------------------------------------------------------------------*/
uint8 B_LUX_V20_Multiple_read(vid)
{ 
	uint8 vRval = 0;
  uint8 i;	
  B_LUX_V20_Start();                                //起始信号
  vRval += B_LUX_V20_SendByte(B_LUX_V20_SlaveAddress+1);         //发送设备地址+读信号
  
  for (i=0; i<3; i++)                           //连续读取6个地址数据,存储中BUF
  {
    m_LUX_V20_BUF[i] = B_LUX_V20_RecvByte();                //BUF[0]存储0x32地址中的数据
    if (i == 0x02)
    {
      
      B_LUX_V20_SendACK(1);                         //最后一个数据需要回NOACK
    }
    else
    {		
      B_LUX_V20_SendACK(0);                         //回应ACK
    }
  }
  
  B_LUX_V20_Stop();                                 //停止信号
  //B_LUX_V20_Delay5ms();
	
	return vRval;
}

只需要记住每一次通信都是:起始信号、收发数据、应答信号、停止信号,只要按着这个步骤写,并且地址和读写位和数据位都正确一般都可以写得出来。

和BH1750FVI相近的模块是MAX44009,两个模块基本差不多。
(欠解决问题:IO口的模式应该如何正确配置)

这里附上完整的代码工程和数据手册(提取码:37go)
BH1750FVI代码和手册资料,

上一篇:复健型力扣刷题笔记d2


下一篇:error C2248: “CObject::CObject”: 无法访问 private 成员(在“CObject”类中声明)---解决办法