树莓派驱动开发--驱动文件代码的浅度分析(以iic的为例)

2024-04-15 12:36

本文主要是介绍树莓派驱动开发--驱动文件代码的浅度分析(以iic的为例),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言:我使用的代码是正点原子的驱动代码,我们借鉴学习,看多了别人优秀的代码是我们自主完成代码编写的前提!

一. 总体层面梳理

总线-驱动-设备 模型

--把不同功能的外设归类,然后实现统一接口,无法归类的使用虚拟总线来形容,从而实现总线-驱动-设备模型.

--为什么要这样?比如,像iic外设的驱动代码都是一样的,所以内核里保留一套统一的就好,这样既方便了开发者的使用,也统一了形式,使得内核代码美观统一,这就是所谓的平台总线,在iic看来也为iic适配器.

总线:把重复的东西,比如iic的时序实现代码(这样一样的重复的)统一让总线完成,然后提供统一的接口让外面调用.

驱动:新字符设备的那一套东西.

设备:具体的设备在设备树上的描述.

所以,我们的思路是,按照内核规定的统一标准流程来开发,也既按照总线-驱动-设备模型流程.

我们知道,总线是统一的东西,那么内核肯定是已经帮我们写好写死了,我们只需要知道怎么去和它联系怎么和它挂钩就好.注意:总线是连接驱动和设备的桥梁.

驱动无非就是把驱动侧的设备对设备树侧的设备匹配的信息结构体给初始化,然后注册进内核,内核里面也是同样的操作,根据厂家在设备树上描述的外设把内核实现好的总线激活,这样总线则会自动去匹配设备和驱动,匹配后执行probe函数按照新字符设备那套流程向内核注册一个具体的字符设备.外设的驱动和具体设备的驱动流程基本是一样的,只不过外设的驱动是在开发板启动时就完成了,而具体设备的驱动,是我们insmod.ko时才完成的,当然,如果把驱动模块写入内核编译,那也会在启动时完成.(不理解没关系,下面代码会解释)

设备就是修改设备树,把设备信息描述上去.

二. 代码理解

链接:https://pan.baidu.com/s/1nvEqah5w6NlIQ-7RG5vu7g?pwd=hsw1 
提取码:hsw1

参考代码在什么链接,一定要去拿到对照学习.

(1)驱动无非就是把驱动侧的设备对设备树侧的设备匹配的信息结构体给初始化,然后注册进内核,内核里面也是同样的操作,根据厂家在设备树上描述的外设把内核实现好的总线激活,这样总线则会自动去匹配设备和驱动,匹配后执行probe函数按照新字符设备那套流程向内核注册一个具体的字符设备.外设的驱动和具体设备的驱动流程基本是一样的,只不过外设的驱动是在开发板启动时就完成了,而具体设备的驱动,是我们insmod.ko时才完成的,当然,如果把驱动模块写入内核编译,那也会在启动时完成.(不理解没关系,下面代码会解释)

(2)总线是连接驱动和设备的桥梁.

按这两句话理顺驱动开发编写流程.

1.把驱动侧的设备对设备树侧的设备匹配的信息结构体给初始化

/* 传统匹配方式ID列表 */
static const struct i2c_device_id ap3216c_id[] = {{"alientek,ap3216c", 0},  {}
};/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {{ .compatible = "alientek,ap3216c" },{ /* Sentinel */ }
};/* i2c驱动结构体 */	
static struct i2c_driver ap3216c_driver = {.probe = ap3216c_probe,.remove = ap3216c_remove,.driver = {.owner = THIS_MODULE,.name = "ap3216c",.of_match_table = ap3216c_of_match, },.id_table = ap3216c_id,
};

说白了就是怎么让设备和驱动匹配------compatible属性!或者传统的方式!

匹配后干嘛------执行probe.

.name = "ap3216c", 表示设备树节点下设备名称,传统匹配方法之一就是根据名字

2.注册进内核,总线是连接驱动和设备的桥梁.

/** @description	: 驱动入口函数* @param 		: 无* @return 		: 无*/
static int __init ap3216c_init(void)
{int ret = 0;ret = i2c_add_driver(&ap3216c_driver);return ret;
}/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static void __exit ap3216c_exit(void)
{i2c_del_driver(&ap3216c_driver);
}/* module_i2c_driver(ap3216c_driver) */module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

这里就没必要纠结注册函数为什么是i2c_add_driver,用到的时候查一下即可,毕竟现在的学习资源丰富.

我们在insmod.ko后,总线便会工作,看看有没有驱动和设备树上的设备匹配,一旦有,它就会获取设备树上的设备信息,比如reg描述的设备地址等,存储在内核统一规定的结构体中,比如iic的client结构体,驱动使用client结构体就相当于访问设备信息.所以说总线是连接设备和驱动的桥梁.

结合1 2点,将第一步总结为

让驱动匹配设备,注册进内核,总线实现驱动和设备的匹配并以结构体的形式返回设备信息

3.内核里面也是同样的操作,根据厂家在设备树上描述的外设把内核实现好的总线激活,这样总线则会自动去匹配设备和驱动

内核源码里面有外设的驱动的代码,流程也是结构体初始化然后注册,只不过这些比较复杂统一,是厂家写好的,已经实现好了.它匹配的设备树上的外设也是厂家根据自己板子有的外设来描述的.启动时就已经加载好了.

4.匹配后执行probe函数按照新字符设备那套流程向内核注册一个具体的字符设备

#define AP3216C_CNT	1
#define AP3216C_NAME	"ap3216c"struct ap3216c_dev {dev_t devid;			/* 设备号 	 */struct cdev cdev;		/* cdev 	*/struct class *class;	/* 类 		*/struct device *device;	/* 设备 	 */struct device_node	*nd; /* 设备节点 */int major;			/* 主设备号 */void *private_data;	/* 私有数据 */unsigned short ir, als, ps;		/* 三个光传感器数据 */
};/** @description		: 打开设备* @param - inode 	: 传递给驱动的inode* @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量* 					  一般在open的时候将private_data指向设备结构体。* @return 			: 0 成功;其他 失败*/
static int ap3216c_open(struct inode *inode, struct file *filp)
{filp->private_data = &ap3216cdev;/* 初始化AP3216C */ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04);		/* 复位AP3216C 			*/mdelay(50);														/* AP3216C复位最少10ms 	*/ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03);		/* 开启ALS、PS+IR 		*/return 0;
}/** @description		: 从设备读取数据 * @param - filp 	: 要打开的设备文件(文件描述符)* @param - buf 	: 返回给用户空间的数据缓冲区* @param - cnt 	: 要读取的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 读取的字节数,如果为负值,表示读取失败*/
static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{short data[3];long err = 0;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;
}/** @description		: 关闭/释放设备* @param - filp 	: 要关闭的设备文件(文件描述符)* @return 			: 0 成功;其他 失败*/
static int ap3216c_release(struct inode *inode, struct file *filp)
{return 0;
}/* AP3216C操作函数 */
static const struct file_operations ap3216c_ops = {.owner = THIS_MODULE,.open = ap3216c_open,.read = ap3216c_read,.release = ap3216c_release,
};static struct ap3216c_dev ap3216cdev;/** @description     : i2c驱动的probe函数,当驱动与*                    设备匹配以后此函数就会执行* @param - client  : i2c设备* @param - id      : i2c设备ID* @return          : 0,成功;其他负值,失败*/
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_ops);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;
}/** @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行* @param - client 	: i2c设备* @return          : 0,成功;其他负值,失败*/
static int ap3216c_remove(struct i2c_client *client)
{/* 删除设备 */cdev_del(&ap3216cdev.cdev);unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);/* 注销掉类和设备 */device_destroy(ap3216cdev.class, ap3216cdev.devid);class_destroy(ap3216cdev.class);return 0;
}

这里重点讲一下:ap3216cdev.private_data = client;

这句话体现了总线实现驱动和设备的匹配并以结构体的形式返回设备信息

先看看这个结构体

里面的成员便是设备的信息体,这个总线帮我们获取好了,故这里只需要将其赋值给我们编写的字符驱动设备,我们就可以访问它了.

#define AP3216C_NAME    "ap3216c"

这个就是我们字符设备在内核的bus下的名称.

file_operations ap3216c_ops是驱动和app的连接桥梁.

结合3 4点,将第二步总结为

在probe中注册字符驱动,并将获取的设备信息给到字符驱动,实现ops打通app和驱动的桥梁.

5.具体设备对应的具体的iic操作实现

/** @description	: 从ap3216c读取多个寄存器数据* @param - dev:  ap3216c设备* @param - reg:  要读取的寄存器首地址* @param - val:  读取到的数据* @param - len:  要读取的数据长度* @return 		: 操作结果*/
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
{int ret;struct i2c_msg msg[2];struct i2c_client *client = (struct i2c_client *)dev->private_data;/* msg[0]为发送要读取的首地址 */msg[0].addr = client->addr;			/* ap3216c地址 */msg[0].flags = 0;					/* 标记为发送数据 */msg[0].buf = ®					/* 读取的首地址 */msg[0].len = 1;						/* reg长度*//* msg[1]读取数据 */msg[1].addr = client->addr;			/* ap3216c地址 */msg[1].flags = I2C_M_RD;			/* 标记为读取数据*/msg[1].buf = val;					/* 读取数据缓冲区 */msg[1].len = len;					/* 要读取的数据长度*/ret = i2c_transfer(client->adapter, msg, 2);if(ret == 2) {ret = 0;} else {printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);ret = -EREMOTEIO;}return ret;
}/** @description	: 向ap3216c多个寄存器写入数据* @param - dev:  ap3216c设备* @param - reg:  要写入的寄存器首地址* @param - val:  要写入的数据缓冲区* @param - len:  要写入的数据长度* @return 	  :   操作结果*/
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
{u8 b[256];struct i2c_msg msg;struct i2c_client *client = (struct i2c_client *)dev->private_data;b[0] = reg;					/* 寄存器首地址 */memcpy(&b[1],buf,len);		/* 将要写入的数据拷贝到数组b里面 */msg.addr = client->addr;	/* ap3216c地址 */msg.flags = 0;				/* 标记为写数据 */msg.buf = b;				/* 要写入的数据缓冲区 */msg.len = len + 1;			/* 要写入的数据长度 */return i2c_transfer(client->adapter, &msg, 1);
}/** @description	: 读取ap3216c指定寄存器值,读取一个寄存器* @param - dev:  ap3216c设备* @param - reg:  要读取的寄存器* @return 	  :   读取到的寄存器值*/
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{u8 data = 0;ap3216c_read_regs(dev, reg, &data, 1);return data;#if 0struct i2c_client *client = (struct i2c_client *)dev->private_data;return i2c_smbus_read_byte_data(client, reg);
#endif
}/** @description	: 向ap3216c指定寄存器写入指定的值,写一个寄存器* @param - dev:  ap3216c设备* @param - reg:  要写的寄存器* @param - data: 要写入的值* @return   :    无*/
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);
}/** @description	: 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!*				: 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms* @param - ir	: ir数据* @param - ps 	: ps数据* @param - ps 	: als数据 * @return 		: 无。*/
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); 
}

struct i2c_client *client = (struct i2c_client *)dev->private_data;

这一步体现了我们ap3216cdev.private_data = client;这一步的作用!

函数i2c_transfer(client->adapter, msg, 2); 便是iic在内核的统一对外api,不要纠结我怎么知道是这个,用的时候查一查就好,毕竟这是人家的东西,整个内核复杂要死,一个一个理解不可能,用多了自然会领悟.

结合第5点,将第三步总结为

具体设备iic操作实现

三. 总结

需掌握:总线-驱动-设备架构含义.  
心中有流程:

1.让驱动匹配设备,注册进内核,总线实现驱动和设备的匹配并以结构体的形式返回设备信息

2.在probe中注册字符驱动,并将获取的设备信息给到字符驱动,实现ops打通app和驱动的桥梁

3.具体设备操作实现

这篇关于树莓派驱动开发--驱动文件代码的浅度分析(以iic的为例)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/905827

相关文章

Mysql的主从同步/复制的原理分析

《Mysql的主从同步/复制的原理分析》:本文主要介绍Mysql的主从同步/复制的原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录为什么要主从同步?mysql主从同步架构有哪些?Mysql主从复制的原理/整体流程级联复制架构为什么好?Mysql主从复制注意

深入解析 Java Future 类及代码示例

《深入解析JavaFuture类及代码示例》JavaFuture是java.util.concurrent包中用于表示异步计算结果的核心接口,下面给大家介绍JavaFuture类及实例代码,感兴... 目录一、Future 类概述二、核心工作机制代码示例执行流程2. 状态机模型3. 核心方法解析行为总结:三

python获取cmd环境变量值的实现代码

《python获取cmd环境变量值的实现代码》:本文主要介绍在Python中获取命令行(cmd)环境变量的值,可以使用标准库中的os模块,需要的朋友可以参考下... 前言全局说明在执行py过程中,总要使用到系统环境变量一、说明1.1 环境:Windows 11 家庭版 24H2 26100.4061

pandas实现数据concat拼接的示例代码

《pandas实现数据concat拼接的示例代码》pandas.concat用于合并DataFrame或Series,本文主要介绍了pandas实现数据concat拼接的示例代码,具有一定的参考价值,... 目录语法示例:使用pandas.concat合并数据默认的concat:参数axis=0,join=

java -jar命令运行 jar包时运行外部依赖jar包的场景分析

《java-jar命令运行jar包时运行外部依赖jar包的场景分析》:本文主要介绍java-jar命令运行jar包时运行外部依赖jar包的场景分析,本文给大家介绍的非常详细,对大家的学习或工作... 目录Java -jar命令运行 jar包时如何运行外部依赖jar包场景:解决:方法一、启动参数添加: -Xb

基于Python开发一个有趣的工作时长计算器

《基于Python开发一个有趣的工作时长计算器》随着远程办公和弹性工作制的兴起,个人及团队对于工作时长的准确统计需求日益增长,本文将使用Python和PyQt5打造一个工作时长计算器,感兴趣的小伙伴可... 目录概述功能介绍界面展示php软件使用步骤说明代码详解1.窗口初始化与布局2.工作时长计算核心逻辑3

C#代码实现解析WTGPS和BD数据

《C#代码实现解析WTGPS和BD数据》在现代的导航与定位应用中,准确解析GPS和北斗(BD)等卫星定位数据至关重要,本文将使用C#语言实现解析WTGPS和BD数据,需要的可以了解下... 目录一、代码结构概览1. 核心解析方法2. 位置信息解析3. 经纬度转换方法4. 日期和时间戳解析5. 辅助方法二、L

Python使用Code2flow将代码转化为流程图的操作教程

《Python使用Code2flow将代码转化为流程图的操作教程》Code2flow是一款开源工具,能够将代码自动转换为流程图,该工具对于代码审查、调试和理解大型代码库非常有用,在这篇博客中,我们将深... 目录引言1nVflRA、为什么选择 Code2flow?2、安装 Code2flow3、基本功能演示

IIS 7.0 及更高版本中的 FTP 状态代码

《IIS7.0及更高版本中的FTP状态代码》本文介绍IIS7.0中的FTP状态代码,方便大家在使用iis中发现ftp的问题... 简介尝试使用 FTP 访问运行 Internet Information Services (IIS) 7.0 或更高版本的服务器上的内容时,IIS 将返回指示响应状态的数字代

Apache 高级配置实战之从连接保持到日志分析的完整指南

《Apache高级配置实战之从连接保持到日志分析的完整指南》本文带你从连接保持优化开始,一路走到访问控制和日志管理,最后用AWStats来分析网站数据,对Apache配置日志分析相关知识感兴趣的朋友... 目录Apache 高级配置实战:从连接保持到日志分析的完整指南前言 一、Apache 连接保持 - 性