I2C实验

I2C 是最常用的通信接口,众多的传感器都会提供I2C 接口来和主控相连,比如陀螺仪、加速度计、触摸屏等等。所以I2C 是做嵌入式开发必须掌握的,I.MX6U 有4 个I2C 接口,可以通过这4 个I2C 接口来连接一些I2C 外设。I.MX6U-ALPHA 使用I2C1 接口连接了一个距离传感器AP3216C,本章我们就来学习如何使用I.MX6U 的I2C 接口来驱动AP3216C,读取AP3216C 的传感器数据。

在这里插入图片描述
这里是引用

I2C & AP3216C 简介

I2C 协议

I2C 是很常见的一种总线协议, I2C 是 NXP 公司设计的, I2C 使用两条线在主控制器和从机之间进行数据通信。一条是 SCL(串行时钟线),另外一条是 SDA(串行数据线),这两条数据线需要接上拉电阻,总线空闲的时候 SCL 和 SDA 处于高电平。 I2C 总线标准模式下速度可以达到 100Kb/S,快速模式下可以达到 400Kb/S。 I2C 总线工作是按照一定的协议来运行的,接下来就看一下 I2C 协议。

I2C 是支持多从机的,也就是一个 I2C 控制器下可以挂多个 I2C 从设备,这些不同的 I2C从设备有不同的器件地址,这样 I2C 主控制器就可以通过 I2C 设备的器件地址访问指定的 I2C设备了,一个 I2C 总线连接多个 I2C 设备如下图所示:
在这里插入图片描述
图26.1.1.1 I2C 多个设备连接结构图

上图中 SDA 和 SCL 这两根线必须要接一个上拉电阻,一般是 4.7K。其余的 I2C 从器件都挂接到 SDA 和 SCL 这两根线上,这样就可以通过 SDA 和 SCL 这两根线来访问多个 I2C设备。

接下来看一下I2C 协议有关的术语:

起始位

顾名思义,也就是 I2C 通信起始标志,通过这个起始位就可以告诉 I2C 从机,“我”要开始进行 I2C 通信了。在 SCL 为高电平的时候, SDA 出现下降沿就表示为起始位,如下图所示:
在这里插入图片描述

2、停止位

停止位就是停止 I2C 通信的标志位,和起始位的功能相反。在 SCL 位高电平的时候, SDA出现上升沿就表示为停止位,如下图所示:
在这里插入图片描述

3、数据传输

I2C 总线在数据传输的时候要保证在 SCL 高电平期间, SDA 上的数据稳定,因此 SDA 上的数据变化只能在 SCL 低电平期间发生,如下图所示:
在这里插入图片描述

4、应答信号

当 I2C 主机发送完 8 位数据以后会将 SDA 设置为输入状态,等待 I2C 从机应答,也就是等到 I2C 从机告诉主机它接收到数据了。应答信号是由从机发出的,主机需要提供应答信号所需的时钟,主机发送完 8 位数据以后紧跟着的一个时钟信号就是给应答信号使用的。从机通过将 SDA 拉低来表示发出应答信号,表示通信成功,否则表示通信失败。

5、I2C 写时序(主机向从机)

主机通过 I2C 总线与从机之间进行通信不外乎两个操作:写和读, I2C 总线单字节写时序如下图所示:
在这里插入图片描述
上图就是 I2C 写时序,我们来看一下写时序的具体步骤:

  • 1)、开始信号。
  • 2)、发送 I2C 设备地址,每个 I2C 器件都有一个设备地址,通过发送具体的设备地址来决定访问哪个 I2C 器件。这是一个 8 位的数据,其中高 7 位是设备地址,最后 1 位是读写位,为1 的话表示这是一个读操作,为 0 的话表示这是一个写操作。
  • 3)、 I2C 器件地址后面跟着一个读写位,为 0 表示写操作,为 1 表示读操作。
  • 4)、从机发送的 ACK 应答信号
  • 5)、重新发送开始信号。
  • 6)、发送要写写入数据的寄存器地址。
  • 7)、从机发送的 ACK 应答信号
  • 8)、发送要写入寄存器的数据
  • 9)、从机发送的 ACK 应答信号
  • 10)、停止信号。

6、I2C 读时序(主机向从机)

I2C 总线单字节读时序如下图所示:
在这里插入图片描述
I2C 单字节读时序比写时序要复杂一点,读时序分为 4 大步,第一步是发送设备地址,第二步是发送要读取的寄存器地址,第三步重新发送设备地址,最后一步就是 I2C 从器件输出要读取的寄存器值,我们具体来看一下这步。

  • 1)、主机发送起始信号。
  • 2)、主机发送要读取的 I2C 从设备地址。
  • 3)、读写控制位,因为是向 I2C 从设备发送数据,因此是写信号。
  • 4)、从机发送的 ACK 应答信号。
  • 5)、重新发送 START 信号。
  • 6)、主机发送要读取的寄存器地址。
  • 7)、从机发送的 ACK 应答信号。
  • 8)、重新发送 START 信号。
  • 9)、重新发送要读取的 I2C 从设备地址。
  • 10)、读写控制位,这里是读信号,表示接下来是从 I2C 从设备里面读取数据。
  • 11)、从机发送的 ACK 应答信号。
  • 12)、从 I2C 器件里面读取到的数据
  • 13)、主机发出 NO ACK 信号,表示读取完成,不需要从机再发送 ACK 信号了。
  • 14)、主机发出 STOP 信号,停止 I2C 通信。

7、I2C 多字节读写时序

有时候我们需要读写多个字节,多字节读写时序和单字节的基本一致,只是在读写数据的时候可以连续发送多个自己的数据,其他的控制时序都是和单字节一样的。

I.MX6U I2C 简介

I.MX6U 提供了4 个I2C 外设,通过这四个I2C 外设即可完成与I2C 从器件进行通信,I.MX6U 的I2C 外设特性如下:

①、与标准I2C 总线兼容。
②、多主机运行
③、软件可编程的64 中不同的串行时钟序列。
④、软件可选择的应答位。
⑤、开始/结束信号生成和检测。
⑥、重复开始信号生成。
⑦、确认位生成。
⑧、总线忙检测

I.MX6U 的I2C 支持两种模式:标准模式和快速模式,标准模式下I2C 数据传输速率最高是100Kbits/s,在快速模式下数据传输速率最高为400Kbits/s。

我们接下来看一下I2C 的几个重要的寄存器,首先看一下I2Cx_IADR(x=1~4)寄存器,这是I2C 的地址寄存器,此寄存器结构如图26.1.2.1 所示:
在这里插入图片描述
寄存器I2Cx_IADR 只有ADR(bit7:1)位有效,用来保存I2C 从设备地址数据。当我们要访问某个I2C 从设备的时候就需要将其设备地址写入到ADR 里面。接下来看一下寄存器I2Cx_IFDR,这个是I2C 的分频寄存器,寄存器结构如图26.1.2.2 所示:
在这里插入图片描述
寄存器I2Cx_IFDR 也只有IC(bit5:0)这个位,用来设置I2C 的波特率,I2C 的时钟源可以选择IPG_CLK_ROOT=66MHz,通过设置IC 位既可以得到想要的I2C 波特率。IC 位可选的设置如图26.1.2.3 所示:
在这里插入图片描述
图26.1.2.3 IC 设置

不像其他外设的分频设置一样可以随意设置,图26.1.2.3 中列出了IC 的所有可选值。比如现在I2C 的时钟源为66MHz,我们要设置I2C 的波特率为100KHz,那么IC 就可以设置为0X15,也就是640 分频。66000000/640=103.125KHz≈100KHz。

接下来看一下寄存器I2Cx_I2CR,这个是I2C 控制寄存器,此寄存器结构如图26.1.2.4 所示:
在这里插入图片描述
寄存器I2Cx_I2CR 的各位含义如下:

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,这个是I2C 的状态寄存器,寄存器结构如图26.1.2.5 所示:
在这里插入图片描述

寄存器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,这是I2C 的数据寄存器,此寄存器只有低8 位有效,当要发送数据的时候将要发送的数据写入到此寄存器,如果要接收数据的话直接读取此寄存器即可得到接收到的数据。

关于I2C 的寄存器就介绍到这里,关于这些寄存器详细的描述,请参考《I.MX6ULL 参考手册》第1462 页的31.7 小节。

AP3216C 简介

I.MX6U-ALPHA 开发板上通过I2C1 连接了一个三合一环境传感器:AP3216C,AP3216C是由敦南科技推出的一款传感器,其支持环境光强度(ALS)、接近距离(PS)和红外线强度(IR)这三个环境参数检测。该芯片可以通过IIC 接口与主控制相连,并且支持中断,AP3216C 的特点如下:

①、I2C 接口,快速模式下波特率可以到400Kbit/S
②、多种工作模式选择:ALS、PS+IR、ALS+PS+IR、PD 等等。
③、内建温度补偿电路。
④、宽工作温度范围(-30°C ~ +80°C)。
⑤、超小封装,4.1mm x 2.4mm x 1.35mm
⑥、环境光传感器具有16 位分辨率。
⑦、接近传感器和红外传感器具有10 位分辨率。

AP3216C 常被用于手机、平板、导航设备等,其内置的接近传感器可以用于检测是否有物体接近,比如手机上用来检测耳朵是否接触听筒,如果检测到的话就表示正在打电话,手机就会关闭手机屏幕以省电。也可以使用环境光传感器检测光照强度,可以实现自动背光亮度调节。
AP3216C 结构如图26.1.3.1 所示:
在这里插入图片描述
AP3216 的设备地址为0X1E,同几乎所有的I2C 从器件一样,AP3216C 内部也有一些寄存器,通过这些寄存器我们可以配置AP3216C 的工作模式,并且读取相应的数据。AP3216C 我们用的寄存器如表26.1.3.1 所示:
在这里插入图片描述
在这里插入图片描述

在表26.1.3.1 中,0X00 这个寄存器是模式控制寄存器,用来设置AP3216C 的工作模式,一般开始先将其设置为0X04,也就是先软件复位一次AP3216C。接下来根据实际使用情况选择合适的工作模式,比如设置为0X03,也就是开启ALS+PS+IR。从0X0A~0X0F 这6 个寄存器就是数据寄存器,保存着ALS、PS 和IR 这三个传感器获取到的数据值。如果同时打开ALS、PS 和IR 则读取间隔最少要112.5ms,因为AP3216C 完成一次转换需要112.5ms。关于AP3216C的介绍就到这里,如果要想详细的研究此芯片的话,请大家自行查阅其数据手册。

本章实验中我们通过I.MX6U 的I2C1 来读取AP3216C 内部的ALS、PS 和IR 这三个传感器的值,并且在LCD 上显示。开机会先检测AP3216C 是否存在,一般的芯片是有个ID 寄存器,通过读取ID 寄存器判断ID 是否正确就可以检测芯片是否存在。但是AP3216C 没有ID 寄存器,所以我们就通过向寄存器0X00 写入一个值,然后再读取0X00 寄存器,判断读出得到值和写入的是否相等,如果相等就表示AP3216C 存在,否则的话AP3216C 就不存在。本章的配置步骤如下:

1、初始化相应的IO
初始化I2C1 相应的IO,设置其复用功能,如果要使用AP3216C 中断功能的话,还需要设置AP3216C 的中断IO。
2、初始化I2C1
初始化I2C1 接口,设置波特率。
3、初始化AP3216C
初始化AP3216C,读取AP3216C 的数据。

硬件原理分析

本试验用到的资源如下:
①、指示灯LED0。
②、RGB LCD 屏幕。
③、AP3216C
④、串口

AP3216C 是在I.MX6U-ALPHA 开发板底板上,原理图如图26.2.1 所示:

在这里插入图片描述
从图26.2.1 可以看出AP3216C 使用的是I2C1,其中I2C1_SCL 使用的UART4_TXD 这个IO、I2C1_SDA 使用的是UART4_R XD 这个IO。

实验程序编写

本实验对应的例程路径为:开发板光盘-> 1、裸机例程-> 17_i2c。

本章实验在上一章例程的基础上完成,更改工程名字为“ap3216c”,然后在bsp 文件夹下创建名为“i2c”和“ap3216c”的文件夹。在bsp/i2c 中新建bsp_i2c.c 和bsp_i2c.h 这两个文件,在bsp/ap3216c 中新建bsp_ap3216c.c 和bsp_ap3216c.h 这两个文件。bsp_i2c.c 和bsp_i2c.h 是I.MX6U 的I2C 文件,bsp_ap3216c.c 和bsp_ap3216c.h 是AP3216C 的驱动文件。在sp_i2c.h 中输入如下内容:

1 #ifndef _BSP_I2C_H
2 #define _BSP_I2C_H
3 /***************************************************************
4 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
5 文件名: bsp_i2c.h
6 作者: 左忠凯
7 版本: V1.0
8 描述: IIC驱动文件。
9 其他: 无
10 论坛: www.openedv.com
11 日志: 初版V1.0 2019/1/15 左忠凯创建
12 ***************************************************************/
13 #include "imx6ul.h"
14
15 /* 相关宏定义*/
16 #define I2C_STATUS_OK (0)
17 #define I2C_STATUS_BUSY (1)
18 #define I2C_STATUS_IDLE (2)
19 #define I2C_STATUS_NAK (3)
20 #define I2C_STATUS_ARBITRATIONLOST (4)
21 #define I2C_STATUS_TIMEOUT (5)
22 #define I2C_STATUS_ADDRNAK (6)
23
24 /*
25 * I2C方向枚举类型
26 */
27 enum i2c_direction
28 {
29 kI2C_Write = 0x0, /* 主机向从机写数据*/
30 kI2C_Read = 0x1, /* 主机从从机读数据*/
31 };
32
33 /*
34 * 主机传输结构体
35 */
36 struct i2c_transfer
37 {
38 unsigned char slaveAddress; /* 7位从机地址*/
39 enum i2c_direction direction; /* 传输方向*/
40 unsigned int subaddress; /* 寄存器地址*/
41 unsigned char subaddressSize; /* 寄存器地址长度*/
42 unsigned char *volatile data; /* 数据缓冲区*/
43 volatile unsigned int dataSize; /* 数据缓冲区长度*/
44 };
45
46 /*
47 *函数声明
48 */
49 void i2c_init(I2C_Type *base);
50 unsigned char i2c_master_start(I2C_Type *base,
unsigned char address,
enum i2c_direction direction);
51 unsigned char i2c_master_repeated_start(I2C_Type *base,
unsigned char address,
enum i2c_direction direction);
52 unsigned char i2c_check_and_clear_error(I2C_Type *base,
unsigned int status);
53 unsigned char i2c_master_stop(I2C_Type *base);
54 void i2c_master_write(I2C_Type *base, const unsigned char *buf,
unsigned int size);
55 void i2c_master_read(I2C_Type *base, unsigned char *buf,
unsigned int size);
56 unsigned char i2c_master_transfer(I2C_Type *base,
struct i2c_transfer *xfer);
57
58 #endif



第16 到22 行定义了一些I2C 状态相关的宏。第27 到31 行定义了一个枚举类型i2c_direction,此枚举类型用来表示I2C 主机对从机的操作,也就是读数据还是写数据。第36 到44 行定义了一个结构体i2c_transfer,此结构体用于I2C 的数据传输。剩下的就是一些函数声明了,总体来说bsp_i2c.h 文件里面的内容还是很简单的。接下来在文件bsp_i2c.c 里面输入如下内容:

示例代码26.3.2 bsp_i2c.c 文件代码
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名: bsp_i2c.c
作者: 左忠凯
版本: V1.0
描述: IIC驱动文件。
其他: 无
论坛: www.openedv.com
日志: 初版V1.0 2019/1/15 左忠凯创建
***************************************************************/
1 #include "bsp_i2c.h"
2 #include "bsp_delay.h"
3 #include "stdio.h"
4
5 /*
6 * @description : 初始化I2C,波特率100KHZ
7 * @param – base : 要初始化的IIC设置
8 * @return : 无
9 */
10 void i2c_init(I2C_Type *base)
11 {
12 /* 1、配置I2C */
13 base->I2CR &= ~(1 << 7); /* 要访问I2C的寄存器,首先需要先关闭I2C */
14
15 /* 设置波特率为100K
16 * I2C的时钟源来源于IPG_CLK_ROOT=66Mhz
17 * IFDR设置为0X15,也就是640分频,
18 * 66000000/640=103.125KHz≈100KHz。
19 */
20 base->IFDR = 0X15 << 0;
21
22 /* 设置寄存器I2CR,开启I2C */
23 base->I2CR |= (1<<7);
24 }
25
26 /*
27 * @description : 发送重新开始信号
28 * @param - base : 要使用的IIC
29 * @param - addrss : 设备地址
30 * @param - direction : 方向
31 * @return : 0 正常其他值出错
32 */
33 unsigned char i2c_master_repeated_start(I2C_Type *base,
unsigned char address,
enum i2c_direction direction)
34 {
35 /* I2C忙并且工作在从模式,跳出*/
36 if(base->I2SR & (1 << 5) && (((base->I2CR) & (1 << 5)) == 0))
37 return 1;
38
39 /*
40 * 设置寄存器I2CR
41 * bit[4]: 1 发送
42 * bit[2]: 1 产生重新开始信号
43 */
44 base->I2CR |= (1 << 4) | (1 << 2);
45
46 /*
47 * 设置寄存器I2DR,bit[7:0] : 要发送的数据,这里写入从设备地址
48 */
49 base->I2DR = ((unsigned int)address << 1) |
((direction == kI2C_Read)? 1 : 0);
50 return 0;
51 }
52
53 /*
54 * @description : 发送开始信号
55 * @param - base : 要使用的IIC
56 * @param - addrss : 设备地址
57 * @param - direction : 方向
58 * @return : 0 正常其他值出错
59 */
60 unsigned char i2c_master_start(I2C_Type *base,
unsigned char address,
enum i2c_direction direction)
61 {
62 if(base->I2SR & (1 << 5)) /* I2C忙*/
63 return 1;
64
65 /*
66 * 设置寄存器I2CR
67 * bit[5]: 1 主模式
68 * bit[4]: 1 发送
69 */
70 base->I2CR |= (1 << 5) | (1 << 4);
71
72 /*
73 * 设置寄存器I2DR,bit[7:0] : 要发送的数据,这里写入从设备地址
74 */
75 base->I2DR = ((unsigned int)address << 1) |
((direction == kI2C_Read)? 1 : 0);
76 return 0;
77 }
78
79 /*
80 * @description : 检查并清除错误
81 * @param - base : 要使用的IIC
82 * @param - status : 状态
83 * @return : 状态结果
84 */
85 unsigned char i2c_check_and_clear_error(I2C_Type *base,
unsigned int status)
86 {
87 if(status & (1<<4)) /* 检查是否发生仲裁丢失错误*/
88 {
89 base->I2SR &= ~(1<<4); /* 清除仲裁丢失错误位*/
90 base->I2CR &= ~(1 << 7); /* 先关闭I2C */
91 base->I2CR |= (1 << 7); /* 重新打开I2C */
92 return I2C_STATUS_ARBITRATIONLOST;
93 }
94 else if(status & (1 << 0)) /* 没有接收到从机的应答信号*/
95 {
96 return I2C_STATUS_NAK; /* 返回NAK(No acknowledge) */
97 }
98 return I2C_STATUS_OK;
99 }
100
101 /*
102 * @description : 停止信号
103 * @param - base : 要使用的IIC
104 * @param : 无
105 * @return : 状态结果
106 */
107 unsigned char i2c_master_stop(I2C_Type *base)
108 {
109 unsigned short timeout = 0XFFFF;
110
111 /* 清除I2CR的bit[5:3]这三位*/
112 base->I2CR &= ~((1 << 5) | (1 << 4) | (1 << 3));
113 while((base->I2SR & (1 << 5))) /* 等待忙结束*/
114 {
115 timeout--;
116 if(timeout == 0) /* 超时跳出*/
117 return I2C_STATUS_TIMEOUT;
118 }
119 return I2C_STATUS_OK;
120 }
121
122 /*
123 * @description : 发送数据
124 * @param - base : 要使用的IIC
125 * @param - buf : 要发送的数据
126 * @param - size : 要发送的数据大小
127 * @param - flags : 标志
128 * @return : 无
129 */
130 void i2c_master_write(I2C_Type *base, const unsigned char *buf,
unsigned int size)
131 {
132 while(!(base->I2SR & (1 << 7))); /* 等待传输完成*/
133 base->I2SR &= ~(1 << 1); /* 清除标志位*/
134 base->I2CR |= 1 << 4; /* 发送数据*/
135 while(size--)
136 {
137 base->I2DR = *buf++; /* 将buf中的数据写入到I2DR寄存器*/
138 while(!(base->I2SR & (1 << 1))); /* 等待传输完成*/
139 base->I2SR &= ~(1 << 1); /* 清除标志位*/
140
141 /* 检查ACK */
142 if(i2c_check_and_clear_error(base, base->I2SR))
143 break;
144 }
145 base->I2SR &= ~(1 << 1);
146 i2c_master_stop(base); /* 发送停止信号*/
147 }
148
149 /*
150 * @description : 读取数据
151 * @param - base : 要使用的IIC
152 * @param - buf : 读取到数据
153 * @param - size : 要读取的数据大小
154 * @return : 无
155 */
156 void i2c_master_read(I2C_Type *base, unsigned char *buf,
unsigned int size)
157 {
158 volatile uint8_t dummy = 0;
159
160 dummy++; /* 防止编译报错*/
161 while(!(base->I2SR & (1 << 7))); /* 等待传输完成*/
162 base->I2SR &= ~(1 << 1); /* 清除中断挂起位*/
163 base->I2CR &= ~((1 << 4) | (1 << 3)); /* 接收数据*/
164 if(size == 1) /* 如果只接收一个字节数据的话发送NACK信号*/
165 base->I2CR |= (1 << 3);
166
167 dummy = base->I2DR; /* 假读*/
168 while(size--)
169 {
170 while(!(base->I2SR & (1 << 1))); /* 等待传输完成*/
171 base->I2SR &= ~(1 << 1); /* 清除标志位*/
172
173 if(size == 0)
174 i2c_master_stop(base); /* 发送停止信号*/
175 if(size == 1)
176 base->I2CR |= (1 << 3);
177 *buf++ = base->I2DR;
178 }
179 }
180
181 /*
182 * @description : I2C数据传输,包括读和写
183 * @param – base : 要使用的IIC
184 * @param – xfer : 传输结构体
185 * @return : 传输结果,0 成功,其他值失败;
186 */
187 unsigned char i2c_master_transfer(I2C_Type *base,
struct i2c_transfer *xfer)
188 {
189 unsigned char ret = 0;
190 enum i2c_direction direction = xfer->direction;
191
192 base->I2SR &= ~((1 << 1) | (1 << 4)); /* 清除标志位*/
193 while(!((base->I2SR >> 7) & 0X1)){}; /* 等待传输完成*/
194 /* 如果是读的话,要先发送寄存器地址,所以要先将方向改为写*/
195 if ((xfer->subaddressSize > 0) && (xfer->direction ==
kI2C_Read))
196 direction = kI2C_Write;
197 ret = i2c_master_start(base, xfer->slaveAddress, direction);
198 if(ret)
199 return ret;
200 while(!(base->I2SR & (1 << 1))){}; /* 等待传输完成*/
201 ret = i2c_check_and_clear_error(base, base->I2SR);
202 if(ret)
203 {
204 i2c_master_stop(base); /* 发送出错,发送停止信号*/
205 return ret;
206 }
207
208 /* 发送寄存器地址*/
209 if(xfer->subaddressSize)
210 {
211 do
212 {
213 base->I2SR &= ~(1 << 1); /* 清除标志位*/
214 xfer->subaddressSize--; /* 地址长度减一*/
215 base->I2DR = ((xfer->subaddress) >> (8 *
xfer->subaddressSize));
216 while(!(base->I2SR & (1 << 1))); /* 等待传输完成*/
217 /* 检查是否有错误发生*/
218 ret = i2c_check_and_clear_error(base, base->I2SR);
219 if(ret)
220 {
221 i2c_master_stop(base); /* 发送停止信号*/
222 return ret;
223 }
224 } while ((xfer->subaddressSize > 0) && (ret ==
I2C_STATUS_OK));
225
226 if(xfer->direction == kI2C_Read) /* 读取数据*/
227 {
228 base->I2SR &= ~(1 << 1); /* 清除中断挂起位*/
229 i2c_master_repeated_start(base, xfer->slaveAddress,
kI2C_Read);
230 while(!(base->I2SR & (1 << 1))){}; /* 等待传输完成*/
231
232 /* 检查是否有错误发生*/
233 ret = i2c_check_and_clear_error(base, base->I2SR);
234 if(ret)
235 {
236 ret = I2C_STATUS_ADDRNAK;
237 i2c_master_stop(base); /* 发送停止信号*/
238 return ret;
239 }
240 }
241 }
242
243 /* 发送数据*/
244 if ((xfer->direction == kI2C_Write) && (xfer->dataSize > 0))
245 i2c_master_write(base, xfer->data, xfer->dataSize);
246 /* 读取数据*/
247 if ((xfer->direction == kI2C_Read) && (xfer->dataSize > 0))
248 i2c_master_read(base, xfer->data, xfer->dataSize);
249 return 0;
250 }


文件bsp_i2c.c中一共有8 个函数,我们依次来看一下这些函数的功能,首先是函数i2c_init,此函数用来初始化I2C,重点是设置I2C 的波特率,初始化完成以后开启I2C。第2 个函数是i2c_master_repeated_start,此函数用来发送一个重复开始信号,发送开始信号的时候也会顺带发送从设备地址。第3 个函数是i2c_master_start,此函数用于发送一个开始信号,发送开始信号
的时候也顺带发送从设备地址。第4 个函数是i2c_check_and_clear_error,此函数用于检查并清除错误。第5 个函数是i2c_master_stop,用于产生一个停止信号。第6 和第7 个函数分别为i2c_master_write 和i2c_master_read,这两个函数分别用于完成向I2C 从设备写数据和从I2C 从设备读数据。最后一个函数是i2c_master_transfer,此函数就是用户最终调用的,用于完成I2C
通信的函数,此函数会使用前面的函数拼凑出I2C 读/写时序。此函数就是按照26.1.1 小节讲解的I2C 读写时序来编写的。

I2C 的操作函数已经准备好了,接下来就是使用前面编写I2C 操作函数来配置AP3216C 了,配置完成以后就可以读取AP3216C 里面的传感器数据,在bsp_ap3216c.h 输入如下所示内容:

3 /***************************************************************
4 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
5 文件名: bsp_ap3216c.h
6 作者: 左忠凯
7 版本: V1.0
8 描述: AP3216C驱动头文件。
9 其他: 无
10 论坛: www.openedv.com
11 日志: 初版V1.0 2019/3/26 左忠凯创建
12 ***************************************************************/
13 #include "imx6ul.h"
14
15 #define AP3216C_ADDR 0X1E /* AP3216C器件地址*/
16
17 /* AP3316C寄存器*/
18 #define AP3216C_SYSTEMCONG 0x00 /* 配置寄存器*/
19 #define AP3216C_INTSTATUS 0X01 /* 中断状态寄存器*/
20 #define AP3216C_INTCLEAR 0X02 /* 中断清除寄存器*/
21 #define AP3216C_IRDATALOW 0x0A /* IR数据低字节*/
22 #define AP3216C_IRDATAHIGH 0x0B /* IR数据高字节*/
23 #define AP3216C_ALSDATALOW 0x0C /* ALS数据低字节*/
24 #define AP3216C_ALSDATAHIGH 0X0D /* ALS数据高字节*/
25 #define AP3216C_PSDATALOW 0X0E /* PS数据低字节*/
26 #define AP3216C_PSDATAHIGH 0X0F /* PS数据高字节*/
27
28 /* 函数声明*/
29 unsigned char ap3216c_init(void);
30 unsigned char ap3216c_readonebyte(unsigned char addr,
unsigned char reg);
31 unsigned char ap3216c_writeonebyte(unsigned char addr,
unsigned char reg,
unsigned char data);
32 void ap3216c_readdata(unsigned short *ir, unsigned short *ps,
unsigned short *als);
33
34 #endif


第15 到26 行定义了一些宏,分别为AP3216C 的设备地址和寄存器地址,剩下的就是函数声明。接下来在bsp_ap3216c.c 中输入如下所示内容:

版本: V1.0
描述: AP3216C驱动文件。
其他: 无
论坛: www.openedv.com
日志: 初版V1.0 2019/3/26 左忠凯创建
***************************************************************/
1 #include "bsp_ap3216c.h"
2 #include "bsp_i2c.h"
3 #include "bsp_delay.h"
4 #include "cc.h"
5 #include "stdio.h"
6
7 /*
8 * @description : 初始化AP3216C
9 * @param : 无
10 * @return : 0 成功,其他值错误代码
11 */
12 unsigned char ap3216c_init(void)
13 {
14 unsigned char data = 0;
15
16 /* 1、IO初始化,配置I2C IO属性
17 * I2C1_SCL -> UART4_TXD
18 * I2C1_SDA -> UART4_RXD
19 */
20 IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL, 1);
21 IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA, 1);
22 IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_I2C1_SCL, 0x70B0);
23 IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_I2C1_SDA, 0X70B0);
24
25 /* 2、初始化I2C1 */
26 i2c_init(I2C1);
27
28 /* 3、初始化AP3216C */
29 /* 复位AP3216C */
30 ap3216c_writeonebyte(AP3216C_ADDR, AP3216C_SYSTEMCONG, 0X04);
31 delayms(50); /* AP33216C复位至少10ms */
32
33 /* 开启ALS、PS+IR */
34 ap3216c_writeonebyte(AP3216C_ADDR, AP3216C_SYSTEMCONG, 0X03);
35
36 /* 读取刚刚写进去的0X03 */
37 data = ap3216c_readonebyte(AP3216C_ADDR, AP3216C_SYSTEMCONG);
38 if(data == 0X03)
39 return 0; /* AP3216C正常*/
40 else
41 return 1; /* AP3216C失败*/
42 }
43
44 /*
45 * @description : 向AP3216C写入数据
46 * @param – addr : 设备地址
47 * @param - reg : 要写入的寄存器
48 * @param – data : 要写入的数据
49 * @return : 操作结果
50 */
51 unsigned char ap3216c_writeonebyte(unsigned char addr,
unsigned char reg,
unsigned char data)
52 {
53 unsigned char status=0;
54 unsigned char writedata=data;
55 struct i2c_transfer masterXfer;
56
57 /* 配置I2C xfer结构体*/
58 masterXfer.slaveAddress = addr; /* 设备地址*/
59 masterXfer.direction = kI2C_Write; /* 写入数据*/
60 masterXfer.subaddress = reg; /* 要写入的寄存器地址*/
61 masterXfer.subaddressSize = 1; /* 地址长度一个字节*/
62 masterXfer.data = &writedata; /* 要写入的数据*/
63 masterXfer.dataSize = 1; /* 写入数据长度1个字节*/
64
65 if(i2c_master_transfer(I2C1, &masterXfer))
66 status=1;
67
68 return status;
69 }
70
71 /*
72 * @description : 从AP3216C读取一个字节的数据
73 * @param – addr : 设备地址
74 * @param - reg : 要读取的寄存器
75 * @return : 读取到的数据。
76 */
77 unsigned char ap3216c_readonebyte(unsigned char addr,
unsigned char reg)
78 {
79 unsigned char val=0;
80
81 struct i2c_transfer masterXfer;
82 masterXfer.slaveAddress = addr; /* 设备地址*/
83 masterXfer.direction = kI2C_Read; /* 读取数据*/
84 masterXfer.subaddress = reg; /* 要读取的寄存器地址*/
85 masterXfer.subaddressSize = 1; /* 地址长度一个字节*/
86 masterXfer.data = &val; /* 接收数据缓冲区*/
87 masterXfer.dataSize = 1; /* 读取数据长度1个字节*/
88 i2c_master_transfer(I2C1, &masterXfer);
89
90 return val;
91 }
92
93 /*
94 * @description : 读取AP3216C的原始数据,包括ALS,PS和IR, 注意!如果
95 * :同时打开ALS,IR+PS两次数据读取的时间间隔要大于112.5ms
96 * @param - ir : ir数据
97 * @param - ps : ps数据
98 * @param - ps : als数据
99 * @return : 无。
100 */
101 void ap3216c_readdata(unsigned short *ir, unsigned short *ps, unsigned short *als)
102 {
103 unsigned char buf[6];
104 unsigned char i;
105
106 /* 循环读取所有传感器数据*/
107 for(i = 0; i < 6; i++)
108 {
109 buf[i] = ap3216c_readonebyte(AP3216C_ADDR,
AP3216C_IRDATALOW + i);
110 }
111
112 if(buf[0] & 0X80) /* IR_OF位为1,则数据无效*/
113 *ir = 0;
114 else /* 读取IR传感器的数据*/
115 *ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
116
117 *als = ((unsigned short)buf[3] << 8) | buf[2];/* 读取ALS数据*/
118
119 if(buf[4] & 0x40) /* IR_OF位为1,则数据无效*/
120 *ps = 0;
121 else /* 读取PS传感器的数据*/
122 *ps = ((unsigned short)(buf[5] & 0X3F) << 4) |
(buf[4] & 0X0F);
123 }


文件bsp_ap3216c.c 里面共有4 个函数,第1 个函数是ap3216c_init,顾名思义,此函数用于初始化AP3216C,初始化成功的话返回0,如果初始化失败就返回其他值。此函数先初始化所使用到的IO,比如初始化I2C1 的相关IO,并设置其复用为I2C1。然后此函数会调用i2c_init来初始化I2C1,最后初始化AP3216C。第2 个和第3 个函数分别为ap3216c_writeonebyte 和
ap3216c_readonebyte,这两个函数分别是向AP3216C 写入数据和从AP3216C 读取数据。这两个函数都通过调用bsp_i2c.c 中的函数i2c_master_transfer 来完成对AP3216C 的读写。最后一个函数就是ap3216c_readdata,此函数用于读取AP3216C 中的ALS、PS 和IR 传感器数据。

最后在main.c 中输入如下代码:

/**************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名: main.c
作者: 左忠凯
版本: V1.0
描述: I.MX6U开发板裸机实验18 IIC实验
其他: IIC是最常用的接口,ALPHA开发板上有多个IIC外设,本实验就
来学习如何驱动I.MX6U的IIC接口,并且通过IIC接口读取板载
AP3216C的数据值。
论坛: www.openedv.com
日志: 初版V1.0 2019/1/15 左忠凯创建
**************************************************************/
1 #include "bsp_clk.h"
2 #include "bsp_delay.h"
3 #include "bsp_led.h"
4 #include "bsp_beep.h"
5 #include "bsp_key.h"
6 #include "bsp_int.h"
7 #include "bsp_uart.h"
8 #include "bsp_lcd.h"
9 #include "bsp_rtc.h"
10 #include "bsp_ap3216c.h"
11 #include "stdio.h"
12
13 /*
14 * @description : main函数
15 * @param : 无
16 * @return : 无
17 */
18 int main(void)
19 {
20 unsigned short ir, als, ps;
21 unsigned char state = OFF;
22
23 int_init(); /* 初始化中断(一定要最先调用!) */
24 imx6u_clkinit(); /* 初始化系统时钟*/
25 delay_init(); /* 初始化延时*/
26 clk_enable(); /* 使能所有的时钟*/
27 led_init(); /* 初始化led */
28 beep_init(); /* 初始化beep */
29 uart_init(); /* 初始化串口,波特率115200 */
30 lcd_init(); /* 初始化LCD */
31
32 tftlcd_dev.forecolor = LCD_RED;
33 lcd_show_string(30, 50, 200, 16, 16,
(char*)"ALPHA-IMX6U IIC TEST");
34 lcd_show_string(30, 70, 200, 16, 16, (char*)"AP3216C TEST");
35 lcd_show_string(30, 90, 200, 16, 16, (char*)"ATOM@ALIENTEK");
36 lcd_show_string(30, 110, 200, 16, 16, (char*)"2019/3/26");
37
38 while(ap3216c_init()) /* 检测不到AP3216C */
39 {
40 lcd_show_string(30, 130, 200, 16, 16,
(char*)"AP3216C Check Failed!");
41 delayms(500);
42 lcd_show_string(30, 130, 200, 16, 16,
(char*)"Please Check! ");
43 delayms(500);
44 }
45
46 lcd_show_string(30, 130, 200, 16, 16, (char*)"AP3216C Ready!");
47 lcd_show_string(30, 160, 200, 16, 16, (char*)" IR:");
48 lcd_show_string(30, 180, 200, 16, 16, (char*)" PS:");
49 lcd_show_string(30, 200, 200, 16, 16, (char*)"ALS:");
50 tftlcd_dev.forecolor = LCD_BLUE;
51 while(1)
52 {
53 ap3216c_readdata(&ir, &ps, &als); /* 读取数据*/
54 lcd_shownum(30 + 32, 160, ir, 5, 16); /* 显示IR数据*/
55 lcd_shownum(30 + 32, 180, ps, 5, 16); /* 显示PS数据*/
56 lcd_shownum(30 + 32, 200, als, 5, 16); /* 显示ALS数据*/
57 delayms(120);
58 state = !state;
59 led_switch(LED0,state);
60 }
61 return 0;
62 }

第38 行调用ap3216c_init 来初始化AP3216C,如果AP3216C 初始化失败的话就会进入循环,会在LCD 上不断的闪烁字符串“AP3216C Check Failed!”和“Please Check!”,直到AP3216C初始化成功。

第53 行调用函数ap3216c_readdata 来获取AP3216C 的ALS、PS 和IR 传感器数据值,获取完成以后就会在LCD 上显示出来。

文件main.c 里面的内容总体上还是很简单的,实验程序的编写就到这里。

编译下载验证

编写Makefile 和链接脚本

修改Makefile 中的TARGET 为ap3216c,然后在在INCDIRS 和SRCDIRS 中加入“bsp/i2c”和“bsp/ap3216c”,修改后的Makefile 如下:

示例代码26.4.1.1 Makefile 文件代码
1 CROSS_COMPILE ?= arm-linux-gnueabihf-
2 TARGET ?= ap3216c
3
4 /* 省略掉其它代码...... */
5
6 INCDIRS := imx6ul \
7 stdio/include \
8 bsp/clk \
9 bsp/led \
10 bsp/delay \
11 bsp/beep \
12 bsp/gpio \
13 bsp/key \
14 bsp/exit \
15 bsp/int \
16 bsp/epittimer \
17 bsp/keyfilter \
18 bsp/uart \
19 bsp/lcd \
20 bsp/rtc \
21 bsp/i2c \
22 bsp/ap3216c
23
24 SRCDIRS := project \
25 stdio/lib \
26 bsp/clk \
27 bsp/led \
28 bsp/delay \
29 bsp/beep \
30 bsp/gpio \
31 bsp/key \
32 bsp/exit \
33 bsp/int \
34 bsp/epittimer \
35 bsp/keyfilter \
36 bsp/uart \
37 bsp/lcd \
38 bsp/rtc \
39 bsp/i2c \
40 bsp/ap3216c
41
42 /* 省略掉其它代码...... */
43
44 clean:
45 rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)

第2 行修改变量TARGET 为“ap3216c”,也就是目标名称为“ap3216c”。

第21 和22 行在变量INCDIRS 中添加I2C 和AP3216C 的驱动头文件(.h)路径。

第39 和40 行在变量SRCDIRS 中添加I2C 和AP3216C 驱动文件(.c)路径。
链接脚本保持不变。

编译下载

使用Make 命令编译代码,编译成功以后使用软件imxdownload 将编译完成的ap3216c.bin文件下载到SD 卡中,命令如下:

chmod 777 imxdownload //给予imxdownload 可执行权限,一次即可
./imxdownload ap3216c.bin /dev/sdd //烧写到SD 卡中,不能烧写到/dev/sda 或sda1 里面!

烧写成功以后将SD 卡插到开发板的SD 卡槽中,然后复位开发板。程序运行以后LCD界面如图26.4.2.1 所示:
在这里插入图片描述
图26.4.2.1 LCD 显示界面

图26.4.2.1 中显示出了AP3216C 的三个传感器的数据,大家可以用手遮住或者靠近AP3216C,LCD 上的三个数据就会变化。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
TMPO06数字式I2C总线温度传感器的温度值转换需要根据其数据手册中的温度转换公式进行计算。以下是一个简单的C语言代码示例,用于从TMPO06读取温度值并进行转换。 ```c #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <linux/i2c-dev.h> #include <sys/ioctl.h> #include <unistd.h> #define I2C_ADDR 0x4F // TMPO06的I2C地址 int main() { int fd; // I2C设备文件描述符 char *device = "/dev/i2c-1"; // I2C总线设备文件 char buf[2]; // 读取数据的缓冲区 float temp; // 温度值 if((fd = open(device, O_RDWR)) < 0) { printf("Failed to open I2C device.\n"); return -1; } if(ioctl(fd, I2C_SLAVE, I2C_ADDR) < 0) { printf("Failed to set I2C address.\n"); return -1; } // 发送读取温度的命令 buf[0] = 0x00; buf[1] = 0x00; if(write(fd, buf, 2) != 2) { printf("Failed to send command to the device.\n"); return -1; } // 读取温度值 if(read(fd, buf, 2) != 2) { printf("Failed to read data from the device.\n"); return -1; } // 计算温度值 int raw = (buf[0] << 8) | buf[1]; if(raw & 0x8000) { // 负温度 raw = -(raw & 0x7FFF); } temp = 0.03125 * raw; printf("Temperature: %.2f Celsius.\n", temp); close(fd); return 0; } ``` 在以上代码中,我们首先打开I2C总线设备文件,并设置TMPO06的I2C地址。然后发送读取温度的命令,并读取温度数据。最后根据TMPO06的温度转换公式,将读取到的原始温度值转换为实际温度值。需要注意的是,实际应用中需要根据具体情况对程序进行相应的修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

行稳方能走远

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值