文章目录

前言

韦东山嵌入式Linux应用开发基础知识学习笔记
文章中大多内容来自韦东山老师的文档,还有部分个人根据自己需求补充的内容

视频教程地址: https://www.bilibili.com/video/BV1kk4y117Tu

1、IIC协议和SMBUS协议

1.1、IIC协议

1.1.1、硬件框架

在这里插入图片描述

▲硬件框架图
  • 在一个芯片(SoC)内部,有一个或多个I2C控制器
  • 在一个I2C控制器上,可以连接一个或多个I2C设备
  • I2C总线只需要2条线:时钟线SCL、数据线SDA
  • 在I2C总线的SCL、SDA线上,都有上拉电阻

1.1.2、软件框架

在这里插入图片描述

▲软件框架
  1. APP:调用设备驱动程序提供的接口读写指定位置内容
  2. I2C Devices Driver:
      知道设备要求的地址和数据格式
      知道什么样的信号可以让设备执行指定操作
      知道什么样的信号代表着设备的一些状态
      根据APP要求构造好一系列数据发送给I2C Controller
  3. I2C Controller Driver:
      它根据I2C协议发出各类信号:I2C设备地址、I2C存储地址、数据
      它根据I2C协议判断各类信号:I2C设备地址、I2C存储地址、数据

1.1.3、读写数据格式

写操作

  • 主芯片要发出一个start信号
  • 然后发出一个设备地址(用来确定是往哪一个芯片写数据),方向(读/写,0表示写,1表示读)
  • 从设备回应(用来确定这个设备是否存在),然后就可以传输数据
  • 主设备发送一个字节数据给从设备,并等待回应
  • 每传输一字节数据,接收方要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据。
  • 数据发送完之后,主芯片就会发送一个停止信号。
    在这里插入图片描述
▲写操作

读操作

  • 主芯片要发出一个start信号
  • 然后发出一个设备地址(用来确定是往哪一个芯片写数据),方向(读/写,0表示写,1表示读)
  • 从设备回应(用来确定这个设备是否存在),然后就可以传输数据
  • 从设备发送一个字节数据给主设备,并等待回应
  • 每传输一字节数据,接收方要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据。
  • 数据发送完之后,主芯片就会发送一个停止信号。
    在这里插入图片描述
▲读操作

IIC信号分类
  I2C协议中数据传输的单位是字节,也就是8位。但是要用到9个时钟:前面8个时钟用来传输8数据,第9个时钟用来传输回应信号。传输时,先传输最高位(MSB)。

  • 开始信号(S):SCL为高电平时,SDA山高电平向低电平跳变,开始传送数据。
  • 结束信号(P):SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
  • 响应信号(ACK):接收器在接收到8位数据后,在第9个时钟周期,拉低SDA
  • SDA上传输的数据必须在SCL为高电平期间保持稳定,SDA上的数据只能在SCL为低电平期间变化

在这里插入图片描述

▲I2C协议信号

1.1.4、硬件结构–在硬件上是如何实现双向传输

  • 如何在SDA上实现双向传输?
    主芯片通过一根SDA线既可以把数据发给从设备,也可以从SDA上读取数据,连接SDA线的引脚里面必然有两个引脚(发送引脚/接受引脚)。
  • 主、从设备都可以通过SDA发送数据,肯定不能同时发送数据,怎么错开时间?
    在9个时钟里,
    前8个时钟由主设备发送数据的话,第9个时钟就由从设备发送数据;
    前8个时钟由从设备发送数据的话,第9个时钟就由主设备发送数据。
  • 双方设备中,某个设备发送数据时,另一方怎样才能不影响SDA上的数据?
    设备的SDA中有一个三极管,使用开极/开漏电路(三极管是开极,CMOS管是开漏,作用一样),如

在这里插入图片描述

▲主从设备的SDA
ABSDA
001(由上拉电阻决定)
010
100
110
▲真值表

从真值表和电路图我们可以知道:

  • 当某一个芯片不想影响SDA线时,那就不驱动这个三极管
  • 想让SDA输出高电平,双方都不驱动三极管(SDA通过上拉电阻变为高电平)
  • 想让SDA输出低电平,就驱动三极管

从下面的例子可以看看数据是怎么传的(实现双向传输)
举例:主设备发送(8bit)给从设备

  • 前8个clk
    从设备不要影响SDA,从设备不驱动三极管
    主设备决定数据,主设备要发送1时不驱动三极管,要发送0时驱动三极管第9个clk,由从设备决定数据
  • 主设备不驱动三极管
    从设备决定数据,要发出回应信号的话,就驱动三极管让SDA变为0
    从这里也可以知道ACK信号是低电平
    从上面的例子,就可以知道怎样在一条线上实现双向传输,这就是SDA上要使用上拉电阻的原因

为何SCL也要使用上拉电阻?(为了实现时钟延展功能) I2C时钟延展
在第9个时钟之后,如果有某一方需要更多的时间来处理数据,它可以一直驱动三极管把SCL拉低。
当SCL为低电平时候,大家都不应该使用IIC总线,只有当SCL从低电平变为高电平的时候,IIC总线才能被使用。
当它就绪后,就可以不再驱动三极管,这是上拉电阻把SCL变为高电平,其他设备就可以继续使用I2C总线了。

1.2、SMBUS协议

SMBus: System Management Bus,系统管理总线。
SMBus是基于I2C协议的,SMBus要求更严格,SMBus是I2C协议的子集。
SMBus和I2C协议的差别有哪些?

1.2.1、SMBus和I2C协议的差异

  • VDD的极限值不一样
       I2C协议:范围很广,甚至讨论了高达12V的情况
       SMBus:1.8V~5V
  • 最小时钟频率、最大的 Clock Stretching
       Clock Stretching含义:某个设备需要更多时间进行内部的处理时,它可以把SCL拉低占住I2C总线
       I2C协议:时钟频率最小值无限制,Clock Stretching时长也没有限制
       SMBus:时钟频率最小值是10KHz,Clock Stretching的最大时间值也有限制
  • 地址回应(Address Acknowledge)
       一个I2C设备接收到它的设备地址后,是否必须发出回应信号?
       I2C协议:没有强制要求必须发出回应信号
       SMBus:强制要求必须发出回应信号,这样对方才知道该设备的状态:busy,failed,或是被移除了
  • SMBus协议明确了数据的传输格式
       I2C协议:它只定义了怎么传输数据,但是并没有定义数据的格式,这完全由设备来定义
       SMBus:定义了几种数据格式(后面分析)
  • REPEATED START Condition(重复发出S信号)
       比如读EEPROM时,涉及2个操作:
          把存储地址发给设备
          读数据
       在写、读之间,可以不发出P信号,而是直接发出S信号:这个S信号就是 REPEATED START
       如下图所示
    在这里插入图片描述
▲REPEATED START Condition(重复发出S信号)
  • SMBus Low Power Version
      SMBus也有低功耗的版本

1.2.2、SMBus协议明确了数据的传输格式

对于I2C协议,它只定义了怎么传输数据,但是并没有定义数据的格式,这完全由设备来定义。
对于SMBus协议,它定义了几种数据格式。

  • 下面文档中的 Functionality flag 是Linux的某个I2C控制器驱动所支持的功能。
  • 比如 Functionality flag: I2C_FUNC_SMBUS_QUICK ,表示需要I2C控制器支持 SMBus Quick Command
symbols符号
S (1 bit) : 					Start bit(开始位)
Sr (1 bit) : 				重复的开始位
P (1 bit) : 					Stop bit(停止位)
R/W# (1 bit) :			Read/Write bit. Rd equals 1, Wr equals 0.(读写位)
A, N (1 bit) : 			Accept and reverse accept bit.(回应位)
Address(7 bits): 	I2C 7 bit address. Note that this can be expanded as usual to get a 10 bit I2C address.
									(地址位,7位地址)
Command Code (8 bits): Command byte, a data byte which often selects a register on the device.
												 	(命令字节,一般用来选择芯片内部的寄存器)
Data Byte (8 bits): 				A plain data byte. Sometimes, I write DataLow, DataHigh for 16 bit data.
													(数据字节,8位;如果是16位数据的话,用2个字节来表示:DataLow、DataHigh)
Count (8 bits): 						A data byte containing the length of a block operation.
													(在block操作总,表示数据长度)
[..]: 											Data sent by I2C device, as opposed to data sent by the host adapter.
													(中括号表示I2C设备发送的数据,没有中括号表示host adapter发送的数据)
SMBus Quick Command

在这里插入图片描述

▲ SMBus Quick Command

只是用来发送一位数据:R/W#可以用来表示设备的开关
Functionality flag: I2C_FUNC_SMBUS_QUICK

SMBus Receive Byte

在这里插入图片描述

▲SMBus Receive Byte

I2C-tools中的函数:i2c_smbus_read_byte()
读取一个字节,Host adapter接收到一个字节后不需要发出回应信号(上图中N表示不回应)
Functionality flag: I2C_FUNC_SMBUS_READ_BYTE

SMBus Send Byte

在这里插入图片描述

▲SMBus Send Byte

I2C-tools中的函数:i2c_smbus_write_byte()
发送一个字节
Functionality flag: I2C_FUNC_SMBUS_WRITE_BYTE

SMBus Read Byte

在这里插入图片描述

▲SMBus Read Byte

I2C-tools中的函数:i2c_smbus_read_byte_data()
先发出 Command Code (它一般表示芯片内部的寄存器地址),再读取一个字节的数据
上面介绍的 SMBus Receive Byte 是不发送Comand,直接读取数据
Functionality flag: I2C_FUNC_SMBUS_READ_BYTE_DATA

SMBus Read Word

在这里插入图片描述

▲SMBus Read Word

I2C-tools中的函数:i2c_smbus_read_word_data()
先发出 Command Code (它一般表示芯片内部的寄存器地址),再读取2个字节(一个字)的数据
Functionality flag: I2C_FUNC_SMBUS_READ_WORD_DATA

SMBus Write Byte

在这里插入图片描述

▲SMBus Write Byte

I2C-tools中的函数:i2c_smbus_write_byte_data()
先发出 Command Code (它一般表示芯片内部的寄存器地址),再发出1个字节的数据
Functionality flag: I2C_FUNC_SMBUS_WRITE_BYTE_DATA

SMBus Write Word

在这里插入图片描述

▲SMBus Write Word

I2C-tools中的函数:i2c_smbus_write_word_data()
先发出 Command Code (它一般表示芯片内部的寄存器地址),再发出2个字节(1个字)的数据
Functionality flag: I2C_FUNC_SMBUS_WRITE_WORD_DATA

SMBus Block Read

在这里插入图片描述

▲SMBus Block Read

I2C-tools中的函数:i2c_smbus_read_block_data()
先发出 Command Code (它一般表示芯片内部的寄存器地址),再发起度操作:
  先读到一个字节(Block Count),表示后续要读的字节数
  然后读取全部数据
Functionality flag: I2C_FUNC_SMBUS_READ_BLOCK_DATA

SMBus Block Write

在这里插入图片描述

▲SMBus Block Write
I2C-tools中的函数:`i2c_smbus_write_block_data()` 先发出 Command Code (它一般表示芯片内部的寄存器地址),再发出1个字节的 Byte Conut (表示后续要发出的数据字节数),最后发出全部数据 `Functionality flag: I2C_FUNC_SMBUS_WRITE_BLOCK_DATA`
I2C Block Read

在一般的I2C协议中,也可以连续读出多个字节。
它跟 SMBus Block Read 的差别在于设备发出的第1个数据不是长度N,如下图所示:
在这里插入图片描述

▲I2C Block Read

I2C-tools中的函数:i2c_smbus_read_i2c_block_data()
先发出 Command Code (它一般表示芯片内部的寄存器地址),再发出1个字节的 Byte Conut (表示后续
要发出的数据字节数),最后发出全部数据
Functionality flag: I2C_FUNC_SMBUS_READ_I2C_BLOCK

I2C Block Write

在一般的I2C协议中,也可以连续发出多个字节。
它跟 SMBus Block Write 的差别在于发出的第1个数据不是长度N,如下图所示:
在这里插入图片描述

▲I2C Block Write

I2C-tools中的函数:i2c_smbus_write_i2c_block_data()
先发出 Command Code (它一般表示芯片内部的寄存器地址),再发出1个字节的 Byte Conut (表示后续要发出的数据字节数),最后发出全部数据
Functionality flag: I2C_FUNC_SMBUS_WRITE_I2C_BLOCK

SMBus Block Write - Block Read Process Call

在这里插入图片描述

▲SMBus Block Write - Block Read Process Call

先写一块数据,再读一块数据
Functionality flag: I2C_FUNC_SMBUS_BLOCK_PROC_CALL

Packet Error Checking (PEC)

CRC校验详解(附代码示例)
PEC是一种错误校验码,如果使用PEC,那么在P信号之前,数据发送方要发送一个字节的PEC码(它是CRC-8码)。
以 SMBus Send Byte 为例,下图中,一个未使用PEC,另一个使用PEC:
在这里插入图片描述

▲Packet Error Checking (PEC)

1.3、使用建议

因为很多设备都实现了SMBus,而不是更宽泛的I2C协议,所以优先使用SMBus。
即使I2C控制器没有实现SMBus,软件方面也是可以使用I2C协议来模拟SMBus。
所以:Linux建议优先使用SMBus。

2、I2C系统的重要结构体

使用一句话概括I2C传输:APP通过I2C Controller与I2C Device传输数据。

2.1、几个重要的结构体_映射到I2C通信需要的一些单元和数据

2.1.1、怎么表示I2C Controller

  • 一个芯片里可能有多个I2C Controller,比如第0个、第1个、……
  • 对于使用者,只要确定是第几个I2C Controller即可
  • 使用i2c_adapter表示一个I2C BUS,或称为I2C Controller
  • i2c_adapter里面有2个重要的成员:
      nr:第几个I2C BUS(I2C Controller)
      i2c_algorithm,里面有该I2C BUS的传输函数,用来收发I2C数据
    在这里插入图片描述
▲i2c_adapter

在这里插入图片描述

▲i2c_algorithm

2.1.2、怎么表示I2C Device

  • 一个I2C Device,一定有设备地址
  • 它连接在哪个I2C Controller上,即对应的i2c_adapter是什么
  • 使用i2c_client来表示一个I2C Device
    在这里插入图片描述
▲i2c_client

2.1.3、怎么表示要传输的数据

  • 在上面的i2c_algorithm结构体中可以看到要传输的数据被称为:i2c_msg
    在这里插入图片描述
▲i2c_msg

   flags用来表示传输方向:bit 0等于I2C_M_RD表示读,bit 0等于0表示写

  • 举个栗子🌰:设备地址为0x50的EEPROM,要读取它里面存储地址为0x10的一个字节,应该构造几个i2c_msg?
       要构造2个i2c_msg
      第一个i2c_msg表示写操作,把要访问的存储地址0x10发给设备
      第二个i2c_msg表示读操作
      代码如下:
u8 data_addr = 0x10;
i8 data;
struct i2c_msg msgs[2];
msgs[0].addr = 0x50;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = &data_addr;
msgs[1].addr = 0x50;
msgs[1].flags = I2C_M_RD;
msgs[1].len = 1;
msgs[1].buf = &data;

2.2、总结一下内核里是怎么传输数据的

  • APP通过I2C Controller(映射i2c_adapter)与I2C Device(映射i2c_client)传输数据
  • APP通过i2c_adapter与i2c_client传输i2c_msg
  • 还有一种方式:内核函数i2c_transfer
      i2c_msg里含有addr,所以这个函数里不需要i2c_client
    在这里插入图片描述
▲i2c_transfer

3、无需编写驱动直接访问设备_I2C-Tools介绍

I2C-Tools-4.2: https://mirrors.edge.kernel.org/pub/software/utils/i2c-tools/

3.1、交叉编译

在这里插入图片描述

▲修改makefile

3.1.1、不使用静态库

不使用静态库需要将动态库同时放入开发板中
在这里插入图片描述
在这里插入图片描述

▲编译后产生的文件

在这里插入图片描述
在这里插入图片描述

▲开发板上需要的文件

3.1.2、使用静态库

使用静态库需要在make后面添加关键字USE_STATIC_LIB=1
在这里插入图片描述

▲编译后产生的文件

将产生的文件放入开发板的/sbin目录下即可

3.2、用法

3.2.1、i2cdetect:I2C检测

i2cdetect -l:列出当前的I2C Adapter(或称为I2C Bus、I2C Controller)

[root@100ask:~]# i2cdetect -l
i2c-1   i2c             STM32F7 I2C(0x40013000)                 I2C adapter
i2c-2   i2c             STM32F7 I2C(0x5c002000)                 I2C adapter
i2c-0   i2c             STM32F7 I2C(0x40012000)                 I2C adapter

i2cdetect -F I2CBUS :打印某个I2C Adapter的Functionalities, I2CBUS为0、1、2等整数

[root@100ask:~]# i2cdetect -F 0
Functionalities implemented by /dev/i2c-0:
I2C                              							yes
SMBus Quick Command              yes
SMBus Send Byte                  			yes
SMBus Receive Byte               		yes
SMBus Write Byte                			 yes
SMBus Read Byte                 			 yes
SMBus Write Word                 			yes
SMBus Read Word                  		yes
SMBus Process Call               			yes
SMBus Block Write                			yes
SMBus Block Read                 			yes
SMBus Block Process Call         	yes
SMBus PEC                        					yes
I2C Block Write                  				yes
I2C Block Read                   				yes

i2cdetect -y -a I2CBUS:看看有哪些I2C设备, I2CBUS为0、1、2等整数
在这里插入图片描述

▲i2cdetect -y -a I2CBUS

3.2.2、i2cget:I2C读

[root@100ask:~]# i2cget 
Usage: i2cget [-f] [-y] [-a] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]
  I2CBUS is an integer or an I2C bus name
  ADDRESS is an integer (0x03 - 0x77, or 0x00 - 0x7f if -a is given)
  MODE is one of:
    b (read byte data, default)
    w (read word data)
    c (write byte/read byte)
    Append p for SMBus PEC

i2cget -f -y 0 0x1e 0xc w :从I2CBUS0的地址为0x1e的设备上从其0xc地址寄存器开始读一个字长(两个字节)的数据

 [root@100ask:~]# i2cget -f -y 0 0x1e 0xc w                                                                                                                                                                         
0x0118

3.2.3、i2cset:I2C写

[root@100ask:~]# i2cset 
Usage: i2cset [-f] [-y] [-m MASK] [-r] [-a] I2CBUS CHIP-ADDRESS DATA-ADDRESS [VALUE] ... [MODE]
  I2CBUS is an integer or an I2C bus name
  ADDRESS is an integer (0x03 - 0x77, or 0x00 - 0x7f if -a is given)
  MODE is one of:
    c (byte, no value)
    b (byte data, default)
    w (word data)
    i (I2C block data)
    s (SMBus block data)
    Append p for SMBus PEC 

i2cset -f -y 0 0x1e 0 0x4:向I2CBUS0上地址为0x1e的设备的寄存器0中写入0x4

[root@100ask:~]# i2cset -f -y 0 0x1e 0 0x4

3.2.4、i2ctransfer:I2C传输(不是基于SMBus)

[root@100ask:~]# i2ctransfer 
Usage: i2ctransfer [-f] [-y] [-v] [-V] [-a] I2CBUS DESC [DATA] [DESC [DATA]]...
  I2CBUS is an integer or an I2C bus name
  DESC describes the transfer in the form: {r|w}LENGTH[@address]
    1) read/write-flag 2) LENGTH (range 0-65535) 3) I2C address (use last one if omitted)
  DATA are LENGTH bytes for a write message. They can be shortened by a suffix:
    = (keep value constant until LENGTH)
    + (increase value by 1 until LENGTH)
    - (decrease value by 1 until LENGTH)
    p (use pseudo random generator until LENGTH with value as seed)

Example (bus 0, read 8 byte at offset 0x64 from EEPROM at 0x50):
  # i2ctransfer 0 w1@0x50 0x64 r8
Example (same EEPROM, at offset 0x42 write 0xff 0xfe ... 0xf0):
  # i2ctransfer 0 w17@0x50 0x42 0xff-

i2ctransfer -f -y 0 w2@0x1e 0 0x4:向I2CBUS0上的地址为0x1e的设备写两个字节 0和0x4(向寄存器0中写入0x4)
i2ctransfer -f -y 0 w1@0x1e 0xc r2:向I2CBUS0上的地址为0x1e的设备写一个字节 0x1e,再读取两个字节(从0x1e地址开始读两个字节)

3.2.5、i2cdump:设备0x00~0xFF地址数据的读出

在这里插入图片描述

▲i2cdump

3.3、使用I2C-Tools操作传感器AP3216C

AP3216C是红外、光强、距离三合一的传感器,以读出光强、距离值为例,步骤如下:

  • 复位:往寄存器0写入0x4
  • 使能:往寄存器0写入0x3
  • 读光强:读寄存器0xC、0xD得到2字节的光强
  • 读距离:读寄存器0xE、0xF得到2字节的距离值
    AP3216C的设备地址是0x1E,假设节在I2C BUS0上,操作命令如下:
  • 使用SMBus协议
i2cset -f -y 0 0x1e 0 0x4
i2cset -f -y 0 0x1e 0 0x3
i2cget -f -y 0 0x1e 0xc w
i2cget -f -y 0 0x1e 0xe w

使用I2C协议

i2ctransfer -f -y 0 w2@0x1e 0 0x4
i2ctransfer -f -y 0 w2@0x1e 0 0x3
i2ctransfer -f -y 0 w1@0x1e 0xc r2
i2ctransfer -f -y 0 w1@0x1e 0xe r2

3.4、I2C-Tools的访问I2C设备的2种方式

I2C-Tools可以通过SMBus来访问I2C设备,也可以使用一般的I2C协议来访问I2C设备。
使用一句话概括I2C传输:APP通过I2C Controller与I2C Device传输数据
在APP里,有这几个问题:

  • 怎么指定I2C控制器?
       i2c-dev.c提供为每个I2C控制器(I2C Bus、I2C Adapter)都生成一个设备节点:/dev/i2c-0、/dev/i2c-1等
       open某个/dev/i2c-X节点,就是去访问该I2C控制器下的设备
  • 怎么指定I2C设备?
      通过ioctl指定I2C设备的地址
      ioctl(file, I2C_SLAVE, address)
        如果该设备已经有了对应的设备驱动程序,则返回失败
      ioctl(file, I2C_SLAVE_FORCE, address)
        如果该设备已经有了对应的设备驱动程序
        但是还是想通过i2c-dev驱动来访问它
        则使用这个ioctl来指定I2C设备地址
  • 怎么传输数据?
      两种方式
      一般的I2C方式:ioctl(file, I2C_RDWR, &rdwr)
      SMBus方式:ioctl(file, I2C_SMBUS, &args)

4、功能实现流程图

4.1、I2C方式

示例代码:i2ctransfer.c
在这里插入图片描述

▲I2C方式

4.2、SMBus方式

示例代码:i2cget.c、i2cset.c
在这里插入图片描述

▲SMBus方式

5、编写APP直接访问AP3216C

  • 环境光光电传感器(Ambient Light Photo Sensor )
      16位有效线性输出(0~65535)
      4 个用户可选择的动态范围
      抗闪烁抑制(抑制 50/60Hz)
      高灵敏度@深色玻璃
      窗口损失补偿

  • 接近探测器(Proximity Detector)
      10位有效线性输出(0~1023)
      4路可编程IR LED电流输出
      高环境光抑制
      串扰补偿

5.1、AP3216C寄存器列表、控制命令及工作信息

AP3216C 是一个集成的 ALS 和 PS 模块,在单个封装中包含一个数字环境光传感器 [ALS]、一个接近传感器 [PS] 和一个 IR LED。
从地址有 7 位。 主设备应将读/写位附加到从地址,以便与设备正确通信。 该设备的从机地址为 0x1E。
在这里插入图片描述

▲寄存器列表

其中的Threshold是指阈值,分为LowerHigher两种,对应上限和下限,向这类寄存器中输入数据可以确定数据测量后中断触发的阈值

在这里插入图片描述

▲系统设置寄存器

可以使用以下命令获得需要的信息

reset:			i2cset -y -f 0 0x1e 0 0x4
all onece:	i2cset -y -f 0 0x1e 0 0x7
IR data:		i2cget -y -f 0 0x1e 0xa w
ALS data:		i2cget -y -f 0 0x1e 0xc w
PS data:		i2cget -y -f 0 0x1e 0xe w

5.1.1、软件复位 (100)

  当主机写入此设置时,设备的所有寄存器将在 10ms 后变为默认值。 请不要在这 10ms 的时间内强制命令,以免运行异常
在这里插入图片描述

▲软件复位 (100)

5.1.2、 ALS 和 PS + IR 功能一次 (111)

  当主机写入此设置时,设备将在短时间内交替运行 ALS 和 PS+IR 功能。
  设备获取到 ALS、PS 和 IR 数据后,设备会自动断电。 该时间通常为 232ms,不受 PS 等待的影响。 如果设备被此命令休眠,则 ALS、PS 和 IR 数据将被保留而不被清除。
在这里插入图片描述

▲ALS 和 PS + IR 功能一次 (111)

5.1.3、传感器数据寄存器

红外数据寄存器

在这里插入图片描述

▲红外数据寄存器

  IR 的 ADC 通道数据表示为跨两个寄存器(IR 数据低和 ID 数据高)的 10 位数据。 这两个将分别提供 ADC 值的低字节和高字节。 IR DATA可以显示环境IR的强度。 所有通道数据寄存器都是只读的。
  如果 IR 强度高,会影响 PS 数据,导致 PS 数据无效。 有一个溢出标志(IR_OF)来指示PS数据在高IR光下看是否有效。 如果 IR_OF 设置为 1,设备将强制 PS 对象状态为离开状态。
  读取低字节寄存器后,可以读取高字节寄存器。 当读取低字节寄存器时,高字节存储在临时寄存器中,该寄存器由后续读取高字节来读取。 即使额外的积分周期在读取低字节和高字节数据寄存器之间结束,高字节寄存器也会读取正确的值。

unsigned char data_0x0A;
unsigned char data_0x0B;
int data_raw = 0;

if( !(data_0x0A >> 7) ){//IR_OF == 0
	data_raw = ((int )data_0x0B << 2) | ((int)data_0x0A & 0x03);
}else{
	printf("IR Data invalid\n");
}
环境光传感器数据寄存器

在这里插入图片描述

▲环境光传感器数据寄存器

  ALS 的 ADC 通道数据表示为 16 位数据,分布在两个寄存器(ALS Data Low 和 ALS Data High)上。 这两个将分别提供 ADC 值的低字节和高字节。 所有通道数据寄存器都是只读的。
  读取低字节寄存器后,可以读取高字节寄存器。 当读取低字节寄存器时,高字节存储在临时寄存器中,该寄存器由后续读取高字节来读取。 即使额外的积分周期在读取低字节和高字节数据寄存器之间结束,高字节寄存器也会读取正确的值。

unsigned char data_0x0C;
unsigned char data_0x0D;
int data_raw = 0;

data_raw = ((int )data_0x0D << 8) | (int)data_0x0C ;
环境光传感器校准寄存器

在这里插入图片描述

▲环境光传感器校准寄存器

  为了补偿组装引起的 ALS 窗口损失,该设备提供了一个参数端口来设置一个值,以消除每个组件之间的差异。 它是一个浮点数,有效范围为 0 ~3.98。 8bits 寄存器表示 00.000000(0.00) ~11.111111 (3.98),分辨率为 1/64。 如果它是由 0x50 写入的,那么它相当于 1.25 (80* 1/64)。
例如 :
  1.原始 ALS 数据(组件级别)= 1000 个计数
  2.组装后 ALS 数据 = 800 计数
  3.应补偿窗口损失=1000/800=1.25
  4.寄存器要设置为0x50(80*1/64=1.25)

环境光传感器配置寄存器

在这里插入图片描述

▲环境光传感器配置寄存器

1、ALS 增益(环境光可检测范围)。 AP3216C 有以下 4 个范围。
  00~20661lux Resoulution = 0.35 lux/count
  01~5162lux Resoulution = 0.0788 lux/count
  10~1291lux Resoulution = 0.0197 lux/count
  11~323lux Resoulution = 0.0049 lux/count

2、ALS 中断过滤器:可配置的中断过滤器是提供在中断触发连续 N 次转换时间后产生的硬件中断。 ALS 中断过滤器位确定 N。

距离传感器数据寄存器

在这里插入图片描述

▲距离传感器数据寄存器

  PS 的 ADC 通道数据表示为跨两个寄存器 PS 数据低和 PS 数据高的 10 位数据。这两个将分别提供 ADC 值的低字节和高字节。
  PS 对象状态 (OBJ) 位显示对象的位置。当物体远离传感器且 PS 的计数超过 PS 的阈值时,OBJ 位将被重置为 0。反之,物体靠近传感器并且 OBJ 位设置为 1,表示物体接近。
  IR 溢出标志 (IR_OF) 指示 PS 数据是否有效。如果该位设置为 1,表示 PS 的数据在高强度红外光下无效。请参考 IR 数据寄存器的说明。
  在读取相应的低字节寄存器后,可以读取高字节寄存器。当读取低字节寄存器时,高字节存储在临时寄存器中,该寄存器由后续读取高字节来读取。
  即使额外的积分周期在读取低字节和高字节数​​据寄存器之间结束,高字节寄存器也会读取正确的值。所有通道数据寄存器都是只读的。

unsigned char data_0x0E;
unsigned char data_0x0F;
int data_raw = 0;

if( !((data_0x0E >> 6) & 0x01) ){//IR_OF == 0
	if( !(data_0x0E >> 7) ){//OBJ == 0
		data_raw = ((int)data_0x0F << 4) |  ((int )data_0x0E & 0x0F);
	}else{
		printf("Measurement overrun\n");
	}
}else{
	printf("PS Data invalid\n");
}
距离传感器配置寄存器

PS 配置寄存器用于设置 PS 积分时间、PS 增益、LED 等待时间和 PS 中断滤波器。
在这里插入图片描述

▲距离传感器配置寄存器

PS 积分时间设置 ADC 的采样/转换时间,它会影响分辨率和灵敏度。更长的积分时间提高了 IR ADC&PS ADC 的分辨率和灵敏度

PS 增益设置可用于增加/减少 OBJ 的检测距离。更高的增益可以增加检测距离,但也会增加噪声或串扰信号。 IR ADC 不会受到影响。
PS 中断滤波器可防止由噪声触发的中断。当 PS 对象状态发生变化时触发中断,并保持该变化连续 M 次转换时间。 PS 中断过滤器位确定 M.

距离传感器LED控制寄存器

在这里插入图片描述

▲距离传感器LED控制寄存器

PS LED 控制寄存器用于控制 IR LED。
LED 脉冲位设置将在一个 PS 转换时间内传输的 PS 脉冲数。更多脉冲会增加 LED 电流消耗,但也会增加 PS 检测距离。
在这里插入图片描述

LED 驱动器比率设置 LED 驱动器的最大电流。较高的比率会增加 LED 电流消耗,但也会增加 PS 检测距离
在这里插入图片描述

距离传感器中断模式寄存器

模式 1 中断行为如图 1 所示。 高/低绝对阈值在开始时设置。 如果 PS DA TA 增加/减少超过高/低阈值并保持 N(1~8) 次持续时间,将触发中断。
当每个中断被断言时,主机可以通过读取 E/F 寄存器来取消断言 INT 引脚,然后主机将设置另一个新的为下一次中断指定的高/低绝对阈值。 PS阈值由寄存器0x2A/0x2B/0x2C/0x2D设置
在这里插入图片描述

▲模式 1

模式 2 执行滞后行为。 假设 PS 对象状态为“near”,只有当 PS 数据低于 PS 低阈值时才会触发 PS 中断(当 PS 对象状态改变时触发中断)。 另一方面,假设 PS 对象状态为“离开”,只有当 PS 数据超过 PS 高阈值时才会触发 PS 中断。 PS阈值可以通过寄存器0x2A/0x2B/0x2C/0x2D设置。
在这里插入图片描述

▲模式 2
距离传感器积分时间寄存器

在这里插入图片描述

▲ 距离传感器积分时间寄存器
距离传感器LED等待时间寄存器

在这里插入图片描述

▲ 距离传感器LED等待时间寄存器

PS 等待时间设置 LED 的 ON/OFF 时间,它也影响接近传感器的响应时间。通过设置更长的等待时间可以节省系统的功耗,但它也增加了对系统的响应时间。 PS 等待时间的时序图如下所示。
在这里插入图片描述

▲ PS 等待时间的时序图

较长的 PS 等待时间也会增加 PS 转换时间。 PS等待时间与转换时间的表格如下所示。 PS转换时间将由PS等待时间设置(基于PS平均时间设置=50ms)。
在这里插入图片描述

距离传感器校准寄存器

在这里插入图片描述

▲距离传感器校准寄存器

与用于窗口损耗补偿的 ALS 校准类似,该设备还具有 PS 校准,可用于消除组装后由顶盖/玻璃引起的串扰差异。共有 9 位动态范围可用于重置串扰,让 PS 计数直接指示距离。方便用户设置相同的 PS 高/低阈值,不受各设备个体差异的影响。

在这里插入图片描述

5.2、程序分析

程序功能:测量红外光、环境光强、距离数据并输出
main.c:主函数

#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <i2c/smbus.h>
#include "i2cbusses.h"
#include <time.h>

/** 
* @file 	AP3216C.c
* @brief 	驱动AP3216C
* @version 	1.1
* @author 	JosephCooper
* @date 	2021.03.02
*/

#define MODE_ALSPSIR_ONCE 0x3

int AP3216C_set_mode(int file, unsigned char mode);
int AP3216C_read_val_raw(int file, int *data_buf);

/* 
 * ./AP3216C <i2c_bus_number> r
 */

int main(int argc, char **argv)
{
	unsigned char dev_addr = 0x1E;

	int file;
	char filename[20];
	int buf[3];

	int ret;
	
	if (argc != 2)
	{
		printf("Usage:\n");
		printf("%s <i2c_bus_number>\n", argv[0]);
		return -1;
	}

	file = open_i2c_dev(argv[1][0]-'0', filename, sizeof(filename), 0);
	if (file < 0)
	{
		printf("can't open %s\n", filename);
		return -1;
	}

	if (set_slave_addr(file, dev_addr, 1))
	{
		printf("can't set_slave_addr\n");
		return -1;
	}

	if( AP3216C_set_mode(file,MODE_ALSPSIR_ONCE) == -1 )
	{
		printf("AP3216C_set_mode err!\n");
		return -1;
	}

	memset(buf,0,sizeof(buf));
	if( AP3216C_read_val_raw(file,buf) == -1 )
	{
		printf("AP3216C_read_val_raw err!\n");
		return -1;
	}

	printf("IR_raw_data = %d\n", buf[0]);
	printf("ALS_raw_data = %d\n", buf[1]);
	printf("ALS_data = %.2lf lux\n", (double)buf[1] * 0.35);
	printf("PS_raw_data = %d\n", buf[2]);
	
	return 0;

	/*	ALS data compute:
	*	00~20661lux    Resoulution = 0.35 lux/count
	*	01~5162lux	   Resoulution = 0.0788 lux/count
	*	10~1291lux     Resoulution = 0.0197 lux/count
	*	11~323lux      Resoulution = 0.0049 lux/count
	*/
	
}

int AP3216C_set_mode(int file, unsigned char mode):设定AP3216C工作模式为mode

/**
*@ Description:设定AP3216C工作模式为mode
* @param file - AP3216C dev file
* @param mode - (111) ALS and PS+IR once
* @return 成功:1 ,失败:-1
*/

int AP3216C_set_mode(int file, unsigned char mode)
{
	int ret;
	struct timespec req;
	

	ret = i2c_smbus_write_byte_data(file, 0, 0x4);//reset IC
	if(ret)
	{
		printf("AP3216C reset err!\n");
		return -1;
	}
	req.tv_sec  = 0;
	req.tv_nsec = 10000000; /* 10ms */
	nanosleep(&req, NULL);


	//设定LED Control Register 0xFF即使用 3 pulses  100% driver ratio
	ret = i2c_smbus_write_byte_data(file, 0x21, 0xFF);//write mode
	if(ret)
	{
		printf("AP3216C LED Control Register write err!\n");
		return -1;
	}
	req.tv_sec  = 0;
	req.tv_nsec = 10000000; /* 10ms */
	nanosleep(&req, NULL);


	ret = i2c_smbus_write_byte_data(file, 0, mode);//write mode
	if(ret)
	{
		printf("AP3216C write mode err!\n");
		return -1;
	}
	req.tv_sec  = 0;
	req.tv_nsec = 240000000; /* 240ms */
	nanosleep(&req, NULL);
	
	return 1;
}

int AP3216C_read_val_raw(int file, int *data_buf):阅读AP3216C中的数据

/**
*@ Description:阅读AP3216C中的数据
* @param file - AP3216C dev file
* @param data_buf - 报存数据的指针
* @return 成功:1 ,失败:-1
*/

#define IRADDR 	0x0A
#define ALSADDR 0x0C
#define PSADDR  0x0E

int AP3216C_read_val_raw(int file, int *data_buf)
{
	int data_IR  = 0;
	int data_ALS = 0;
	int data_PS  = 0;

	unsigned char buf[2];
	int ret = 0;

	/**read IR data */
	memset(buf,0,sizeof(buf));
	ret = i2c_smbus_read_i2c_block_data(file, IRADDR, 2, buf);
	if(ret == 0)
	{
		printf("IR data read err!\n");
		return -1;
	}
	if( !(buf[0] >> 7) ){//IR_OF == 0
		data_IR = ((int )buf[1] << 2) | ((int)buf[0] & 0x03);
	}else{
		printf("IR Data invalid\n");
	}

	/**read ALS data */
	memset(buf,0,sizeof(buf));
	ret = i2c_smbus_read_i2c_block_data(file, ALSADDR, 2, buf);
	if(ret == 0)
	{
		printf("ALS data read err!\n");
		return -1;
	}
	data_ALS = ((int )buf[1] << 8) | (int)buf[0] ;

	/**read PS data */
	memset(buf,0,sizeof(buf));
	ret = i2c_smbus_read_i2c_block_data(file, PSADDR, 2, buf);
	if(ret == 0)
	{
		printf("PS data read err!\n");
		return -1;
	}
	if( !((buf[0] >> 6) & 0x01) ){//IR_OF == 0
		if( !(buf[0] >> 7) ){//OBJ == 0
			data_PS = ((int)buf[1] << 4) |  ((int )buf[0] & 0x0F);
		}else{
			printf("Measurement does not exceed threshold!\n");
		}
	}else{
		printf("PS Data invalid\n");
	}
	
	data_buf[0] = data_IR;
	data_buf[1] = data_ALS;
	data_buf[2] = data_PS;

	return 1;
}

参考资料

I2C时钟延展
CRC校验详解(附代码示例)

Logo

更多推荐