正点原子 发表于 2020-6-8 11:37:38

【正点原子Linux连载】第二十六章I2C实验--摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南

本帖最后由 正点原子 于 2020-10-24 16:07 编辑

1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html
4)本章实例源码下载:    
5)对正点原子Linux感兴趣的同学可以加群讨论:876919289
6)关注正点原子公众号,获取最新资料






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

26.1 I2C & AP3216C简介
26.1.1 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所示:

图26.1.1.1 I2C多个设备连接结构图
      图26.1.1.1中SDA和SCL这两根线必须要接一个上拉电阻,一般是4.7K。其余的I2C从器件都挂接到SDA和SCL这两根线上,这样就可以通过SDA和SCL这两根线来访问多个I2C设备。
      接下来看一下I2C协议有关的术语:
      1、起始位
      顾名思义,也就是I2C通信起始标志,通过这个起始位就可以告诉I2C从机,“我”要开始进行I2C通信了。在SCL为高电平的时候,SDA出现下降沿就表示为起始位,如图26.1.1.2所示:

图26.1.1.2 I2C通信起始位
      2、停止位
      停止位就是停止I2C通信的标志位,和起始位的功能相反。在SCL位高电平的时候,SDA出现上升沿就表示为停止位,如图26.1.1.3所示:

图26.1.1.3 I2C通信停止位
      3、数据传输
      I2C总线在数据传输的时候要保证在SCL高电平期间,SDA上的数据稳定,因此SDA上的数据变化只能在SCL低电平期间发生,如图26.1.1.4所示:

图26.1.1.4 I2C数据传输
      4、应答信号
      当I2C主机发送完8位数据以后会将SDA设置为输入状态,等待I2C从机应答,也就是等到I2C从机告诉主机它接收到数据了。应答信号是由从机发出的,主机需要提供应答信号所需的时钟,主机发送完8位数据以后紧跟着的一个时钟信号就是给应答信号使用的。从机通过将SDA拉低来表示发出应答信号,表示通信成功,否则表示通信失败。
      5、I2C写时序
      主机通过I2C总线与从机之间进行通信不外乎两个操作:写和读,I2C总线单字节写时序如图26.1.1.5所示:

图26.1.1.5 I2C写时序
      图26.1.1.5就是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总线单字节读时序如图26.1.1.6所示:

图26.1.1.6 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多字节读写时序
      又时候我们需要读写多个字节,多字节读写时序和单字节的基本一致,只是在读写数据的时候可以连续发送多个自己的数据,其他的控制时序都是和单字节一样的。
26.1.2 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所示:

图26.1.2.1寄存器I2Cx_IADR结构
      寄存器I2Cx_IADR只有ADR(bit7:1)位有效,用来保存I2C从设备地址数据。当我们要访问某个I2C从设备的时候就需要将其设备地址写入到ADR里面。接下来看一下寄存器I2Cx_IFDR,这个是I2C的分频寄存器,寄存器结构如图26.1.2.2所示:

图26.1.2.2寄存器I2Cx_IFDR结构
      寄存器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所示:

图26.1.2.4寄存器I2Cx_I2CR结构
      寄存器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所示:

图26.1.2.5寄存器I2Cx_I2SR结构
      寄存器I2Cx_I2SR的各位含义如下:
      ICF(bit7):数据传输状态位,为0的时候表示数据正在传输,为1的时候表示数据传输完成。
      IAAS(bit6):当为1的时候表示I2C地址,也就是I2Cx_IADR寄存器中的地址是从设备地址。
      IBB(bit5):I2C总线忙标志位,当为0的时候表示I2C总线空闲,为1的时候表示I2C总线忙。
      IAL(bit4):仲裁丢失位,为1的时候表示发生仲裁丢失。
      SRW(bit3):从机读写状态位,当I2C作为从机的时候使用,此位用来表明主机发送给从机的是读还是写命令。为0的时候表示主机要向从机写数据,为1的时候表示主机要从从机读取数据。
      IIF(bit1):I2C中断挂起标志位,当为1的时候表示有中断挂起,此位需要软件清零。
      RXAK(bit0):应答信号标志位,为0的时候表示接收到ACK应答信号,为1的话表示检测到NO ACK信号。
      最后一个寄存器就是I2Cx_I2DR,这是I2C的数据寄存器,此寄存器只有低8位有效,当要发送数据的时候将要发送的数据写入到此寄存器,如果要接收数据的话直接读取此寄存器即可得到接收到的数据。
关于I2C的寄存器就介绍到这里,关于这些寄存器详细的描述,请参考《I.MX6ULL参考手册》第1462页的31.7小节。
26.1.3 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所示:

图26.1.3.1 AP3216C结构图
      AP3216的设备地址为0X1E,同几乎所有的I2C从器件一样,AP3216C内部也有一些寄存器,通过这些寄存器我们可以配置AP3216C的工作模式,并且读取相应的数据。AP3216C我们用的寄存器如表26.1.3.1所示:


表26.1.3.1本章使用的AP3216C寄存器表
      在表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的数据。
26.2硬件原理分析
      本试验用到的资源如下:
、指示灯LED0。
、 RGB LCD屏幕。
③、AP3216C
④、串口
      AP3216C是在I.MX6U-ALPHA开发板底板上,原理图如图26.2.1所示:

图26.2.1 AP3216C原理图
      从图26.2.1可以看出AP3216C使用的是I2C1,其中I2C1_SCL使用的UART4_RXD这个IO、I2C1_SDA使用的是UART4_TXD这个IO。
26.3实验程序编写
本实验对应的例程路径为:开发板光盘-> 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的驱动文件。在bsp_i2c.h中输入如下内容:
示例代码26.3.1 bsp_i2c.h文件代码
1#ifndef _BSP_I2C_H
2#define _BSP_I2C_H
3/***************************************************************
4Copyright © 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*/
27enum i2c_direction
28{
29   kI2C_Write =0x0,                              /* 主机向从机写数据      */
30   kI2C_Read =0x1,                              /* 主机从从机读数据      */
31};
32
33/*
34* 主机传输结构体
35*/
36struct i2c_transfer
37{
38unsignedchar slaveAddress;      /* 7位从机地址      */
39enum i2c_direction direction;      /* 传输方向      */
40unsignedint subaddress;      /* 寄存器地址      */
41unsignedchar subaddressSize;      /* 寄存器地址长度      */
42unsignedchar*volatile data;      /* 数据缓冲区      */
43volatileunsignedint dataSize;      /* 数据缓冲区长度      */
44};
45
46/*
47*函数声明
48*/
49void i2c_init(I2C_Type *base);
50unsignedchar i2c_master_start(I2C_Type *base,
unsignedchar address,
enum i2c_direction direction);
51unsignedchar i2c_master_repeated_start(I2C_Type *base,
unsignedchar address,
enum i2c_direction direction);
52unsignedchar i2c_check_and_clear_error(I2C_Type *base,
unsignedint status);
53unsignedchar i2c_master_stop(I2C_Type *base);
54void i2c_master_write(I2C_Type *base,constunsignedchar*buf,
unsignedint size);
55void i2c_master_read(I2C_Type *base,unsignedchar*buf,
unsignedint size);
56unsignedchar 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    */
10void 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   */
33unsignedchar i2c_master_repeated_start(I2C_Type *base,
unsignedchar address,
enum i2c_direction direction)
34{
35/* I2C忙并且工作在从模式,跳出 */
36if(base->I2SR &(1<<5)&&(((base->I2CR)&(1<<5))==0))
37return1;
38
39/*
40       * 设置寄存器I2CR
41       * bit: 1 发送
42       * bit: 1 产生重新开始信号
43       */
44      base->I2CR |=(1<<4)|(1<<2);
45
46/*
47       * 设置寄存器I2DR,bit : 要发送的数据,这里写入从设备地址
48       */
49      base->I2DR =((unsignedint)address <<1)|
((direction == kI2C_Read)?1:0);
50return0;
51}
52
53/*
54   * @description                     : 发送开始信号
55   * @param - base                : 要使用的IIC
56   * @param - addrss            : 设备地址
57   * @param - direction         : 方向
58   * @return                      : 0 正常其他值出错
59   */
60unsignedchar i2c_master_start(I2C_Type *base,
unsignedchar address,
enum i2c_direction direction)
61{
62if(base->I2SR &(1<<5))/* I2C忙 */
63return1;
64
65/*
66       * 设置寄存器I2CR
67       * bit: 1 主模式
68       * bit: 1 发送
69       */
70      base->I2CR |=(1<<5)|(1<<4);
71
72/*
73       * 设置寄存器I2DR,bit : 要发送的数据,这里写入从设备地址
74       */
75      base->I2DR =((unsignedint)address <<1)|
((direction == kI2C_Read)?1:0);
76return0;
77}
78
79/*
80   * @description             : 检查并清除错误
81   * @param - base            : 要使用的IIC
82   * @param - status          : 状态
83   * @return                  : 状态结果
84   */
85unsignedchar i2c_check_and_clear_error(I2C_Type *base,
unsignedint status)
86{
87if(status &(1<<4))                              /* 检查是否发生仲裁丢失错误      */
88{
89          base->I2SR &=~(1<<4);      /* 清除仲裁丢失错误位      */
90          base->I2CR &=~(1<<7);      /* 先关闭I2C                     */
91          base->I2CR |=(1<<7);      /* 重新打开I2C                     */
92return I2C_STATUS_ARBITRATIONLOST;
93}
94elseif(status &(1<<0))      /* 没有接收到从机的应答信号      */
95{
96return I2C_STATUS_NAK;      /* 返回NAK(No acknowledge) */
97}
98return I2C_STATUS_OK;
99}
100
101/*
102* @description             : 停止信号
103* @param - base            : 要使用的IIC
104* @param                   : 无
105* @return                  : 状态结果
106*/
107unsignedchar i2c_master_stop(I2C_Type *base)
108{
109unsignedshort timeout =0XFFFF;
110
111/* 清除I2CR的bit这三位 */
112   base->I2CR &=~((1<<5)|(1<<4)|(1<<3));
113while((base->I2SR &(1<<5)))      /* 等待忙结束      */
114{
115         timeout--;
116if(timeout ==0)                        /* 超时跳出                */
117return I2C_STATUS_TIMEOUT;
118}
119return I2C_STATUS_OK;
120}
121
122/*
123* @description             : 发送数据
124* @param - base            : 要使用的IIC
125* @param - buf             : 要发送的数据
126* @param - size            : 要发送的数据大小
127* @param - flags         : 标志
128* @return                  : 无
129*/
130void i2c_master_write(I2C_Type *base,constunsignedchar*buf,
unsignedint size)
131{
132while(!(base->I2SR &(1<<7)));                /* 等待传输完成      */
133   base->I2SR &=~(1<<1);                              /* 清除标志位      */
134   base->I2CR |=1<<4;                              /* 发送数据                */
135while(size--)
136{
137         base->I2DR =*buf++;/* 将buf中的数据写入到I2DR寄存器 */
138while(!(base->I2SR &(1<<1)));      /* 等待传输完成      */
139         base->I2SR &=~(1<<1);      /* 清除标志位      */
140
141/* 检查ACK */
142if(i2c_check_and_clear_error(base, base->I2SR))
143break;
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*/
156void i2c_master_read(I2C_Type *base,unsignedchar*buf,
unsignedint size)
157{
158volatileuint8_t dummy =0;
159
160   dummy++;                                                      /* 防止编译报错                */
161while(!(base->I2SR &(1<<7)));      /* 等待传输完成                */
162   base->I2SR &=~(1<<1);      /* 清除中断挂起位      */
163   base->I2CR &=~((1<<4)|(1<<3));/* 接收数据      */
164if(size ==1)      /* 如果只接收一个字节数据的话发送NACK信号 */
165         base->I2CR |=(1<<3);
166
167   dummy = base->I2DR;                                        /* 假读                        */
168while(size--)
169{
170while(!(base->I2SR &(1<<1)));      /* 等待传输完成      */
171         base->I2SR &=~(1<<1);      /* 清除标志位      */
172
173if(size ==0)
174             i2c_master_stop(base);      /* 发送停止信号      */
175if(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*/
187unsignedchar i2c_master_transfer(I2C_Type *base,
struct i2c_transfer *xfer)
188{
189unsignedchar ret =0;
190enum i2c_direction direction = xfer->direction;
191
192   base->I2SR &=~((1<<1)|(1<<4));                /* 清除标志位      */
193while(!((base->I2SR >>7)&0X1)){};      /* 等待传输完成      */
194/* 如果是读的话,要先发送寄存器地址,所以要先将方向改为写 */
195if((xfer->subaddressSize >0)&&(xfer->direction ==
kI2C_Read))
196         direction = kI2C_Write;
197   ret = i2c_master_start(base, xfer->slaveAddress, direction);
198if(ret)
199return ret;
200while(!(base->I2SR &(1<<1))){};      /* 等待传输完成 */
201   ret = i2c_check_and_clear_error(base, base->I2SR);
202if(ret)
203{
204         i2c_master_stop(base);                /* 发送出错,发送停止信号 */
205return ret;
206}
207
208/* 发送寄存器地址 */
209if(xfer->subaddressSize)
210{
211do
212{
213             base->I2SR &=~(1<<1);      /* 清除标志位      */
214             xfer->subaddressSize--;      /* 地址长度减一      */
215             base->I2DR =((xfer->subaddress)>>(8*
xfer->subaddressSize));
216while(!(base->I2SR &(1<<1)));      /* 等待传输完成 */
217/* 检查是否有错误发生 */
218             ret = i2c_check_and_clear_error(base, base->I2SR);
219if(ret)
220{
221               i2c_master_stop(base);                /* 发送停止信号 */
222return ret;
223}
224}while((xfer->subaddressSize >0)&&(ret ==
I2C_STATUS_OK));
225
226if(xfer->direction == kI2C_Read)      /* 读取数据               */
227{
228             base->I2SR &=~(1<<1);      /* 清除中断挂起位 */
229             i2c_master_repeated_start(base, xfer->slaveAddress,
kI2C_Read);
230while(!(base->I2SR &(1<<1))){};      /* 等待传输完成      */
231
232/* 检查是否有错误发生 */
233             ret = i2c_check_and_clear_error(base, base->I2SR);
234if(ret)
235{
236               ret = I2C_STATUS_ADDRNAK;
237               i2c_master_stop(base);                /* 发送停止信号 */
238return ret;
239}
240}
241}
242
243/* 发送数据 */
244if((xfer->direction == kI2C_Write)&&(xfer->dataSize >0))
245         i2c_master_write(base, xfer->data, xfer->dataSize);
246/* 读取数据 */
247if((xfer->direction == kI2C_Read)&&(xfer->dataSize >0))
248         i2c_master_read(base, xfer->data, xfer->dataSize);
249return0;
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输入如下所示内容:
示例代码26.3.3 bsp_ap3216c.h文件代码
1#ifndef _BSP_AP3216C_H
2#define _BSP_AP3216C_H
3/***************************************************************
4Copyright © 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_ALSDATAHIGH0X0D/* ALS数据高字节      */
25 #define AP3216C_PSDATALOW            0X0E/* PS数据低字节      */
26 #define AP3216C_PSDATAHIGH   0X0F/* PS数据高字节      */
27
28/* 函数声明 */
29unsignedchar ap3216c_init(void);
30unsignedchar ap3216c_readonebyte(unsignedchar addr,
unsignedchar reg);
31unsignedchar ap3216c_writeonebyte(unsignedchar addr,
unsignedchar reg,
unsignedchar data);
32void ap3216c_readdata(unsignedshort*ir,unsignedshort*ps,
unsignedshort*als);
33
34 #endif
      第45到26行定义了一些宏,分别为AP3216C的设备地址和寄存器地址,剩下的就是函数声明。接下来在bsp_ap3216c.c中输入如下所示内容:
示例代码26.3.4 bsp_ap3216c.c文件代码
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名   : 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   */
12unsignedchar ap3216c_init(void)
13{
14unsignedchar 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);
38if(data ==0X03)
39return0;/* AP3216C正常    */
40else
41return1;/* AP3216C失败    */
42}
43
44/*
45   * @description         : 向AP3216C写入数据
46   * @param – addr      : 设备地址
47   * @param - reg         : 要写入的寄存器
48   * @param – data      : 要写入的数据
49   * @return            : 操作结果
50   */
51unsignedchar ap3216c_writeonebyte(unsignedchar addr,
unsignedchar reg,
unsignedchar data)
52{
53unsignedchar status=0;
54unsignedchar writedata=data;
55struct 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
65if(i2c_master_transfer(I2C1,&masterXfer))
66          status=1;
67
68return status;
69}
70
71/*
72   * @description         : 从AP3216C读取一个字节的数据
73   * @param – addr      : 设备地址
74   * @param - reg         : 要读取的寄存器
75   * @return            : 读取到的数据。
76   */
77unsignedchar ap3216c_readonebyte(unsignedchar addr,
unsignedchar reg)
78{
79unsignedchar val=0;
80
81struct 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
90return 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*/
101void ap3216c_readdata(unsignedshort*ir,unsignedshort*ps,unsignedshort*als)
102{
103unsignedchar buf;
104unsignedchar i;
105
106/* 循环读取所有传感器数据 */
107for(i =0; i <6; i++)
108{
109         buf= ap3216c_readonebyte(AP3216C_ADDR,
AP3216C_IRDATALOW + i);
110}
111
112if(buf&0X80)      /* IR_OF位为1,则数据无效      */
113*ir =0;
114else      /* 读取IR传感器的数据                */
115*ir =((unsignedshort)buf<<2)|(buf&0X03);
116
117*als =((unsignedshort)buf<<8)| buf;/* 读取ALS数据 */
118
119if(buf&0x40)      /* IR_OF位为1,则数据无效      */
120*ps =0;
121else      /* 读取PS传感器的数据    */
122*ps =((unsignedshort)(buf&0X3F)<<4)|
(buf&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中输入如下代码:
示例代码26.3.5 main.c文件代码
/**************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名   :    mian.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*/
18int main(void)
19{
20      unsignedshort ir, als, ps;
21      unsignedchar 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      return0;
62}
      第38行调用ap3216c_init来初始化AP3216C,如果AP3216C初始化失败的话就会进入循环,会在LCD上不断的闪烁字符串“AP3216C Check Failed!”和“Please Check!”,直到AP3216C初始化成功。
      第53行调用函数ap3216c_readdata来获取AP3216C的ALS、PS和IR传感器数据值,获取完成以后就会在LCD上显示出来。
文件main.c里面的内容总体上还是很简单的,实验程序的编写就到这里。
26.4编译下载验证
26.4.1 编写Makefile和链接脚本
修改Makefile中的TARGET为ap3216c,然后在在INCDIRS和SRCDIRS中加入“bsp/i2c”和“bsp/ap3216c”,修改后的Makefile如下:
示例代码26.4.1.1Makefile文件代码
1CROSS_COMPILE          ?= arm-linux-gnueabihf-
2TARGET                  ?=ap3216c
3
4/* 省略掉其它代码...... */
5
6INCDIRS      :=      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:
45rm -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)路径。
      链接脚本保持不变。
26.4.2编译下载
      使用Make命令编译代码,编译成功以后使用软件imxdownload将编译完成的ap3216c.bin文件下载到SD卡中,命令如下:
chmod 777 imxdownload                              //给予imxdownload可执行权限,一次即可
./imxdownload ap3216c.bin /dev/sdd                //烧写到SD卡中
      烧写成功以后将SD卡插到开发板的SD卡槽中,然后复位开发板。程序运行以后LCD界面如图26.4.2.1所示:

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

armok. 发表于 2020-6-8 11:42:17

正点原子的文档总是让我震惊:这需要多大的工作量啊!
页: [1]
查看完整版本: 【正点原子Linux连载】第二十六章I2C实验--摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南