stm32的光强度测量-I2C与TSL2561

1,336 阅读18分钟

文章参考:基于STM32f407的TSL2561模块的使用TSL2561 STM32驱动程序STM32系统学习——I2C (读写EEPROM)STM32的I2C框图详解及通讯过程I2C总线读写I2C协议小结I2C总线之(一)---概述I2C总线之(二)---时序I2C总线之(三)---以C语言理解IICIIC 时序分析,stm32软件模拟驱动编写

硬件:STM32F107VC( Cortex-M3)

软件:Keil μVision4,JLINK

一、TSL256x

1. TSL256x简介

       TSL256x是TAOS公司推出的一种高速、低功耗、宽量程、可编程灵活配置的光强度数字转换芯片。该芯片可广泛应用于各类显示屏的监控,目的是在多变的光照条件下,使得显示屏提供最佳的显示亮度并尽可能降低电源功耗,还可以用于街道光照控制、安全照明等众多场合。

        该芯片有两种不同的封装但是都是六引脚。封装图和六个引脚的含义如下:

                 

           

       ADDR引脚连接不同可选择不同的器件地址,有接地,接Vdd,以及浮空三种接法。器件访问地址与引脚2电平的对应关系如下:

           

2. TSL256x的内部结构和工作原理

         TSL256x是第二代周围环境光强度传感器,其内部结构如下图所示。

                     

        通道0和通道1是两个光敏二极管,其中通道0对可见光和红外线都敏感,而通道1仅对红外线敏感。积分式A/D转换器对流过光敏二极管的电流进行积分,并转换为数字量,在转换结束后将转换结果存入芯片内部通道0和通道1各自的寄存器中。当一个积分周期完成之后,积分式A/D转换器将自动开始下一个积分转换过程。微控制器和TSL2560可通过标准的SMBus(System Management Bus) V1.1或V2.0实现,TSL2561则可通过I2C总线协议访问。

       对TSL256x的控制是通过对其内部的16个寄存器的读写来实现的,TSL2561内部寄存器地址及作用如下表:

                

2.1 命令寄存器(COMMAND Register)

        在MaskWord.h里定义了相关的command register。

/*******************************************************
About COMMAND Register
*********************************************************/
#define	IntClearClrMask	0xBF
#define	AddressClrMask	0xF0

      command寄存器主要用来设置要配置的寄存器地址,其中高四位为控制位,低四位就是寄存器地址。

2.2 控制寄存器(Control Register)

        在MaskWord.h里定义了相关的Control Register。

/********************************************************
About Control Register
*********************************************************/
#define	PowerUp		0x03
#define	PowerDown	0x00

    Control寄存器主要用来控制模块的上电和断电。其值为0x03时上电;为0x00时为断电。

2.3 时间寄存器(Timing Register)

      在MaskWord.h里定义了相关的Timing Register。

/*********************************************************
About Timing Register
*********************************************************/
#define	GainClrMask	0xEF
#define	GainSetLow	0x00
#define	GainSetHigh	0x10
#define	ManualMask	0xF7
#define	ManualSet	0x08
#define	ManualClr	0x00
#define IntegMask	0xFC
//最后两位设置积分时间,NOMINAL INTEGRATION TIME 13.7ms 101ms 402ms#define IntegScal0034	0x00	//积分时间13.7ms
#define	IntegScal0252	0x01    //积分时间101ms
#define	IntegScal1000	0x02    //积分时间402ms

    TIMING寄存器主要用于选择增益和积分时间。

2.4 中断控制寄存器(Interrupt Control Register)

/*About Interrupt Control Register*/
#define	IntrMask	0xCF
#define	IntrDisabled	0x00
#define	IntrLevelInt	0x10
#define	IntrSMBAlert	0x20
#define	IntrTestMode	0x30

#define	PersistMask	0xF0
#define	EveryADCCycle	0x00
#define	AnyOutsideValue	0x01
#define	TowPeriods	0x02
#define	ThreePeriods	0x03
#define	FourPeriods	0x04
#define	FivePeriods	0x05
#define	SixePeriodS	0x06
#define	SevenPeriods	0x07
#define	EightPeriods	0x08
#define	NinePeriods	0x09
#define	TenPeriods	0x0A
#define	ElevenPeriods	0x0B
#define	TwelvePeriods   0x0C
#define	ThirteenPeriods	0x0D
#define	FourteenPeriods	0x0E
#define	FifteenPeriods	0x0F

2.5   ID寄存器(ID Register)

/*About ID Register*/
#define PARTNOMask	0x0F
#define REVNOMask	0xF0

3. TSL256x应用设计

       TSL256x的访问遵循标准的SMBus和I2C协议。例如,TSL2561能够通过I2C总线访问,所以硬件接口电路很简单。假如所选用的微控制器带有I2C总线控制器,则将该总线的时钟线和数据线直接和TSL2561的I2C总线的SCL和SDA分别相连;假如微控制器内部没有上拉电阻,则还需要再用2个上拉电阻接到总线上。假如微控制器不带I2C总线控制器,则将TSL2561的I2C总线的SCL和SDA和普通I/O口连接即可;但编程时需要模拟I2C总线的时序来访问TSL2561,INT引脚接微控制器的外部中断。芯片接口如图:

               

        微控制器可以通过I2C总线协议对TSL2561进行读写。写数据时,先发送器件地址,然后发送要写的数据。TSL2561的写过程如下:先发送一组器件地址;然后写命令码,命令码是指接下来写寄存器的地址00h-0fh和写寄存器的方式,是以字节、字或块为大单位进行写操作;最后发送要写的数据,根据命令码规定写寄存器的方式,连续发送要写的数据,内部写寄存器会自动加1。流程如下:

                                           

        当积分式A/D转换器转换完成后,能够从通道0寄存器和通道1寄存器读取相应的值CH0和CH1,但是要以Lux(流明)为单位,还要根据CH0和CH1进行计算。对于CHIPSCALE封装,计算公式能够查看相应的芯片资料。对于TMB封装,假设光强为E(单位为Lux),则计算公式如下:

  • 0 < CH1/CH0 ≤ 0.5 E=0.0304×CH0 - 0.062×CH0 ×(CH1/CH0)/4
  • 0.5< CH1/CH0 ≤ 0.61 E=0.0224×CH0 - 0.031×CH1
  • 0.61 < CH1/CH0 ≤ 0.8 E=0.0128×CH0 - 0.0153×CH1
  • 0.8 < CH1/CH0 ≤ 1.3 E=0.00146×CH0 - 0.00112×CH1
  • 1.3 < CH1/CH0 E=0

对应的代码calclux.c。

二、I2C概述

1. I2C简介

       I2C(芯片间)总线接口连接微控制器和串行I2C总线。它提供多主机功能,控制所有I2C总线特定的时序、协议、仲裁和定时。支持标准和快速两种模式,同时与SMBus 2.0兼容。I2C总线采用了器件地址的硬件设置方法,通过软件寻址完全避免了器件的片选线寻址方法,从而使硬件系统具有最简单而灵活的扩展方法。 I2C接口的标准传输速率为100Kbit/s,快速传输可400Kbit/s,目前还增加了高速模式,最高传输速率可达3.4Mbit/s。

       I2C模块有多种用途,包括CRC码的生成和校验、SMBus(系统管理总线—System ManagementBus)和PMBus(电源管理总线—Power Management Bus)。

       I2C用于连接集成电路和功能模块,在它们之间交互数据或控制信息。很多设备如:键盘和LED控制器,以及存储设备EEPROM和FLASH都配备了I2C 总线接口。根据特定设备的需要,可以使用DMA以减轻CPU的负担。

       I2C模块接收和发送数据,并将数据从串行转换成并行,或并行转换成串行。可以开启或禁止中断。接口通过数据引脚(SDA)和时钟引脚(SCL)连接到I2C总线。允许连接到标准(高达100kHz)或快速(高达400kHz)的I2C总线。

2. I2C的输出级

        I²C 只是用两条双向的线,一条 Serial Data Line (SDA) ,另一条Serial Clock (SCL)。

       SCL:上升沿将数据输入到每个EEPROM器件中;下降沿驱动EEPROM器件输出数据。(边沿触发)

       SDA:双向数据线,为OD门,与其它任意数量的OD与OC门成"线与"关系。

       每一个I2C总线器件内部的SDA、SCL引脚电路结构都是一样的,引脚的输出驱动与输入缓冲连在一起。其中输出为漏极开路的场效应管,输入缓冲为一只高输入阻抗的同相器,这种电路具有两个特点:

       1)由于SDA、SCL为漏极开路结构(OD),因此它们必须接有上拉电阻,阻值的大小常为 1k8, 4k7 和10k ,但1k8 时性能最好;当总线空闲时,两根线均为高电平。连到总线上的任一器件输出的低电平,都将使总线的信号变低,即各器件的SDA及SCL都是线"与"关系。

       2)引脚在输出信号的同时还将引脚上的电平进行检测,检测是否与刚才输出一致,为"时钟同步"和"总线仲裁"提供了硬件基础。

3. 主设备与从设备、发送器和接收器

       系统中的所有外围器件都具有一个7位的"从器件专用地址码",其中高4位为器件类型,由生产厂家制定,低3位为器件引脚定义地址,由使用者定义。主控器件通过地址码建立多机通信的机制,因此I2C总线省去了外围器件的片选线,这样无论总线上挂接多少个器件,其系统仍然为简约的二线结构。终端挂载在总线上,有主端和从端之分,主端必须是带有CPU的逻辑模块,在同一总线上同一时刻使能有一个主端,可以有多个从端,从端的数量受地址空间和总线的最大电容 400pF的限制。

  • 主端主要用来驱动SCL line;产生时钟,产生起始和停止信号。
  • 从设备(从机)对主设备(主机)产生响应;

        二者都可以传输数据,但是从设备(从机)不能发起传输,且传输是受到主设备(主机)控制。

       发送器是本次传输中发送数据到总线的器件;接收器是本次传输中从总线接收数据的器件。主设备(主机)通常是微控制器,从设备(从机)是被主机寻址的器件,可以是发送器或接收器。所以接口运行的模式有四种:从发送器模式、从接收器模式、主发送器模式、主接收器模式。

4. "时钟同步"和"总线仲裁"

       时钟同步:

       仲裁过程中,实际由于电气特性,已经对各个不同主设备的时钟进行同步处理,所以各个设备使用的时钟都是一样的、同步后的SCL。同步规则就是:SCL线上时钟低电平时间由各器件中时钟最长的低电平时间决定,而时钟高电平时间则由高电平时间最短的器件决定。总的原则是:低电平否决高电平。

总线仲裁:

        当两个设备同时发出起始位进行数据传输时,相互竞争的设备使它们的时钟保持同步。没有检测到冲突之前,每个设备都认为只有自己在使用总线。每个设备都有仲裁功能,它们都去检测SDA线,检测到自己发送的数据和总线上数据不匹配的设备就失去仲裁能力。 

       如果两个或更多的设备发送的第一个字节的内容相同,那么仲裁就发生在随后传输中。失去仲裁的主设备转变成从接收模式。 

       注意:数据冲突是因为逻辑低电平可以把逻辑高电平否决。在同一时刻如果两设备一个发高电平一个发低电平,那么总线上是低电平。 

5. I2C函数

三、I2C总线的接口时序和代码

       STM32 芯片有多个I2C 外设,它们的I2C 通讯信号引出到不同的GPIO 引脚上,使用时必须配置到这些指定的引脚,使用的硬件:STM32F107VC( Cortex-M3),则引脚如表。

       I2C 的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。

1.  起始和停止

      在数据传送过程中,必须确认数据传输的开始和结束,这通过起始和结束信号识别。

      起始位:SCL=1时,SDA上有下降沿

       停止位:SCL=1时,SDA上有上升沿

       所以只有当SCL为低时,才允许SDA改变数据,不然会被误判为停止位而造成错误。

1.1 I2C_Trans_Start

void I2C_Trans_Start(void){
	TSL2561_DAT_OUT;   //PB6推挽输出, PB7推挽输出,即SCL、SDA设置为输出
	TSL2561_DAT_H;     //PB7推挽输出,PB7=1,即SDA置高电平
	TSL2561Delay1();   //延迟	
	TSL2561_CLK_H;	   //PB6 = 1,即SCL置高电平
	Delay(8); //本为5...//1.3us; //t(BUF),at least 1.3us needed
	TSL2561_DAT_L;     //PB7推挽输出,PB7=0,即SDA置低电平
	Delay(5); //本为3...//0.827us; //t(HDSTA),at least 0.6us needed
	TSL2561_CLK_L;     //PB6 = 0,即SCL置低电平
                           //拉低SCL,因为只有在SCL为低电平期间,SDA上的高低电平状态才允许变化
	Delay(8); //t(Low),at least 1.3us needed;
}   

起始位:SCL(PB6)=1时,SDA(PB7)上有下降沿

1.2  I2C_Trans_Stop

void I2C_Trans_Stop(void){
	 TSL2561_DAT_OUT;  //PB6,PB7推挽输出,即SCLSDA设置为输出
	 TSL2561_CLK_H;   //PB6=1,即SCL置高电平
	 Delay(8);//5...//t(SUSTO), at least 0.6us needed	////////////////////////
	 TSL2561_DAT_H;  //PB7=1,即SDA置高电平,拉高SDA实现终止信号
} 

I2C_Trans_Stop函数调用之前,PB6=PB7=0,即SCL、SDA都是低电平。

停止位:SCL(PB6)=1时,SDA(PB7)上有上升沿。

2. 数据有效性

      I2C使用 SDA信号线来传输数据,使用 SCL信号线进行数据同步。SDA数据线在 SCL的每个时钟周期传输一位数据。I2C读写每一位数据都是在SCL为高时完成,读写每一位数据的时候SCL都有一个高脉冲的过程,也就是SCL为高时读写SDA的值,这意味着在SCL拉高之前,SDA必须为一稳定值,不然读写就不准确 。只有当SCL为低时,才允许SDA改变数据,不然会被误判为停止位而造成错误。

3.  响应

    I2C 的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。

3.1 主机写完数据等待从机应答

       数据传送格式:每一个字节必须保证是8位长度。数据传送时,先传高位,每个被传送的字节后面必须跟随一位应答位(ACK)。可以向从机传输 N 个数据,这个 N 没有大小限制。当数据传输结束时,主机向从机发送一个停止传输信号(P),表示不再传输数据。

      I2C总线数据传送时,每传送一个字节数据后都必须有应答信号(ACK)。 I2C因为有两种寻址模式,所以它具体的格式有两种,这里主要按分类部分来描述数据格式。7位寻址模式如下:

    10位寻址模式如下:

       主控器接收数据时,如果要结束通信时,将在停止位之前发送非应答信号。回应ACK是个低电平脉冲,并且这是由从器件的硬件来完成的,不需要主器件来软件操作,只需要等待。

      在主机向从机传送每发送8个BIT数据时,应答位由从机产生,主机检测从机产生的应答信号。

   //功能:接收从机应答信号,读取SDA,看是否有应答信号ACK(负脉冲)

     TSL2561_DAT_IN;   //PB6推挽输出,PB7浮空输入,即SCL设置为输出,SDA设置为输入	TSL2561_CLK_H;	 //PB6=1,即SCL置高电平
	Delay(8);//5...//0.828us;//t(HIGH), at least 0.6us needed
	
        //检测从机是否产生负脉冲
        if(TSL2561_DATA==1/*c*/)
        {
		c=1;//非应答信号
	}
	else {
		c=0; //应答信号,正常情况下
	}	 
	
	TSL2561_CLK_L;  //PB6=0,即SCL置低电平
	Delay(11);//8...//t(LOW), at least 1.3us needed
	if(c) return 0;  //no ack, failed
	return 1;//success!	

3.2  主机读完数据向从机发送应答信号

       主机读数据时有这样的规定:当主机每读一个数据,主机都要回复应答信号。当最后一个字节数据读完后,主机要返回以“非应答”信号,并终止信号读出操作,这里的应答信号和非应答信号作用是给从机判断是否继续读取数据。由下图可以看出应答信号是在SDA低电平期间,SCL由低置高再置低。非应答信号是在SDA位高电平期间,SCL由低置高再置低。

//主机读完数据向从机发送应答信号
void I2C_Ack_L(void){
	 TSL2561_DAT_OUT;//PB6推挽输出, PB7推挽输出,即SCL、SDA设置为输出
	 TSL2561_DAT_L;  //PB7=0,即SDA置低电平
	 TSL2561_CLK_H; //PB6=1,即SCL置高电平
	 Delay(8);	 //5...
	 TSL2561_CLK_L;//PB6 = 0,即SCL置低电平
	 Delay(11);//8...
}

//主机读完数据向从机发送非应答信号
void I2C_Ack_H(void){
	 TSL2561_DAT_OUT;//PB6推挽输出, PB7推挽输出,即SCL、SDA设置为输出
	 TSL2561_DAT_H;	 //PB7=1,即SDA置高电平
	 TSL2561_CLK_H; //PB6=1,即SCL置高电平
	 Delay(8);//5...
	 TSL2561_CLK_L;//PB6 = 0,即SCL置低电平
	 Delay(11);//8...
}

4. 数据读写

       主机发送起始位,这会通知总线上的所有设备传输开始了,接下来主机发送设备地址(从机地址,SLAVE_ADDRESS,7位或10位)。在 I2C 总线上,每个设备的地址都是唯一的,当主机广播的地址与某个设备地址相同时,这个设备就被选中了,没被选中的设备将会忽略之后的数据信号。与这一地址匹配的设备slave将继续这一传输过程,而其它设备slave将会忽略接下来的传输并等待下一次传输的开始。

       在从机地址位之后,是传输方向的选择位(R/W)。该位为 0 时,表示后面的数据传输方向是由主机传输至从机,即主机向从机写数据。该位为 1时,则相反。

       从机接收到匹配的地址后,主机或从机会返回一个应答(ACK)或非应答(NACK)信号,只有接收到应答信号后,主机才能继续发送或接收数据。

       主机寻址到从机后,发送它所要读取或写入的从机的内部寄存器地址; 之后,发送数据。数据发送完毕后,发送停止位

4.1  主机向从机发送数据

SDA线

SDA+SCL

过程如下

  • 发送起始位
  • 发送从机的地址和读/写选择位;释放总线,等到从机拉低总线进行应答;如果从机接收成功,则进行应答;若没有握手成功或者发送的数据错误时从机不产生应答,此时要求重发或者终止。
  • 主机发送想要写入的内部寄存器地址;从机对其发出应答;
  • 发送数据
  • 发送停止位

对应的代码如下:

// Address是从机地址;CommandCode是Command寄存器,包含寄存器地址;DataNumToBeSend是要发送的数据个数,DataBufferTx是要发送的数据
int I2C_SendData_TSL2561(unsigned char Address, unsigned char CommandCode, int DataNumToBeSend, unsigned char DataBufferTx[]){
	int i=0;
        //发送起始位
	I2C_Trans_Start();

        //发送从机的地址和读/写选择位
	if(!I2C_Write_Byte(Address<<1))
		return 0;//No ACK

        //发送想要写入的内部寄存器地址,CommandCode高四位为控制位,低四位为要写入数据的地址,即寄存器地址
	if(!I2C_Write_Byte(CommandCode))
		return 0;

	//发送数据
	while(i<DataNumToBeSend){		
		if(!I2C_Write_Byte(DataBufferTx[i++]))
			return 0;
	}
        //发送停止位
	I2C_Trans_Stop();

	return 1;
}

4.1.1 Address是从机地址

       从给的数据手册可以看出有一个引脚ADDR SEL用来设置芯片地址,当引脚接地时从机地址为0x29,浮空时从机地址0x39,接高电平时为0x49。

在Address.h里定义了从机地址。

#define	SlaveAddressGND	0x29
#define	SlaveAddressFloat 0x39
#define	SlaveAddressVDD	0x49

      在TSL2561PowerUp()函数里,AddressSlaveAddressVDD,定义是#define SlaveAddressVDD 0x49。用Address<<1表示了7位的从机地址7'b1001001和一位的读写选择位1'b0(写操作)。

4.1.2 CommandCode是Command寄存器

       command寄存器主要用来设置要配置的寄存器地址,其中高四位为控制位,低四位就是寄存器地址。

    每个寄存器地址和几个常用的寄存器地址宏定义如下。

    在Address.h里定义了寄存器地址。

/********************************************************************************
Register Address Define
*********************************************************************************/
/*#define	COMMAND     */
#define	CtrlRegAdd	0x00
#define	TimingRegAdd	0x01
#define	ThreshLowLowRegAdd	0x02		
#define	ThreshLowHighRegAdd	0x03
#define	ThreshHighLowRegAdd	0x04
#define	ThreshHighHighRegAdd	0x05
#define	InterruptRegAdd 	0x06
/*#define		0x07*/
#define	CRCRegAdd		0x08
/*#define		0x09*/
#define	IDRegAdd		    0x0A
#define BlockWriteAdd		0x0B
#define	Data0LowRegAdd		0x0C
#define	Data0HighRegAdd 	0x0D
#define	Data1LowRegAdd		0x0E
#define	Data1HighRegAdd 	0x0F

      在TSL2561PowerUp()函数里,CommandCodePowerUpCode,定义是#define PowerUpCode 0x80|CtrlRegAdd。控制位是0x8,表示选择Command寄存器,寄存器地址是#define CtrlRegAdd 0x00,表示是控制寄存器,控制芯片是否工作。

4.1.3 写数据

int I2C_Write_Byte(unsigned char b){
	int i=0;
	unsigned char c=0;
	TSL2561_DAT_OUT;      //PB6,PB7推挽输出,即SCL、SDA设置为输出

	for(i=0;i<8;i++){
		if(b&0x80)  //先传高位再传低位
                {
			TSL2561_DAT_H; //PB7=1,即SDA置高电平
		}
		else {
			TSL2561_DAT_L;//PB7=0,即SDA置低电平		}
		
		b=b<<1;
		TSL2561_CLK_H;   //PB6 = 1,即SCL置高电平,拉高SCL,此时SDA上的数据稳定
                Delay(8);//5...//t(HIGH), at least 0.6us needed
		TSL2561_CLK_L;    //PB6 = 0,即SCL置低电平
                                  //拉低SCL,SDA上的数据才允许变化,为下次数据传输做好准备 
		Delay(11);//8...//1.6us//t(LOW), at least 1.3us needed				
	}
       

//功能:接收从机应答信号,读取SDA,看是否有应答信号ACK(负脉冲)

      TSL2561_DAT_IN;   //PB6推挽输出,PB7浮空输入,即SCL设置为输出,SDA设置为输入
      TSL2561_CLK_H;	 //PB6=1,即SCL置高电平
      Delay(8);//5...//0.828us;//t(HIGH), at least 0.6us needed
	
        //检测从机是否产生负脉冲
        if(TSL2561_DATA==1/*c*/)  //读PB7即SDA的值
        {
		c=1;//非应答信号
	}
	else {
		c=0; //应答信号,正常情况下
	}	 
	
	TSL2561_CLK_L;  //PB6=0,即SCL置低电平
	Delay(11);//8...//t(LOW), at least 1.3us needed
	if(c) return 0;  //no ack, failed
	return 1;//success!			
}

4.2  主机从从机读取数据

   

     读的过程比较复杂,在从slave读出数据前,你必须先要告诉它哪个内部寄存器是你想要读取的,因此必须先对其进行写入(dummy write)。

过程如下:

  • 发送起始位; 
  • 发送slave地址+write bit set; 
  • 发送内部寄存器地址; 
  • 重新发送起始位,即restart; 
  • 重新发送slave地址+read bit set; 
  • 读取数据
  • 主机接收器在接收到最后一个字节后,也不会发出ACK信号。于是,从机发送器释放SDA线,以允许主机发出P信号结束传输。
  •  发送停止位    

对应的代码如下:

// Address是从机地址;CommandCode是Command寄存器,包含寄存器地址;DataNumToBeSend是要接收的数据个数,DataBufferTx是要接收的数据
int I2C_ReceivData(unsigned char Address, unsigned char CommandCode, int DataNumReceived, unsigned char DataBufferRx[]){
	int i=0;

	I2C_Trans_Start();  //发送起始位
	if(!I2C_Write_Byte(Address<<1)) // 发送从机地址+写操作
		return 0;//No ACK

	if(!I2C_Write_Byte(CommandCode))//发送要读取的内部寄存器地址
		return 0;

	I2C_Trans_Start();  //重新发送起始位
	

	if(!I2C_Write_Byte((Address<<1)|0x01)) // 发送从机地址+读操作
		return 0;//No ACK
        //读取数据
	while(i<DataNumReceived){
		DataBufferRx[i++]=I2C_Read_Byte();
		I2C_Ack_L();        //主机读完数据向从机发送应答信号		}

	I2C_Trans_Stop();//发送停止位

	return 1;
}

4.2.1 Address和CommandCode

       AddressCommandCode的具体定义看5.1.1和5.1.2。

          这里的AddressSlaveAddressVDD,定义是#define SlaveAddressVDD 0x49

          用Address<<1表示了7位的从机地址7'b1001001和一位的读写选择位1'b0(写操作)。用(Address<<1)|0x01)表示了7位的从机地址7'b1001001和一位的读写选择位1'b1(读操作)。

       这里CommandCodePowerUpCode,定义是#define Data0LowRegCode 0xC0|Data0LowRegAdd //reading Data and Clear Interrupt。控制位是0xc,表示选择Command寄存器并清除中断,寄存器地址是#define Data0LowRegAdd 0x0C,表示是数据寄存器,指的是通道0低字节。

4.2.2 读数据

       将SDA的数据放入temp里,每读一次SDA,temp就左移一位,直至读到8位数据,输出temp。

unsigned char I2C_Read_Byte(void){
	int i=0;
	unsigned char temp=0;
	TSL2561_DAT_IN; //PB6推挽输出,PB7浮空输入,即SCL设置为输出,SDA设置为输入
	for(i=0;i<8;i++){
		temp=temp<<1;
		TSL2561_CLK_H;  //PB6 = 1,即SCL置高电平
               //SCL上升沿时,I2C设备将数据放在SDA线上,并在SCL高电平期间数据已经稳定,可以接收

		Delay(8);//5...

		//读PB7即SDA的值
              if(TSL2561_DATA==0x01){
			temp|=1;  //sda=1,temp|=sda
			}		
               TSL2561_CLK_L;   //PB6=0,即SCL置低电平,拉低SCL,使发送端可以把数据放在SDA上
  		Delay(11);//8...
	}

	return temp;
}

五、代码解析

    在tsl2561.h里定义了部分函数的寄存器操作。

1. TSL2561_CLK_H

#define TSL2561_CLK_H   GPIOB->BSRR = GPIO_Pin_6	//set PB6 = 1

GPIO_BSRR,高16位控制对应引脚输出低电平:写入1,对应引脚输出低电平,写入0,没有任何作用;低16位控制对应引脚输出高电平:写入1,对应引脚输出高电平;写入0,没有任何作用。

GPIOB->BSRR = GPIO_Pin_6,就是GPIOB->BSRR [6]=1,BS6=1,即PB6=1。

2. TSL2561_CLK_L

#define TSL2561_CLK_L   GPIOB->BRR = GPIO_Pin_6	//set PB6 = 0

GPIO_BRR,32位寄存器,其中高16位保留,低16位的每1位写入1,对应引脚输出低电平,写入0,没有任何作用。

GPIOB->BRR = GPIO_Pin_6,即GPIOB_BRR[6]=1,BR6=1,即PB6=0。

3. TSL2561_DAT_H

//PB7输出,将相关位置位。output, set PB7 =1
#define TSL2561_DAT_H   
GPIOB->CRL |= 0x30000000; //CNF7[1:0]=00,通用推挽输出模式;MODE7[1:0]=11:输出模式,最大速度50MHz,也就是PB7推挽输出,最大速度50MHz
GPIOB->BSRR = GPIO_Pin_7  //PB7	=1 

       GPIO_CRL用于控制GPIO的低8个引脚(Pin7-Pin0)。 其中MODE对应GPIO的最大输出速度,CNF对应GPIO的工作(配置)模式。

GPIOB->CRL |= 0x30000000; 最终结果GPIOB->CRL[31:28]=0x3,即MODE7[1:0]=11,输出模式,最大输出速度为50MHz,CNF7[1:0]=00,通用推挽输出模式,即PB7推挽输出,最大输出速度为50MHz。

GPIOB->BSRR = GPIO_Pin_7,就是GPIOB->BSRR [7]=1,BS7=1,即PB7=1。

4. TSL2561_DAT_L

//PB7输出,将相关的位复位。output, set PB7 =0
#define TSL2561_DAT_L   
GPIOB->CRL |= 0x30000000; //CNF7[1:]=00,通用推挽输出模式;MODE7[1:0]=11:输出模式,最大速度50MHz,也就是PB7推挽输出,最大速度50MHz
GPIOB->BRR = GPIO_Pin_7	//PB7 =0	

     GPIOB->BRR = GPIO_Pin_7,即GPIOB_BRR[7]=1,BR7=1,即PB7=0。

5. TSL2561_DATA

//read PB7 vlaue
#define TSL2561_DATA 
((GPIOB->IDR & GPIO_Pin_7) >> 7)	//读PB7的值		

       GPIO_IDR,32位寄存器,其中高16位保留,低16位的每1位用于存放对应引脚的电平状态, 0:对应引脚输入低电平, 1:对应引脚输入高电平。

((GPIOB->IDR & GPIO_Pin_7) >> 7)=IDR7,即读IDR7的值,为PB7的状态。

6. TSL2561_DAT_IN

//PB7 input, PB6 output
#define TSL2561_DAT_IN   
GPIOB->CRL |= 0x40000000;  
GPIOB->CRL &= 0x4FFFFFFF; //CNF7[1:]=01,浮空输入模式;MODE7[1:0]=00:输入模式,也就是PB7浮空输入
GPIOB->CRL |= 0x03000000; 
GPIOB->CRL &= 0xF3FFFFFF////CNF6[1:0]=00,通用推挽输出模式;MODE6[1:0]=11:输出模式,最大速度50MHz,也就是PB6推挽输出,最大速度50MHz	

  GPIOB->CRL |= 0x40000000; GPIOB->CRL &= 0x4FFFFFFF;最终结果GPIOB->CRL[31:28]=0x4,即MODE7[1:0]=00,输入模式,CNF7[1:0]=01,浮空输入模式,即PB7浮空输入。

     GPIOB->CRL |= 0x03000000; GPIOB->CRL &= 0xF3FFFFFF;最终结果GPIOB->CRL[27:24]=0x3,即MODE6[1:0]=11,输出模式,最大输出速度为50MHz,CNF6[1:0]=00,通用推挽输出模式,PB6推挽输出,最大输出速度为50MHz。

7. TSL2561_DAT_OUT

//PB7 output, PB6 output 
#define TSL2561_DAT_OUT  
GPIOB->CRL |= 0x30000000;  
GPIOB->CRL &= 0x3FFFFFFF; //CNF7[1:0]=00,通用推挽输出模式;MODE7[1:0]=11:输出模式,最大速度50MHz,也就是PB7推挽输出,最大速度50MHz
GPIOB->CRL |= 0x03000000; 
GPIOB->CRL &= 0xF3FFFFFF  //CNF6[1:0]=00,通用推挽输出模式;MODE6[1:0]=11:输出模式,最大速度50MHz,也就是PB6推挽输出,最大速度50MHz	

     GPIOB->CRL |= 0x30000000; GPIOB->CRL &= 0x3FFFFFFF;最终结果GPIOB->CRL[31:28]=0x3,即MODE7[1:0]=11,输出模式,最大输出速度为50MHz,CNF7[1:0]=00,通用推挽输出模式,即PB7推挽输出,最大输出速度为50MHz。

    GPIOB->CRL |= 0x03000000; GPIOB->CRL &= 0xF3FFFFFF;最终结果GPIOB->CRL[27:24]=0x3,即MODE6[1:0]=11,输出模式,最大输出速度为50MHz,CNF6[1:0]=00,通用推挽输出模式,PB6推挽输出,最大输出速度为50MHz。

8.  TSL2561Delay1 

__asm void TSL2561Delay1(){
    NOP;
    bx      lr;
}

     NOP,这个指令在汇编中的作用是空指令,意味着什么都不做的意思,一般用来控制CPU的时间周期,达到时钟延时的效果,NOP也就占用一个时钟周期。

     bx lr的作用等同于mov pc,lr,即跳转到lr中存放的地址处。lr就是连接寄存器(Link Register, LR),在ARM体系结构中LR的特殊用途有两种:一是用来保存子程序返回地址;二是当异常发生时,LR中保存的值等于异常发生时PC的值减4(或者减2),因此在各种异常模式下可以根据LR的值返回到异常发生前的相应位置继续执行。(见BX LR

9.  Delay()

void Delay(int Nus){
	while(Nus--){
		TSL2561Delay1();
	}
}

    Delay(Nus)括号里的参数Nus,表示延迟Nus个周期。

六、实验

6.1 程序说明

       该示例程序实现了如何通过IO口模拟串行数据时序,来读取光强度传感器的数值,读取到的数值被转换成流明值,显示在LCD屏幕上。

6.2 配置

      连接JP18的两个管脚,确保I2C_SCK跳线被短接;将CN15处的传感器换成光强度度传感器;查看LCD所显示的光强的大小,而且可以在不同的光强下查看数值的大小的变化。

6.3 流程图

/* Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"
#include "stm3210c_eval_lcd.h"
#include "stm32_eval.h"
#include "tsl2561.h"
#include <stdio.h>

/* Private variables ---------------------------------------------------------*/
ErrorStatus HSEStartUpStatus;   

/* Private function prototypes -----------------------------------------------*/
void RCC_Configuration(void);
void GPIO_Configuration(void);


int main(void)
{
  /* System clocks configuration ---------------------------------------------*/
  RCC_Configuration();

  /* GPIO configuration ------------------------------------------------------*/
  GPIO_Configuration();

  STM3210C_LCD_Init();
  LCD_Clear(White);
  /* Set the LCD Text Color */
  LCD_SetTextColor(Black);
  printf("   STM3210C-EVAL    \n");
  printf("    Light  sensor   \n");
  	
  TSL2561PowerUp();
  TSL2561SetTimmingReg(GainSetHigh, ManualClr,IntegScal1000);
  TSL2561SetInterruptCtrlReg(IntrLevelInt, EveryADCCycle);
  
  while (1)
  {
    TSL2561ReadINT2();
  }
}

void RCC_Configuration(void)
{
    /* RCC system reset(for debug purpose) */
  RCC_DeInit();

  /* Enable HSE */
  RCC_HSEConfig(RCC_HSE_ON);

  /* Wait till HSE is ready */
  HSEStartUpStatus = RCC_WaitForHSEStartUp();

  if(HSEStartUpStatus == SUCCESS)
  {
    /* Enable Prefetch Buffer */
    FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);

    /* Flash 2 wait state */
    FLASH_SetLatency(FLASH_Latency_2);
  
    /* HCLK = SYSCLK */
    RCC_HCLKConfig(RCC_SYSCLK_Div1); 
  
    /* PCLK2 = HCLK */
    RCC_PCLK2Config(RCC_HCLK_Div1); 

    /* PCLK1 = HCLK/2 */
    RCC_PCLK1Config(RCC_HCLK_Div2);

    /* ADCCLK = PCLK2/4 */
    RCC_ADCCLKConfig(RCC_PCLK2_Div4); 
  
#ifndef STM32F10X_CL  
    /* PLLCLK = 8MHz * 7 = 56 MHz */
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_7);

#else
    /* Configure PLLs *********************************************************/
    /* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
    RCC_PREDIV2Config(RCC_PREDIV2_Div5);
    RCC_PLL2Config(RCC_PLL2Mul_8);

    /* Enable PLL2 */
    RCC_PLL2Cmd(ENABLE);

    /* Wait till PLL2 is ready */
    while (RCC_GetFlagStatus(RCC_FLAG_PLL2RDY) == RESET)
    {}

    /* PLL configuration: PLLCLK = (PLL2 / 5) * 7 = 56 MHz */ 
    RCC_PREDIV1Config(RCC_PREDIV1_Source_PLL2, RCC_PREDIV1_Div5);
    RCC_PLLConfig(RCC_PLLSource_PREDIV1, RCC_PLLMul_7);
#endif

    /* Enable PLL */ 
    RCC_PLLCmd(ENABLE);

    /* Wait till PLL is ready */
    while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
    {
    }

    /* Select PLL as system clock source */
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

    /* Wait till PLL is used as system clock source */
    while(RCC_GetSYSCLKSource() != 0x08)
    {
    }
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOC , ENABLE );
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 |RCC_APB1Periph_TIM3 | RCC_APB1Periph_TIM4 | RCC_APB1Periph_SPI2, ENABLE );

  }


}

void GPIO_Configuration(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7;   
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
  GPIO_Init(GPIOB, &GPIO_InitStructure);   
 } 


#ifdef  USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line)
{ 
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */

  /* Infinite loop */
  while (1)
  {
  }
}
#endif



6.4 函数解析

6.4.1 TSL2561PowerUp()

int TSL2561PowerUp(void){
	DataBufferTx[0]=PowerUp;
	return I2C_SendData_TSL2561(SlaveAddressVDD, PowerUpCode, 1, DataBufferTx);
}

  涉及的参数是

#define	PowerUp		0x03
#define	SlaveAddressVDD	0x49
#define PowerUpCode	0x80|CtrlRegAdd
#define	CtrlRegAdd	0x00

6.4.2  TSL2561SetTimmingReg(GainSetHigh, ManualClr,IntegScal1000)

/**********************************************************************************
GAIN: should be GainSetLow or GainSetHigh
Manual: should be ManualSet or ManualClr
INTEG: should be IntegScal0034, IntegScal0252 or IntegScal1000
**********************************************************************************/
int TSL2561SetTimmingReg(u8 GAIN, u8 Manual, u8 INTEG){
		u8 temp=0;
		temp&=GainClrMask&ManualMask&IntegMask;
		temp|=GAIN|Manual|INTEG;
		DataBufferTx[0]=temp;
		return I2C_SendData_TSL2561(SlaveAddressVDD, TimingRegCode, 1, DataBufferTx);
}

涉及的参数

#define	GainClrMask	0xEF
#define	ManualMask	0xF7
#define IntegMask	0xFC
#define	GainSetHigh	0x10
#define	ManualClr	0x00
#define	IntegScal1000	0x02

#define	SlaveAddressVDD	0x49#define TimingRegCode 0x80|TimingRegAdd
#define	TimingRegAdd	0x01

6.4.3 TSL2561SetInterruptCtrlReg(IntrLevelInt, EveryADCCycle) 

/*************************************************************************************
InterruptLogic:  
				should be one of these: IntrDisabled,IntrLevelInt,IntrSMBAlert,
										 IntrTestMode
InterruptRate:
				should be one of these: EveryADCCycle,AnyOutsideValue,TowPeriods,
 										ThreePeriods,FourPeriods,FivePeriods,
										SixePeriodS,SevenPeriods,EightPeriods,
										NinePeriods,TenPeriods,ElevenPeriods,
										TwelvePeriods,ThirteenPeriods,FourteenPeriods,
										FifteenPeriods
**************************************************************************************/											 										  
int TSL2561SetInterruptCtrlReg(unsigned char InterruptLogic, unsigned char InterruptRate){
		unsigned char temp=0;
		temp&=IntrMask;
		temp&=PersistMask;
		temp|=InterruptLogic|InterruptRate;
		DataBufferTx[0]=temp;
		return I2C_SendData_TSL2561(SlaveAddressVDD, InterruptRegCode, 1, DataBufferTx);			
}

涉及的参数

#define	IntrMask	0xCF
#define	PersistMask	0xF0
#define	IntrLevelInt	0x10
#define	EveryADCCycle	0x00

#define	SlaveAddressVDD	0x49
#define InterruptRegCode 0x80|InterruptRegAdd
#define	InterruptRegAdd 0x06

6.4.4 TSL2561ReadINT2()

void TSL2561ReadINT2(void){
	u16 LightIntensity0=0xFFFF;
	u16 LightIntensity1=0xFFFF;
	unsigned long Lux=0xFFFFFFFF;

	TSL2561ReadADC(0, &LightIntensity0);	//读取通道0寄存器值
        TSL2561ReadADC(1, &LightIntensity1);    //读取通道1寄存器值
        //计算流明值
         Lux=CalculateLux(GainSetHigh>>4, IntegScal1000, LightIntensity0, LightIntensity1, T);	Xaddr=0;
	Yaddr=Line5;
	printf("The Light Intensity is: %d lux\n", Lux);			
	xDelay(0x01ffff);
}

涉及的参数

#define	GainSetHigh	0x10
#define	IntegScal1000	0x02
#define T	0x0000

6.4.5 TSL2561ReadADC()

       在读数据的时候,因为TSL2561的ADC的数据寄存器分为DATE0_LOW,DATE0_HIGH,DATE1_LOW,DATE1_HIGH,一共两路,每路16位。因此一次读数据要有两次的I2C读操作,并把DATE_HIGH移到高位。

/**********************************************************************************
ChannelNum: shoule be 0 or 1
*LightIntensity : fetch the Channel Number;
**********************************************************************************/
//读取通道寄存器值
int TSL2561ReadADC(int ChannelNum, u16 *LightIntensity){
		if(ChannelNum==0){
			if(I2C_ReceivData(SlaveAddressVDD, Data0LowRegCode, 2, DataBufferRx)){
				*LightIntensity=(DataBufferRx[1]<<8)|DataBufferRx[0];
				return 1;
			}
			return 0;//No Ack;				
		}
		
		if(ChannelNum==1){
			if(I2C_ReceivData(SlaveAddressVDD, Data1LowRegCode, 2, DataBufferRx)){
				*LightIntensity=(DataBufferRx[1]<<8)|DataBufferRx[0];
				return 1;	
			}
			return 0;//No ACK;
		}
		
		return 2;//Wrong ChannelNum;	 	
}