当前位置: 首页 > ops >正文

嵌入式Linux I2C驱动开发

嵌入式Linux I2C驱动开发

1. I2C总线协议基础

1.1 I2C总线概述

I2C(Inter-Integrated Circuit)总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息:串行数据线(SDA)和串行时钟线(SCL)。

I2C总线的主要特点:

  • 简单的双线接口(SDA和SCL)
  • 支持多主控和多从设备
  • 支持不同速率模式(标准模式100kbps,快速模式400kbps,高速模式3.4Mbps)
  • 设备通过唯一的7位或10位地址进行寻址
  • 支持数据流控制和仲裁机制

1.2 I2C通信协议

I2C通信的基本单位是字节,数据传输以字节为单位进行。通信过程包含以下几个关键要素:

起始和停止条件

  • 起始条件:当SCL为高电平时,SDA从高电平向低电平跳变
  • 停止条件:当SCL为高电平时,SDA从低电平向高电平跳变

数据有效性

  • SDA线上的数据必须在SCL高电平期间保持稳定
  • 数据在SCL低电平期间可以改变

应答机制

  • 每传输一个字节后,接收方必须发送一个应答位(ACK)
  • 应答位为低电平表示应答,高电平表示非应答(NACK)

1.3 I2C数据传输格式

I2C数据传输遵循特定的格式:

  1. 主设备发送数据到从设备

    • 主设备发送起始条件
    • 主设备发送从设备地址(7位)+ 写位(0)
    • 从设备应答
    • 主设备发送寄存器地址
    • 从设备应答
    • 主设备发送数据
    • 从设备应答
    • 主设备发送停止条件
  2. 主设备从从设备读取数据

    • 主设备发送起始条件
    • 主设备发送从设备地址(7位)+ 写位(0)
    • 从设备应答
    • 主设备发送寄存器地址
    • 从设备应答
    • 主设备发送重复起始条件
    • 主设备发送从设备地址(7位)+ 读位(1)
    • 从设备应答
    • 从设备发送数据
    • 主设备应答(除最后一个字节外)
    • 主设备发送非应答(NACK)和停止条件

2. Linux内核I2C子系统架构

2.1 I2C子系统层次结构

Linux内核中的I2C子系统采用分层架构设计,主要包含以下层次:

  1. I2C核心层(I2C Core)

    • 提供统一的API接口
    • 管理I2C适配器和I2C设备
    • 处理设备注册和注销
    • 提供I2C通信的基本功能
  2. I2C适配器层(I2C Adapter)

    • 对应具体的I2C控制器硬件
    • 实现底层硬件操作
    • 提供与I2C核心层的接口
  3. I2C设备驱动层(I2C Device Driver)

    • 针对具体的I2C设备
    • 实现设备特定的功能
    • 通过I2C核心层与硬件交互

2.2 I2C核心数据结构

Linux内核中定义了几个关键的数据结构来管理I2C系统:

struct i2c_adapter

struct i2c_adapter {struct module *owner;unsigned int class;          /* classes to allow probing for */const struct i2c_algorithm *algo; /* the algorithm to access the bus */void *algo_data;/* --- administration stuff. */int (*client_register)(struct i2c_client *);int (*client_unregister)(struct i2c_client *);/* --- i2c_adapter is shared between several drivers */struct mutex bus_lock;/* --- settings related to the transaction */u32 timeout;                 /* in jiffies */u32 retries;struct device dev;           /* the adapter device */int nr;char name[48];struct completion dev_released;
};

struct i2c_algorithm

struct i2c_algorithm {int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,unsigned short flags, char read_write,u8 command, int size, union i2c_smbus_data *data);/* To determine what the adapter supports */u32 (*functionality)(struct i2c_adapter *);
};

struct i2c_client

struct i2c_client {unsigned short flags;        /* div., see below */unsigned short addr;         /* chip address - NOTE: 7bit */char name[I2C_NAME_SIZE];struct i2c_adapter *adapter; /* the adapter we sit on */struct i2c_driver *driver;   /* and our access routines */struct device dev;           /* the device structure */int irq;                     /* irq issued by device (or -1) */struct list_head detected;
};

struct i2c_driver

struct i2c_driver {unsigned int class;/* Standard driver model interfaces */int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);int (*remove)(struct i2c_client *client);/* driver model interfaces that don't relate to enumeration */void (*shutdown)(struct i2c_client *client);int (*suspend)(struct i2c_client *client, pm_message_t mesg);int (*resume)(struct i2c_client *client);/* Old style "attach" slot, for use by drivers that don't use hot* plugging*/int (*attach_adapter)(struct i2c_adapter *adapter);/* a ioctl like command that can be used to perform specific functions* with the device.*/int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);struct device_driver driver;const struct i2c_device_id *id_table;/* Device detection callback for automatic device creation */int (*detect)(struct i2c_client *client, struct i2c_board_info *info);const unsigned short *address_list;struct list_head clients;
};

3. I2C设备驱动开发详解

3.1 I2C设备驱动框架

I2C设备驱动遵循Linux内核的标准驱动框架,主要包括以下几个部分:

  1. 模块初始化和退出函数
  2. I2C驱动结构体定义
  3. probe和remove函数实现
  4. 文件操作函数实现

3.2 AP3216C传感器介绍

AP3216C是一款集成的环境光传感器、接近传感器和红外传感器模块。它通过I2C接口与主控制器通信,提供以下功能:

  • 环境光传感器(ALS):测量环境光照强度
  • 接近传感器(PS):检测物体接近程度
  • 红外传感器(IR):测量红外光强度

AP3216C的主要特性:

  • 工作电压:2.4V-3.6V
  • I2C地址:0x1E(7位地址)
  • 通信速率:支持标准模式(100kbps)和快速模式(400kbps)
  • 低功耗设计
  • 集成ADC和数字信号处理

3.3 驱动代码详细分析

3.3.1 头文件包含和宏定义
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/delay.h>#include "ap3216creg.h"

这些头文件包含了驱动开发所需的基本功能:

  • <linux/module.h>:模块相关功能
  • <linux/kernel.h>:内核基本定义
  • <linux/init.h>:初始化相关宏
  • <linux/fs.h>:文件系统相关功能
  • <asm/io.h>:I/O操作
  • <asm/uaccess.h>:用户空间访问
  • <linux/cdev.h>:字符设备相关
  • <linux/device.h>:设备模型
  • <linux/of.h>:设备树相关
  • <linux/slab.h>:内存分配
  • <linux/gpio.h>:GPIO操作
  • <linux/i2c.h>:I2C相关功能
  • <linux/delay.h>:延时函数
3.3.2 设备结构体定义
struct ap3216c_dev
{dev_t devid;int major;int minor;struct cdev cdev;struct class *class;struct device *device;struct device_node *node;void *private_data;unsigned short ir, als, ps;
};
struct ap3216c_dev ap3216cdev;

这个结构体定义了AP3216C设备的所有相关信息:

  • devid:设备号
  • majorminor:主次设备号
  • cdev:字符设备结构体
  • classdevice:设备模型相关
  • node:设备树节点
  • private_data:私有数据,用于存储i2c_client指针
  • iralsps:存储传感器读取的数据
3.3.3 I2C读写函数实现

ap3216c_read_regs函数

static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
{struct i2c_client *client = dev->private_data;struct i2c_msg msg[] = {{.addr = client->addr,.flags = 0,.buf = &reg,.len = 1,},{.addr = client->addr,.flags = I2C_M_RD,.buf = val,.len = len,}};return i2c_transfer(client->adapter, msg, 2);
}

这个函数实现了多字节读取功能:

  • 创建两个i2c_msg结构体,第一个用于发送寄存器地址,第二个用于读取数据
  • 使用i2c_transfer函数执行I2C传输
  • 返回传输结果

ap3216c_write_regs函数

static int ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, int len)
{u8 b[256];struct i2c_msg msg;struct i2c_client *client;client = dev->private_data;b[0] = reg;memcpy(&b[1], buf, len);msg.addr = client->addr;msg.flags = 0;msg.buf = b;msg.len = len + 1;return i2c_transfer(client->adapter, &msg, 1);
}

这个函数实现了多字节写入功能:

  • 将寄存器地址和数据合并到一个缓冲区中
  • 创建一个i2c_msg结构体
  • 使用i2c_transfer函数执行I2C传输

ap3216c_read_reg和ap3216c_write_reg函数
这两个函数是单字节读写函数的封装,分别调用多字节读写函数实现单字节操作。

3.3.4 文件操作函数实现

ap3216c_open函数

static int
ap3216c_open(struct inode *inode, struct file *filp)
{u8 data = 0;filp->private_data = &ap3216cdev;ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x4);mdelay(50);ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x3);ap3216c_read_reg(&ap3216cdev, AP3216C_SYSTEMCONG, &data);return 0;
}

这个函数在设备打开时被调用:

  • 设置文件私有数据
  • 向AP3216C写入配置值0x4,进入软件复位状态
  • 延时50ms
  • 写入配置值0x3,进入正常工作状态

ap3216c_readdata函数

void ap3216c_readdata(struct ap3216c_dev *dev)
{unsigned char buf[6];unsigned char i;/* 循环读取所有传感器数据 */for (i = 0; i < 6; i++){ap3216c_read_reg(dev, AP3216C_IRDATALOW + i, &buf[i]);}if (buf[0] & 0X80) /* IR_OF位为1,则数据无效 */dev->ir = 0;else /* 读取IR传感器的数据   		*/dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);dev->als = ((unsigned short)buf[3] << 8) | buf[2]; /* 读取ALS传感器的数据 			 */if (buf[4] & 0x40) /* IR_OF位为1,则数据无效 			*/dev->ps = 0;else /* 读取PS传感器的数据    */dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
}

这个函数读取所有传感器数据:

  • 从AP3216C_IRDATALOW开始连续读取6个寄存器的数据
  • 根据数据格式解析IR、ALS和PS传感器的值
  • 对于IR和PS传感器,需要检查数据有效性标志位

ap3216c_read函数

static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *loff)
{unsigned short data[3], ret = 0;struct ap3216c_dev *dev = filp->private_data;ap3216c_readdata(dev);data[0] = dev->ir;data[1] = dev->ps;data[2] = dev->als;printk("User: ir %d, ps %d, als %d\r\n", data[0], data[1], data[2]);ret = copy_to_user(buf, data, sizeof(data));if (ret){ret = -EINVAL;printk("Driver: copy_to_user");}return sizeof(data);
}

这个函数实现了read系统调用:

  • 调用ap3216c_readdata读取传感器数据
  • 将数据复制到用户空间
  • 返回读取的数据大小
3.3.5 I2C驱动结构体实现

probe函数

static int
ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{/* 1、设置设备号 */if (ap3216cdev.major){ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);}else{alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME);ap3216cdev.major = MAJOR(ap3216cdev.devid);}/* 2、注册设备      */cdev_init(&ap3216cdev.cdev, &ap3216c_fops);cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);/* 3、创建类      */ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);if (IS_ERR(ap3216cdev.class)){return PTR_ERR(ap3216cdev.class);}/* 4、创建设备 */ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);if (IS_ERR(ap3216cdev.device)){return PTR_ERR(ap3216cdev.device);}ap3216cdev.private_data = client;return 0;
}

probe函数在设备匹配成功时被调用:

  • 分配和注册字符设备号
  • 初始化和添加字符设备
  • 创建设备类和设备节点
  • 保存i2c_client指针到私有数据

remove函数

static int ap3216c_remove(struct i2c_client *client)
{cdev_del(&ap3216cdev.cdev);                              /*  删除cdev */unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT); /* 注销设备号 */device_destroy(ap3216cdev.class, ap3216cdev.devid);class_destroy(ap3216cdev.class);return 0;
}

remove函数在设备移除时被调用:

  • 删除字符设备
  • 注销设备号
  • 销毁设备和类
3.3.6 I2C设备ID和设备树匹配表
static struct i2c_device_id ap3216c_i2c_id[] = {{"alientek,ap3216c", 0},{}};static struct of_device_id ap3216c_of_id[] = {{.compatible = "alientek,ap3216c",},{/* sentinel */}};

这两个表用于设备匹配:

  • i2c_device_id用于传统的I2C设备匹配
  • of_device_id用于设备树匹配
3.3.7 I2C驱动注册
struct i2c_driver ap3216c_driver = {.probe = ap3216c_probe,.remove = ap3216c_remove,.id_table = ap3216c_i2c_id,.driver = {.name = "ap3216c",.owner = THIS_MODULE,.of_match_table = of_match_ptr(ap3216c_of_id),},
};static int __init
ap3216c_init(void)
{int ret = 0;ret = i2c_add_driver(&ap3216c_driver);if (ret < 0){ret = -EINVAL;goto fail_i2c_add;}return 0;
fail_i2c_add:printk("Driver: fail_i2c_add\r\n");return 0;
}static void __exit
ap3216c_exit(void)
{i2c_del_driver(&ap3216c_driver);
}module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("alientek");

驱动的初始化和退出函数:

  • 使用i2c_add_driver注册I2C驱动
  • 使用i2c_del_driver注销I2C驱动
  • 使用module_init和module_exit宏指定初始化和退出函数

4. 设备树配置详解

4.1 设备树基本概念

设备树(Device Tree)是一种描述硬件配置的数据结构,它将硬件信息从内核代码中分离出来,使得同一个内核可以支持不同的硬件平台而无需重新编译。

4.2 I2C总线设备树配置

在提供的设备树文件imx6ull-alientek-emmc.dts中,I2C总线的配置如下:

&i2c1 {clock-frequency = <100000>;pinctrl-names = "default";pinctrl-0 = <&pinctrl_i2c1>;status = "okay";ap3216c@1e {compatible = "alientek,ap3216c";reg = <0x1e>;};
};

clock-frequency:设置I2C总线的时钟频率为100kHz,对应标准模式。

pinctrl-names和pinctrl-0:配置I2C引脚的复用功能和电气特性。

status:设置为"okay"表示启用该I2C总线。

ap3216c@1e节点

  • 节点名称:ap3216c@1e,其中1e是设备的I2C地址
  • compatible:匹配字符串,用于驱动匹配
  • reg:设备在I2C总线上的地址

4.3 引脚复用配置

pinctrl_i2c1: i2c1grp {fsl,pins = <MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0>;
};

这段配置将UART4的TX和RX引脚复用为I2C1的SCL和SDA:

  • MX6UL_PAD_UART4_TX_DATA__I2C1_SCL:将UART4_TX_DATA引脚复用为I2C1_SCL
  • MX6UL_PAD_UART4_RX_DATA__I2C1_SDA:将UART4_RX_DATA引脚复用为I2C1_SDA
  • 0x4001b8b0:引脚配置参数,包含驱动强度、上拉/下拉等设置

5. 应用程序开发

5.1 用户空间程序分析

int main(int argc, char *argv[])
{int cnt = 0;if (argc != 2){fprintf(stderr, "Usage: %s <led_device> <0|1>\n", argv[0]);return -1;}char *fileanme;unsigned short databuf[3];fileanme = argv[1];int fd = 0;fd = open(fileanme, O_RDWR);if (fd < 0){perror("User: open file device error\r\n");return -1;}while (1){int ret = read(fd, databuf, sizeof(databuf));if (ret > 0){printf("User: ir %d, ps %d, als %d\r\n", databuf[0], databuf[1], databuf[2]);}// sleep(200);}close(fd);return 0;
}

这个应用程序的主要功能:

  • 检查命令行参数
  • 打开设备文件
  • 循环读取传感器数据
  • 打印读取到的数据
  • 关闭设备文件

5.2 编译和运行

  1. 编译驱动

    make
    
  2. 加载驱动

    insmod ap3216c.ko
    
  3. 编译应用程序

    gcc ap3216cAPP.c -o ap3216cAPP
    
  4. 运行应用程序

    ./ap3216cAPP /dev/ap3216c
    

源码仓库位置:https://gitee.com/dream-cometrue/linux_driver_imx6ull

http://www.xdnf.cn/news/19115.html

相关文章:

  • 从零到一:使用Flask构建“我的笔记”网站
  • [光学原理与应用-337]:ZEMAX - 自带的用于学习的样例设计
  • LeetCode100-240搜索二维矩阵Ⅱ
  • Mysql常用函数
  • 针对 “TCP 会话维持与身份验证” 的攻击
  • LabVIEW测斜设备承压试验台
  • SQL学习记录
  • 使用git bash ,出现Can‘t get terminal settings: The handle is invalid. 的解决方法与思路
  • 【OpenGL ES】光栅化插值原理和射线拾取原理
  • 把 AI 塞进「智能跳绳」——基于 MEMS 传感器的零样本卡路里估算器
  • [HFCTF2020]EasyLogin
  • UCIE Specification详解(九)
  • 平安养老险深分开展“金融护航,安居鹏城”新市民金融服务宣传活动
  • React Native 初体验
  • LeetCode 完全背包 279. 完全平方数
  • 任意函数都有原像
  • Linux之Shell编程(二)
  • Python中一些包的使用
  • 【黑客技术零基础入门】黑客入门教程(非常详细)从零基础入门到精通,看完这一篇就够了
  • Python结构化模式匹配:解析器的革命性升级
  • playbook剧本
  • Centos卸载anaconda
  • 力扣p1011在D天送达包裹的能力 详解
  • 【网弧软著正版】2025最强软著材料AI生成系统,基于GPT5.0
  • 嵌入式Linux驱动开发:i.MX6ULL中断处理
  • 【面试场景题】怎么做业务领域划分
  • 163.在 Vue3 中使用 OpenLayers 解析 GeoJSON,并给 Feature 填充 pattern(图案)颜色
  • 交叉编译 手动安装 libzip 库 移植ARM 需要 zlib的
  • mysql安全运维之安全模型与原则-构建坚不可摧的数据库防护体系
  • 《AI智脉速递》2025 年 8 月22 日 - 29 日