使用开发板:正点原子-阿尔法开发板
简介
I2C是一种常用总线协议,由NXP公司设计,主从模式,由主机发起通信。I2C使用两条线进行数据传输,一条是SCL(串行时钟线),另外一条是SDA(数据线),这两条数据线需要接上拉电阻。
I2C总线特点
- I2C是由数据线(SDA)和时钟线(SCL)构成的串行总线,数据线用来传输数据,时钟线用来同步数据收发。
- 总线上的每个器件都有一个唯一的地址识别。
- SDA和SCL都是双向线路,通过一个电流源或上拉电阻连接到正电压,所以总线空闲时,SDA和SCL都是高电平。
- 总线上数据传输速率,标准模式下100kbit/s,快速模式下400kbit/s,高速模式下3.4Mbit/s。
- 总线支持设备连接,支持多个主机多个从机,连接到总线上的接口数量有总线电容400pF的限制决定。
总线空闲时,SDA和SCL为高电平。如下图所示:
起始位
起始信号,就是开始I2C通信的开始信号,该信号由主机发送,从机接收。功能是告诉从机从现在“我”要开始通信I2C通信了。
开始信号:SCL为高电平,SDA出现下降沿(即SDA由高电平变为低电平)。信号变化如图所示:
停止位
停止位就是停止I2C通信的信号,在SCL为高电平的时候,SDA出现上升沿(即SDA由低电平变为高电平)。
数据传输
I2C总线在传输数据时,必须保证在SCL为高电平期间,SDA上的电平稳定,因此,SDA数据的变化只能在SCL为低电平期间(如果在SCL为高电平期间,SDA上的电平发生变化,会被认为是起始或停止信号)。
应答信号
当I2C主机发送完8位数据后,需要将SDA设置为输入状态,等待从机的应答,也就是等待I2C从机告诉主机它接收到数据了。应答信号是从机发送的,主机需要提供应答信号所需的时钟。主机发送完8位数据后紧跟着的一个时钟信号就是给应答信号使用的。
从机通过将SDA信号拉低来表示发出应答信号。主机接收到应答信号表示通信成功,否则表示通信失败。
I2C时序
主机通过I2C总线与从机通讯,主要就是两个操作:读和写
I2C写时序
I2C单字节写时序如图:
时序图中数字含义:
- 发送开始信号
- 发送I2C设备地址,设备地址是一个7位的数据,和‘3’共构成一个字节。
- I2C器件地址湖面跟着的读写位。0:写操作,1:读操作。
- 从机发送应答信号。
- 重新发送起始信号。
- 要发送的数据的寄存器地址。
- 从机应答信号。
- 发送要写入的数据。
- 从机发送应答信号。
- 停止信号。
I2C读时序
I2C单字节的读要比写复杂些,读时序大约分为4个步骤,
- 发送设备地址
- 发送要读取的寄存器地址
- 发送设备地址
- I2C从器件输出要读取的寄存器地址
具体步骤如下:
- 主机发送开始信号
- 主机发送要读取的设备地址
- 读写控制位,因为是向主机发送设备地址,所以此位应该是0(写信号)
- 从机发送ACK应答信号
- 重新发送START信号
- 主机发送要读取的寄存器地址
- 从机发送ACK应答信号
- 重新发送START信号
- 重新发送要读取的I2C从设备地址
- 读写控制位,这里是读信号(1),表示接下来要从I2C里面读取数据
- 从机发送的ACK应答信号
- 从I2C器件里面读取到色数据
- 主机发送NO ACK信号,表示读取完成,不需要从机在发送ACK信号
- 主机发送STOP信号,停止I2C通信
I2C读写多个字节
读写多个字节和单字节基本一致,只是在读写数据的时候可以连续发送多个字节的数据,其他的控制时序是和单字节一样的。
I.MX6UL I2C简介
I.MX6UL提供了4个I2C外设,支持标准模式和快速模式,特征如下:
- 与标准I2C兼容
- 多主机运行
- 软件可编程的64种不同的串行时钟序列
- 软件可选择的应答位
- 开始/结束信号生成和检测
- 重复开始信号生成
- 确认位生成
- 总线忙检测
主要寄存器
(x=1~4)
- I2Cx_IADR
I2C地址寄存器,只有ADR[7:1]位有效,用来保存设备地址数据,当要访问某个I2C从设备的时候,就需要将其设备地址写入到ADR里面。
位 | 功能 |
---|---|
IEN[7] | I2C使能位,1:使能I2C,0:关闭I2C |
IIEN[6] | I2C中断使能位, 1:使能中断,0:关闭中断 |
MSTA[5] | 主从模式选择位,1:主模式, 0:从模式 |
MTX[4] | 传输方向选择位,1:发送,0:接收 |
TXAK[3] | 传输应答使能,1:发送NO ACK,0:发送ACK |
RSTA[2] | 重复开始信号,1:产生一个重新开始信号 |
-
I2Cx_IFDR
I2C分频寄存器,IC[5:0]有效,用来设置I2C的波特率,I2C的时钟源可以选择IPG_CLK_ROOT=66MHz,通过设置IC可以得到想要的波特率。I2C可选分频如下图:
寄存器I2Cx_IFR也只有IC(bit5:0)这个位,用来设置I2C的波特率。
-
I2Cx_I2CR
I2C控制寄存器
位 | 功能 |
---|---|
IEN(bit7) | I2C使能位,1:使能I2C, 0:关闭I2C |
IIEN(bit6) | I2C中断使能位,1:使能I2C中断, 0:I2C关闭中断 |
MSTA(bit5) | 主从模式选择位,1:主模式,0:从模式 |
MTX(bit4) | 传输方向选择位,0:接收, 1:发送 |
TXAK(bit3) | 传输应答使能位,0:发送ACK信号,1:NO ACK信号 |
RSTA(bit2) | 重复开始信号,1:产生一个重新开始信号 |
- I2Cx_I2SR
I2C状态寄存器
位 | 功能 |
---|---|
ICF(bit7) | 数据传输状态位,0:数据正在传输,1:数据传输完成 |
IAAS(bit6) | |
IBB(bit5) | I2C总线忙标志位,0:I2C总线空闲,1:总线忙 |
IAL(bit4) | 仲裁丢失位,1:仲裁丢失 |
SRW(bit2) | 从机读写状态位,0:主机向从机写数据,1:主机要从从机读取数据 |
IIF(bit1) | I2C中断挂起标志位 |
RXAK(bit0) | 应答信号标志位 |
- I2Cx_I2DR
I2C数据寄存器
AP3216C简介
三合一环境传感器,支持环境光强度(ALS),接近距离(PS)和红外强度(IR)。该芯片可以通过IIC接口与主控制相连,并且支持中断,AP3216C的特点如下:
- I2C接口,快速模式下波特率可以达到400Kbit/S
- 多种工作模式选择:ALS、PS+IR、ALS+PS+IR、PD等等
- 内建温度补偿电路
- 宽工作温度范围(-30°~+80°C)
- 超小封装
- 环境光传感器具有16位分辨率
- 接近传感器和红外传感器具有10位分辨率
寄存器地址 | 位 | 寄存器功能 | 描述 |
0X00 | 2:0 | 系统模式 | 000:掉电模式(默认) 001:使能ALS 010:使能PS+IR 011:使能ALS+PS+IR 100:软复位 101:ALS单次模式 110:PS+IR单次模式 111:ALS+PS+IR 单次模式 |
0X0A | 7 | IR低位数据 | 0:IR&PS数据有效位 1:无效 |
1:0 | IR最低两位数据 | ||
0X0B | 7:0 | IR高位数据 | IR高8位数据 |
0X0C | 7:0 | ALS低位数据 | ALS低8位数据 |
0X0D | 7:0 | ALS高位数据 | ALS高8位数据 |
0X0E | 7 | PS低位数据 | 0:物体在远离,1:物体在接近 |
6 | 0:IR&PS数据有效,1:IR&PS数据无效 | ||
3:0 | PS最低4位数据 | ||
0X0F | 7 | PS高位数据 | 0:物体在远离,1:物体在接近 |
6 | 0:IR&PS数据有效,1:IR&PS数据无效 | ||
5:0 | PS高6位数据 |
程序编写
I2C驱动
I2C驱动包括:
- I2C初始化(I2C频率设置)
- I2C开始信号、重新开始信号和停止信号(数据传输时的时序,开始信号、重新开始信号函数都有从设备地址)
- I2C检测和清除错误(传输过程中的状态检测)
- I2C主机读、写(主机发送或接收数据)
- I2C数据传输(对读写函数的进一步封装)
最终的对外接口有两个,一个是I2C初始化,一个是I2C数据传输。
I2C的 .h文件,为正点原子例程中代码
#ifndef BSP_I2C_H
#define BSP_I2C_H
#include "imx6ul.h"
/* 定义相关宏 为i2c总线的状态 */
#define I2C_STATUS_OK (0) /* 传输完成 */
#define I2C_STATUS_BUSY (1) /* 总线忙 */
#define I2C_STATUS_LDLE (2) /* */
#define I2C_STATUS_NAK (3) /* 无应答着 */
#define I2C_STATUS_ARBITRATIONLOST (4) /* 仲裁错误 */
#define I2C_STATUS_TIMEOUT (5) /* 传输超时 */
#define I2C_STATUS_ADDRNAK (6) /* */
/*
* I2C方向枚举类型
* 传输方向
*/
enum i2c_direction
{
kI2C_Write = 0, /* 主机向从机写数据 */
kI2C_Read = 1, /* 主机从从机读数据 */
};
/* 主机传输结构体 */
struct i2c_transfer
{
unsigned char slaveAddress; /* 7位从机地址 */
enum i2c_direction direction; /* 传输方向 */
unsigned int subaddress; /* 寄存器地址 */
unsigned char subaddressSize; /* 寄存器长度 */
unsigned char *volatile data; /* 数据缓冲区 */
volatile unsigned int dataSize; /* 数据缓冲区长度 */
};
/* 函数声明 */
void i2c_init(I2C_Type *base);
unsigned char i2c_master_start(I2C_Type *base, unsigned char address, enum i2c_direction direction);
unsigned char i2c_master_repeated_start(I2C_Type *base, unsigned char address, enum i2c_direction direction);
unsigned char i2c_check_and_clear_error(I2C_Type *base, unsigned int status);
unsigned char i2c_master_stop(I2C_Type *base);
void i2c_master_write(I2C_Type *base, const unsigned char *buf, unsigned int size);
void i2c_master_read(I2C_Type *base, unsigned char *buf, unsigned int size);
unsigned char i2c_master_transfer(I2C_Type *base, struct i2c_transfer *xfer);
#endif
I2C的 .c文件,为正点原子例程中代码
#include "bsp_i2c.h"
#include "bsp_delay.h"
#include "stdio.h"
/*
* @description : 初始化I2C,波特率100kHZ
* @param - base : 要初始化的IIC
* @return : 无
*/
void i2c_init(I2C_Type *base)
{
/* 1、配置 I2C */
base->I2CR &= ~(1 << 7); /* 要访问I2C的寄存器,首先需要关闭I2C */
/* 设置波特率 100khz*/
base->IFDR = 0X15 << 0; /* 640分频,时钟选择66Mhz*/
/* 设置寄存器 I2CR,开启I2C */
base->I2CR |= (1 << 7);
}
/*
* @description : 发送重新开始信号
* @param - base : 要使用的IIC
* @param - addrss : 设备地址
* @param - direction :方向
* @return : 无
*/
unsigned char i2c_master_repeated_start(I2C_Type *base, unsigned char address,enum i2c_direction direction)
{
/* I2C 忙 并且工作在从模式, 跳出。 从模式不用发送重新开始信号 */
if(base->I2SR & (1 << 5) && (((base->I2CR) & (1 << 5)) == 0))
return 1;
/*
* 设置寄存器I2CR
* bit[4]: 1 发送
* bit[2]: 1 产生重新开始信号
*/
base->I2CR |= (1 << 4) | (1 << 2);
/* 设置寄存器 I2DR,bit[7:0] :要发送的数据,这里写入从设备地址 */
base->I2DR = ( (unsigned int) address << 1 ) | ( (direction == kI2C_Read) ? 1 : 0 );
return 0;
}
/*
* @description : 发送开始信号
* @param - base : 要使用的IIC
* @param - addrss : 设备地址
* @param - direction :方向
* @return : 无
*/
unsigned char i2c_master_start(I2C_Type *base, unsigned char address, enum i2c_direction direction)
{
if(base->I2SR & (1 << 5)) /* 忙 跳出*/
return 1;
/*
* 设置寄存器I2C
* bit[5]: 1 主模式
* bit[4]: 1 发送
*/
base->I2CR |= (1 << 5) | (1 << 4);
/*
* 设置寄存器I2DR,bit[7:0]: 要发送的数据,这里写入从设备地址
*/
base->I2DR = ( (unsigned int)address << 1) | ( (direction == kI2C_Read)? 1 : 0);
return 0;
}
/*
* @description : 检查并清除错误
* @param - base : 要使用的IIC
* @param - status : 状态
* @return : 状态结果
*/
unsigned char i2c_check_and_clear_error(I2C_Type *base, unsigned int status)
{
if(status & (1 << 4)) /* 检查是否发生仲裁丢失错误 */
{
base->I2SR &= ~(1 << 4); /* 清除仲裁丢失错误位 */
base->I2CR &= ~(1 << 7); /* 先关闭I2C */
base->I2CR |= (1 << 7); /* 重新打开 I2C */
return I2C_STATUS_ARBITRATIONLOST;
}
else if(status & (1 << 0)){ /* 没有接收到从机的应答信号 */
return I2C_STATUS_NAK;
}
return I2C_STATUS_OK;
}
/*
* @description : 停止信号
* @param - base : 要使用的IIC
* @param : 无
* @return : 状态结果
*/
unsigned char i2c_master_stop(I2C_Type *base)
{
unsigned short timeout = 0xFFFF;
/* 清除I2CR的bit[5:3]这三位 */
base->I2CR &= ~((1 << 5) | (1 << 4) | (1 << 3));
while ((base->I2SR & (1 << 5))) /* 等待忙结束 */
{
timeout --;
if(timeout == 0) /* 超时跳出 */
{
return I2C_STATUS_TIMEOUT;
}
}
return I2C_STATUS_OK;
}
/*
* @description : 发送数据
* @param - base : 要使用的IIC
* @param - buf : 要发送的数据
* @param - size : 要发送的数据大小
* @param - flags : 标志
* @return : 无
*/
void i2c_master_write(I2C_Type *base, const unsigned char *buf, unsigned int size)
{
while(!(base->I2SR & (1 << 7))); /* 等待数据发送完成 */
base->I2SR &= ~(1 << 1); /* 清除标志位 */
base->I2CR |= 1 << 4; /* 发送数据 */
while(size--)
{
base->I2DR = *buf++; /* 将数据写入buf */
while(!(base->I2SR & (1 << 1))); /* 等待发送完成,注意和上面的等待发送判断的不是同一标志位 */
base->I2SR &= ~(1 << 1); /* 清除标志位 */
/* 检查ACK信号 */
if(i2c_check_and_clear_error(base, base->I2SR))
break;
}
base->I2SR &= ~(1 << 1);
i2c_master_stop(base); /* 发送停止位 */
}
/*
* @description : 读取数据
* @param - base : 要使用的IIC
* @param - buf : 读取到的数据
* @param - size : 要读取的数据大小
* @return : 无
*/
void i2c_master_read(I2C_Type *base, unsigned char *buf,unsigned int size)
{
volatile uint8_t dummy = 0;
dummy ++; /* 防止编译报错 */
while(! (base->I2SR & (1 << 7))); /* 等待传输完成 */
base->I2SR &= ~(1 << 1); /* 清除中断挂起位 */
base->I2CR &= ~((1 << 4) | (1 << 3)); /* 接收数据 */
/* 如果只接收一个字节的数据发送应答信号 */
if(size == 1)
{
base->I2CR |= (1 << 3);
}
dummy = base->I2DR; /* 假读 */
while(size--)
{
while(!((base->I2SR) & (1 << 1))); /* 等待传输完成 */
base->I2SR &= ~(1 << 1); /* 清除标志位 */
if(size == 0)
i2c_master_stop(base); /* 发送停止信号 */
if(size == 1)
base->I2CR |= (1 << 3); /* 发送停止应答信号 */
*buf++ = base->I2DR;
}
}
/*
* @description : I2C数据传输
* @param - base : 要使用的IIC
* @param - xfer : 传输结构体
* @return : 传输结果 , 0 成功, 其他值 失败
*/
unsigned char i2c_master_transfer(I2C_Type *base, struct i2c_transfer *xfer)
{
unsigned char ret = 0;
enum i2c_direction direction = xfer->direction;
base->I2SR &= ~((1 << 1) | (1 << 4)); /* 清除标志位 */
while(!((base->I2SR >> 7) & 0x01)){}; /* 等待传输完成 */
/* 如果是读的话,要先发送寄存器地址,所以要先将方向改为写 */
if((xfer->subaddressSize > 0) && (xfer->direction == kI2C_Read))
{
direction = kI2C_Write;
}
ret = i2c_master_start(base,xfer->slaveAddress,direction);
//printf("ret = %d\n",ret);
if(ret)
{
return ret;
}
while(!(base->I2SR & (1 << 1))){};
ret = i2c_check_and_clear_error(base,base->I2SR);
if(ret)
{
i2c_master_stop(base);
return ret;
}
/* 发送寄存器地址 */
if(xfer->subaddressSize)
{
/* 寄存器地址为多个字节 */
do
{
base->I2SR &= ~(1 << 1);
xfer->subaddressSize--;
base->I2DR = ((xfer->subaddress) >> (8 * xfer->subaddressSize)); /* 先发送高字节 */
while(!(base->I2SR & (1 << 1)));
ret = i2c_check_and_clear_error(base, base->I2SR);
if(ret)
{
i2c_master_stop(base);
return ret;
}
} while ((xfer->subaddressSize > 0) && (ret == I2C_STATUS_OK));
/* 读从机数据需要发送重新开始信号 */
if(xfer->direction == kI2C_Read)
{
base->I2SR &= ~(1 << 1);
i2c_master_repeated_start(base, xfer->slaveAddress, kI2C_Read);
//printf("send restart \n");
while(!(base->I2SR & (1 << 1))){};
ret = i2c_check_and_clear_error(base, base->I2SR);
//printf("read ret = %d\n",ret);
if(ret)
{
ret = I2C_STATUS_ADDRNAK;
i2c_master_stop(base);
return ret;
}
}
}
/* 发送数据 */
if((xfer->direction == kI2C_Write) && (xfer->dataSize > 0))
i2c_master_write(base,xfer->data, xfer->dataSize);
/* 读取数据 */
if((xfer->direction == kI2C_Read) && (xfer->dataSize > 0))
i2c_master_read(base, xfer->data, xfer->dataSize);
return 0;
}
AP3216C应用程序
获取AP3216C的ALS、PS、IR三个参数值,采用I2C通信。AP3216C应用程序中主要包括:
- AP3216C初始化(引脚配置)
- 读写AP3216C中的寄存器数据
- 获取ALS、PS、IR值
AP3216C的 .h文件,为正点原子例程中代码
#ifndef __BSP_AP3216C_H
#define __BSP_AP3216C_H
#include "imx6ul.h"
#define AP3216C_ADDR 0x1E /* AP3216C器件地址 */
/* AP3216C寄存器地址 */
#define AP3216C_SYSTEMCONG 0x00 /* 配置寄存器 */
#define AP3216C_INTSTATUS 0x01 /* 中断状态寄存器 */
#define AP3216C_INTCLEAR 0x02 /* 中断清除寄存器*/
#define AP3216C_IRDATALOW 0x0A /* IR数据低字节 */
#define AP3216C_IRDATAHIGH 0x0B /* IR数据高字节 */
#define AP3216C_ALSDATALOW 0x0C /* ALS数据低字节 */
#define AP3216C_ALSDATAHIGH 0x0D /* ALS数据高字节 */
#define AP3216C_PSDATALOW 0x0E /* PS数据低字节 */
#define AP3216C_PSDATAHIGH 0x0F /* PS数据高字节 */
/* 函数声明 */
unsigned char ap3216c_init(void);
unsigned char ap3216c_readonebyte(unsigned char addr, unsigned char reg);
unsigned char ap3216c_writeonebyte(unsigned char addr, unsigned char reg,unsigned char data);
void ap3216c_readdata(unsigned short *ir, unsigned short *ps, unsigned short *als);
#endif
AP3216C的 .c文件,为正点原子例程中代码
#include "bsp_ap3216c.h"
#include "bsp_i2c.h"
#include "bsp_delay.h"
#include "cc.h"
#include "stdio.h"
#include "bsp_uart.h"
/*
* @description : 初始化AP3216C
* @param : 无
* @return : 0 成功, 其他值 错误代码
*/
unsigned char ap3216c_init(void)
{
unsigned char data = 0;
/*
* 1、IO初始化,配置IO属性 使用I2C1,
* I2C1_SCL -> UART4_TXD
* I2C1_SDA -> UART4_RXD
*/
IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL,1);
IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA,1);
IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_I2C1_SCL, 0x70B0);
IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_I2C1_SDA, 0x70B0);
/* 2、初始化I2C1 */
i2c_init(I2C1);
/*
* 3、初始化AP3216C
* 复位AP3216C
*/
ap3216c_writeonebyte(AP3216C_ADDR, AP3216C_SYSTEMCONG, 0x04);
delayms(50); /* AP3216C复位至少10ms,*/
/* 开启 ALS、PS+IR模式 */
ap3216c_writeonebyte(AP3216C_ADDR, AP3216C_SYSTEMCONG, 0x03);
/* 读取刚刚写进去的 0X03 */
data = ap3216c_readonebyte(AP3216C_ADDR, AP3216C_SYSTEMCONG);
//printf("data = %x\n", data);
if(data == 0x03)
return 0; /* AP3216C 正常 */
else
return 1; /* AP3216C 失败 */
}
/*
* @description : 向AP3216C写数据
* @param - addr : 设备地址
* @param - reg : 要写入的寄存器
* @param - data : 要写入的数据
* @return : 操作结果
*/
unsigned char ap3216c_writeonebyte(unsigned char addr,
unsigned char reg,
unsigned char data)
{
unsigned char status = 0;
unsigned char writedata = data;
struct i2c_transfer masterXfer;
/* 配置I2C xfer结构体 */
masterXfer.slaveAddress = addr; /* 设备地址 */
masterXfer.direction = kI2C_Write; /* 写入数据 */
masterXfer.subaddress = reg; /* 要写入数据的寄存器地址 */
masterXfer.subaddressSize = 1; /* 地址长度 1个字节 */
masterXfer.data = &writedata; /* 要写入的数据 */
masterXfer.dataSize = 1; /* 数据长度 1个字节 */
if(i2c_master_transfer(I2C1,&masterXfer))
status = 1;
return status;
}
/*
* @description : 向AP3216C读数据
* @param - addr : 设备地址
* @param - reg : 要读取的寄存器
* @return : 读到的数据
*/
unsigned char ap3216c_readonebyte(unsigned char addr,
unsigned char reg)
{
unsigned char val = 0;
struct i2c_transfer masterXfer;
masterXfer.slaveAddress = addr;
masterXfer.direction = kI2C_Read;
masterXfer.subaddress = reg;
masterXfer.subaddressSize = 1;
masterXfer.data = &val;
masterXfer.dataSize = 1;
i2c_master_transfer(I2C1,&masterXfer);
return val;
}
/*
* @description : 读取AP3216C的原始数据,包括ALS,PS,IR,
* : 同时打开ALS,IR+PS,两次数据读取的时间间隔需要大于112.5ms
* @param - ir : ir数据
* @param - ps : 数据
* @param - als : als数据
* @return : 无
*/
void ap3216c_readdata(unsigned short *ir,
unsigned short *ps,
unsigned short *als)
{
unsigned char buf[6];
unsigned char i;
/* 循环读取数据 */
for(i=0; i<6; i++)
{
/* 读取0X0A~0X0F的数据*/
buf[i] = ap3216c_readonebyte(AP3216C_ADDR, AP3216C_IRDATALOW + i); /* +i 是地址偏移,*/
}
if(buf[0] & 0x08) /* IR_OF位为1,则数据无效 */
*ir = 0;
else
*ir = ((unsigned short)buf[1] << 2 | (buf[0] & 0x03));
*als = ((unsigned short)buf[3] << 8) | buf[2];
if(buf[4] & 0x40)
*ps = 0;
else
*ps = ((unsigned short)(buf[5] & 0x3F) << 4) | (buf[4] & 0x0F);
}