Linux驱动开发(使用I2C总线设备驱动模型编写AT24C02驱动程序)

news2024/11/22 6:33:55

文章目录

  • 前言
  • 一、I2C总线设备驱动模型
  • 二、设备树编写
  • 三、驱动程序编写
    • 1.提供i2c_driver结构体变量并且注册
    • 2.注册file_operations结构体
    • 3.操作AT24C02
  • 四、应用程序编写
  • 五、上机测试
  • 总结


前言

本篇文章将讲解如何使用I2C总线设备驱动模型编写AT24C02驱动程序。

一、I2C总线设备驱动模型

I2C设备模型驱动程序是一种新的I2C设备驱动模型,引入了设备树(Device Tree)这一机制,可以在I2C设备和相应的Linux设备节点之间建立关联。在I2C设备模型中,所有I2C设备节点共用一个I2C设备模型驱动程序,不需要为每个I2C设备节点编写独立的设备驱动程序。

下图来自百问网:
在这里插入图片描述
在i2c总线下分别有i2c_client和i2c_driver。i2c_client就是硬件设备(比如本篇文章用到的AT24C02),i2c_driver就是我们需要编写的驱动程序。

在这里插入图片描述
i2c_client由设备树提供。

i2c_driver是我们自己编写的驱动程序,里面提供了probe函数,当驱动和设备树中的compatible属性匹配后调用probe函数。

二、设备树编写

因为我使用的AT24C02是挂载在i2c1这根总线上的所有需要在i2c1这个节点下添加at24c02这个子节点。
reg属性代表的是AT24C02的设备地址。

&i2c1 {
    clock-frequency = <100000>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c1>;
    status = "okay";

        at24c02 {
                compatible = "my,at24c02";
                reg = <0x50>;
        };
};

三、驱动程序编写

1.提供i2c_driver结构体变量并且注册

这里和之前编写的驱动程序的思路都是一样的提供一个driver结构体变量并且将其注册。

这里和之前最大的不同就是需要在i2c_driver结构体变量中提供id_table成员。

在内核源码中发现缺少了probe函数或者缺少了id_table成员都是无法进行正确的匹配的。
在这里插入图片描述

static const struct of_device_id at24c02_of_match[] = {
	{.compatible = "my,at24c02"},
	{}
};


static const struct i2c_device_id at24c02_ids[] = {
	{ "xxxxyyy",	(kernel_ulong_t)NULL },
	{ /* END OF LIST */ }
};



static struct i2c_driver at24c02_drv = {
	.driver = {
		.name = "myat24c02",
		.of_match_table = at24c02_of_match,
	},
	.probe = at24c02_probe,
	.remove = at24c02_remove,
	.id_table = at24c02_ids,
};


/* 2. 在入口函数注册platform_driver */
static int __init at24c02_init(void)
{
    int err;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
    return i2c_add_driver(&at24c02_drv);
	
	return err;
}

/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 *     卸载platform_driver
 */
static void __exit at24c02_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    i2c_del_driver(&at24c02_drv);
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */


module_init(at24c02_init);
module_exit(at24c02_exit);

MODULE_LICENSE("GPL");

2.注册file_operations结构体

这里我们使用ioctl来操作AT24C02,ioctl既可以读又可以写,可以对read和write函数进行替换。

/* 定义自己的file_operations结构体                                              */
static struct file_operations at24c02_fops = {
	.owner	 = THIS_MODULE,
	.unlocked_ioctl    = at24c02_chrdev_ioctl,
};

static int at24c02_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	at24c02_client = client;

	/* 注册file_operations 	*/
	major = register_chrdev(0, "100ask_at24c02", &at24c02_fops);  /* /dev/at24c02 */

	at24c02_class = class_create(THIS_MODULE, "100ask_at24c02_class");
	if (IS_ERR(at24c02_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "100ask_at24c02");
		return PTR_ERR(at24c02_class);
	}

	device_create(at24c02_class, NULL, MKDEV(major, 0), NULL, "100ask_at24c02"); /* /dev/100ask_at24c02 */

	return 0;
}

static int at24c02_remove(struct i2c_client *client)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(at24c02_class, MKDEV(major, 0));
	class_destroy(at24c02_class);
	unregister_chrdev(major, "100ask_at24c02");

	return 0;
}

3.操作AT24C02

根据AT24C02的数据手册我们可以清楚的知道如何去读写AT24C02。

写AT24C02时序:
在这里插入图片描述
写AT24C02时需要发送设备地址和需要写入的数据,写入数据的地址,只需要构造一个msg消息即可。

读AT24C02时序:
在这里插入图片描述

读AT24C02时首先需要写入设备地址,再去读取指定要读取数据的地址。 然后再发起一次操作,指定设备地址,指定读取数据保存的地址。一共需要构造两个msg消息。

#define IOC_AT24C02_READ  100
#define IOC_AT24C02_WRITE 101

/* 主设备号                                                                 */
static int major = 0;
static struct class *at24c02_class;
struct i2c_client *at24c02_client;


static long at24c02_chrdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	unsigned char addr;
	unsigned char data;
	unsigned int ker_buf[2];
	unsigned int *usr_buf = (unsigned int *)arg;
	unsigned char byte_buf[2];
	
	struct i2c_msg msgs[2];

	copy_from_user(ker_buf, usr_buf, 8);

	addr = ker_buf[0];
	
	switch (cmd)
	{
		case IOC_AT24C02_READ:
		{
			/* 读AT24C02 */
			msgs[0].addr  = at24c02_client->addr;
			msgs[0].flags = 0; /* 写 */
			msgs[0].len   = 1;
			msgs[0].buf   = &addr;

			msgs[1].addr  = at24c02_client->addr;
			msgs[1].flags = I2C_M_RD; /* 读 */
			msgs[1].len   = 1;
			msgs[1].buf   = &data;
		
			i2c_transfer(at24c02_client->adapter, msgs, 2);

			ker_buf[1] = data;
			copy_to_user(usr_buf, ker_buf, 8);
			
			break;
		}
		case IOC_AT24C02_WRITE:
		{
			/* 写AT24C02 */
			byte_buf[0] = addr;
			byte_buf[1] = ker_buf[1];
			
			msgs[0].addr  = at24c02_client->addr;
			msgs[0].flags = 0; /* 写 */
			msgs[0].len   = 2;
			msgs[0].buf   = byte_buf;

			i2c_transfer(at24c02_client->adapter, msgs, 1);

			mdelay(20);
			
			break;
		}
	}

	return 0;

}

完整代码:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/log2.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/of.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <asm/uaccess.h>


#define IOC_AT24C02_READ  100
#define IOC_AT24C02_WRITE 101



/* 主设备号                                                                 */
static int major = 0;
static struct class *at24c02_class;
struct i2c_client *at24c02_client;


static long at24c02_chrdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{



	unsigned char addr;
	unsigned char data;
	unsigned int ker_buf[2];
	unsigned int *usr_buf = (unsigned int *)arg;
	unsigned char byte_buf[2];
	
	struct i2c_msg msgs[2];

	copy_from_user(ker_buf, usr_buf, 8);

	addr = ker_buf[0];
	
	switch (cmd)
	{
		case IOC_AT24C02_READ:
		{
			/* 读AT24C02 */
			msgs[0].addr  = at24c02_client->addr;
			msgs[0].flags = 0; /* 写 */
			msgs[0].len   = 1;
			msgs[0].buf   = &addr;

			msgs[1].addr  = at24c02_client->addr;
			msgs[1].flags = I2C_M_RD; /* 读 */
			msgs[1].len   = 1;
			msgs[1].buf   = &data;
		
			i2c_transfer(at24c02_client->adapter, msgs, 2);

			ker_buf[1] = data;
			copy_to_user(usr_buf, ker_buf, 8);
			
			break;
		}
		case IOC_AT24C02_WRITE:
		{
			/* 写AT24C02 */
			byte_buf[0] = addr;
			byte_buf[1] = ker_buf[1];
			
			msgs[0].addr  = at24c02_client->addr;
			msgs[0].flags = 0; /* 写 */
			msgs[0].len   = 2;
			msgs[0].buf   = byte_buf;

			i2c_transfer(at24c02_client->adapter, msgs, 1);

			mdelay(20);
			
			break;
		}
	}

	return 0;

}


/* 定义自己的file_operations结构体                                              */
static struct file_operations at24c02_fops = {
	.owner	 = THIS_MODULE,
	.unlocked_ioctl    = at24c02_chrdev_ioctl,
};


static int at24c02_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	at24c02_client = client;

	/* 注册file_operations 	*/
	major = register_chrdev(0, "100ask_at24c02", &at24c02_fops);  /* /dev/at24c02 */

	at24c02_class = class_create(THIS_MODULE, "100ask_at24c02_class");
	if (IS_ERR(at24c02_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "100ask_at24c02");
		return PTR_ERR(at24c02_class);
	}

	device_create(at24c02_class, NULL, MKDEV(major, 0), NULL, "100ask_at24c02"); /* /dev/100ask_at24c02 */

	return 0;
}

static int at24c02_remove(struct i2c_client *client)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(at24c02_class, MKDEV(major, 0));
	class_destroy(at24c02_class);
	unregister_chrdev(major, "100ask_at24c02");

	return 0;
}

static const struct of_device_id at24c02_of_match[] = {
	{.compatible = "my,at24c02"},
	{}
};


static const struct i2c_device_id at24c02_ids[] = {
	{ "xxxxyyy",	(kernel_ulong_t)NULL },
	{ /* END OF LIST */ }
};



static struct i2c_driver at24c02_drv = {
	.driver = {
		.name = "myat24c02",
		.of_match_table = at24c02_of_match,
	},
	.probe = at24c02_probe,
	.remove = at24c02_remove,
	.id_table = at24c02_ids,
};


/* 2. 在入口函数注册platform_driver */
static int __init at24c02_init(void)
{
    int err;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
    return i2c_add_driver(&at24c02_drv);
	
	return err;
}

/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 *     卸载platform_driver
 */
static void __exit at24c02_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    i2c_del_driver(&at24c02_drv);
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */


module_init(at24c02_init);
module_exit(at24c02_exit);

MODULE_LICENSE("GPL");

四、应用程序编写

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>


#define IOC_AT24C02_READ  100
#define IOC_AT24C02_WRITE 101


/*
 * at24c02_test /dev/myat24c02 r 10
 * at24c02_test /dev/myat24c02 w 10 123
 */

int main(int argc, char **argv)
{
	int fd;
	int buf[2];

	if ((argc != 4) && (argc != 5))
	{
		printf("Usage: %s <dev> r <addr>\n", argv[0]);
		printf("       %s <dev> w <addr> <val>\n", argv[0]);
		return -1;
	}
	
	fd = open(argv[1], O_RDWR);
	if (fd < 0)
	{
		printf(" can not open %s\n", argv[1]);
		return -1;
	}

	if (argv[2][0] == 'r')
	{
		buf[0] = strtoul(argv[3], NULL, 0);
		ioctl(fd, IOC_AT24C02_READ, buf);
		printf("Read addr 0x%x, get data 0x%x\n", buf[0], buf[1]);
	}
	else
	{
		buf[0] = strtoul(argv[3], NULL, 0);
		buf[1] = strtoul(argv[4], NULL, 0);
		ioctl(fd, IOC_AT24C02_WRITE, buf);
	}
	
	return 0;
}



五、上机测试

装载驱动后进入/sys/bus/i2c/devices目录下找到我们自己编写的驱动程序:
在这里插入图片描述
进入0-0050目录使用cat命令查看具体信息:
在这里插入图片描述
根据信息可以得知驱动程序装载成功。

进行at24c02的读写操作:
在这里插入图片描述
读写测试通过。

总结

本篇文章主要讲解了i2C总线设备驱动模型编写AT24C02驱动程序,这里大家主要需要掌握的就是i2C总线设备驱动这个模型,只要掌握好了这个模型那么剩下的就是裸机的操作了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/617582.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux platform 设备驱动实验

目录 1. platform平台简介 1.1 platform总线 1.2 platform 驱动 1.3 platform设备 2.platform平台总线初始化 3. platform驱动框架 4.实验 4.1 无设备树的platform设备注册 4.2 无设备树的platform驱动 4.3 有设备树的platform驱动 1. platform平台简介 当我们向系统…

java设计模式(十二)代理模式

目录 定义模式结构角色职责代码实现静态代理动态代理jdk动态代理cglib代理 适用场景优缺点 定义 代理模式给某一个对象提供一个代理对象&#xff0c;并由代理对象控制对原对象的引用。说简单点&#xff0c;代理模式就是设置一个中间代理来控制访问原目标对象&#xff0c;以达到…

高度平衡二叉搜索树(AVLTree)(插入与旋转)

目录 简介 AVL的结点类 平衡因子的性质 AVL树的插入 更新平衡因子的接口(ChangeBf) 第一种情况&#xff1a;插入后父节点的平衡因子为0 第二种情况&#xff1a;更新后父节点的平衡因子的绝对值为1 第三种情况&#xff1a;更新后父节点的平衡因子的绝对值为2 旋转接口(…

cam_lidar_calibration代码详解(一)采样优化部分

目录 一、launch启动程序 1.1 run_optimiser.launch标定优化程序 1.2 assess_results.launch重投影误差评估程序 二、主要代码 2.1 feature_extraction_node.cpp文件 2.2 feature_extractor.cpp文件 2.2.1 FeatureExtractor::callback_camerainfo函数 2.2.2 serviceCB函…

QT快速操作Excel的实现介绍及操作类封装

QT中操作Excel还是比较简单的&#xff0c;Qt提供了QAxObject&#xff0c;包装COM组件的类&#xff0c;通过COM通过COM操作使用QAxObject类&#xff0c;使用此类&#xff0c;需要在pro文件中添加"QT   axcontainer "。 基本流程介绍 QAxObject&#xff1a; QAxObj…

SAP从入门到放弃系列之pMRP

最近学习pMRP&#xff0c;查了很多博客&#xff0c;机翻一篇内容非常详细的文章&#xff0c;感谢大佬&#xff1a; 原文地址&#xff1a; pMRP–Predictive Material and Resource Planning in SAP S/4HANA : Step by Step execution pMRP https://blogs.sap.com/2020/04/14/p…

机器学习【线性回归】

机器学习【线性回归】 回归预测的结果是离散型变量&#xff0c;身高和年龄 损失函数&#xff1a;SSE&#xff08;误差平方和&#xff09;&#xff0c;RSS&#xff08;残差平方和&#xff09;&#xff0c;误差越大越差 最小二乘法&#xff1a;通过最小化真实值和预测值之间的…

大数据:sparkSQL,历史,DataSet,DataFrame,sparkSession

大数据&#xff1a;sparkSQL 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle&#xff0c;尤其sql要学…

第十章:创建和管理表

第十章&#xff1a;创建和管理表 10.1&#xff1a;基础知识 一条数据存储的过程 ​ 存储数据是处理数据的第一步。只有正确地把数据存储起来&#xff0c;我们才能进行有效的处理和分析。否则&#xff0c;只能是一团乱麻&#xff0c;无从下手。 ​ 在MySQL中&#xff0c;一个完…

使用模板方法模式封装协议消息

目录 整体框架 模板方法介绍 关于本案例设计 c impl惯用法 cimpl惯用法好处 此案例impl惯用法的设计 关于序列化和反序列化 序列化和反序列化 本项目使用介绍 谷歌测试 谷歌测试环境 谷歌测试用例 完整源码地址 概述 本文介绍了从 设计模式之模板方法模式协议消…

面对CPU狂飙时的5步解决方案

现在企业对后端开发的要求越来越高&#xff0c;不仅要求我们会写代码&#xff0c;还要我们能够进行部署和运维&#xff01; 项目上线并运行一段时间后&#xff0c;可能会发现部署所在的Linux服务器CPU占用过高&#xff0c;该如何排查解决&#xff1f; 本文用5步带你搞定线上CPU…

操作系统-进程和线程-同步、互斥、死锁

目录 一、同步互斥 二、互斥的实现方法 2.1软件实现 2.1.1单标志法 2.1.2双标志先检查 2.1.3双标志后检查 2.1.4Petersons算法 2.2硬件实现 2.2.1 TestAndSet指令 2.2.2 Swap指令 三、信号量机制 3.1整形变量 3.2 记录型变量 3.3用信号量实现进程互斥、同步、前驱关系…

Sui与F1甲骨文红牛车队达成合作

在近期达成的一项为期多年的合作协议中&#xff0c;甲骨文红牛车队将利用Sui网络开发&#xff0c;为粉丝带来全新的数字化体验。 甲骨文红牛车队的粉丝将很快在Sui网络上体验到他们最爱的一级方程式车队带来的激情。最近几个赛季一直统治着F1赛场的甲骨文红牛车队&#xff0c;与…

代码随想录算法训练营第五十三天 | 力扣 1143.最长公共子序列, 1035.不相交的线, 53. 最大子序和

1143.最长公共子序列 题目 1143. 最长公共子序列 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符…

2022年值得关注的7个主要SD-WAN趋势

随着SD-WAN技术在2022年继续发展成熟&#xff0c;该技术在集成远程访问、自动化和多云连接方面的支持有望得到更多的改进。 软件定义WAN仍然是增强用户体验(UX)&#xff0c;提高安全性&#xff0c;以及提供与基于云计算的应用程序的连接的一项关键技术。 随着SD-WAN的成熟&…

java设计模式之:原型模式

在我们的生活中&#xff0c;有很多例子&#xff0c;都是用到了原型模式&#xff0c;例如&#xff1a;我们去配钥匙的时候&#xff0c;肯定先要有一个原配钥匙才可以去配钥匙&#xff1b;《西游记》中孙悟空可以用猴毛根据自己的形象&#xff0c;复制&#xff08;又称“克隆”或…

时间序列预测 | Matlab基于北方苍鹰算法优化随机森林(NGO-RF)与随机森林(RF)的时间序列预测对比

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 时间序列预测 | Matlab基于北方苍鹰算法优化随机森林(NGO-RF)与随机森林(RF)的时间序列预测对比 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码 %-----------…

【Apache-Flink零基础入门】「入门到精通系列」手把手+零基础带你玩转大数据流式处理引擎Flink(基础概念解析)

手把手零基础带你玩转大数据流式处理引擎Flink 前言介绍Apache Flink 的定义、架构及原理Flink应用服务Streams有限数据流和无限数据流的区别 StateTimeAPI Flink架构体系 Flink操作处理Flink 的应用场景Flink 的应用场景&#xff1a;Data Pipeline实时数仓搜索引擎推荐 Flink …

华为诺亚 VanillaNet

文章标题&#xff1a;《VanillaNet: the Power of Minimalism in Deep Learning》 文章地址&#xff1a;https://arxiv.org/abs/2305.12972 github地址&#xff1a;https://github.com/huawei-noah/VanillaNet 华为诺亚方舟实验室和悉尼大学&#xff0c;2023年5月代码刚开源的…

基于HAL库的STM32的单定时器的多路输入捕获测量脉冲频率(外部时钟实现)

目录 写在前面 一般的做法&#xff08;定时器单通道输入捕获) 以外部时钟的方式(高低频都适用) 测试效果 写在前面 STM32的定时器本身有输入捕获的功能。可选择双端捕获&#xff0c;上升沿捕获或者是下降沿捕获。对应捕获频率来说,连续捕获上升沿或下降沿的时间间隔就是其脉…