I2C

文章目录

        • 1. 平台简述
        • 2. I2C介绍
        • 3. I2C初始化
        • 4. I2C主设备模式
          • 4.1 主发送模式数据格式
          • 4.2 主接收模式数据格式
          • 4.3 start条件和stop条件函数
          • 4.4 发送地址i2c_send_sla函数
          • 4.5 发送数据i2c_send_dat和接收数据i2c_recv_dat函数
        • 5. ISL29003光照传感器
          • 5.1 ISL29003_I2C读取时序图
          • 5.2 ISL29003_I2C写入时序图
          • 5.3 ISL29003_I2C程序

1. 平台简述

  • 所用开发板为基于ARM Cortex-M0 的LPC111x/LPC11Cxx 系列微控制器
  • 低功耗,32 位微控制器家族中的一员,面向8、16 位微处理应用,具有高性能,低功耗,简单指令集,统一编址寻址等优点,而且,相对于现在市场上存在的8/16 位架构来说,它有效的降低了代码长度。

2. I2C介绍

根据方向位的状态(读/ 写),在I2C 总线上可能:
有两种类型的数据传输:

  • 数据传输从一个主设备的发送器到一个从设备的接收器。主设备发送的第一个字节是从设备的地址。紧接着的是大量的数据字节。从设备在每个字节接收完成后返回一个应答位。
  • 数据传输从一个从设备的发送器到一个主设备的接收器。由主设备发送的第一个字节(从设备的地址),然后返回一个应答位,紧接着的数据字节由从设备传输到主设备。除了最后一个字节之外,主设备在其他每个字节接收完成之后返回一个应答位。在最后一个字节接收完成后,返回一个“ 非应答”。主设备产生的所有的串行时钟脉冲与起始(START)和停止(STOP)条件。一次传输由STOP 条件或重复START 条件来结束。由于重复START 条件将开始下一个串行传输,故I2C 总线不会被释放。
    I2C_第1张图片

3. I2C初始化

I2C 总线接口通过以下寄存器进行配置:

  1. 引脚: I2C引脚功能和I2C模式都通过IOCONFIG寄存器进行配置。
  2. 电源和外设时钟: 通过SYSAHBCLKCTRL 寄存器中第5位进行设置
  3. 复位: 再访问I2C模块之前,确保PRESETCTRL寄存器中I2C_RST_N 位被设置为1.这将拉高I2C模块的复位信号。
  4. I2C频率设置与使能
  • 引脚与模式配置
    I2C_第2张图片
    I2C_第3张图片

    	LPC_IOCON->PIO0_4 = 0x01;//管脚复用选择i2c
    	LPC_IOCON->PIO0_5 = 0x01;//管脚复用选择i2c
    	/*I2CMODE默认为00---->标准模式/快速I2C模式*/
    
  • 电源和外设时钟配置
    系统 AHB 时钟控制寄存器(SYSAHBCLKCTRL) 寄存器第5位置1,允许I2C时钟
    电源和外设时钟配置

    	LPC_SYSCON->SYSAHBCLKCTRL |= 1 << 5;//i2c时钟允许位
    
  • 复位设置

    	//在访问SPI和I2C外设之前,要写1到相应位中用以确保外设复位无效,I2C复位无效
    	LPC_SYSCON->PRESETCTRL |= 1 << 1;
    
  • 频率设置和使能

    	LPC_I2C->SCLH = 480;//决定i2c时钟高电平时间
    	LPC_I2C->SCLL = 480;//决定i2c时钟低电平时间
    	LPC_I2C->CONCLR |= (1 << 2) | (1 << 3) | (1 << 5) | (1 << 6);//i2c清除寄存器(写1清除 )
    	LPC_I2C->CONSET |= (1 << 6);//i2c接口允许
    
    • 第1,2行是通过设置SCLH和SCLL寄存器,决定I2C设备的占空比和频率

    I2C_第4张图片
    I2C_第5张图片

    • 第3,4行,向CONCLR对应位写入1是将CONSET对应位清0,向CONSET对应位写1是置1。
      因为要设置设备为主发送模式,所以在这种模式下,数据从主设备传输到从设备。在可以进入主发送模式之前,必须如下表所示初始化CONSET 寄存器。
      CONSET配置
      I2EN 必须设置为1,以允许I2C 功能。如果AA位为0,当其他设备是总线主机时, I2C 接口将不识别任何地址,所以它不能进入从模式。 STA、STO和SI 位必须为0,其中SI 位可以通过写‘1’ 到CONCLR寄存器的SIC 位来清除;STA 位必须在写从地址之后清除。
void i2c_init(void)
{
	//基本配置
	LPC_SYSCON->PRESETCTRL |= 1 << 1;//在访问SPI和I2C外设之前,要写1到相应位中用以确保外设复位无效,I2C复位无效
	LPC_SYSCON->SYSAHBCLKCTRL |= 1 << 5;//i2c时钟允许位
	LPC_IOCON->PIO0_4 = 0x01;//管脚复用选择i2c
	LPC_IOCON->PIO0_5 = 0x01;//管脚复用选择i2c
	
	LPC_I2C->SCLH = 480;//决定i2c时钟高电平时间
	LPC_I2C->SCLL = 480;//决定i2c时钟低电平时间
	LPC_I2C->CONCLR |= (1 << 2) | (1 << 3) | (1 << 5) | (1 << 6);//i2c清除寄存器(写1清除 )
	LPC_I2C->CONSET |= (1 << 6);//i2c接口允许
}

4. I2C主设备模式

4.1 主发送模式数据格式
  • 数据格式
    I2C_第6张图片
  • 第一个S为START条件,当软件将START 标志位(STA)置1时, I2C 接口将进入主发送模式。总线一旦空闲, I2C 逻辑将发送START 条件;
    START 条件发送后,中断标志(SI) 位将被置1 ;
    STAT 寄存器中的状态码将被设置(比如0x08),这个状态码将会作为一个状态服务程序的向量;状态服务程序将会加载从设备地址和写I2DAT寄存器,并清除中断标志(SI) 位。
    SI 位可以通过写‘1’ 到CONCLR 寄存器的SIC 位清除。
  • 传送的第一个字节(SLAVE_ADDRESS and RW)包含接收设备的从地址(7 位)和数据方向位。在此模式下,数据方向位0是写,1是读。
  • 设备地址和数据方向发送完成之后,从设备会返回一个应答位( A A A),此时,中断标志(SI) 位将被置1,并设定STAT的状态码为相应值。
  • 之后便是传送数据,按字节形式发送,每发送一个字节,从设备都会返回一个应答位( A A A),最后一个字节从设备会返回一个非应答位 ( A ‾ \overline{A} A)
  • 最后主设备会设置CONSET的停止标志位为1,发送停止标志(STOP)。发送成功后,硬件会将STOP标志位清零。
4.2 主接收模式数据格式
  • 在主接收模式,从一个从发送器接收数据。传输以与主发送模式同样的方式启动。当START 条件已经传送,必须加载从地址和数据方向位到I2C 数据寄存器(I2DAT)中,然后清除的SI 位。在这种情况下,数据方向位(读/ 写)应为1,表示读,下图为主接收模式数据格式。
    I2C_第7张图片

  • 在一个重复的起始条件后, I2C 会转到主发送模式。下图为主接收模式转换为主发送模式数据格式
    I2C_第8张图片

4.3 start条件和stop条件函数
bool i2c_start(void)
{
	LPC_I2C->CONSET = (1 << 5);	//设置start标志位						
	while (!(LPC_I2C->CONSET & (1 << 3)));//等待中断
	if (LPC_I2C->STAT != 0x08 && LPC_I2C->STAT != 0x10) //判断STAT状态标志
	{
		/*0x08(状态码)已发送START 条件																												
	  	  0x10(状态码)已发重复START 条件
	  	  如果不是这两种状态,说明出了问题,这时主设备发送stop条件
		*/
		i2c_stop();//状态标志不为0x08或者0x10表示发送start失败
		return false;
	}
	return true;
}

bool i2c_stop(void)
{
	LPC_I2C->CONSET = (1 << 4);	//设置stop标志位 
  	LPC_I2C->CONCLR = (1 << 3);	//清除中断标志
  	while(LPC_I2C->CONSET & (1 << 4));//stop位清零,发送stop完成
		return true;
}
4.4 发送地址i2c_send_sla函数
bool i2c_send_sla(unsigned char addr)
{
	LPC_I2C->DAT = addr;	//将地址写入数据寄存器,准备发送给从设备
	LPC_I2C->CONCLR = (1 << 5) | (1 << 3);	//清除中断和start标志
	while (!(LPC_I2C->CONSET & (1 << 3)));	//等待从设备返回ACK,产生中断
	if (LPC_I2C->STAT != 0x18 && LPC_I2C->STAT != 0x40) //判断STAT状态标志
	{
	/*0x18(状态码)主机接收模式SLA(设备地址)+R(读)传输,已收到ACK(应答)																												
	  0x40(状态码)主机发送模式SLA(设备地址)+W(写)传输,已收到ACK(应答)
	  如果不是这两种状态,说明出了问题,这时主设备发送stop条件
	*/
		i2c_stop();
		return false;
	} else
		return true;
}
4.5 发送数据i2c_send_dat和接收数据i2c_recv_dat函数
bool i2c_send_dat(unsigned char dat)
{
	LPC_I2C->DAT = dat;
	LPC_I2C->CONCLR = (1 << 5) | (1 << 3);	//清除中断标志位和start标志位
	while (!(LPC_I2C->CONSET & (1 << 3)));	//等待中断位置1
	if (LPC_I2C->STAT != 0x28) {
		/*0x28(状态码)主机发送模式 DAT中数据字节已传输,已收到ACK(应答)																												
		  如果不是这种状态,说明出了问题,这时主设备发送stop条件
		*/
		i2c_stop();
		return false;
	} else
		return true;
}

bool i2c_recv_dat(unsigned char *dat, bool ack)
{
	/*	bool ack
		数据不是最后一字节,开启应答位,发送应答位(ACK)
		数据为最后一字节时,关闭应答位,发送非应答位(NOT ACK)
	*/
	if (!ack)
		LPC_I2C->CONCLR = 1 << 2;//关应答位
	else
	LPC_I2C->CONSET = 1 << 2;//开启应答位
	LPC_I2C->CONCLR = 1 << 3;//清除中断标志
	while (!(LPC_I2C->CONSET & (1 << 3)));	//等待中断标志置1
	if (LPC_I2C->STAT != 0x50 && LPC_I2C->STAT != 0x58) 
	{
															
		/*0x50(状态码)主机接收模式 已收到数据字节,收到应答(ACK)																											
		  0x58(状态码)主机接收模式 已收到数据字节,收到非应答(NOT ACK)
		  如果不是这两种状态,说明出了问题,这时主设备发送stop条件
		*/
		i2c_stop();
		return false;
	}
	*dat = LPC_I2C->DAT;
	return true;
}

5. ISL29003光照传感器

5.1 ISL29003_I2C读取时序图

I2C_第9张图片

  • 首先需要主设备发送Start条件
  • 之后主设备要发送ISL29003的设备地址A6~A0 和数据方向W/R(0/1),上图中所示为1000_1000,即0x88 (代码实现是0x44<<1 | 0 ) 数据方向为写。
  • 收到应答位后,继续发送ISL29003寄存器偏移地址,具体寄存器功能可以看ISL29003的芯片手册
  • 收到应答之后,接着发送STOP条件
  • 接着再发送START条件,发送设备地址和数据方向 (代码实现是0x44<<1 | 1 ),这时数据方向W/R(0/1)为1,方向为读。
  • 主设备开始接受光照数据,接受完成之后发送STOP条件

根据上述流程,可以编写I2C的读数据i2c_read函数。

int i2c_read(unsigned char addr, unsigned char reg, unsigned char *buf, int len)
{
	int i = len;	
	/* 1.发送Start条件 */
	if (!i2c_start())
		return -1;
	/* 2.发送设备地址和数据方向		*/
	/*	  设备地址左移一位,数据方向位为0,为写方向*/
	if (!i2c_send_sla(addr << 1))
		return -1;
	/* 3. 发送寄存器地址 */
	if (!i2c_send_dat(reg))
		return -1;
	/* 4. 发送STOP条件 */
	i2c_stop();	
	/* 5. 发送Start条件 */
	if (!i2c_start())
		return -1;
	/* 5. 发送设备地址和数据方向	 	*/
	/*	  设备地址左移一位,数据方向位为1,为读方向*/
	if (!i2c_send_sla((addr << 1) | 1))
		return -1;	
	/* 6. 接受光照数据 */
	while (i) 
	{
		if (i == 1) {
			if (!i2c_recv_dat(buf + len - i, false))
				return len - i;
		} else {
			if (!i2c_recv_dat(buf + len - i, true))
				return len - i;
		}
		i--;
	}	
	/* 7. 发送STOP条件 */
	i2c_stop();	
	return len;
}
5.2 ISL29003_I2C写入时序图

I2C_第10张图片

  • 前面部分是和读时序图是相同的,发送的FUNCTIONS(功能)是对寄存器(REGISTER)的设定,以设定传感器运行相应功能。
int i2c_write(unsigned char addr, unsigned char reg, unsigned char *buf, int len)
{
	int i;	
	/* 1. 发送Start条件 */
	if (!i2c_start())
		return -1;
	/* 2. 发送设备地址和数据方向,写方向*/
	if (!i2c_send_sla(addr << 1))
		return -1;
	/* 3. 发送寄存器地址  */
	if (!i2c_send_dat(reg))
		return -1;	
	/* 4. 发送FUNCTION */
	for (i = 0; i < len; i++) {
		if (!i2c_send_dat(buf[i]))
			return i;
	}
	/* 5. 发送Stop条件 */
	i2c_stop();	
	return len;
}
5.3 ISL29003_I2C程序

I2C_第11张图片

  • 地址定义与初始化
#define ISL29003_ADDR		0x44 //设备地址左移1位,| 数据方向位
#define COMMAND				0
#define CONTROL				1
#define THRHI				2
#define THRLO				3
#define LUXLSB				4
#define LUXMSB				5
#define TMRLSB				6
#define TMRMSB				7

void light_init(void)
{
	/*
	 * 时钟周期数2^16, 二极管的电流为无符号16bit, 
	 * 集成内部定时,正常运行,启用adc内核
	 */
	unsigned char cmd = (1 << 7);
	/*
	 * 设置范围为 0~64000 lux (勒克斯)
	 * 中断在8个集成周期后触发
	 */
	unsigned char ctl = (3 << 2) | (2 << 0);
	
	i2c_write(ISL29003_ADDR, COMMAND, &cmd, 1);//设定COMMAND寄存器的值
	i2c_write(ISL29003_ADDR, CONTROL, &ctl, 1);//设定CONTROL寄存器的值
}
  • 读取光照
unsigned short light_get(void)
{
	unsigned short lux;
	/*从LUXLSB寄存器读取光照值*/
	i2c_read(ISL29003_ADDR, LUXLSB, (unsigned char *)&lux, 2);
	return lux;
}

你可能感兴趣的:(I2C)