imx6ull-裸机学习实验16——I2C 实验

目录

前言

I2C简介

基本特性​​

[I2C 协议](#I2C 协议)

起始位

停止位

数据传输

应答信号

[I2C 写时序](#I2C 写时序)

[I2C 读时序](#I2C 读时序)

[I.MX6U I2C](#I.MX6U I2C)

简介

寄存器

地址寄存器I2Cx_IADR(x=1~4)

分频寄存器I2Cx_IFDR

控制寄存器I2Cx_I2CR

状态寄存器I2Cx_I2SR

数据寄存器I2Cx_I2DR

[AP3216C 简介](#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 位地址为例):

  1. START 信号(SDA 由高→低,SCL 高电平)
  2. 发送从机地址 + 写位(0)(7位地址 + 1位方向,共 8 位)
  3. 从机应答 ACK(SDA 被从机拉低)
  4. 发送数据字节(8 位)
  5. 从机应答 ACK
  6. 重复 4~5(可选,连续写入多字节)
  7. 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 这三个传感器的值,然后使用串口打印出来。

配置步骤如下:

  1. 初始化相应的 IO:初始化 I2C1 相应的 IO,设置其复用功能,如果要使用 AP3216C 中断功能的话,还需要设置 AP3216C 的中断 IO。
  2. 初始化 I2C1:初始化 I2C1 接口,设置波特率。
  3. 初始化 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的值随之改变。

相关推荐
lixzest1 天前
Vim 快捷键速查表
linux·编辑器·vim
ICscholar1 天前
ExaDigiT/RAPS
linux·服务器·ubuntu·系统架构·运维开发
sim20201 天前
systemctl isolate graphical.target命令不能随便敲
linux·mysql
米高梅狮子1 天前
4. Linux 进程调度管理
linux·运维·服务器
再创世纪1 天前
让USB打印机变网络打印机,秀才USB打印服务器
linux·运维·网络
fengyehongWorld1 天前
Linux ssh端口转发
linux·ssh
知识分享小能手1 天前
Ubuntu入门学习教程,从入门到精通, Ubuntu 22.04中的Shell编程详细知识点(含案例代码)(17)
linux·学习·ubuntu
Xの哲學1 天前
深入解析 Linux systemd: 现代初始化系统的设计与实现
linux·服务器·网络·算法·边缘计算
龙月2 天前
journalctl命令以及参数详解
linux·运维
EndingCoder2 天前
TypeScript 的基本类型:数字、字符串和布尔
linux·ubuntu·typescript