零死角玩转stm32中级篇2-IIC总线

一.IIC基础知识

1.什么是IIC总线

IIC(Inter-Integrated Circuit)总线是一种短距离的数字通信协议,由Philips公司在1980年推出。它用于连接各种数字集成电路芯片,并能够支持多主机和多从机的通信。
IIC总线的特点包括:只需两条信号线(SDA和SCL),能够实现高效、可靠的数据传输;支持数据的双向传输;具有地址识别功能,可以同时与多个从设备进行通信;具有仲裁功能,解决主从设备之间的冲突问题;支持多种传输速率。
在多种应用中,IIC总线被广泛应用于传感器、存储器、实时时钟以及其他各种数字设备之间的通信。

在这里插入图片描述
备注:图片来源于:https://blog.csdn.net/weixin_42881419/article/details/104288391

2.IIC总线和串口有什么区别

IIC总线和串口都是数字通信协议,但它们之间存在着一些区别:

  1. 信号线数目:IIC总线只需两条信号线(SDA和SCL),而串口需要至少三条信号线(TXD、RXD和GND)。

  2. 数据的传输方式:IIC总线使用的是同步传输,数据在时钟的控制下以位序列的方式被传输。而串口(单片机)则使用的是异步传输,即每个数据字节之间有一个确定的时间间隔,没有时钟信号进行同步。

  3. 传输速率:IIC总线的传输速率通常比串口更低,最高只有几百Kbps。而串口可达到数Mbps的传输速率。

  4. 应用场景:IIC总线通常用于连接各种数字芯片,如存储器、实时时钟、传感器等,而串口通常用于连接外设,如鼠标、键盘、调试工具等。

总的来说,IIC总线和串口两种协议各有优劣,应根据具体的应用需求来选择合适的通信协议。

3.IIC总线是怎么实现多机通信

IIC总线可以实现多机通信,其技术原理是通过IIC总线上设备的唯一地址来实现的。每一个从机设备都有自己的7位地址,并且这些地址是唯一的。主机可以根据从机的地址来发送数据和接收数据。
在IIC总线通信中,主机负责发起通信,并控制整个通信过程的时序;从机等待主机的命令,并返回所需信息给主机。主机发送地址码并进行 仲裁,将总线控制权赋予被选中的从机,从机在确认后回复 应答 信号,然后主机就可以对该从机进行读写操作。
需要注意的是,在多机通信时,一个从机的地址可能会与其他从机的地址相同。为避免 地址冲突 ,在IIC总线协议中,规定了不同层次的 地址冲突解决方法 ,最终保证所有 从机设备的地址唯一,并且可以正确地进行通信。

4.仲裁是什么

在IIC总线中,当多个主机同时请求访问总线时,就可能会产生冲突。为了解决这个问题,IIC总线采用了仲裁机制。
具体来说,任何一个主机在向总线发送数据之前,都必须先检测是否有其他主机正在使用总线。如果当前总线空闲,则该主机可以开始使用总线进行通信;如果当前总线被占用,则该主机必须放弃控制权,并等待一段时间后再次检测总线是否空闲。等待的时间是随机的,这样可以避免主机在同一时间重复试图获取总线的情况。如果多个主机同时请求访问总线,每一个主机根据IIC总线定义的优先级来决定是否放弃控制权。
最终,只有一个主机可以获得总线的控制权,其他主机必须等待。当一个主机完成操作并释放总线后,下一个主机可以接着使用总线进行通信。仲裁机制保证了IIC总线通信的可靠性和高效性,使得多个主从设备可以在同一总线上进行通信。

5.如果当前有一个从机进行了IIC通信又来了一个优先级高的从机,这时会打断前一个通信吗?

当多个从机同时向主机发送数据时,IIC总线的仲裁机制会根据从机的地址、请求访问的优先级来进行决策。如果当前正在进行IIC通信的从机与另一个请求访问的从机的地址相同,那么未完成的IIC通信将被中断,控制权转移到新到来的请求访问的从机上。
如果当前正在进行IIC通信的从机与新到来的请求访问的从机的地址不同,那么新的请求访问从机必须等待当前从机通信结束后,才能获得总线的控制权进行通信。
需要注意的是,在IIC总线中,每个从机在通信时都必须拥有唯一的地址,以便主机可以识别和控制各个从机的通信。同时,主机也必须采用合适数值的延迟时间和合理的响应时间,以确保正常的通信过程,尽量避免出现通信失败或者中断的情况。

6.IIC是怎么保证地址的唯一性

IIC总线地址的唯一性是通过各个设备的硬件地址引脚来保证的。每个IIC设备在出厂时,都会被设置一个唯一的7位地址。而在IIC总线上,主控设备通过发送一个地址帧来选择要通信的从机设备,从机设备接收到相应的地址帧后, 会根据自己的地址与该地址进行比较,如果匹配成功,从机就会发送一个ACK信号,表示可以进行数据传输。如果地址不匹配,则不会发送ACK信号。
通过这样的方式,可以确保在IIC总线上每个设备的地址都是唯一的,从而保证了设备之间的通信互不干扰。此外,对于多个从机设备使用同一个地址的问题,可以通过在7位地址中添加一个“哑位”来解决。具体做法是,在设备地址最高位使用0和1两种状态,这样就可以将一个地址空间划分为两个部分,从而使得两个从机设备的地址不会重复。

7.在IIC总线协议中,规定了不同层次的地址冲突解决方法,具体有哪一些?

在IIC总线协议中,地址冲突主要是由于多个主设备同时访问IIC总线造成的。在IIC总线协议中,针对不同层次的地址冲突,有以下解决方法:

  1. Start-stop condition arbitration:当多个主设备同时发起IIC总线访问请求时,通过比较发起start信号的主设备的地址信息,来确定哪个主设备有权访问总线。

  2. Bit-by-bit arbitration:当有多个设备同时在总线上传输数据时,通过比较每个设备发送的数据位,来确定哪个设备有权继续传输数据。

  3. Priority resolution:设置设备的优先级,高优先级设备有权先访问总线。

需要注意的是,以上方法都是IIC总线协议中提供的机制,具体的实现可能会略有不同。

8.IIC总线采用的地址方式

在IIC总线中,每个从机都必须拥有唯一的地址,以便主机可以识别和控制各个从机的通信。IIC总线采用了两种不同级别的地址,分别是7位地址和10位地址。

  • 对于7位地址,总线上最多可以连接128个从机设备,其中0x00和0x78 - 0x7F这些地址是保留地址,不能被使用,实际可用的地址为0x01~0x77。这样,如果一个芯片带有IIC接口,那么它的地址必须是这128个地址中的一个,并且不能与其他芯片冲突。
  • 对于10位地址,总线上最多可以连接1024个从机设备。但由于这种地址编码方式使用的时钟周期比较复杂,因此使用起来相对较少。

此外,在设计IIC总线时,还可以采用一些其他方法来确保地址的唯一性,如将地址码存储在EEPROM中,或者通过特殊的管脚引脚来设置地址码等等。这些方法可以有效地避免从机地址的冲突问题,保证IIC总线的稳定性和可靠性。

9.讲解一下IIC的通信原理

IIC(Inter-Integrated Circuit)总线是用于连接微处理器和外设的串行通信接口,其基本原理为同步串行通信,即数据按位进行传输,时钟信号用于同步两端的数据传输节奏。
IIC总线由两根线构成,一根为数据线SDA(Serial Data Line),一根为时钟线SCL(Serial Clock Line)。SDA线上的数据在SCL线上的时钟周期控制下进行传输。IIC总线采用主从式方式工作,其中主机可以是微处理器、DSP、FPGA等,而从机可以是EEPROM、温度传感器、ADC等各种外设设备。
IIC通信分为两种模式:传输数据和传输地址。在传输数据时,主机发送起始信号,然后发送从机地址和读/写位,接着从机发出应答信号,主机再发送要读或写的数据,接着从机返回数据应答信息。在传输地址时,主机首先发出起始信号,随后发送从机地址和读/写控制位,接着从机回应应答信号以确认地址接收正确,最后主机结束通信。
在IIC总线中,每个从机都必须拥有唯一的地址,以便主机可以识别和控制各个从机的通信。通过地址传输和应答机制,实现了从机与主机之间的通信。同时,IIC总线还采用了仲裁技术来解决多主机同时请求访问总线的情况。在主机对从机进行读写操作过程中,从机可以返回应答信号以表示数据是否成功接收。
总之,IIC总线具有通信速度快、可靠性高、适用于多种设备等特点,因此被广泛应用于各种嵌入式系统和智能设备中。

上面的通信讲解的太过笼统,实际上可以分为下列几步:

  • 数据位的有效性规定

在这里插入图片描述

所以一般会在SCL,SDA与电源之间接一个上拉电阻,使空闲状态下SCL,SDA默认为高电平(上拉电阻一般在4.7k~10k之间)

在这里插入图片描述

  • 起始与终止信号:SCL为高期间

SDA : 由高到低,起始信号(上升沿)
SDA:由低到高,终止信号(下降沿)

在这里插入图片描述

在这里插入图片描述

  • 主机为什么要先发送起始信号,再发送从机地址+读/写

主机在IIC总线上进行数据通信时,需要通过发送一系列的信号来控制通信的过程。首先,主机需要发送起始信号(Start Signal),告诉从机通信即将开始,这是一种同步信号。接着,主机需要发送从机地址信息和读/写控制位(R/W bit),以确定要访问哪个从机,同时指定当前操作是读还是写。
主机先发送起始信号是为了向总线上的所有从机发出信号,让它们保持等待接受指令的状态,并且从机不会像SPI那样自行去取数据或保存好数据。起始信号的传输方式是SCL为高电平时,SDA线由高电平转向低电平。这样,其他设备接收到起始信号后知道后续通信是针对自己的而非其他设备的。
然后,主机将从机地址加上读/写控制位一起发送,并等待从机回传应答信号,以确认地址是否被正确识别。如果从机没有回传应答信号,则表示没有接收到该地址,通信失败。如果从机回传了应答信号,则主机就可以根据所需读写的数据类型决定下一步的操作。
总之,每次IIC总线通信都需要发送起始信号,它是整个通信序列的开端,同时也用于同步通信两端的节奏,使得通信过程可以顺畅地执行。由主机先发送从机地址和读/写控制位,是为了告诉从机这个操作是针对它的,并且确认从机地址是否被正确识别。

  • 接收端应答信号

每当发送器传输完一个字节的数据之后,发送端会等待一定的时间,等接收方的应答信号。接收端通过拉低SDA数据线,给发送端发送一个应答信号,以提醒发送端我这边已经接受完成,数据可以继续传输,接下来,发送端就可以继续发送数据了

备注:来源于:https://blog.csdn.net/shaguahaha/article/details/70766665

  • 主机IIC总线向从机写入数据 ( 重点 )

在这里插入图片描述

备注:应答信号是有从机向主机回复的信息,上文中第一个应答信号其中0:写 1:读 其他的应答信号0:收到信息;1:没有收到或读取完成

  • 主机IIC总线读取从机设备的数据 ( 重点 )

在这里插入图片描述

  • 从机24C02的地址为1010 000 寄存器地址为0000 0001:

在这里插入图片描述

在这里插入图片描述

二.STM32代码实例

下面代码通过IIC实现MCU读取BH1750光照传感器的值,MCU采用STM32F103C8T6, SCL为PB6,SDA为PB7。

  • BH1750模块原理图(从原理图可以看出SCL和SDA确立接了上拉电阻的)

在这里插入图片描述

  • BH1750实物图(从实物图可以看出需要外接5个引脚到单片机上就可以了,则五个引脚分别是VCC,GND,SCL,SDA,ADDR)

在这里插入图片描述

  • BH1750外接端子(单片机部分)

在这里插入图片描述

  • STM32最小系统外接端子(单片机部分)

在这里插入图片描述

  • 编程实现

怎么通过代码来实现获取BH1750模块上的数据呢,如果购买的BH1750模块有商家提供的实例代码(测试代码),那么我可以直接通过修改测试代码来实现BH1750传感器的读取,如果没有相应的测试代码,我们可以通过阅读数据手册的方式编写相应的驱动代码,购买硬件的时候,商家一般会提供数据手册的,所以下面我们通过分析数据手册编写获取BH1750传感器数据的实例代码。

  • BH1750的数据手册获取

详细的传感器资料下载,通过这个链接:https://pan.baidu.com/s/13IatouiUd-_x8y4Kmr2VDg进行下载。

在这里插入图片描述

  • BH1750介绍

BH1750FVI是一款带有 I2C总线 接口的 数字环境光传感器芯片。该芯片非常适用于获取环境光数据,以调整移动电话的LCD和键盘背光功率。它能够以高分辨率检测广泛的光照范围( 1 - 65535lux )。

  • Absolute Maximum Ratings

通过下表可知该芯片能够支持的最大供电电压为4.5V

在这里插入图片描述

  • 二种模式(连续模式和一次模式)

连续 H 分辨率模式是指传感器以连续的方式进行测量,并使用高分辨率来获取环境光数据。在这种模式下,传感器将以一定的频率自动进行测量,不需要每次都发送指令。(地址引脚ADDR置高进入该模式)
一次性 L 分辨率模式是指传感器仅进行一次测量,并使用低分辨率来获取环境光数据。在这种模式下,传感器只在接收到相应的指令后执行一次测量,然后返回结果。(地址引脚ADDR置低进入该模式)

在这里插入图片描述

数据手册中记录了三种模式,另外一种模式为连续模式2,连续模式2的分辨率为0.5lx要比连续模式1的分辨率更高一些

在这里插入图片描述

  • 二种模式的区别

一次性模式和连续模式是两种不同的工作模式,它们在传感器的数据采集和输出方式上有所区别。

  1. 一次性模式:在一次性模式下,传感器只在接收到相应的指令后执行一次测量,并将测量结果返回。这种模式适用于需要定期或按需获取环境光数据的应用。一次性模式通常使用低分辨率来进行测量,因此可以在较短的时间内完成测量。

  2. 连续模式:在连续模式下,传感器以一定的频率自动进行测量,并持续输出测量结果。这种模式适用于需要实时监测环境光强度变化的应用。连续模式通常使用高分辨率来进行测量,以提供更精确的环境光数据。

选择一次性模式还是连续模式取决于具体的应用需求。如果只需要偶尔获取环境光数据,而且对实时性要求不高,可以选择一次性模式以节省能量和资源。如果需要持续监测环境光强度的变化,并要求较高的精度和实时性,那么连续模式可能更适合。

  • 连续模式读取数据步骤(注意:地址为7位!,所以ST之后发生的数据为:地址+读/写)

由于在硬件设计上并没有连接ADDR引脚,所以我只能通过连续模式进行数据的读取,连续模式读取数据的步骤如下:

在这里插入图片描述

① 发送"连续 H 分辨率模式"指令
在这里插入图片描述

对于其他指令,数据手册也给出所有的指令集:

在这里插入图片描述

中文版本的解释如下:

在这里插入图片描述

② 等待完成第一次 H 分辨率模式的测量(最多 180 毫秒)。
③ 读取测量结果。
在这里插入图片描述

④ 数据

当数据的高字节为 “10000011”,低字节为 “10010000” 时,如何计算呢?计算为:( 215 + 29 + 28 + 27 + 24 ) / 1.2 ≒ 28067 [ lx ]

⑤ 间隔时间

在 H 分辨率模式下,典型更新时间为 120 毫秒;在 L 分辨率模式下,典型更新时间为 16 毫秒。

备注:连续模式和一次模式的通信步骤基本上差不多,只是相应的读取的时间,更新时间不一致和ADDR地址不相同。

  • I2C Bus Access

① 通信时序图

起始位为,SCL为高电平时,SDA为下降沿触发;停止位:SCL为低电平;SDA为上升沿

在这里插入图片描述

② 从机地址

地址有二种类型,分别对应的电压范围和对应的从机地址如下:
ADDR = ‘H’ ( ADDR ≧ 0.7VCC ) → “101 1100“
ADDR = ‘L’ ( ADDR ≦ 0.3VCC ) → “010 0011“
注意:地址宏定义的时候我们最好把读写位加上,默认读写位为0,当进行读写时将地址+读写位即可;例如ADDR='L’的宏定义地址可以设置为01000110 即0x46。当进行读操作时0x46 | 1=0x47;当进行写操作时,0x46 | 0 = 0x46.

③ 写模式(W:0)

BH1750FVI无法在没有停止条件的情况下接受多个命令。请在每个操作码之间插入停止条件(SP)

在这里插入图片描述

④ 读模式(R:1)

在这里插入图片描述

  • 通过数据手册中的连续模式步骤,I2C Bus Access并结合测试流程进行代码编写

在这里插入图片描述

根据测试图可知,初始化状态为Power Down,如果需要执行相应命令需要先将Power Down变为Power On,通过写入0x01命令即可,然后通过连续模式1读取数据就按到连续模式1的步骤进行数据的读取;在连续模式步骤中的ST和SP就是对应的起始标识和停止标识,通过I2C Bus Access中的时序图可知起始位和停止位;详细的代码如下:

  • bh1750.h
	#ifndef __BH1750_H
	#define __BH1750_H	 
	#include "sys.h"
	 
	//BH1750的地址
	#define BH1750_Addr			0x46  //ADDR = ‘L’ ( ADDR ≦ 0.3VCC ) → “010 0011 0“  第1位为读写位,默认为0
	
	//BH1750指令码
	#define POWER_OFF			0x00
	#define POWER_ON			0x01
	#define MODULE_RESET		0x07
	#define	CONTINUE_H_MODE		0x10
	#define CONTINUE_H_MODE2	0x11
	#define CONTINUE_L_MODE		0x13
	#define ONE_TIME_H_MODE		0x20
	#define ONE_TIME_H_MODE2	0x21
	#define ONE_TIME_L_MODE		0x23
	
	//测量模式
	#define Measure_Mode			CONTINUE_H_MODE
	
	//分辨率	光照强度(单位lx)=(High Byte  + Low Byte)/ 1.2 * 测量精度
	#if ((Measure_Mode==CONTINUE_H_MODE2)|(Measure_Mode==ONE_TIME_H_MODE2))
		#define Resolurtion		0.5
	#elif ((Measure_Mode==CONTINUE_H_MODE)|(Measure_Mode==ONE_TIME_H_MODE))
		#define Resolurtion		1
	#elif ((Measure_Mode==CONTINUE_L_MODE)|(Measure_Mode==ONE_TIME_L_MODE))
		#define Resolurtion		4
	#endif
	
	#define BH1750_I2C_WR	0		/* 写控制bit */
	#define BH1750_I2C_RD	1		/* 读控制bit */
	
	/* 定义I2C总线连接的GPIO端口, 只需要修改下面4行代码即可任意改变SCL和SDA的引脚 */
	#define BH1750_GPIO_PORT_I2C	GPIOB			/* GPIO端口 */
	#define BH1750_RCC_I2C_PORT 	RCC_APB2Periph_GPIOB		/* GPIO端口时钟 */
	#define BH1750_I2C_SCL_PIN		GPIO_Pin_6			/* 连接到SCL时钟线的GPIO */
	#define BH1750_I2C_SDA_PIN		GPIO_Pin_7			/* 连接到SDA数据线的GPIO */
	
	
	
	/* 定义读写SCL和SDA的宏,已增加代码的可移植性和可阅读性 */
	#if 0	/* 条件编译: 1 选择GPIO的库函数实现IO读写 */
		#define BH1750_I2C_SCL_1()  GPIO_SetBits(BH1750_GPIO_PORT_I2C, BH1750_I2C_SCL_PIN)		/* SCL = 1 */
		#define BH1750_I2C_SCL_0()  GPIO_ResetBits(BH1750_GPIO_PORT_I2C, BH1750_I2C_SCL_PIN)		/* SCL = 0 */
		
		#define BH1750_I2C_SDA_1()  GPIO_SetBits(BH1750_GPIO_PORT_I2C, BH1750_I2C_SDA_PIN)		/* SDA = 1 */
		#define BH1750_I2C_SDA_0()  GPIO_ResetBits(BH1750_GPIO_PORT_I2C, BH1750_I2C_SDA_PIN)		/* SDA = 0 */
		
		#define BH1750_I2C_SDA_READ()  GPIO_ReadInputDataBit(BH1750_GPIO_PORT_I2C, BH1750_I2C_SDA_PIN)	/* 读SDA口线状态 */
	#else	/* 这个分支选择直接寄存器操作实现IO读写 */
	    /* 注意:如下写法,在IAR最高级别优化时,会被编译器错误优化 */
		#define BH1750_I2C_SCL_1()  BH1750_GPIO_PORT_I2C->BSRR = BH1750_I2C_SCL_PIN				/* SCL = 1 */
		#define BH1750_I2C_SCL_0()  BH1750_GPIO_PORT_I2C->BRR = BH1750_I2C_SCL_PIN				/* SCL = 0 */
		
		#define BH1750_I2C_SDA_1()  BH1750_GPIO_PORT_I2C->BSRR = BH1750_I2C_SDA_PIN				/* SDA = 1 */
		#define BH1750_I2C_SDA_0()  BH1750_GPIO_PORT_I2C->BRR = BH1750_I2C_SDA_PIN				/* SDA = 0 */
		
		#define BH1750_I2C_SDA_READ()  ((BH1750_GPIO_PORT_I2C->IDR & BH1750_I2C_SDA_PIN) != 0)	/* 读SDA口线状态 */
	#endif
	
	
	void i2c_Start(void);
	void i2c_Stop(void);
	void i2c_SendByte(uint8_t _ucByte);
	uint8_t i2c_ReadByte(void);
	uint8_t i2c_WaitAck(void);
	void i2c_Ack(void);
	void i2c_NAck(void);
	uint8_t i2c_CheckDevice(uint8_t _Address);
	
	void BH1750_Init(void);			//未包含IIC初始化
	float LIght_Intensity(void);	//读取光照强度的值
	uint8_t BH1750_Byte_Write(uint8_t data);
	uint16_t BH1750_Read_Measure(void);
	void BH1750_Power_ON(void);
	void BH1750_Power_OFF(void);
	void BH1750_RESET(void);
			 				    
	#endif



  • bh1750.c
#include "bh1750.h"
#include "sys.h"
/*
	应用说明:
	在访问I2C设备前,请先调用 i2c_CheckDevice() 检测I2C设备是否正常,该函数会配置GPIO
*/


static void I2C_BH1750_GPIOConfig(void);


/*
*********************************************************************************************************
*	函 数 名: i2c_Delay
*	功能说明: I2C总线位延迟,最快400KHz
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
static void i2c_Delay(void)
{
	uint8_t i;

	/* 
	 	下面的时间是通过逻辑分析仪测试得到的。
    工作条件:CPU主频72MHz ,MDK编译环境,1级优化
  
		循环次数为10时,SCL频率 = 205KHz 
		循环次数为7时,SCL频率 = 347KHz, SCL高电平时间1.5us,SCL低电平时间2.87us 
	 	循环次数为5时,SCL频率 = 421KHz, SCL高电平时间1.25us,SCL低电平时间2.375us 
	*/
	for (i = 0; i < 10; i++);
}

/*
*********************************************************************************************************
*	函 数 名: i2c_Start
*	功能说明: CPU发起I2C总线启动信号
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void i2c_Start(void)
{
	/* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */
	BH1750_I2C_SDA_1();
	BH1750_I2C_SCL_1();
	i2c_Delay();
	BH1750_I2C_SDA_0();
	i2c_Delay();
	BH1750_I2C_SCL_0();
	i2c_Delay();
}

/*
*********************************************************************************************************
*	函 数 名: i2c_Start
*	功能说明: CPU发起I2C总线停止信号
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void i2c_Stop(void)
{
	/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
	BH1750_I2C_SDA_0();
	BH1750_I2C_SCL_1();
	i2c_Delay();
	BH1750_I2C_SDA_1();
}

/*
*********************************************************************************************************
*	函 数 名: i2c_SendByte
*	功能说明: CPU向I2C总线设备发送8bit数据
*	形    参:_ucByte : 等待发送的字节
*	返 回 值: 无
*********************************************************************************************************
*/
void i2c_SendByte(uint8_t _ucByte)
{
	uint8_t i;

	/* 先发送字节的高位bit7 */
	for (i = 0; i < 8; i++)
	{		
		if (_ucByte & 0x80)
		{
			BH1750_I2C_SDA_1();
		}
		else
		{
			BH1750_I2C_SDA_0();
		}
		i2c_Delay();
		BH1750_I2C_SCL_1();
		i2c_Delay();	
		BH1750_I2C_SCL_0();
		if (i == 7)
		{
			 BH1750_I2C_SDA_1(); // 释放总线
		}
		_ucByte <<= 1;	/* 左移一个bit */
		i2c_Delay();
	}
}

/*
*********************************************************************************************************
*	函 数 名: i2c_ReadByte
*	功能说明: CPU从I2C总线设备读取8bit数据
*	形    参:无
*	返 回 值: 读到的数据
*********************************************************************************************************
*/
uint8_t i2c_ReadByte(void)
{
	uint8_t i;
	uint8_t value;

	/* 读到第1个bit为数据的bit7 */
	value = 0;
	for (i = 0; i < 8; i++)
	{
		value <<= 1;
		BH1750_I2C_SCL_1();
		i2c_Delay();
		if (BH1750_I2C_SDA_READ())
		{
			value++;
		}
		BH1750_I2C_SCL_0();
		i2c_Delay();
	}
	return value;
}

/*
*********************************************************************************************************
*	函 数 名: i2c_WaitAck
*	功能说明: CPU产生一个时钟,并读取器件的ACK应答信号
*	形    参:无
*	返 回 值: 返回0表示正确应答,1表示无器件响应
*********************************************************************************************************
*/
uint8_t i2c_WaitAck(void)
{
	uint8_t re;

	BH1750_I2C_SDA_1();	/* CPU释放SDA总线 */
	i2c_Delay();
	BH1750_I2C_SCL_1();	/* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
	i2c_Delay();
	if (BH1750_I2C_SDA_READ())	/* CPU读取SDA口线状态 */
		re = 1;
	else
		re = 0;
	BH1750_I2C_SCL_0();
	i2c_Delay();
	return re;
}

/*
*********************************************************************************************************
*	函 数 名: i2c_Ack
*	功能说明: CPU产生一个ACK信号
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void i2c_Ack(void)
{
	BH1750_I2C_SDA_0();	/* CPU驱动SDA = 0 */
	i2c_Delay();
	BH1750_I2C_SCL_1();	/* CPU产生1个时钟 */
	i2c_Delay();
	BH1750_I2C_SCL_0();
	i2c_Delay();
	BH1750_I2C_SDA_1();	/* CPU释放SDA总线 */
}

/*
*********************************************************************************************************
*	函 数 名: i2c_NAck
*	功能说明: CPU产生1个NACK信号
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void i2c_NAck(void)
{
	BH1750_I2C_SDA_1();	/* CPU驱动SDA = 1 */
	i2c_Delay();
	BH1750_I2C_SCL_1();	/* CPU产生1个时钟 */
	i2c_Delay();
	BH1750_I2C_SCL_0();
	i2c_Delay();	
}

/*
*********************************************************************************************************
*	函 数 名: I2C_BH1750_GPIOConfig
*	功能说明: 配置I2C总线的GPIO,采用模拟IO的方式实现
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
static void I2C_BH1750_GPIOConfig(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(BH1750_RCC_I2C_PORT, ENABLE);	/* 打开GPIO时钟 */

	GPIO_InitStructure.GPIO_Pin = BH1750_I2C_SCL_PIN | BH1750_I2C_SDA_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;  	/* 开漏输出 */
	GPIO_Init(BH1750_GPIO_PORT_I2C, &GPIO_InitStructure);

	/* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 */
	i2c_Stop();
}

/*
*********************************************************************************************************
*	函 数 名: i2c_CheckDevice
*	功能说明: 检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在
*	形    参:_Address:设备的I2C总线地址
*	返 回 值: 返回值 0 表示正确, 返回1表示未探测到
*********************************************************************************************************
*/
uint8_t i2c_CheckDevice(uint8_t _Address)
{
	uint8_t ucAck;
	i2c_Start();		/* 发送启动信号 */
	/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
	i2c_SendByte(_Address | BH1750_I2C_WR);
	ucAck = i2c_WaitAck();	/* 检测设备的ACK应答 */

	i2c_Stop();			/* 发送停止信号 */

	return ucAck;
}

//BH1750写一个字节
//返回值	成功:0		失败:非0 
uint8_t BH1750_Byte_Write(uint8_t data)
{
	i2c_Start();
	//发送写地址
	i2c_SendByte(BH1750_Addr|0);
	if(i2c_WaitAck()==1)
		return 1;
	//发送控制命令
	i2c_SendByte(data);
	if(i2c_WaitAck()==1)
		return 2;
	i2c_Stop();
	return 0;
}

//BH1750读取测量数据
//返回值 成功:返回光照强度 	失败:返回0
uint16_t BH1750_Read_Measure(void)
{
	uint16_t receive_data=0; 
	i2c_Start();
	//发送读地址
	i2c_SendByte(BH1750_Addr|1);
	if(i2c_WaitAck()==1)
		return 0;
	//读取高八位
	receive_data=i2c_ReadByte();
	i2c_Ack();
	//读取低八位
	receive_data=(receive_data<<8)+i2c_ReadByte();
	i2c_NAck();
	i2c_Stop();
	return receive_data;	//返回读取到的数据
}


//BH1750s上电
void BH1750_Power_ON(void)
{
	BH1750_Byte_Write(POWER_ON);
}

//BH1750s断电
void BH1750_Power_OFF(void)
{
	BH1750_Byte_Write(POWER_OFF);
}

//BH1750复位	仅在上电时有效
void BH1750_RESET(void)
{
	BH1750_Byte_Write(MODULE_RESET);
}

//BH1750初始化
void BH1750_Init(void)
{
	I2C_BH1750_GPIOConfig();		/* 配置GPIO */
	
	BH1750_Power_ON();	//BH1750s上电
	//BH1750_RESET();			//BH1750复位
	BH1750_Byte_Write(Measure_Mode);
	//SysTick_Delay_ms(120);
}

//获取光照强度
float LIght_Intensity(void)
{
	return (float)(BH1750_Read_Measure()/1.1f*Resolurtion);
}


  • main.c
float Light = 0; //光照度
 int main(void){
BH1750_Init();          //初始化BH1750
while(1)
{

	// 延时
	delay_ms(100);
 	if (!i2c_CheckDevice(BH1750_Addr))
	{
		Light = LIght_Intensity();             
	}	

}
return 0;
}
  • 5
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嘟嘟的程序员铲屎官

你的鼓励将是我最大的动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值