Linux应用开发基础知识——I2C应用编程(十三)

简介: Linux应用开发基础知识——I2C应用编程(十三)

一、无需编写驱动程序即可访问 I2C 设备

       APP 访问硬件肯定是需要驱动程序的,对于 I2C 设备,内核提供了驱动程序 drivers/i2c/i2c-dev.c,通过它可以直接使用下面的 I2C 控制器驱动程序来访问 I2C 设备。

i2c-tools 是一套好用的工具,也是一套示例代码。

1.体验 I2C-Tools

       使用一句话概括 I2C 传输:APP 通过 I2C Controller 与 I2C Device 传 输数据。 所以使用 I2C-Tools 时也需要指定:

        哪个 I2C 控制器(或称为 I2C BUS、I2C Adapter)

        哪个 I2C 设备(设备地址)

        数据:读还是写、数据本身

(1)交叉编译

       配置环境:vim ~/.bashrc

export ARCH=arm
export CROSS_COMPILE= arm-buildroot-linux-gnueabihf-gccexport PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin

       修改 I2C-Tools 的 Makefile 指定交叉编译工具链

CC ?= gcc
AR ?= ar
STRIP ?= strip

       改为(指定交叉编译工具链前缀, 去掉问号):

CC = $(CROSS_COMPILE)gcc
AR = $(CROSS_COMPILE)ar
STRIP = $(CROSS_COMPILE)strip

       在 Makefile 中,“?=”在第一次设置变量时才会起效果,如果之前设置过 该变量,则不会起效果。

(2)执行 make

       执行 make 时,是动态链接,需要把 libi2c.so 也放到单板上

       想静态链接的话,执行:make USE_STATIC_LIB=1

(3)用法

        i2cdetect:I2C 检测

// 列出当前的 I2C Adapter(或称为 I2C Bus、I2C Controller)
i2cdetect -l
// 打印某个 I2C Adapter 的 Functionalities, I2CBUS 为 0、1、2 等整数
i2cdetect -F I2CBUS
// 看看有哪些 I2C 设备, I2CBUS 为 0、1、2 等整数
i2cdetect -y -a I2CBUS
// 效果如下
# 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 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
// --表示没有该地址对应的设备, UU 表示有该设备并且它已经有驱动程序,
// 数值表示有该设备但是没有对应的设备驱动
# i2cdetect -y -a 0 
 0 1 2 3 4 5 6 7 8 9 a b c d e f
00: 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- UU -- -- -- 1e --
20: -- -- UU -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

(4)i2cget:I2C 读

 使用说明:

# 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

 使用示例:

// 读一个字节: I2CBUS 为 0、1、2 等整数, 表示 I2C Bus; CHIP-ADDRESS 表示设备地址
i2cget -f -y I2CBUS CHIP-ADDRESS
// 读某个地址上的一个字节: 
// I2CBUS 为 0、1、2 等整数, 表示 I2C Bus
// CHIP-ADDRESS 表示设备地址
// DATA-ADDRESS: 芯片上寄存器地址
// MODE:有 2 个取值, b-使用`SMBus Read Byte`先发出 DATA-ADDRESS, 再读一个字节, 中间无
P 信号
// c-先 write byte, 在 read byte,中间有 P 信号
i2cget -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS MODE
// 读某个地址上的 2 个字节: 
// I2CBUS 为 0、1、2 等整数, 表示 I2C Bus
// CHIP-ADDRESS 表示设备地址
// DATA-ADDRESS: 芯片上寄存器地址
// MODE:w-表示先发出 DATA-ADDRESS,再读 2 个字节
i2cget -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS MODE

(5)i2cset:I2C 写

 使用说明:

# 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

 使用示例:

// 写一个字节: I2CBUS 为 0、1、2 等整数, 表示 I2C Bus; CHIP-ADDRESS 表示设备地址
 // DATA-ADDRESS 就是要写的数据
 i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS
 
 // 给 address 写 1 个字节(address, value):
 // I2CBUS 为 0、1、2 等整数, 表示 I2C Bus; CHIP-ADDRESS 表示设备地址
 // DATA-ADDRESS: 8 位芯片寄存器地址; 
 // VALUE: 8 位数值
 // MODE: 可以省略,也可以写为 b
 i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE [b]
 
 // 给 address 写 2 个字节(address, value):
 // I2CBUS 为 0、1、2 等整数, 表示 I2C Bus; CHIP-ADDRESS 表示设备地址
 // DATA-ADDRESS: 8 位芯片寄存器地址; 
 // VALUE: 16 位数值
 // MODE: w
 i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE w
 
 // SMBus Block Write:给 address 写 N 个字节的数据
 // 发送的数据有:address, N, value1, value2, ..., valueN
 // 跟`I2C Block Write`相比, 需要发送长度 N
 // I2CBUS 为 0、1、2 等整数, 表示 I2C Bus; CHIP-ADDRESS 表示设备地址
 // DATA-ADDRESS: 8 位芯片寄存器地址; 
 // VALUE1~N: N 个 8 位数值
 // MODE: s
 i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE1 ... VALUEN s
 
 // I2C Block Write:给 address 写 N 个字节的数据
 // 发送的数据有:address, value1, value2, ..., valueN
 // 跟`SMBus Block Write`相比, 不需要发送长度 N
 // I2CBUS 为 0、1、2 等整数, 表示 I2C Bus; CHIP-ADDRESS 表示设备地址
 // DATA-ADDRESS: 8 位芯片寄存器地址; 
 // VALUE1~N: N 个 8 位数值
 // MODE: i
 i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE1 ... VALUEN i
 
[root@100ask:~]# i2cset -f -y 0 0xle 0 0x4
[root@100ask:~]# i2cset -f -y 0 0x1e 0 0x3
[root@100ask:~]# i2cset -f -y 0 0x1e 0 0xc
[root@100ask:~]# i2cset -f -y 0 0x1e 0 0xe
//i2cset         I2C写数据
//-f                 强制写入
//-y                默认同意
//0                 往0号总线写
//0xle            0号总线地址
//0x4             把0x4写到里面进行复位
//0x3             把0x3写到里面进行使能

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

 使用说明:

# 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 omit
ted)
 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

 使用举例:

// Example (bus 0, read 8 byte at offset 0x64 from EEPROM at 0x50):
# i2ctransfer -f -y 0 w1@0x50 0x64 r8
// Example (bus 0, write 3 byte at offset 0x64 from EEPROM at 0x50):
# i2ctransfer -f -y 0 w9@0x50 0x64 val1 val2 val3
// Example 
// first: (bus 0, write 3 byte at offset 0x64 from EEPROM at 0x50)
// and then: (bus 0, read 3 byte at offset 0x64 from EEPROM at 0x50)
# i2ctransfer -f -y 0 w9@0x50 0x64 val1 val2 val3 r3@0x50 
# i2ctransfer -f -y 0 w9@0x50 0x64 val1 val2 val3 r3 //如果设备地址不变,后面的设备地址可
省略

2.使用 I2C-Tools 操作传感器 AP3216C

开发板上有光感芯片 AP3216C:

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

        复位:往寄存器 0 写入 0x4

        使能:往寄存器 0 写入 0x3

       读光强:读寄存器 0xC、0xD 得到 2 字节的光强

        读距离:读寄存器 0xE、0xF 得到 2 字节的距离值 AP3216C 的设备地址是 0x1E,假设节在 I2C BUS0 上,操作命令如下:

(1)使用 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

//0xc             把0xc写到里面进行读光强

//0xe             把0xe写到里面进行读距离  

(2)使用 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) 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)

3.源码流程分析

(1)使用 I2C 方式

示例代码:i2ctransfer.c

(2)使用 SMBus 方式

示例代码:i2cget.c、i2cset.c

二、编写 APP 直接访问 EEPROM

1.硬件连接

IMX6ULL 的 I2C 模块连接方法:

2.AT24C02 访问方法

(1)设备地址

从芯片手册上可以知道,AT24C02 的设备地址跟它的 A2、A1、A0 引脚有关:

打开 I2C 模块的原理图:

从原理图可知,A2A1A0 都是 0,所以 AT24C02 的设备地址是:0b1010000,即 0x50。

(2)写数据

(3)读数据

       可以读 1 个字节,也可以连续读出多个字节。连续读多个字节时,芯片内部 的地址会自动累加。当地址到达存储空间最后一个地址时,会从 0 开始。

3 .使用 I2C-Tools 的函数编程

(1)at24c02_test.c

  1 
  2 #include <sys/ioctl.h>
  3 #include <errno.h>
  4 #include <string.h>
  5 #include <stdio.h>
  6 #include <stdlib.h>
  7 #include <unistd.h>
  8 #include <linux/i2c.h>
  9 #include <linux/i2c-dev.h>
 10 #include <i2c/smbus.h>
 11 #include "i2cbusses.h"
 12 #include <time.h>
 13 
 14 
 15 /* ./at24c02 <i2c_bus_number> w "100ask.taobao.com"
 16  * ./at24c02 <i2c_bus_number> r
 17  */
 18 
 19 int main(int argc, char **argv)
 20 {
 21     unsigned char dev_addr = 0x50;
 22     unsigned char mem_addr = 0;
 23     unsigned char buf[32];
 24 
 25     int file;
 26     char filename[20];
 27     unsigned char *str;
 28 
 29     int ret;
 30 
 31     struct timespec req;
 32     
 33     if (argc != 3 && argc != 4)
 34     {
 35         printf("Usage:\n");
 36         printf("write eeprom: %s <i2c_bus_number> w string\n", argv[0]);
 37         printf("read  eeprom: %s <i2c_bus_number> r\n", argv[0]);
 38         return -1;
 39     }
 40 
 41     file = open_i2c_dev(argv[1][0]-'0', filename, sizeof(filename), 0);
 42     if (file < 0)
 43     {
 44         printf("can't open %s\n", filename);
 45         return -1;
 46     }
 47 
 48     if (set_slave_addr(file, dev_addr, 1))
 49     {
 50         printf("can't set_slave_addr\n");
 51         return -1;
 52     }
 53 
 54     if (argv[2][0] == 'w')
 55     {
 56         // write str: argv[3]
 57         str = argv[3];
 58 
 59         req.tv_sec  = 0;
 60         req.tv_nsec = 20000000; /* 20ms */
 61         
 62         while (*str)
 63         {
 64             // mem_addr, *str
 65             // mem_addr++, str++
 66             ret = i2c_smbus_write_byte_data(file, mem_addr, *str);
 67             if (ret)
 68             {
 69                 printf("i2c_smbus_write_byte_data err\n");
 70                 return -1;
 71             }
 72             // wait tWR(10ms)
 73             nanosleep(&req, NULL);
 74             
 75             mem_addr++;
 76             str++;
 77         }
 78         ret = i2c_smbus_write_byte_data(file, mem_addr, 0); // string end char
 79         if (ret)
 80         {
 81             printf("i2c_smbus_write_byte_data err\n");
 82             return -1;
 83         }
 84     }
 85     else
 86     {
 87         // read
 88         ret = i2c_smbus_read_i2c_block_data(file, mem_addr, sizeof(buf), buf);
 89         if (ret < 0)
 90         {
 91             printf("i2c_smbus_read_i2c_block_data err\n");
 92             return -1;
 93         }
 94         
 95         buf[31] = '\0';
 96         printf("get data: %s\n", buf);
 97     }
 98     
 99     return 0;
100     
101 }

第21行:确定设备地址

第22行:确定存储空间大小

第23行:确定存储空间的缓冲区buf

第31行:定义一个时间结构体以便于后续休眠

第33~39行:打印用法

第41行:打开设备节点

第48行:检验地址是否设置成功

第54~84行:进行写操作

       第66行:写入字节

       第73行:写入后进行延时休眠十毫秒

       第78行:写入结束符

第85~97行:进行读操作

      第88行:读出数据

       第95行:将字符串最后一行设置为‘\0’来防止循环

       第98行:打印出来数据

(2)编译

IMX6ULL 编译时,有如下错误:

       这是因为 IMX6ULL 的工具链自带的 include 目录中,没有 smbus.h,需要我们自己提供这个头文件,解决方法:

 Makefile源码:

  1 all:
  2     $(CROSS_COMPILE)gcc -I ./include -o at24c02_test at24c02_test.c i2cbusses.c smbus.c
  3

(3)上机测试

      挂载 NFS

root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt

      复制、执行程序

root@100ask:~]# cp /mnt/at24c02_test /bin
[root@100ask:~]# at24c02_test 0 w abcd
[root@100ask:~]# at24c02_test 0 r
get data: abcd


目录
相关文章
|
2月前
|
Shell Linux
Linux shell编程学习笔记30:打造彩色的选项菜单
Linux shell编程学习笔记30:打造彩色的选项菜单
|
5天前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
21 5
|
18天前
|
运维 监控 Shell
深入理解Linux系统下的Shell脚本编程
【10月更文挑战第24天】本文将深入浅出地介绍Linux系统中Shell脚本的基础知识和实用技巧,帮助读者从零开始学习编写Shell脚本。通过本文的学习,你将能够掌握Shell脚本的基本语法、变量使用、流程控制以及函数定义等核心概念,并学会如何将这些知识应用于实际问题解决中。文章还将展示几个实用的Shell脚本例子,以加深对知识点的理解和应用。无论你是运维人员还是软件开发者,这篇文章都将为你提供强大的Linux自动化工具。
|
20天前
|
存储 安全 关系型数据库
Linux系统在服务器领域的应用与优势###
本文深入探讨了Linux操作系统在服务器领域的广泛应用及其显著优势。通过分析其开源性、安全性、稳定性和高效性,揭示了为何Linux成为众多企业和开发者的首选服务器操作系统。文章还列举了Linux在服务器管理、性能优化和社区支持等方面的具体优势,为读者提供了全面而深入的理解。 ###
|
2月前
|
Shell Linux
Linux shell编程学习笔记82:w命令——一览无余
Linux shell编程学习笔记82:w命令——一览无余
|
2月前
|
Shell Linux Python
python执行linux系统命令的几种方法(python3经典编程案例)
文章介绍了多种使用Python执行Linux系统命令的方法,包括使用os模块的不同函数以及subprocess模块来调用shell命令并处理其输出。
33 0
|
Linux
Linux驱动开发(使用I2C总线设备驱动模型编写AT24C02驱动程序)
Linux驱动开发(使用I2C总线设备驱动模型编写AT24C02驱动程序)
149 0
|
Linux 芯片
Linux驱动之I2C设备驱动
下面的代码分析主要都在注释中,会按照驱动中函数的执行顺序分析。
|
存储 网络协议 Linux
Linux I2C设备驱动基本规范
不同于单片机驱动开发,即使是简单的I2C设备驱动程序,如果要在Linux上实现同种功能的驱动程序,事情也会变的复杂起来。对于初学者而言,主要的困难就是不知道如何使用Linux现有的驱动框架,去完成驱动程序的开发。I2C设备驱动,相对来说比较简单,但由于Linux大部分设备驱动框架十分的类似,所以,通过对于I2C驱动框架的学习,可以作为继续深入Linux其他设备驱动框架的基础。
531 0