I2C 通信
在本章中,我们将学习如何在应用层中使用 I2C 总线与外部设备的通讯。
示例程序:Code.zip
1.I2C子系统
在 Linux 操作系统中,I2C 子系统是一个关键的驱动框架,用于管理和控制通过 I2C 总线连接的各种外部设备,有关 I2C 子系统的更多详细信息可以在 <Linux内核源码>/Documentation/i2c
目录中找到。I2C 子系统的关键组成部分:
- sysfs 接口:I2C 子系统通过 sysfs 文件系统提供用户空间接口,以便用户可以访问和配置与 I2C 设备相关的信息。其中
/sys/bus/i2c/devices
目录用于管理和配置 I2C 设备的属性和状态信息。用户可以通过读写 sysfs 文件来获取设备信息或控制设备。 - I2C 设备节点:在
/dev
目录下,通常会创建类似/dev/i2c-3
的字符设备节点,这些节点允许用户在用户空间与特定的 I2C 设备或 I2C 适配器进行通信。通过这些节点,用户可以发送和接收数据,以实现对 I2C 设备的操作。
2.I2C测试(Shell)
2.1 引脚分布
开启 I2C 的方法参考 PWM 部分。LuckFox Pico Ultra/Ultra W 引脚图:
2.2 查看设备
在 /sys/bus/i2c/devices
目录中,每个 I2C 设备都有自己的文件夹。这些文件夹的名称通常包含 i2c 和设备编号,例如 /sys/bus/i2c/devices/i2c-3
表示 I2C 总线编号为 3 的设备。如果您想查看系统存在的 I2C 总线,可以使用如下命令:
# ls /sys/bus/i2c/devices/
4-0030 4-0030-1 i2c-4 4-0030-2 i2c-3
2.3 I2C测试
查看i2c-3接口上的设备(若连接设备后检测不到请查看5.1的内容,将引脚配置为上拉)
i2cdetect -a -y 3
读取指定设备的全部寄存器的值
i2cdump -f -y 3 0x68
读取指定IIC设备的某个寄存器的值,如下读取地址为0x68器件中的0x01寄存器值
i2cget -f -y 3 0x68 0x01
写入指定IIC设备的某个寄存器的值,如下设置地址为0x68器件中的0x01寄存器值为0x6f
i2cset -f -y 3 0x68 0x01 0x6f
3.I2C通讯(Python程序)
3.1 完整代码
通过以下程序,可以实现 i2c-3 总线上的设备扫描。
import smbus
def main():
data = [0x01, 0x02]
try:
i2c_bus = smbus.SMBus(3)
print("i2cdetect addr : ", end="")
for address in range(0x7F):
try:
i2c_bus.write_i2c_block_data(address, 0, data)
print("0x{:02X},".format(address), end="")
except OSError:
pass
print()
except Exception as e:
print(f"An error occurred: {e}")
finally:
if i2c_bus:
i2c_bus.close()
if __name__ == "__main__":
main()
3.2 打开I2C设备
在这段代码中,使用 smnbus
库的 SMBus
类打开了一个I2C设备。SMBus
提供了一个简单的接口来与I2C设备进行通信。在此例中,通过 SMBus(3)
打开了I2C总线号为3的设备。这个实例 i2c_bus
被用于后续的I2C通信。
i2c_bus = smbus.SMBus(3)
3.3 发送数据
这段代码通过 write_i2c_block_data
方法向所有可能的I2C地址发送数据块。通过迭代 range(0x7F)
,尝试向每个I2C地址发送 data
数组中的数据块。如果成功发送,会打印出相应的I2C地址(0x00到0x7F),如果由于 OSError
异常发送失败,则忽略该异常,继续尝试下一个地址。
for address in range(0x7F):
try:
i2c_bus.write_i2c_block_data(address, 0, data)
print("0x{:02X},".format(address), end="")
except OSError:
pass
3.4 运行程序
使用 nano 工具打开文件,粘贴并保存代码
# nano i2c.py
运行程序
# python3 i2c.py
实验现象
第一次运行成功与地址为0x3c的设备通信,第二次未连接设备(若连接设备后依然检测不到请查看5.1的内容,将引脚配置为上拉):
4.I2C通讯(C程序)
4.1 ioctl函数
在编写应用程序时需要使用ioctl函数设置i2c相关配置,其函数原型如下
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
当使用ioctl函数进行I2C通信时,常用的request参数主要有以下几种:
I2C_SLAVE
:用于设置I2C从机地址,参数是从机地址的整数值。I2C_SLAVE_FORCE
:类似于I2C_SLAVE
,但是不会检查设备是否存在。如果使用了一个不存在的I2C从机地址,也不会返回错误。I2C_TENBIT
:用于启用或禁用10位地址模式。参数为一个整数,0表示禁用,非零表示启用。I2C_RDWR
:执行I2C读写操作,参数是一个指向struct i2c_rdwr_ioctl_data
结构的指针,该结构包含一系列读写操作。I2C_SMBUS
:用于执行SMBus协议的读写操作,参数是一个指向struct i2c_smbus_ioctl_data
结构的指针,该结构描述了SMBus操作。I2C_RETRIES
:是ioctl函数的一个请求参数,用于设置I2C总线事务的重试次数。在I2C通信中,由于各种原因(例如总线冲突、设备未响应等),可能会出现通信失败的情况,该参数允许您指定在放弃通信之前尝试多少次重试。
4.2 完整代码
通过以下程序,可以实现 i2c-3 总线上的设备扫描。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#define I2C_DEVICE_PATH "/dev/i2c-3"
int main() {
uint8_t data[2] = {0x01,0x02};
const char *i2c_device = I2C_DEVICE_PATH;
int i2c_file;
if ((i2c_file = open(i2c_device, O_RDWR)) < 0) {
perror("Failed to open I2C device");
return -1;
}
ioctl(i2c_file, I2C_TENBIT, 0);
ioctl(i2c_file, I2C_RETRIES, 5);
printf("i2cdetect addr : ");
for (int x = 0; x < 0x7f; x++)
{
if (ioctl(i2c_file, I2C_SLAVE, x) < 0) {
perror("Failed to set I2C slave address");
close(i2c_file);
return -1;
}
if (write(i2c_file, data, 2) == 2)
{
printf("0x%x,", x);
}
}
close(i2c_file);
printf("\r\n");
return 0;
}
4.3 文件路径
这行代码定义了一个宏,用于存储 I2C 设备文件的路径。
#define I2C_DEVICE_PATH "/dev/i2c-3"
4.4 打开I2C设备
这部分代码使用读写的方式打开指定的 I2C 设备文件。
if ((i2c_file = open(i2c_device, O_RDWR)) < 0) {
perror("Failed to open I2C device");
return -1;
}
4.5 配置I2C
这段代码使用 ioctl
函数来配置 I2C 通信。首先,通过 ioctl(i2c_file, I2C_TENBIT, 0)
设置了 I2C 总线为标准 7 位地址模式,而后,通过 ioctl(i2c_file, I2C_RETRIES, 5)
设置了 I2C 通信的重试次数为 5 次。
ioctl(i2c_file, I2C_TENBIT, 0);
ioctl(i2c_file, I2C_RETRIES, 5);
4.6 发送数据
在这段代码中,首先,通过 ioctl(i2c_file, I2C_SLAVE, x)
将 I2C 从机地址设置为 x。接着使用 write
函数向 I2C 设备写入了一个包含 2 字节数据的数据块 data。如果成功写入了 2 字节数据,会打印该 I2C 设备的地址,表示数据已成功发送。
printf("i2cdetect addr : ");
for (int x = 0; x < 0x7f; x++)
{
if (ioctl(i2c_file, I2C_SLAVE, x) < 0) {
perror("Failed to set I2C slave address");
close(i2c_file);
return -1;
}
if (write(i2c_file, data, 2) == 2)
{
printf("0x%x,", x);
}
}
4.7 交叉编译
指定交叉编译工具
首先,我们要将交叉编译工具的路径添加到系统的
PATH
环境变量中,以便可以在任何地方使用交叉编译工具,您可以在shell配置文件中添加以下行(通常是~/.bashrc
或~/.bash_profile
或~/.zshrc
,具体取决于您使用的shell),注意PATH=
后的路径为交叉编译工具所在的目录。gcc路径
<SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc
打开shell配置文件
vi ~/.bashrc
将交叉编译工具的路径添加到系统的PATH环境变量中,将 <SDK Directory> 修改为自己的 SDK 路径,如
/home/luckfox/luckfox-pico/
export PATH=<SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin:$PATH
重新加载shell配置文件,使更改生效
source ~/.bashrc
使用交叉编译工具编译程序
arm-rockchip830-linux-uclibcgnueabihf-gcc i2c.c -o i2c
交叉编译成功后,将在当前目录下生成可在开发板运行的可执行文件
# ls
i2c i2c.c
4.8 运行程序
文件传输
先将
i2c
从虚拟机传输到 Windows,再通过 TFTP 或 ADB 传输到开发板,将文件从 Windows 通过 ADB 将文件传输到开发板的步骤如下:adb push 文件所在路径 开发板存储路径
eg:(将当前目录下i2c文件传输到开发板的根目录)
adb push i2c /运行程序
修改
i2c
文件的操作权限后运行程序# chmod +x i2c
# ./i2c