树莓派驱动开发--驱动文件代码的浅度分析(以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

相关文章

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能