i2c_driver 类似 platform_driver,编写IIC设备驱动重点要处理的内容。
* 当 I2C 设备和驱动匹配成功以后 probe 函数就会执行,和 platform 驱动一样。
- device_driver驱动结构体,如果使用设备树的话,需要设置 device_driver 的of_match_table 成员变量,也就是驱动的兼容(compatible)属性。
- id_table 是传统的、未使用设备树的设备匹配 ID 表。
- 对于 IIC 设备驱动编写人来说,重点工作就是构建 i2c_driver,构建完成以后需要向Linux 内核注册这个 i2c_driver。i2c_driver 注册函数为 int i2c_register_driver,此函数原型如下:
int i2c\_register\_driver(struct module \*owner,
struct i2c\_driver \*driver)
owner:一般为 THIS_MODULE。
driver:要注册的 i2c_driver。
注销IIC设备。
void i2c\_del\_driver(struct i2c\_driver \*driver)
3、IIC设备和驱动匹配过程
设备和驱动匹配过程是通过i2c_bus_type
.match 就是 I2C 总线的设备和驱动匹配函数,在这里就是 i2c_device_match 这个函数,此
函数内容如下:
设备树下匹配过程:
- of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 I2C 设备节点的 compatible 属性和of_device_id 中的 compatible 属性是否相等,如果相等的话就表示 I2C设备和驱动匹配。
4、I.MX6U 的 I2C 适配器驱动分析(可以跳过!!!)
在imx6ull.dtsi下找到文件中找到 I.MX6U 的 I2C1 控制器节点,内容如下:
通过搜索设备树节点信息,找到如下内容:
设备树匹配函数:
再看匹配成功之后的probe执行的代码,匹配成功之后,probe函数执行相关的操作:
i2c_imx_algo 包含 I2C1 适配器与 I2C 设备的通信函数 master_xfer,i2c_imx_algo 结构体定义如下:
这个IIC适配器支持的通信协议:
i2c_imx_xfer函数(不全):
static int i2c\_imx\_xfer(struct i2c\_adapter \*adapter,
struct i2c\_msg \*msgs, int num)
{
unsigned int i, temp;
int result;
bool is_lastmsg = false;
struct imx\_i2c\_struct \*i2c_imx = i2c\_get\_adapdata(adapter);
dev\_dbg(&i2c_imx->adapter.dev, "<%s>\n", \_\_func\_\_);
/\* Start I2C transfer \*/
result = i2c\_imx\_start(i2c_imx);
if (result)
goto fail0;
/\* read/write data \*/
for (i = 0; i < num; i++) {
if (i == num - 1)
is_lastmsg = true;
if (i) {
dev\_dbg(&i2c_imx->adapter.dev,
"<%s> repeated start\n", \_\_func\_\_);
temp = imx\_i2c\_read\_reg(i2c_imx, IMX_I2C_I2CR);
temp |= I2CR_RSTA;
imx\_i2c\_write\_reg(temp, i2c_imx, IMX_I2C_I2CR);
result = i2c\_imx\_bus\_busy(i2c_imx, 1);
if (result)
goto fail0;
}
dev\_dbg(&i2c_imx->adapter.dev,
"<%s> transfer message: %d\n", \_\_func\_\_, i);
/\* write/read data \*/
if (msgs[i].flags & I2C_M_RD)
result = i2c\_imx\_read(i2c_imx, &msgs[i], is_lastmsg);
else {
if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)
result = i2c\_imx\_dma\_write(i2c_imx, &msgs[i]);
else
result = i2c\_imx\_write(i2c_imx, &msgs[i]);
}
if (result)
goto fail0;
}
fail0:
/\* Stop I2C transfer \*/
i2c\_imx\_stop(i2c_imx);
dev\_dbg(&i2c_imx->adapter.dev, "<%s> exit with: %s: %d\n", \_\_func\_\_,
(result < 0) ? "error" : "success msg",
(result < 0) ? result : num);
return (result < 0) ? result : num;
}
- 调用 i2c_imx_start 函数开启 I2C 通信。
- 如果是从 I2C 设备读数据的话就调用 i2c_imx_read 函数。
- 向 I2C 设备写数据,如果要用 DMA 的话就使用 i2c_imx_dma_write 函数来完成写数据。如果不使用 DMA 的话就使用 i2c_imx_write 函数完成写数据。
- I2C 通信完成以后调用 i2c_imx_stop 函数停止 I2C 通信。
5、IIC 设备驱动编写流程(设备树)
NXP 官方的 EVK 开发板在 I2C1 上接了 mag3110 这个磁力计芯片,因此必须在 i2c1 节点下创建 mag3110 子节点,然
后在这个子节点内描述 mag3110 这个芯片的相关信息。
使用设备树节点的时候:
- 节点名字:mag3110@0e,“0e”就是 mag3110 的 I2C 器件地址
- compatible 属性值为“fsl,mag3110”
- reg 属性设置 mag3110 的器件地址,值为 0x0e。
- 重点:I2C 设备节点的创建重点是 compatible 属性和 reg 属性的设置,一个用于匹配驱动,一个用于设置器件地址。
6、IIC 设备数据收发处理流程
初始化 I2C 设备就必须能够对 I2C 设备寄存器进行读写操作,这里要用到 i2c_transfer 函数。i2c_transfer 函数
最终会调用 I2C 适配器中 i2c_algorithm 里面的 master_xfer 函数,对于 I.MX6U 而言就是i2c_imx_xfer 这个函数。
i2c_transfer 函数原型如下:
int i2c\_transfer(struct i2c\_adapter \*adap, struct i2c\_msg \*msgs, int num)
函数参数和返回值含义如下:
adap:所使用的 I2C 适配器,i2c_client 会保存其对应的 i2c_adapter。
msgs:I2C 要发送的一个或多个消息。
num:消息数量,也就是 msgs 的数量。
msgs 参数结构体描述:
I2C设备驱动
i2c_client:表示I2C设备,不需要
我们自己创建i2c_client,一般在设备树里面添加具体的I2C芯片,比如fxls8471,系统在解析设备树的时候就会知道有这个I2C设备,然后会创建对应的i2c_client。
重点
i2c设备驱动框架,i2c_driver初始化与注册,需要II2C设备驱动编写人员编写的,IIC驱动程序就是初始化i2c_driver,然后向系统注册。注册使用i2c_register_driver
、i2c_add_driver
,如果注销i2c_driver使用i2c_del_driver
驱动编写
设备树修改
在IIC1上接了一个AP3216C
1、修改设备树,IO,添加AP3216C的设备节点。
P77左忠凯手撕IIC驱动代码
模仿实现:
2、编写I2C驱动框架,I2C设备驱动框架,字符设备驱动框架。
3、初始化AP3216C,实现ap3216c_read()。
重点是通过IIC控制器来向AP3216C里面发送或者读取数据。使用I2C_transfer这个API来完成数据传输。
adapt:IIC设备对应的适配器,也就是IIC接口,当IIC设备和驱动匹配之后,probe函数会执行,probe函数传递进来的第一个参数就是 i2c_client
,在i2c_client里面保存了此I2C设备所对应的i2c_adapt。
msgs:就是构成的I2C传输的数据。
IIC驱动AP3216C驱动代码流程:
头文件
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of\_address.h>
#include <linux/of\_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/atomic.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/semaphore.h>
#include <linux/of\_irq.h>
#include <linux/irq.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include "ap3216c.h"
IIC的设备结构体以及初始化
#define AP3216C\_CNT 1
#define AP3216C\_NAME "ap3216c"
struct ap3216c\_dev {
struct cdev cdev;
struct class \*class;/\*类:为了自动创建节点\*/
struct device \*device;/\*设备:为了自动创建节点\*/
dev\_t devid; //设备号
int major; //主设备号
int minor; //次设备号
void \*private_data;
unsigned short ir,als,ps;
};
struct ap3216c\_dev ap3216c;
读取AP3216C的N个寄存器的数值
/\*读取AP3216C的N个寄存器的数值\*/
static int ap3216c\_read\_regs(struct ap3216c\_dev \*dev,u8 reg,void \*val,int len)
{
struct i2c\_msg msg[2];
struct i2c\_client \*client = (struct i2c\_client \*)dev->private_data;
/\*msg[0]发送要读取的寄存器首地址\*/
msg[0].addr = client->addr; /\*从机地址\*/
msg[0].flags = 0; /\*要发送的数据\*/
msg[0].buf = ® /\*要发送的数据,也就是寄存器地址\*/
msg[0].len = 1; /\*要发送的寄存器地址为1\*/
/\*msg[1]读取数据\*/
msg[1].addr = client->addr; /\*从机地址\*/
msg[1].flags = I2C_M_RD; /\*表示读数据\*/
msg[1].buf = val; /\*接收到的从机的数据\*/
msg[1].len = len; /\*要读取的寄存器长度\*/
return i2c\_transfer(client->adapter,msg,2);
}
/\*读取AP3216C一个寄存器\*/
static unsigned char ap3216c\_read\_reg(struct ap3216c\_dev \*dev,u8 reg)
{
u8 data = 0;
ap3216c\_read\_regs(dev,reg,&data,1);
return data;
}
向AP3216C写入N个寄存器的数据
/\*向AP3216C写入N个寄存器的数据\*/
static int ap3216c\_write\_regs(struct ap3216c\_dev \*dev,u8 reg,u8 \*buf,int len)
{
struct i2c\_msg msg;
u8 b[256];
struct i2c\_client \*client = (struct i2c\_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; /\*要发送的数据长度:寄存器地址+1\*/
return i2c\_transfer(client->adapter,&msg,1);
}
/\*向AP3216C一个寄存器写数据\*/
static void ap3216c\_write\_reg(struct ap3216c\_dev \*dev,u8 reg,u8 data)
{
u8 buf = 0;
buf = data;
ap3216c\_write\_regs(dev,reg,&buf,1);
}
IIC读取数据
void ap3216c\_readdata(struct ap3216c\_dev \*dev)
{
unsigned char i =0;
unsigned char buf[6];
/\* 循环读取所有传感器数据 \*/
for(i = 0; i < 6; i++)
{
buf[i] = ap3216c\_read\_reg(dev, AP3216C_IRDATALOW + 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);
}
IIC字符设备的ap3216c_probe函数
static int ap3216c\_open(struct inode \*inode, struct file \*filp)
{
/\*设置私有数据\*/
filp->private_data = &ap3216c;
unsigned char val = 0;
printk("ap3216c\_open \r\n");
/\*初始化AP3216C\*/
ap3216c\_write\_reg(&ap3216c,AP3216C_SYSTEMCONG,0X4);//复位
mdelay(50);
ap3216c\_write\_reg(&ap3216c,AP3216C_SYSTEMCONG,0X3);//复位
val = ap3216c\_read\_reg(&ap3216c,AP3216C_SYSTEMCONG);
printk("val = %#x\r\n",val);
return 0;
}
static ssize\_t ap3216c\_read(struct file \*filp, __user char \*buf, size\_t count,loff\_t \*ppos)
{
long err = 0;
short data[3];
struct ap3216c\_dev \*dev = (struct ap3216c\_dev \*)filp->private_data;
ap3216c\_readdata(dev);
data[0] = dev->ir;
data[1] = dev->als;
data[2] = dev->ps;
err = copy\_to\_user(buf, data, sizeof(data));
return 0;
}
static int ap3216c\_release(struct inode \*inode, struct file \*filp)
{
//struct ap3216c\_dev \*dev = (struct ap3216c\_dev \*)filp->private\_data;
return 0;
}
/\*\*
\* 字符设备的操作集合
\*/
const struct file\_operations ap3216c_fops = {
.owner = THIS_MODULE,
.open = ap3216c_open,
.read = ap3216c_read,
.release = ap3216c_release,
};
static int ap3216c\_probe(struct i2c\_client \*client,
const struct i2c\_device\_id \*id)
{
## 最后
**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**
**深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。**
**因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
![img](https://img-blog.csdnimg.cn/img_convert/cbee94d977d7aa65e20919bfacd2ba06.png)
![img](https://img-blog.csdnimg.cn/img_convert/61c6da79ff981fcafef4b12b3b9a9ee2.jpeg)
![img](https://img-blog.csdnimg.cn/img_convert/e6dbf5e3afd105ba6fc14c4ec0622f48.png)
![img](https://img-blog.csdnimg.cn/img_convert/54ca0cf2fe090eaec7e49b54b21cfc5f.png)
![img](https://img-blog.csdnimg.cn/img_convert/34f538f2b5c6851a9e6659624d37066f.png)
![img](https://img-blog.csdnimg.cn/img_convert/1d80ab6f75159e059780a191caaf9fc9.png)
![](https://img-blog.csdnimg.cn/img_convert/48ba87b95aecceb9cac217e4ad9f539e.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!**
[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618654289)
**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**!!
外链图片转存中...(img-iyu9GqSN-1715757268633)]
[外链图片转存中...(img-IqY1OlDT-1715757268633)]
[外链图片转存中...(img-TssjKk7g-1715757268634)]
[外链图片转存中...(img-JRzCwpYF-1715757268635)]
[外链图片转存中...(img-peyefthy-1715757268635)]
[外链图片转存中...(img-hCzCztXQ-1715757268636)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!**
[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618654289)
**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**!!