I2C应用编程:编写APP操作AP3216C

16 阅读5分钟

1. 如何写程序操作AP3216C?

对于应用程序开发,大公司一般提供示例代码/驱动程序,可以直接调用函数,不需要关心硬件细节也不需要写驱动程序;对于中小公司可能从上到下都需要管,虽然不需要写驱动程序和硬件开发,但是需要懂,要知道怎么找到接口。

如下图所示,如果有专用库,优先使用①专用库;如果没有专用库,则使用②通用库;如果没有库,则使用③ open/read/write(底层接口);如果以上都没有,则需要自己④编写驱动程序

应用程序开发.png

如果内核里面没有这款芯片的驱动程序的话,是没办法直接写这个程序的,还需要搞懂芯片的一些细节。 首先需要了解I2C传输协议。

I2C.png

通过接口访问I2C设备1和2的具体步骤:

  • (1)如何分辨?设备地址
  • (2)读写设备:
    • 读:发出地址/方向——对方回应——读数据
    • 写:发出地址/方向——对方回应——写数据

怎么发出地址?方向?对方怎么响应?怎么读/写数据? 使用库。

在i2c-tool-4.2源码中,以eeprog为例子来分析库函数, 下图是eeprog.c和24cXX.c中的示例代码

I2C设备可能有多个,且连接的I2C控制器也不一样,因此需要知道是哪个I2C控制器的哪个设备。

1746258117051.jpg

1746258827871.jpg

③写数据:用接口函数

image.png

④读数据:用接口函数:读的时候需要知道是读哪个存储地址(内存的地址)

读数据时需要读和写两步操作:

①将需要访问的设备的存储地址写入从设备(告诉从设备)

②读设备存储地址里的数据

  • 写:发出设备地址/方向——对方回应——写数据(存储地址)给设备

1746260463114.png

  • 读:发出设备地址/方向——对方回应——读数据

1746260287019.png

2. 基于i2c编写AP3216C程序

AP3216C封装好的专用库函数: 1746261024286.png

1746275126984.png

四个步骤:

1.打开I2C控制器总线(主设备) fd = open_i2c_dev(I2C_BUS, buf, sizeof(buf), 0);

2.设置I2C设备地址(从设备) err = set_slave_addr(fd, AP3216C_ADDR, 1);

构造i2c消息,使用ioctl发起i2c消息,i2c消息里面有具体的读/写信息。

3.写入设备存储地址

msgs[0].addr  = AP3216C_ADDR;
msgs[0].flags = 0;      /* 写:0, 读:I2C_M_RD */
msgs[0].len   = 1;  //写入一个长度数据
msgs[0].buf   = buf_tx;
buf_tx[0] = 0xc;

4.读数据

msgs[1].addr  = AP3216C_ADDR;
msgs[1].flags = I2C_M_RD;      /* 写:0, 读:I2C_M_RD */
msgs[1].len   = 2;
msgs[1].buf   = buf_rx;

1746273191602.png

Makefire文件: 1746278020270.jpg

程序文件:

1746444466254.jpg

实验结果:

1746346976014.png

上面四个库文件和头文件是怎么来的?

对i2c-tools-4.2.tar.xz进行解压,然后修改Makefire(使用vi编辑器基本操作),再make交叉编译i2c-tool,得到头文件、库文件,然后可以将头文件和库文件(include和lib文件夹)放入程序文件中,最后在上传到PC端程序文件中。步骤如下:

1746441652518.jpg

1746442928031.jpg

1746443002282.jpg

1746443101408.jpg

1746443397141.png

1746444008132.jpg

1746445403976.png

3. 基于smbus编写AP3216C程序

1746348489312.png

3.1 使用源码smbus.c

根据SMBus读写源码流程,在基于i2c编写AP3216C程序上进行修改并添加smbus.c文件: 在源码smbuc.c文件里有具体的读写函数,只需要把构造i2c消息方法改成具体的读/写函数。

1746368629962.png

Makefire文件:

1746434410319.jpg

编译结果:

1746434628626.png

如果出现警告:

1746437011528.png

添加头文件:

1746437131741.png

运行结果:

1746434861482.png

3.2 使用例li2c库

1. 源码(smbus.c)与库(libi2c.a/libi2c.so)的区别?

特性源码(smbus.c库(libi2c.a/libi2c.so
内容形式人类可读的 C 语言源文件(.c/.h)。编译后的二进制文件(机器码),不可直接阅读。
编译参与方式直接参与编译(需 #include 头文件)。不参与编译,仅需在链接阶段与目标文件合>并。
可修改性可自由修改代码逻辑或调试。无法修改,只能通过头文件接口调用。
依赖管理需手动维护源码更新和兼容性。依赖库的版本,需确保库与系统环境兼容。

2. 动态库(.so)与静态库(.a)的区别?

特性静态库(.a动态库(.so
链接方式编译时链接库代码被复制到最终可执行文件中。运行时链接可执行文件仅记录库的引用路径,运行时加载。
文件大小可执行文件较大(包含所有依赖的库代码)。可执行文件较小(仅含引用,库文件独立存在)。
内存占用每个进程独立加载库代码,内存占用高。库代码在内存中共享,多个进程共用,内存占用低。
部署复杂度简单(单文件发布,无需额外库文件)。需确保目标系统安装正确版本的动态库。
更新维护需重新编译整个程序以更新库功能。替换动态库文件即可更新功能(需接口兼容)。
兼容性风险无版本冲突(库代码已固化在程序中)。需处理动态库版本兼容性问题(如 libi2c.so.1 vs libi2c.so.2)。
  • 源码 vs 库:源码灵活但需维护;库封装性好但依赖管理复杂。
  • 静态库 vs 动态库
  • 静态库:独立性强、文件大、更新需重编译。
  • 动态库:共享性强、文件小、更新方便但需版本管理。

不使用smbus.c,因为在工作中一般看不到源码(比如smbus.c),但是又必须要使用源码里的函数,那就使用库,在Makefire文件中修改代码,如下:

3.2.1 使用动态库

Makefire文件: 1746367530464.png

编译结果:

1746439144842.png

运行结果: 1746439216049.png

直接运行会出错误,因为动态库需要在运行时指定库的路径

1746439593501.png

或者把三个库文件全部拷贝

1746439991346.png

3.2.2 使用静态库

编译三个.c文件,然后和静态库一起链接

1746438821217.png

1746438928776.png

4. 总结

1746440163913.png