imx6ull-裸机学习实验16——I2C 实验
目录
前言
I2C简介
基本特性
I2C 协议
起始位
停止位
数据传输
应答信号
I2C 写时序
I2C 读时序
I.MX6U I2C
简介
寄存器
地址寄存器I2Cx_IADR(x=1~4)
分频寄存器I2Cx_IFDR
控制寄存器I2Cx_I2CR
状态寄存器I2Cx_I2SR
数据寄存器I2Cx_I2DR
AP3216C 简介
硬件原理图
实验程序编写
i2c.h
i2c.c
ap3216c.h
ap3216c.c
main.c
前言
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 的数据传输。
#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。
/** @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,发送一个重复开始信号,发送开始信号的时候也会顺带发送从设备地址。
/** @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,发送一个开始信号,发送开始信号的时候也顺带发送从设备地址。
/** @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,检查并清除错误。
/** @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,产生一个停止信号。
/** @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 从设备写数据。
/** @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 从设备读数据。
/** @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 读/写时序。
/** @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 的设备地址和寄存器地址。
#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。
/** @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 写入数据。
/** @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 读取数据。
/** @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 传感器数据。
/** @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 上显示出来。
#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的值随之改变。