第二十章 IIC_AP3216C实验
本章,我们将继续使用ESP32-S3的硬件IIC接口去驱动AP3216C传感器,检测环境光强度(ALS)、接近距离(PS)和红外线强度(IR)等环境参数。
本章分为如下几个小节:
20.1 AP3216C介绍
20.2 硬件设计
20.3 软件设计
20.4 下载验证
20.1 AP3216C介绍
AP3216C是敦南科技推出的一款三合一环境传感器, 包含了:数字环境光传感器(ALS)、接近传感器(PS)和一个红外LED(IR)。该芯片通过IIC接口和MCU连接,并支持中断(INT)输出。AP3216C实物图如下图所示:
图20.1.1 AP3216C实物图
AP3216C的特点如下:
·IIC接口,支持高达400KHz通信速率
·支持多种工作模式(ALS、PS+IR、ALS+PS+IR等)
·内置温度补偿电路
·工作温度支持-30~80℃
·环境光传感器具有16位分辨率
·接近传感器具有10位分辨率
·红外传感器具有10位分辨率
·超小封装(4.1*2.4*1.35mm)
因为以上一些特性,AP3216C被广泛应用于智能手机上面,用来检测光强度(自动背光控制),和接近开关控制(听筒靠近耳朵,手机自动灭屏功能)。
AP3216C的框图如下图所示。
图20.1.2 AP3216C框图
AP3216C的引脚说明如下表所示。
表20.1.1 AP3216C引脚说明
AP3216C和主控芯片只需要连接SCL、SDA和INT,就可以实现驱动。其SCL和SDA同24C02、XL9555共用,连接在IO41和IO42上,INT脚连接在XL9555的P0_0上。
20.1.1 AP3216C寻址
要进行IIC通信,首先得知道器件地址,AP3216C器件地址是7位的,具体格式如下表。
表20.1.1.1 AP3216C地址格式
AP3216C器件地址为“0011110”即0x1E。读操作地址就为0x3D(0x1E << 1 | 0x1),即0011 1101;写操作地址就为0x3C(0x1E << 1 | 0x0),即0011 1100。
20.1.2 AP3216C寄存器介绍
AP3216C有一系列寄存器,由这些寄存器来控制AP3216C的工作模式,以及中断配置和数据输出等。这里我们仅介绍在本章需要用到的一些寄存器,其他寄存器的描述和说明,请大家参考AP3216C的数据手册。本章需要用到AP3216C的寄存器如下表所示。
地址 | 有效位 | 指令 | 说明 |
| | | 000:掉电模式(默认) 001:ALS功能激活 010:PS+IR功能激活 011:ALS+PS+IR功能激活 100:软复位 101:ALS单次模式 110:PS+IR单次模式 111:ALS+PS+IR单次模式 |
| | | |
| |
| | | |
| | | |
| | | |
| | | |
| |
| |
| | | |
| |
| |
表20.1.2.1 AP3216C相关寄存器及其说明
上表中,0X00是一个系统模式控制寄存器,主要在初始化的时候配置,初始化的时候,我们先设置其值为100,实行一次软复位,随后设置其值为011,开启ALS+PS+IR检测功能。
剩下的6个寄存器为数据寄存器,输出AP3216C内部三个传感器所检测到的数据(ADC值),并且还存在数据有效位以便判断寄存器的数据是否是有效的,描述如上表所示。
这里需要注意的是:读取间隔至少要大于112.5ms,因为,AP3216C内部完成一次ALS+PS+IR的数据转换,需要112.5ms的时间。
20.1.3 AP3216C时序介绍
ESP32-S3是通过IIC总线跟AP3216C进行通信的,对AP3216C相关寄存器进行写入配置,后面就是对相关数据寄存器进行读取。这里的时序主要就是写寄存器时序和读寄存器时序,我们一一介绍。
写寄存器时序
AP3216C的写寄存器时序如下图所示。
图20.1.3.1 AP3216C写寄存器时序
图中,先发送AP3216C的写操作地址0x3C(器件地址0X1E << 1 | 读写位0x0),随后发送8位寄存器地址,最后发送8位寄存器值。其中:S,表示IIC起始信号;W,表示读/写标志位(W=0表示写,W=1表示读);A,表示应答信号;P,表示IIC停止信号。
读寄存器时序图
AP3216C的读寄存器时序如下图所示。
图20.1.3.2 AP3216C读寄存器时序
图中,同样是先发送AP3216C的写操作地址0x3C,然后再发送寄存器地址,随后,重新发送起始信号(Sr),发送AP3216C的读操作地址0x3D(器件地址0X1E << 1 | 读写位0x1),然后读取寄存器值。其中:Sr,表示重新发送IIC起始信号;N,表示不对AP3216C进行应答;其他简写同上。
AP3216C的简介,我们就介绍到这里,关于该芯片的详细说明,请大家参考其数据手册。
20.2 硬件设计
1. 例程功能
开机的时候先检测AP3216C是否存在,如检测不到AP3216C,则在LCD屏上面显示报错信息。如果检测到AP3216C,则显示正常,并在主循环里面,循环读取ALS+PS+IR的传感器数据,并显示在LCD屏幕上面。
2. 硬件资源
1)USART0
U0TXD-IO43
U0RXD-IO44
2)XL9555
IIC_SDA-IO41
IIC_SCL-IO42
3)SPILCD
CS-IO21
SCK-IO12
SDA-IO11
DC-IO40(在P5端口,使用跳线帽将IO_SET和LCD_DC相连)
PWR- IO1_3(XL9555)
RST- IO1_2(XL9555)
4)AP3216C
IIC_SDA-IO41
IIC_SCL-IO42
AP_INT-IO0_0(XL9555)
3. 原理图
AP3216C相关原理图,如下图所示。
图20.2.1 AP3216C原理图
这里说明一下,AP3216C的AP_INT脚是连接在XL9555器件的IO0_0脚上,如果大家要使用AP3216C的中断输出功能,必须先初始化XL9555器件并配置IO0_0为输入功能,监测XL9555中断引脚是否有中断产生。若发现有中断产生,则判断是否是IO0_0导致的,从而检测到AP3216C的中断。在本章中,并没有用到AP3216C中断功能,所以没有对XL9555器件的IO0_0做设置。
20.3 软件设计
20.3.1 程序流程图
下面看看本实验的程序流程图:
图20.3.1 程序流程图
20.3.2 程序解析
1. AP3216C驱动代码这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。AP3216C驱动源码包括两个文件:ap3216c.cpp和ap3216c.h。
下面我们先解析ap3216c.h的程序。对AP3216C的IIC引脚和器件地址做了相关定义。
#define IIC_SCL 42
#define IIC_SDA 41
#define AP3216C_ADDR 0X1E /* 7位器件地址 */
我们选择使用IO42作为IIC时钟线,IO41作为IIC数据线,AP3216C的器件地址为0x1E。
下面我们再解析ap3216c.cpp的程序,首先先来看一下初始化函数ap3216c_init,代码如下:
/**
* @param 无
* @retval 0:初始化成功;1:初始化失败
*/
uint8_t ap3216c_init(void)
{
uint8_t temp;
Wire.begin(IIC_SDA, IIC_SCL, 400000); /* 初始化IIC连接 */
ap3216c_write_one_byte(0x00, 0X04); /* 复位AP3216C */
delay(50); /* AP3216C复位至少10ms */
ap3216c_write_one_byte(0x00, 0X03); /* 开启ALS、PS+IR */
temp = ap3216c_read_one_byte(0X00); /* 读取刚刚写进去的0X03 */
if (temp == 0X03)
{
return 0; /* AP3216C正常 */
}
else
{
return 1; /* AP3216C失败 */
}
}
在AP3216C的初始化函数中,直接调用Wire.begin函数接口就决定使用了IIC0,然后把IIC_SDA引脚和IIC_SCL引脚作为IIC0的数据线和时钟线使用。由于AP3216C手册中描述到400kHz通信频率,所以这里直接把IIC0的通信速率设为400kHz。初始化IIC后,通过调用ap3216c_write_one_byte对AP3216C进行软件复位,然后开启光强度ALS、接近距离PS和红外线强度IR功能检测。后面再通过调用该ap3216c_read_one_byte函数读一下刚刚写入的配置参数看是否正常写入,以此作为判断AP3216C是否正常的依据。
接下来,看一下前面提及到的向AP3216C寄存器写入数据的函数ap3216c_write_one_byte,代码如下。
/**
* @brief 向ap3216c指定寄存器写入一个数据
* @param reg: 要写入的寄存器
* @param data: 要写入的数据
* @retval 无
*/
void ap3216c_write_one_byte(uint8_t reg, uint8_t data)
{
Wire.beginTransmission(AP3216C_ADDR); /* 发送从机的7位器件地址到发送队列 */
Wire.write(reg); /* 发送要写入从机寄存器的地址到发送队列 */
Wire.write(data); /* 发送要写入从机寄存器的数据到发送队列 */
Wire.endTransmission();
/* IIC 发送 发送队列的数据(不带参数,表示发送stop信号,结束传输) */
}
这里的写操作流程跟前面20.1.3小节中AP3216C写寄存器时序图描述的过程是一致的。首先调用Wire.beginTransmission函数将从机地址0x1E加入到发送数据队列,然后调用Wire.write函数将要写入数据的寄存器地址加入到发送数据队列,继续调用Wire.write函数将要写入寄存器地址的数据加入到发送数据队列,最后调用Wire.endTransmission函数将数据队列的数据发送到AP3216C,最终实现对寄存器数据的写入。
继续看一下读取AP3216C寄存器数据的函数ap3216c_read_one_byte,代码如下。
/**
* @brief 在ap3216c指定寄存器读出一个数据
* @param reg: 要读取的寄存器
* @retval 寄存器的值 / 0xFF:未接收到数据
*/
uint8_t ap3216c_read_one_byte(uint8_t reg)
{
uint8_t rd_num = 0;
uint8_t rd_data = 0;
Wire.beginTransmission(AP3216C_ADDR); /* 发送从机的7位器件地址到发送队列 */
Wire.write(reg); /* 发送要读取从机的寄存器地址到发送队列 */
Wire.endTransmission(0);
/* IIC 发送 发送队列的数据(传参为0,表示重新发送一个start信号,保持IIC总线有效连接) */
rd_num = Wire.requestFrom(AP3216C_ADDR, 1); /* 主机向从机发送数据请求并获取数据 */
if (Wire.available() != 0) /* 得到已经接收到的数据字节数 */
{
return Wire.read(); /* 到数据缓冲区读取数据 */
}
return 0xFF;
}
这里的读操作流程跟前面20.1.3小节中读寄存器时序图描述的过程是一致的。首先调用Wire.beginTransmission函数将从机地址0x1E加入到发送数据队列,然后调用Wire.write函数将要要读取数据的寄存器地址加入到发送数据队列,继续调用Wire.endTransmission函数将数据队列的数据发送到AP3216C。注意:Wire.endTransmission函数带参数0表明会重新发送一个起始信号,保持IIC总线的连接。后面就通过调用Wire.requestFrom函数向AP3216C指定寄存器读取1字节数据并保存到接收缓冲区,Wire.available函数用于查询缓冲区是否有可读数据,而Wire.read函数就是用来读取缓冲区一字节数据。
初始化好AP3216C传感器之后,就可以对其采集到的数据进行获取,实现这个读取过程的函数为ap3216c_read_data,代码如下。
/**
* @brief 读取AP3216C的数据
* @note 读取原始数据,包括ALS,PS和IR
* 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
* @param ir : IR传感器值
* @param ps : PS传感器值
* @param als : ALS传感器值
* @retval 无
*/
void ap3216c_read_data(uint16_t *ir, uint16_t *ps, uint16_t *als)
{
uint8_t buf[6];
uint8_t i;
for (i = 0; i < 6; i++)
{
buf = ap3216c_read_one_byte(0X0A + i); /* 循环读取所有传感器数据 */
}
if (buf[0] & 0X80)
{
*ir = 0; /* IR_OF位为1,则数据无效 */
}
else
{
*ir = ((uint16_t)buf[1] << 2) | (buf[0] & 0X03); /* 读取IR传感器数据 */
}
*als = ((uint16_t)buf[3] << 8) | buf[2]; /* 读取ALS传感器数据 */
if (buf[4] & 0x40)
{
*ps = 0; /* IR_OF位为1,则数据无效 */
}
else
{
/* 读取PS传感器的数据 */
*ps = ((uint16_t)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
}
}
该函数用于读取AP3216C传感器的ALS+PS+IR数据。通过调用ap3216c_read_data函数读取相关寄存器的值,然后判断数据有效位,再整合得到IR值、ALS值、PS值。
2. 14_iic_ap3216c.ino代码在14_iic_ap3216c.ino里面编写如下代码:
#include "uart.h"
#include "xl9555.h"
#include "spilcd.h"
#include "ap3216c.h"
uint16_t ir, als, ps; /* 光照强度(ALS)/接近距离(PS)/红外光强(IR) */
/**
* @brief 当程序开始执行时,将调用setup()函数,通常用来初始化变量、函数等
* @param 无
* @retval 无
*/
void setup()
{
uart_init(0, 115200); /* 串口0初始化 */
xl9555_init(); /* IO扩展芯片初始化 */
lcd_init(); /* LCD初始化 */
lcd_show_string(30, 50, 200, 16, LCD_FONT_16, "ESP32-S3", RED);
lcd_show_string(30, 70, 200, 16, LCD_FONT_16, "AP3216C TEST", RED);
lcd_show_string(30, 90, 200, 16, LCD_FONT_16, "ATOM@ALIENTEK", RED);
while (ap3216c_init()) /* 检测不到AP3216C */
{
lcd_show_string(30,130,200,16,LCD_FONT_16,"AP3216C Check Failed!", RED);
delay(500);
}
lcd_show_string(30, 130, 200, 16, LCD_FONT_16, "AP3216C Ready!", RED);
lcd_show_string(30, 160, 200, 16, LCD_FONT_16, " IR:", RED);
lcd_show_string(30, 180, 200, 16, LCD_FONT_16, " PS:", RED);
lcd_show_string(30, 200, 200, 16, LCD_FONT_16, "ALS:", RED);
}
/**
* @brief 循环函数,通常放程序的主体或者需要不断刷新的语句
* @param 无
* @retval 无
*/
void loop()
{
ap3216c_read_data(&ir, &ps, &als); /* 读取数据 */
lcd_show_num(30 + 32, 160, ir, 5, LCD_FONT_16, BLUE); /* 显示IR数据 */
lcd_show_num(30 + 32, 180, ps, 5, LCD_FONT_16, BLUE); /* 显示PS数据 */
lcd_show_num(30 + 32, 200, als, 5, LCD_FONT_16, BLUE); /* 显示ALS数据 */
delay(500);
}
在setup函数中,调用uart_init函数完成串口初始化,调用xl9555_init函数完成XL9555初始化,调用lcd_init函数完成LCD屏初始化,LCD显示实验信息,后面通过调用ap3216c_init函数完成AP3216C初始化,LCD显示IR、PS、ALS信息。
在loop函数中,间隔500毫秒调用ap3216c_read_data函数获取AP3216C传感器的IR、PS和ALS数据,然后在LCD进行显示。
20.4 下载验证
下载代码后,可以看到LCD显示如下图所示。
图20.4.1 AP3216C实验测试图
大家可以用手遮挡/靠近AP3216C传感器,可以看到三个传感器的数据变化,说明我们的代码是正常的。