Linux IIC 驱动详解
目录
3.IIC 设备驱动层(I2C Device Driver)
一、引言
IIC(Inter - Integrated Circuit)总线是一种广泛应用于集成电路之间通信的串行总线协议。在 Linux 系统中,IIC 驱动提供了一种标准的方式来实现与 IIC 设备的通信。理解 Linux IIC 驱动对于开发基于 IIC 接口的设备驱动程序至关重要。
二、IIC 总线概述
1.物理连接与电气特性
- IIC 总线由两根线组成,分别是数据线 SDA(Serial Data Line)和时钟线 SCL(Serial Clock Line)。SDA 和 SCL 这两根线必须要接一个上拉电阻,一般是 4.7K。其余的 I2C 从器件都挂接到 SDA 和 SCL 这两根线上,这样就可以通过 SDA 和 SCL 这两根线来访问多个 I2C设备。
- 多个 IIC 设备可以连接到同一组 SDA 和 SCL 线上,通过设备地址来区分不同的设备。设备地址是一个 7 位或 10 位的值,在通信开始时用于选择目标设备。
- 当 SCL 为高电平时,SDA 的数据必须保持稳定,只有在 SCL 为低电平时,SDA 的数据才能改变,这是 IIC 协议保证数据正确传输的重要规则。
2.通信协议基础
- IIC 通信以起始条件(Start Condition)开始,起始条件是在 SCL 为高电平时,SDA 从高电平变为低电平。通信以停止条件(Stop Condition)结束,停止条件是在 SCL 为高电平时,SDA 从低电平变为高电平。
- 数据传输时,每个字节(8 位)的数据后面都跟着一个应答位(ACK)。接收方在收到一个字节的数据后,会在第 9 个时钟周期拉低 SDA 表示应答,拉高 SDA 表示非应答。
- IIC 支持不同的传输速率,标准模式下最高速率为 100Kbps,快速模式下最高可达 400Kbps,高速模式下可以达到 3.4Mbps。
三、Linux IIC 驱动架构
1.IIC 核心层(I2C Core)
- 提供了 IIC 总线驱动和设备驱动的注册、注销等管理功能。它定义了统一的接口和数据结构,是 IIC 总线和设备驱动之间的桥梁。
- 例如,
i2c_adapter
结构体用于描述 IIC 总线适配器(通常是芯片内部的 IIC 控制器)的信息,包括操作函数集(如发送起始条件、停止条件、发送数据等操作)。i2c_client
结构体用于描述 IIC 设备的信息,如设备地址、设备名称等。 - 核心层还负责处理 IIC 通信的一些共性问题,如并发访问控制等。
2.IIC 总线驱动层(I2C Bus Driver)
- 直接与硬件 IIC 控制器交互,实现 IIC 通信的底层操作。它需要实现
i2c_adapter
结构体中的操作函数,将 IIC 协议的物理操作(如产生起始条件、发送数据位等)转换为对硬件寄存器的读写操作。 - 不同的芯片有不同的 IIC 控制器,因此总线驱动层需要根据具体的芯片硬件进行定制开发。例如,对于某一款 ARM 芯片的 IIC 控制器,总线驱动需要根据芯片手册配置控制器的寄存器,以实现正确的 IIC 通信。
3.IIC 设备驱动层(I2C Device Driver)
- 主要用于实现对特定 IIC 设备的操作。设备驱动层通过调用 IIC 核心层提供的接口,向 IIC 设备发送命令和数据,以及接收设备返回的数据。
- 设备驱动需要了解所驱动设备的具体协议和寄存器操作。例如,一个温度传感器 IIC 设备驱动,需要知道如何向传感器发送读取温度的命令,以及如何解析传感器返回的温度数据。
四、IIC 驱动开发步骤
1.设备树(Device Tree)配置(如果适用)
- 现在的 Linux 系统中,通常使用设备树来描述硬件设备。对于 IIC 设备,需要在设备树中定义 IIC 设备节点。
- 例如,定义一个 IIC 设备节点,包括设备地址、设备名称、可能还包括设备的一些特殊属性(如传感器的量程等)。
i2c_device {
compatible = "your_device_name";
reg = <0x50>; /* 设备地址 */
/* 其他属性 */
};
2.编写 IIC 设备驱动程序
- 初始化函数:
- 在驱动初始化时,需要注册 IIC 设备驱动。通过
i2c_driver
结构体来定义设备驱动的信息,包括设备驱动的名称、探测函数(probe
函数)、移除函数(remove
函数)等。 - 例如:
- 在驱动初始化时,需要注册 IIC 设备驱动。通过
static struct i2c_driver your_device_driver = {
.driver = {
.name = "your_device_name",
},
.probe = your_device_probe,
.remove = your_device_remove,
};
static int __init your_device_init(void)
{
return i2c_add_driver(&your_device_driver);
}
探测函数(probe
函数):
- 当 IIC 设备与驱动匹配成功时,
probe
函数会被调用。在这个函数中,主要进行设备的初始化操作,如获取 IIC 设备结构体(i2c_client
)、检查设备是否可用、初始化设备的寄存器等。 - 可以通过
i2c_get_clientdata
函数获取设备的私有数据,通过i2c_smbus_read_byte
或i2c_smbus_write_byte
等函数进行简单的 IIC 通信操作来检查设备是否正常工作。
操作函数实现:
- 根据设备的功能,实现相应的操作函数,如读取设备数据、写入设备数据等。这些操作通常是通过 IIC 核心层提供的接口函数来实现,如
i2c_master_send
和i2c_master_recv
函数用于发送和接收数据。 - 例如,对于一个读取 IIC 设备数据的函数:
ssize_t your_device_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct i2c_client *client = filp->private_data;
u8 data[count];
int ret;
ret = i2c_master_recv(client, data, count);
if (ret >= 0) {
if (copy_to_user(buf, data, count)) {
return -EFAULT;
}
return count;
}
return ret;
}
- 移除函数(
remove
函数):
当设备被移除或者驱动被卸载时,remove
函数会被调用。在这个函数中,需要释放设备占用的资源,如注销 IIC 设备、释放内存等操作。
static int your_device_remove(struct i2c_client *client)
{
i2c_unregister_device(client);
// 释放其他资源
return 0;
}
3.编译和加载驱动模块
将编写好的驱动程序编译成内核模块,可以使用 Makefile 来进行编译。然后通过insmod
命令加载驱动模块到内核中。加载成功后,驱动就可以与对应的 IIC 设备进行通信了。
五、IIC 驱动中的关键技术点
1.并发访问控制
- 由于多个进程可能同时访问 IIC 设备,因此需要进行并发访问控制。Linux IIC 驱动通常使用自旋锁(Spin Lock)或互斥锁(Mutex)来实现并发访问控制。
- 例如,在
probe
函数和操作函数中,当访问共享的设备资源(如设备寄存器)时,可以使用自旋锁来保证同一时间只有一个进程能够访问。
2.错误处理和恢复
- IIC 通信可能会出现各种错误,如设备无应答、传输超时等。在驱动开发中,需要考虑这些错误情况并进行相应的处理。
- 可以通过检查 IIC 通信函数的返回值来判断是否出现错误。例如,
i2c_master_send
函数返回负数表示出现错误。当出现错误时,可以尝试重新发送命令或者进行设备复位等恢复操作。
3.设备兼容性和可移植性
为了使 IIC 驱动具有良好的设备兼容性和可移植性,应该尽量使用 IIC 核心层提供的标准接口函数进行通信。同时,在设备树中合理定义设备属性,使得驱动能够根据不同的设备配置进行灵活调整。
六、总结
Linux IIC 驱动是一个复杂但功能强大的系统,它通过分层架构实现了对 IIC 设备的高效管理和通信。通过合理的设备树配置和驱动开发步骤,可以实现对各种 IIC 设备的支持。在开发过程中,需要注意并发访问控制、错误处理和设备兼容性等关键技术点,以确保驱动的稳定性和可靠性。
原文地址:https://blog.csdn.net/qq_38072731/article/details/144018215
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!