目录
[I2C 协议](#I2C 协议)
[I2C 写时序](#I2C 写时序)
[I2C 读时序](#I2C 读时序)
[I.MX6U I2C](#I.MX6U I2C)
[AP3216C 简介](#AP3216C 简介)
前言
I2C 是最常用的通信接口,众多的传感器都会提供 I2C 接口来和主控相连,比如陀螺仪、加速度计、触摸屏等等。
I.MX6U 有 4 个 I2C 接口,其中I2C1 接口连接了一个距离传感器 AP3216C,本讲实验我们就来学习如何使用 I.MX6U 的 I2C 接口来驱动 AP3216C,读取AP3216C 的传感器数据。
I2C简介
I²C(Inter-Integrated Circuit)是一种同步、串行、半双工的通信协议,由飞利浦(现恩智浦)在1980年代设计,主要用于短距离板级设备间的通信。以下是其核心要点:
基本特性
- 两根信号线 :
- SCL(Serial Clock):时钟线,由主设备控制。
- SDA(Serial Data):双向数据线。
- 这两条数据线需要接上拉电阻****(一般是 4.7kΩ),总线空闲的时候 SCL 和 SDA 处于高电平。
- 多主多从架构:支持多个主设备(需仲裁)和多个从设备(通过地址区分)。

- 地址寻址:每个从设备有唯一地址(7位或10位),主设备通过地址发起通信。
- 速度模式 :
- 标准模式(100 kbps)
- 快速模式(400 kbps)
- 高速模式(3.4 Mbps)
关键机制
- 时钟同步:多个主设备可通过SCL线"线与"同步时钟。
- 仲裁:若两个主设备同时发送,SDA数据冲突时,高电平设备退出。
- 上拉电阻:SCL和SDA需接上拉电阻(通常4.7kΩ),确保空闲时为高电平。
I2C 协议
起始位
I2C 通信起始标志,通过这个起始位就可以告诉 I2C 从机,主机要开始进行 I2C 通信了。
SCL为高电平时,SDA由高→低跳变,标志通信开始,如图:

停止位
停止位就是停止 I2C 通信的标志位,和起始位的功能相反。
SCL为高电平时,SDA由低→高跳变,结束通信,如图:

数据传输
I2C 总线在数据传输的时候要保证在 SCL 高电平期间, SDA 上的数据稳定,因此 SDA 上的数据变化只能在 SCL 低电平期间发生,如图:

应答信号
当 I2C 主机发送完 8 位数据以后会将 SDA 设置为输入状态,等待 I2C 从机应答,也就是等到 I2C 从机告诉主机它接收到数据了。

从机通过将 SDA 拉低来表示发出应答信号,表示通信成功,否则表示通信失败。
- ACK(确认):接收方(从机或主机)成功接收数据后,拉低SDA表示应答。
- NACK(非确认):接收方未成功接收(或无需继续通信),保持SDA高电平。
常见场景:
- ACK:从机地址匹配、数据接收成功。
- NACK:从机地址不匹配、接收方忙、主机终止读取(最后一次字节)。
I2C 写时序
I2C 总线单字节写时序如图:

步骤(以 7 位地址为例):
- START 信号(SDA 由高→低,SCL 高电平)
- 发送从机地址 + 写位(0)(7位地址 + 1位方向,共 8 位)
- 从机应答 ACK(SDA 被从机拉低)
- 发送数据字节(8 位)
- 从机应答 ACK
- 重复 4~5(可选,连续写入多字节)
- STOP 信号(SDA 由低→高,SCL 高电平)
I2C 读时序
读时序分为 4 大步:
- 发送设备地址,
- 发送要读取的寄存器地址,
- 重新发送设备地址,
- I2C 从器件输出要读取的寄存器值

I2C 多字节读写时序:
多字节读写时序和单字节的基本一致,只是在读写数据的时候可以连续发送多个自己的数据,
I.MX6U I2C
简介
I.MX6U 提供了 4 个 I2C 外设,通过这四个 I2C 外设即可完成与 I2C 从器件进行通信。
I.MX6U 的 I2C 支持两种模式:标准模式和快速模式,标准模式下 I2C 数据传输速率最高是 100Kbits/s,在快速模式下数据传输速率最高为 400Kbits/s。
寄存器
我们接下来看一下 I2C 的几个重要的寄存器:
- 地址寄存器I2Cx_IADR(x=1~4)
- 分频寄存器I2Cx_IFDR
- 控制寄存器I2Cx_I2CR
- 状态寄存器I2Cx_I2SR
- 数据寄存器I2Cx_I2DR
地址寄存器I2Cx_IADR(x=1~4)
寄存器 I2Cx_IADR 只有 ADR(bit7:1)位有效,用来保存 I2C 从设备地址数据。当我们要访问某个 I2C 从设备的时候就需要将其设备地址写入到 ADR 里面。

分频寄存器I2Cx_IFDR
I2C_IFDR提供了一个可编程的预分频器,用于配置时钟以选择比特率。寄存器不会通过软件重置而重置。
I2C时钟来自PERCLK_ROOT,后者从IPG_CLK_ROOT路由。I2C时钟频率可以通过使用以下公式轻松获得:

寄存器 I2Cx_IFDR 也只有**IC(bit5:0)**这个位,用来设置 I2C 的波特率:

I2C 的时钟源可以选择 IPG_CLK_ROOT=66MHz,通过设置 IC 位来设置波特率。 IC 位可选的设置如图:

这就是IC 的所有可选值。如果I2C的时钟源为66MHz,我们要设置I2C的波特率为100KHz,那么IC就可以设置为0X15,也就是 640 分频。 66000000/640=103.125KHz≈100KHz。
控制寄存器I2Cx_I2CR
I2C_I2CR用于启用I2C和I2C中断。它还包含主从模式选择位。

IEN(bit7): I2C 使能位,为 1 的时候使能 I2C,为 0 的时候关闭 I2C。
IIEN(bit6): I2C 中断使能位,为 1 的时候使能 I2C 中断,为 0 的时候关闭 I2C 中断。

MSTA(bit5):主从模式选择位,设置 IIC 工作在主模式还是从模式,为 1 的时候工作在主模式,为 0 的时候工作在从模式。

MTX(bit4):传输方向选择位,用来设置是进行发送还是接收,为 0 的时候是接收,为 1 的时候是发送。
TXAK(bit3):传输应答位使能,为 0 的话发送 ACK 信号,为 1 的话发送 NO ACK 信号。
RSTA(bit2):重复开始信号,为 1 的话产生一个重新开始信号。

状态寄存器I2Cx_I2SR

ICF(bit7):数据传输状态位,为 0 的时候表示数据正在传输,为 1 的时候表示数据传输完成。
IAAS(bit6):当为 1 的时候表示 I2C 地址,也就是 I2Cx_IADR 寄存器中的地址是从设备地址。
IBB(bit5): I2C 总线忙标志位,当为 0 的时候表示 I2C 总线空闲,为 1 的时候表示 I2C 总线忙。
IAL(bit4):仲裁丢失位,为 1 的时候表示发生仲裁丢失。

SRW(bit2):从机读写状态位,当 I2C 作为从机的时候使用,此位用来表明主机发送给从机的是读还是写命令。为 0 的时候表示主机要向从机写数据,为 1 的时候表示主机要从从机读取数据。
IIF(bit1): I2C 中断挂起标志位,当为 1 的时候表示有中断挂起,此位需要软件清零。
RXAK(bit0): 应答信号标志位,为 0 的时候表示接收到 ACK 应答信号,为 1 的话表示检测到 NO ACK 信号。
数据寄存器I2Cx_I2DR
寄存器 I2Cx_I2DR,是数据寄存器,此寄存器只有低 8 位有效,当要发送数据的时候将要发送的数据写入到此寄存器,如果要接收数据的话直接读取此寄存器即可得到接收到的数据。
AP3216C 简介
AP3216C是一个三合一环境传感器,支持环境光强度(ALS)、接近距离(PS)和红外线强度(IR)这三个环境参数检测。
I.MX6U-ALPHA 开发板上通过 I2C1 连接的该传感器。
AP3216C 常被用于手机、平板、导航设备等,其内置的接近传感器可以用于检测是否有物体接近,比如手机上用来检测耳朵是否接触听筒,如果检测到的话就表示正在打电话,手机就会关闭手机屏幕以省电。也可以使用环境光传感器检测光照强度,可以实现自动背光亮度调节。
AP3216C 结构如图:

AP3216 的设备地址为 0X1E,我们等会写代码发送的从机地址就填这个。
AP3216C 内部也有一些寄存器,通过这些寄存器我们可以配置 AP3216C 的工作模式,并且读取相应的数据。
0X00 这个寄存器是模式控制寄存器,用来设置 AP3216C 的工作模式,先设置为 0X04,也就是先软件复位一次 AP3216C,再设置为 0X03,也就是开启 ALS+PS+IR。
0X0A~0X0F 这 6 个寄存器就是数据寄存器,保存着 ALS、 PS 和 IR 这三个传感器获取到的数据值。如果同时打开 ALS、PS 和 IR 则读取间隔最少要 112.5ms(数据转换间隔)。
硬件原理图
本讲实验我们通过 I.MX6U 的 I2C1 来读取 AP3216C 内部的 ALS、 PS 和 IR 这三个传感器的值,然后使用串口打印出来。
配置步骤如下:
- 初始化相应的 IO:初始化 I2C1 相应的 IO,设置其复用功能,如果要使用 AP3216C 中断功能的话,还需要设置 AP3216C 的中断 IO。
- 初始化 I2C1:初始化 I2C1 接口,设置波特率。
- 初始化 AP3216C:初始化 AP3216C,读取 AP3216C 的数据。
本试验用到的资源如下:指示灯 LED0、AP3216C、串口。
AP3216C 是在 I.MX6U-ALPHA 开发板底板上,原理图如图:

AP3216C 使用的是 I2C1,其中 I2C1_SCL 使用的 UART4_TXD 这个IO、 I2C1_SDA 使用的是 UART4_R XD 这个 IO。
实验程序编写
在 bsp 文件夹下创建名为"i2c"和"ap3216c"的文件夹。
在 bsp/i2c 中新建 bsp_i2c.c 和 bsp_i2c.h 这两个文件,是I.MX6U 的 I2C 文件.
在 bsp/ap3216c 中新建 bsp_ap3216c.c 和 bsp_ap3216c.h 这两个文件,是 AP3216C 的驱动文件。
i2c.h
定义了一些I2C状态相关的宏。
定义了一个枚举类型i2c_direction,此枚举类型用来表示 I2C 主机对从机的操作,也就是读数据还是写数据。
定义了一个结构体 i2c_transfer,此结构体用于 I2C 的数据传输。
cpp
#ifndef _BSP_I2C_H
#define _BSP_I2C_H
#include "imx6ul.h"
/* 相关宏定义 */
#define I2C_STATUS_OK (0)
#define I2C_STATUS_BUSY (1)
#define I2C_STATUS_IDLE (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 = 0x0, /* 主机向从机写数据 */
kI2C_Read = 0x1, /* 主机从从机读数据 */
} ;
/*
* 主机传输结构体
*/
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
i2c.c中一共有8个函数:
- 函数i2c_init
- 函数i2c_master_repeated_start
- 函数 i2c_master_start
- 函数 i2c_check_and_clear_error
- 函数 i2c_master_stop
- 函数i2c_master_write
- 函数 i2c_master_read
- 函数 i2c_master_transfer
函数i2c_init,此函数用来初始化 I2C,重点是设置 I2C 的波特率,初始化完成以后开启 I2C。
cpp
/*
* @description : 初始化I2C,波特率100KHZ
* @param - base : 要初始化的IIC设置
* @return : 无
*/
void i2c_init(I2C_Type *base)
{
/* 1、配置I2C */
base->I2CR &= ~(1 << 7); /* 要访问I2C的寄存器,首先需要先关闭I2C */
/* 设置波特率为100K
* I2C的时钟源来源于IPG_CLK_ROOT=66Mhz
* IC2 时钟 = PERCLK_ROOT/dividison(IFDR寄存器)
* 设置寄存器IFDR,IFDR寄存器参考IMX6UL参考手册P1260页,表29-3,
* 根据表29-3里面的值,挑选出一个还是的分频数,比如本例程我们
* 设置I2C的波特率为100K, 因此当分频值=66000000/100000=660.
* 在表29-3里面查找,没有660这个值,但是有640,因此就用640,
* 即寄存器IFDR的IC位设置为0X15
*/
base->IFDR = 0X15 << 0;
/*
* 设置寄存器I2CR,开启I2C
* bit[7] : 1 使能I2C,I2CR寄存器其他位其作用之前,此位必须最先置1
*/
base->I2CR |= (1<<7);
}
函数i2c_master_repeated_start,发送一个重复开始信号,发送开始信号的时候也会顺带发送从设备地址。
cpp
/*
* @description : 发送重新开始信号
* @param - base : 要使用的IIC
* @param - addrss : 设备地址
* @param - direction : 方向
* @return : 0 正常 其他值 出错
*/
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] : 要发送的数据,这里写入从设备地址
* 参考资料:IMX6UL参考手册P1249
*/
base->I2DR = ((unsigned int)address << 1) | ((direction == kI2C_Read)? 1 : 0);
return 0;
}
函数 i2c_master_start,发送一个开始信号,发送开始信号的时候也顺带发送从设备地址。
cpp
/*
* @description : 发送开始信号
* @param - base : 要使用的IIC
* @param - addrss : 设备地址
* @param - direction : 方向
* @return : 0 正常 其他值 出错
*/
unsigned char i2c_master_start(I2C_Type *base, unsigned char address, enum i2c_direction direction)
{
if(base->I2SR & (1 << 5)) /* I2C忙 */
return 1;
/*
* 设置寄存器I2CR
* bit[5]: 1 主模式
* bit[4]: 1 发送
*/
base->I2CR |= (1 << 5) | (1 << 4);
/*
* 设置寄存器I2DR
* bit[7:0] : 要发送的数据,这里写入从设备地址
* 参考资料:IMX6UL参考手册P1249
*/
base->I2DR = ((unsigned int)address << 1) | ((direction == kI2C_Read)? 1 : 0);
return 0;
}
函数 i2c_check_and_clear_error,检查并清除错误。
cpp
/*
* @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; /* 返回NAK(No acknowledge) */
}
return I2C_STATUS_OK;
}
函数 i2c_master_stop,产生一个停止信号。
cpp
/*
* @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;
}
函数i2c_master_write ,向 I2C 从设备写数据。
cpp
/*
* @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中的数据写入到I2DR寄存器 */
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); /* 发送停止信号 */
}
函数 i2c_master_read,从 I2C 从设备读数据。
cpp
/*
* @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)); /* 接收数据 */
/* 如果只接收一个字节数据的话发送NACK信号 */
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;
}
}
函数 i2c_master_transfer,此函数就是用户最终调用的,用于完成 I2C通信的函数,此函数会使用前面的函数拼凑出 I2C 读/写时序。
cpp
/*
* @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) & 0X1)){};
/* 如果是读的话,要先发送寄存器地址,所以要先将方向改为写 */
if ((xfer->subaddressSize > 0) && (xfer->direction == kI2C_Read))
{
direction = kI2C_Write;
}
ret = i2c_master_start(base, xfer->slaveAddress, direction); /* 发送开始信号 */
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)); //向I2DR寄存器写入子地址
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); /* 发送重复开始信号和从机地址 */
while(!(base->I2SR & (1 << 1))){};/* 等待传输完成 */
/* 检查是否有错误发生 */
ret = i2c_check_and_clear_error(base, base->I2SR);
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.h
我们使用前面编写的 I2C 操作函数来配置 AP3216C ,配置完成以后就可以读取 AP3216C 里面的传感器数据。
ap3216c.h文件里定义了一些宏,分别为 AP3216C 的设备地址和寄存器地址。
cpp
#ifndef _BSP_AP3216C_H
#define _BSP_AP3216C_H
#include "imx6ul.h"
#define AP3216C_ADDR 0X1E /* AP3216C器件地址 */
/* AP3316C寄存器 */
#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
文件 bsp_ap3216c.c 里面共有 4 个函数,
- 函数 ap3216c_init
- 函数ap3216c_writeonebyte
- 函数ap3216c_readonebyte
- 函数 ap3216c_readdata
函数ap3216c_init,用于初始化 AP3216C。
此函数先初始化所使用到的 IO,比如初始化 I2C1 的相关 IO,并设置其复用为 I2C1。然后此函数会调用 i2c_init来初始化 I2C1,最后初始化 AP3216C。
cpp
/*
* @description : 初始化AP3216C
* @param : 无
* @return : 0 成功,其他值 错误代码
*/
unsigned char ap3216c_init(void)
{
unsigned char data = 0;
/* 1、IO初始化,配置I2C IO属性
* 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);
/*
*bit 16:0 HYS关闭
*bit [15:14]: 1 默认47K上拉
*bit [13]: 1 pull功能
*bit [12]: 1 pull/keeper使能
*bit [11]: 0 关闭开路输出
*bit [7:6]: 10 速度100Mhz
*bit [5:3]: 110 驱动能力为R0/6
*bit [0]: 1 高转换率
*/
IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_I2C1_SCL, 0x70B0);
IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_I2C1_SDA, 0X70B0);
i2c_init(I2C1); /* 初始化I2C1 */
/* 2、初始化AP3216C */
ap3216c_writeonebyte(AP3216C_ADDR, AP3216C_SYSTEMCONG, 0X04); /* 复位AP3216C */
delayms(50); /* AP33216C复位至少10ms */
ap3216c_writeonebyte(AP3216C_ADDR, AP3216C_SYSTEMCONG, 0X03); /* 开启ALS、PS+IR */
data = ap3216c_readonebyte(AP3216C_ADDR, AP3216C_SYSTEMCONG); /* 读取刚刚写进去的0X03 */
if(data == 0X03)
return 0; /* AP3216C正常 */
else
return 1; /* AP3216C失败 */
}
函数 ap3216c_writeonebyte ,向 AP3216C 写入数据。
cpp
/*
* @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; /* 地址长度一个字节 */
masterXfer.data = &writedata; /* 要写入的数据 */
masterXfer.dataSize = 1; /* 写入数据长度1个字节 */
if(i2c_master_transfer(I2C1, &masterXfer))
status=1;
return status;
}
函数ap3216c_readonebyte,从 AP3216C 读取数据。
cpp
/*
* @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; /* 读取数据长度1个字节 */
i2c_master_transfer(I2C1, &masterXfer);
return val;
}
函数 ap3216c_readdata,读取 AP3216C 中的 ALS、 PS 和 IR 传感器数据。
cpp
/*
* @description : 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!
* : 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
* @param - ir : ir数据
* @param - ps : ps数据
* @param - ps : 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++)
{
buf[i] = ap3216c_readonebyte(AP3216C_ADDR, AP3216C_IRDATALOW + i);
}
if(buf[0] & 0X80) /* IR_OF位为1,则数据无效 */
*ir = 0;
else /* 读取IR传感器的数据 */
*ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
*als = ((unsigned short)buf[3] << 8) | buf[2]; /* 读取ALS传感器的数据 */
if(buf[4] & 0x40) /* IR_OF位为1,则数据无效 */
*ps = 0;
else /* 读取PS传感器的数据 */
*ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
}
main.c
首先调用 ap3216c_init 来初始化 AP3216C,如果 AP3216C 初始化失败的话就会进入循环,会在 LCD 上不断的闪烁字符串"AP3216C Check Failed!"和"Please Check!",直到 AP3216C初始化成功。
在while(1)里循环调用函数 ap3216c_readdata 来获取 AP3216C 的 ALS、 PS 和 IR 传感器数据值,获取完成以后就会在 LCD 上显示出来。
cpp
#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_led.h"
#include "bsp_beep.h"
#include "bsp_key.h"
#include "bsp_int.h"
#include "bsp_uart.h"
#include "bsp_lcd.h"
#include "bsp_lcdapi.h"
#include "bsp_rtc.h"
#include "bsp_ap3216c.h"
#include "stdio.h"
/*
* @description : main函数
* @param : 无
* @return : 无
*/
int main(void)
{
unsigned short ir, als, ps;
unsigned char state = OFF;
int_init(); /* 初始化中断(一定要最先调用!) */
imx6u_clkinit(); /* 初始化系统时钟 */
delay_init(); /* 初始化延时 */
clk_enable(); /* 使能所有的时钟 */
led_init(); /* 初始化led */
beep_init(); /* 初始化beep */
uart_init(); /* 初始化串口,波特率115200 */
lcd_init(); /* 初始化LCD */
tftlcd_dev.forecolor = LCD_RED;
lcd_show_string(30, 50, 200, 16, 16, (char*)"ALPHA-IMX6U IIC TEST");
lcd_show_string(30, 70, 200, 16, 16, (char*)"AP3216C TEST");
lcd_show_string(30, 90, 200, 16, 16, (char*)"ATOM@ALIENTEK");
lcd_show_string(30, 110, 200, 16, 16, (char*)"2019/3/26");
while(ap3216c_init()) /* 检测不到AP3216C */
{
lcd_show_string(30, 130, 200, 16, 16, (char*)"AP3216C Check Failed!");
delayms(500);
lcd_show_string(30, 130, 200, 16, 16, (char*)"Please Check! ");
delayms(500);
}
lcd_show_string(30, 130, 200, 16, 16, (char*)"AP3216C Ready!");
lcd_show_string(30, 160, 200, 16, 16, (char*)" IR:");
lcd_show_string(30, 180, 200, 16, 16, (char*)" PS:");
lcd_show_string(30, 200, 200, 16, 16, (char*)"ALS:");
tftlcd_dev.forecolor = LCD_BLUE;
while(1)
{
ap3216c_readdata(&ir, &ps, &als); /* 读取数据 */
lcd_shownum(30 + 32, 160, ir, 5, 16); /* 显示IR数据 */
lcd_shownum(30 + 32, 180, ps, 5, 16); /* 显示PS数据 */
lcd_shownum(30 + 32, 200, als, 5, 16); /* 显示ALS数据 */
delayms(120);
state = !state;
led_switch(LED0,state);
}
return 0;
}
使用 Make 命令编译代码,编译成功以后使用软件 imxdownload 将编译完成的 ap3216c.bin文件下载到 SD 卡中,烧写成功以后将 SD 卡插到开发板的 SD 卡槽中,然后复位开发板。
还记得我们说过,AP3216C是一个三合一环境传感器,支持环境光强度(ALS)、接近距离(PS)和红外线强度(IR)这三个环境参数检测。实验现象大概就是,光线改变,ALS的值随之改变;用手靠近或者远离AP3216C,PS的值随之改变。