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

相关文章

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

使用Python开发一个带EPUB转换功能的Markdown编辑器

《使用Python开发一个带EPUB转换功能的Markdown编辑器》Markdown因其简单易用和强大的格式支持,成为了写作者、开发者及内容创作者的首选格式,本文将通过Python开发一个Markd... 目录应用概览代码结构与核心组件1. 初始化与布局 (__init__)2. 工具栏 (setup_t

使用Python实现全能手机虚拟键盘的示例代码

《使用Python实现全能手机虚拟键盘的示例代码》在数字化办公时代,你是否遇到过这样的场景:会议室投影电脑突然键盘失灵、躺在沙发上想远程控制书房电脑、或者需要给长辈远程协助操作?今天我要分享的Pyth... 目录一、项目概述:不止于键盘的远程控制方案1.1 创新价值1.2 技术栈全景二、需求实现步骤一、需求

Spring Shell 命令行实现交互式Shell应用开发

《SpringShell命令行实现交互式Shell应用开发》本文主要介绍了SpringShell命令行实现交互式Shell应用开发,能够帮助开发者快速构建功能丰富的命令行应用程序,具有一定的参考价... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定义S

Java中Date、LocalDate、LocalDateTime、LocalTime、时间戳之间的相互转换代码

《Java中Date、LocalDate、LocalDateTime、LocalTime、时间戳之间的相互转换代码》:本文主要介绍Java中日期时间转换的多种方法,包括将Date转换为LocalD... 目录一、Date转LocalDateTime二、Date转LocalDate三、LocalDateTim

Python 迭代器和生成器概念及场景分析

《Python迭代器和生成器概念及场景分析》yield是Python中实现惰性计算和协程的核心工具,结合send()、throw()、close()等方法,能够构建高效、灵活的数据流和控制流模型,这... 目录迭代器的介绍自定义迭代器省略的迭代器生产器的介绍yield的普通用法yield的高级用法yidle

jupyter代码块没有运行图标的解决方案

《jupyter代码块没有运行图标的解决方案》:本文主要介绍jupyter代码块没有运行图标的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录jupyter代码块没有运行图标的解决1.找到Jupyter notebook的系统配置文件2.这时候一般会搜索到

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序