I2C总线二级外设驱动开发(函数和代码详解)

I2C总线二级外设驱动开发是一个涉及多个步骤和函数调用的过程,主要目的是使得挂接在I2C总线上的外设能够正常工作。

一、I2C总线二级外设驱动开发概述

I2C总线是一种广泛使用的串行通信总线,用于连接微控制器及其外围设备。在Linux内核中,I2C总线驱动开发包括I2C总线控制器驱动(也称为适配器驱动)和挂接在I2C总线上的二级外设驱动(也称为客户驱动或设备驱动)。

**疑惑1:**二级外设驱动编写好,那总线上的那个iic驱动不需要编写吗

当你为挂接在I2C总线上的二级外设编写驱动程序(也称为客户驱动或设备驱动)时,你通常不需要从头开始编写整个I2C总线(也称为I2C适配器)的驱动程序,除非该I2C总线控制器是全新的、且Linux内核中尚不支持的。

Linux内核对I2C总线的支持

本次实验仅是对driver驱动层进行开发,在iic核心层以下则是开发商已经帮我们做好的第一次移植,我们进行第二次移植即可(用mpu6050作为示例,芯片为fs4412)。

I2C设备驱动(driver驱动层)我们需要设计的地方

即挂接在I2C总线上的二级外设的驱动,也称客户(client)驱动,实现对二级外设的各种操作,二级外设的几乎所有操作全部依赖于对其自身内部寄存器的读写,对这些二级外设寄存器的读写又依赖于I2C总线的发送和接收

I2C总线驱动(访问抽象层、硬件实现控制层)内核已经做好初始化,了解如何使用api即可

即对I2C总线自身控制器的驱动,一般SOC芯片都会提供多个I2C总线控制器,每个I2C总线控制器提供一组I2C总线(SDA一根+SCL一根),每一组被称为一个I2C通道,Linux内核里将I2C总线控制器叫做适配器(adapter),适配器驱动主要工作就是提供通过本组I2C总线与二级外设进行数据传输的接口,每个二级外设驱动里必须能够获得其对应的adapter对象才能实现数据传输

I2C核心 内核已经做好初始化

承上启下,为I2C设备驱动和I2C总线驱动开发提供接口,为I2C设备驱动层提供管理多个i2c_driver、i2c_client对象的数据结构,为I2C总线驱动层提供多个i2c_algorithm、i2c_adapter对象的数据结构

四大核心对象之间的关系图

结合第一张图可知:(重点)

  • 一个i2c总线控制器(也叫I2C适配器 )的算法(i2c_algorithm) 可以被n个i2c总线控制器使用。
  • 一个二级外设设备驱动程序(i2c_driver)可以被n个外设(i2c_client)使用。每个外设(通过其i2c_client表示)在系统中都是唯一的,并且通常只与一个i2c_driver相关联。
  • 每个外设(i2c_client)都要与一个i2c总线控制器i2c_adapter(通过其总线编号和I2C地址)和一个i2c_driver(通过其ID表或其他匹配机制)相关联。

二、开发步骤详解

1. 查阅fs4412开发板以及mpu6050原理图

目的:了解二级外设挂在哪条I2C总线上,以及外设的身份标识(如设备地址)。

再去mpu-6050原理图可得:

上图可知:ADO接地为0,SCL与SDA复用引脚GPB2,3查询可得使用i2c5号总线。

以及一些必要的宏设置:

cs 复制代码
#define SMPLRT_DIV  0x19 //陀螺仪采样率,典型值:0x07(125Hz)
#define CONFIG   0x1A //低通滤波频率,典型值:0x06(5Hz)
#define GYRO_CONFIG  0x1B //陀螺仪自检及测量范围,典型值:0xF8(不自检,+/-2000deg/s)
#define ACCEL_CONFIG 0x1C //加速计自检、测量范围,典型值:0x19(不自检,+/-G)
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H  0x41
#define TEMP_OUT_L  0x42
#define GYRO_XOUT_H  0x43
#define GYRO_XOUT_L  0x44
#define GYRO_YOUT_H  0x45
#define GYRO_YOUT_L  0x46
#define GYRO_ZOUT_H  0x47
#define GYRO_ZOUT_L  0x48
#define PWR_MGMT_1  0x6B //电源管理,典型值:0x00(正常启用)

2. 搭建驱动框架

目的:参照platform样式或其他标准驱动框架,为二级外设驱动搭建驱动框架。

cs 复制代码
//其它struct file_operations函数实现原理同硬编驱动

static int mpu6050_probe(struct i2c_client *pclt,const struct i2c_device_id *pid)
{
    //做硬编驱动模块入口函数的活
}

static int mpu6050_remove(struct i2c_client *pclt)
{
    //做硬编驱动模块出口函数的活
}

/*名称匹配时定义struct i2c_device_id数组*/
static struct i2c_device_id mpu6050_ids = 
{
    {"mpu6050",0},
    //.....
    {}
};

/*设备树匹配时定义struct of_device_id数组*/
static struct of_device_id mpu6050_dts =
{
    {.compatible = "invensense,mpu6050"},
    //....
    {}
};

/*通过定义struct i2c_driver类型的全局变量来创建i2c_driver对象,同时对其主要成员进行初始化
  1.向Linux内核的I2C子系统注册一个能够管理和控制这些设备的驱动程序。
  2.i2c_driver结构包含了驱动程序与I2C子系统交互所需的所有关键信息,
    包括如何识别设备、如何与设备通信、如何初始化设备以及如何处理设备的特定操作等。
*/
struct i2c_driver mpu6050_driver = 
{
	.driver = {
        .name = "mpu6050",
        .owner = THIS_MODULE,
        .of_match_table = mpu6050_dts,
    },
    .probe = mpu6050_probe,
    .remove = mpu6050_remove,
    .id_table = mpu6050_ids,
};

/*以下其实是个宏,展开后相当于实现了模块入口函数和模块出口函数*/
module_i2c_driver(mpu6050_driver);
/*
实则为:
int __init mpu6050_driver_init(void)
	{
		i2c_add_driver(&mpu6050_driver);//功能:向内核注册一个i2c_driver对象
	}
	
void __exit mpu6050_driver_exit(void)
	{
		i2c_del_driver(&mpu6050_driver);//从内核注销一个i2c_driver对象
	}

module_init(mpu6050_driver_init);
module_exit(mpu6050_driver_exit);
*/

MODULE_LICENSE("GPL");
上述代码框架解释:
cs 复制代码
struct i2c_driver {
    unsigned int class;

    /* 标准驱动模型接口 */
    int (*probe)(struct i2c_client *, const struct i2c_device_id *);
    int (*remove)(struct i2c_client *);

    /* 与枚举无关的驱动模型接口 */
    void (*shutdown)(struct i2c_client *);
    int (*suspend)(struct i2c_client *, pm_message_t mesg);
    int (*resume)(struct i2c_client *);
	void (*alert)(struct i2c_client *, unsigned int data);

    /* 类似ioctl的命令,可用于执行特定功能 */
    int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

    struct device_driver driver;
    const struct i2c_device_id *id_table;

    /* 用于自动设备创建的设备检测回调 */
    int (*detect)(struct i2c_client *, struct i2c_board_info *);
    const unsigned short *address_list;
    struct list_head clients;
};
/* 重要成员:
probe:在i2c_client与i2c_driver匹配后执行该函数
remove:在取消i2c_client与i2c_driver匹配绑定后执行该函数
driver:这个成员类型在平台设备驱动层中也有,而且使用其中的name成员来实现平台设备匹配,但是i2c子系统中不使用其中的name进行匹配,这也是i2c设备驱动模型和平台设备模型匹配方法的一点区别
id_table:用来实现i2c_client与i2c_driver匹配绑定,当i2c_client中的name成员和i2c_driver中id_table中name成员相同的时候,就匹配上了。

补充:i2c_client与i2c_driver匹配问题
- i2c_client中的name成员和i2c_driver中id_table中name成员相同的时候
- i2c_client指定的信息在物理上真实存放对应的硬件,并且工作是正常的才会绑定上,并执行其中的probe接口函数这第二点要求和平台模型匹配有区别,平台模型不要求设备层指定信息在物理上真实存在就能匹配
*/

/* 功能:向内核注册一个i2c_driver对象
返回值:0成功,负数 失败*/
#define i2c_add_driver(driver)     i2c_register_driver(THIS_MODULE, driver)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);

/* 功能:从内核注销一个i2c_driver对象
返回值:无 */
void i2c_del_driver(struct i2c_driver *driver);

然后需要:

  1. 定义i2c_client结构体:每个二级外设都需要一个对应的i2c_client结构体来表示。这个结构体包含了外设的地址、名称、适配器(adapter)等信息。
  2. 实现probe和remove函数:probe函数是驱动加载时调用的函数,用于初始化外设;remove函数是驱动卸载时调用的函数,用于清理资源。
  3. 定义i2c_driver结构体:这个结构体包含了驱动的名称、probe和remove函数指针等,用于向I2C核心注册驱动。

i2c_client结构体注释:

cs 复制代码
struct i2c_client {
    unsigned short flags;
    unsigned short addr;
    char name[I2C_NAME_SIZE];
    struct i2c_adapter *adapter;
    struct i2c_driver *driver;
    struct device dev;
    int irq;
    struct list_head detected;
};
/*重要成员:
flags:地址长度,如是10位还是7位地址,默认是7位地址。如果是10位地址器件,则设置为I2C_CLIENT_TEN
addr:具体I2C器件如(at24c02),设备地址,低7位
name:设备名,用于和i2c_driver层匹配使用的,可以和平台模型中的平台设备层platform_driver中的name作用是一样的。
adapter:本设备所绑定的适配器结构(CPU有很多I2C适配器,类似单片机有串口1、串口2等等,在linux中每个适配器都用一个结构描述)
driver:指向匹配的i2c_driver结构,不需要自己填充,匹配上后内核会完成这个赋值操作
dev:内嵌的设备模型,可以使用其中的platform_data成员传递给任何数据给i2c_driver使用。
irq:设备需要使用到中断时,把中断编号传递给i2c_driver进行注册中断,如果没有就不需要填充。(有的I2C器件有中断引脚编号,与CPU相连)
*/

struct i2c_adapter *i2c_get_adapter(int nr);
/* 获得/释放 i2c_adapter 路径:i2c-core.c linux-3.5\drivers\i2c */
/*功能:通过i2c总线编号获得内核中的i2c_adapter结构地址,然后用户可以使用这个结构地址就可以给i2c_client结构使用,从而实现i2c_client进行总线绑定,从而增加适配器引用计数。
返回值:
NULL:没有找到指定总线编号适配器结构
非NULL:指定nr的适配器结构内存地址*/

void i2c_put_adapter(struct i2c_adapter *adap);
/*减少引用计数:当使用·i2c_get_adapter·后,需要使用该函数减少引用计数。(如果你的适配器驱动不需要卸载,可以不使用)*/

具体实现代码(部分):

cs 复制代码
struct mpu6050_dev
{
	struct cdev mydev;//说明是一个字符设备
	struct i2c_client *pclt;
	/*存储了MPU6050设备在I2C总线上的相关信息,如设备的I2C地址、适配器(即I2C主设备)、传输速率等。
	通过pclt指针,驱动程序可以访问MPU6050设备在I2C总线上的配置信息,并执行数据的读写操作。*/
};

struct mpu6050_dev *pgmydev = NULL;

static int mpu6050_probe(struct i2c_client *pclt,const struct i2c_device_id *pid)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 手动申请设备号 */
	ret = register_chrdev_region(devno, char_num, "mpu6050");
	if (ret) {
		/* 动态申请设备号 */
		ret = alloc_chrdev_region(&devno, minor, char_num, "mpu6050");
		if(ret){
			printk("get devno failed\n");
			return -1;
		}
		/*申请成功 更新设备号*/
		major = MAJOR(devno);
	}
	
	pgmydev = (struct mpu6050_dev *)kmalloc(sizeof(struct mpu6050_dev), GFP_KERNEL);
	if(NULL == pgmydev) 
	{
		unregister_chrdev_region(devno, char_num);
		printk("kmalloc for 'struct mpu6050_dev' failed\n");
		return -1;
	}
	memset(pgmydev, 0, sizeof(struct mpu6050_dev));
	
	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&pgmydev->mydev, &myops);

	/* 将struct cdev对象添加到内核对应的数据结构中 */
	pgmydev->mydev.owner = THIS_MODULE;
	cdev_add(&pgmydev->mydev, devno, char_num);

	return 0;
}

static int mpu6050_remove(struct i2c_client *pclt)
{

	dev_t devno = MKDEV(major, minor);
	
	/* 从内核中移除一个字符设备 */
	cdev_del(&pgmydev->mydev);

	/* 回收设备号 */
	unregister_chrdev_region(devno, char_num);
	
	/* 释放内存 */
	kfree(pgmydev);
	pgmydev = NULL;
	
	return 0;
}

/*名称匹配时定义struct i2c_device_id数组*/
struct i2c_device_id mpu6050_ids[] = 
{
	{"mpu6050",0},
	{}
};

struct i2c_driver mpu6050_driver = {
	.driver = {
		.name = "mpu6050",
		.owner = THIS_MODULE,
	},
	.probe = mpu6050_probe,
	.remove = mpu6050_remove,
	.id_table = mpu6050_ids,
};

3.注册和匹配以及初始化mpu6050

  1. 名称匹配 :通过定义i2c_device_id结构体数组,并在i2c_driver结构体中指定id_table成员,实现名称匹配。这种方法需要事先知道外设的名称。(本文使用的方法)
  2. 设备树匹配 :在设备树(Device Tree)中定义外设的信息,如compatible属性,然后在i2c_driver结构体中指定of_match_table成员,实现设备树匹配。这种方法更加灵活,适用于大多数现代Linux系统。(本文使用的方法)
  3. 初始化mpu6050.
cs 复制代码
void init_mpu6050(struct i2c_client *pclt)
{
	mpu6050_write_byte(pclt,PWR_MGMT_1,0x00);
	mpu6050_write_byte(pclt,SMPLRT_DIV,0x07);
	mpu6050_write_byte(pclt,CONFIG,0x06);
	mpu6050_write_byte(pclt,GYRO_CONFIG,0xF8);
	mpu6050_write_byte(pclt,ACCEL_CONFIG,0x19);
}

4.数据传输

  1. 使用i2c_transfer函数 :通过这个函数可以发送和接收数据(mpu6050_write_byte,mpu6050_read_byte)。它允许你指定传输的方向(读或写)、传输的数据长度以及数据缓冲区。
  2. 处理中断和错误:根据需要处理外设产生的中断,并处理可能的错误情况,如传输失败、设备无响应等。

i2c_transfer函数注释:

cs 复制代码
/*i2c收发一体化函数,收还是发由参数msgs的成员flags决定*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
/*
功能:根据msgs进行手法控制
参数:
adap:使用哪一个适配器发送信息,一般是取i2c_client结构中的adapter指针作为参数
msgs:具体发送消息指针,一般情况下是一个数组
num:表示前一个参数msgs数组有多少个消息要发送的
返回值:
负数:失败
> 0 表示成功发送i2c_msg数量
*/

struct i2c_adapter
/*i2c_adapter 结构体封装了与特定 I2C 总线相关的所有必要信息,包括硬件细节、当前状态、传输函数等,使得内核能够管理和控制该总线上的设备。*/

struct i2c_msg {
    __u16 addr; /* slave address            */
    __u16 flags;
#define I2C_M_TEN       0x0010  /* this is a ten bit chip address */
#define I2C_M_RD        0x0001  /* read data, from slave to master */
    __u16 len;      /* msg length               */
    __u8 *buf;      /* pointer to msg data          */
};
/* 重要成员:
addr:要读写的二级外设地址
flags:表示地址的长度,读写功能。如果是10位地址必须设置I2C_M_TEN,如果是读操作必须设置有I2C_M_RD······,可以使用或运算合成。
buf:要读写的数据指针。写操作:数据源 读操作:指定存放数据的缓存区
len:读写数据的数据长度
*/

发送和接收数据函数详解:

cs 复制代码
int mpu6050_read_byte(struct i2c_client *pclt,unsigned char reg)
{
	int ret = 0;
	char txbuf[1] = {reg};
	char rxbuf[1] = {0};

	struct i2c_msg msg[2] = 
	{
		{pclt->addr,0,1,txbuf},
		{pclt->addr,I2C_M_RD,1,rxbuf}
	};
	//利用i2c_transfer将msg里面两个数组发送给mpu6050,其中第一个数组是告诉mpu6050要读取哪个寄存器,然后mpu6050将该寄存器地址里面的数据通过msg[1]的rxbuf返回
	ret = i2c_transfer(pclt->adapter,msg,ARRAY_SIZE(msg));
	if(ret < 0)
	{
		printk("ret = %d,in mpu6050_read_byte\n",ret);
		return ret;
	}

	return rxbuf[0];
}

int mpu6050_write_byte(struct i2c_client *pclt,unsigned char reg,unsigned char val)
{
	int ret = 0;
	char txbuf[2] = {reg,val};

	struct i2c_msg msg[1] = 
	{
		{pclt->addr,0,2,txbuf},
	};

	ret = i2c_transfer(pclt->adapter,msg,ARRAY_SIZE(msg));
	if(ret < 0)
	{
		printk("ret = %d,in mpu6050_write_byte\n",ret);
		return ret;
	}

	return 0;
}

5.I2C总线二级外设驱动开发之名称匹配

这种匹配方式需要自己创建i2c_client对象

cs 复制代码
struct i2c_board_info {
    char        type[I2C_NAME_SIZE];
    unsigned short  flags;
    unsigned short  addr;
    void        *platform_data;
    struct dev_archdata *archdata;
    struct device_node *of_node;
    int     irq;
};
/*用来协助创建i2c_client对象
重要成员
type:用来初始化i2c_client结构中的name成员
flags:用来初始化i2c_client结构中的flags成员
addr:用来初始化i2c_client结构中的addr成员
platform_data:用来初始化i2c_client结构中的.dev.platform_data成员
archdata:用来初始化i2c_client结构中的.dev.archdata成员
irq:用来初始化i2c_client结构中的irq成员

关键就是记住该结构和i2c_client结构成员的对应关系。在i2c子系统不直接创建i2c_client结构,只是提供struct i2c_board_info结构信息,让子系统动态创建,并且注册。
*/

介绍两种方法:

1.i2c_new_device:明确二级外设地址的情况下可用

mpu6050_client.c:

cs 复制代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>

//用来协助创建i2c_client对象
static struct i2c_board_info mpu6050_info = 
{
	I2C_BOARD_INFO("mpu6050",0x68)
    //一个在 Linux 内核 I2C 子系统中使用的宏,用于定义一个 I2C 设备的信息
};

/*用于存储通过I2C总线找到的MPU6050设备的i2c_client结构体指针。*/
static struct i2c_client *gpmpu6050_client = NULL;


static int __init mpu6050_client_init(void)
{
	struct i2c_adapter *padp = NULL;
	
	/*通过I2C适配器编号(这里是5)获取I2C适配器的指针*/
	padp = i2c_get_adapter(5);
	
	/*使用之前获取的I2C适配器和设备信息,创建一个新的I2C设备。
	如果成功,它会返回一个指向新创建的i2c_client结构体的指针,并将其存储在gpmpu6050_client中。*/
	gpmpu6050_client = i2c_new_device(padp,&mpu6050_info);
	
	/*释放之前获取的I2C适配器指针。*/
	i2c_put_adapter(padp);
	
	return 0;
}

static void  mpu6050_client_exit(void)
{
	/*注销之前注册的MPU6050 I2C设备*/
	i2c_unregister_device(gpmpu6050_client);
}

module_init(mpu6050_client_init);
module_exit(mpu6050_client_exit);
MODULE_LICENSE("GPL");

完整代码展示:

mpu6050.h:

cs 复制代码
#ifndef MPU_6050_H
#define MPU_6050_H

//加速度
struct accel_data
{
	unsigned short x;
	unsigned short y;
	unsigned short z;
};
//角速度
struct gyro_data
{
	unsigned short x;
	unsigned short y;
	unsigned short z;
};
//联合体
union mpu6050_data
{
	struct accel_data accel;
	struct gyro_data gyro;
	unsigned short temp;//温度
};

#define MPU6050_MAGIC 'K'

#define GET_ACCEL _IOR(MPU6050_MAGIC,0,union mpu6050_data)//读操作
#define GET_GYRO _IOR(MPU6050_MAGIC,1,union mpu6050_data)
#define GET_TEMP _IOR(MPU6050_MAGIC,2,union mpu6050_data)


#endif

mpu6050_drv.c:

cs 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <asm/uaccess.h>
#include <asm/ioctl.h>

#include "mpu6050.h"

/****************MPU6050内部寄存器地址****************/

#define	SMPLRT_DIV		0x19	//陀螺仪采样率,典型值:0x07(125Hz)
#define	CONFIG			0x1A	//低通滤波频率,典型值:0x06(5Hz)
#define	GYRO_CONFIG		0x1B	//陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
#define	ACCEL_CONFIG	0x1C	//加速计自检、测量范围及高通滤波频率,典型值:0x18(不自检,2G,5Hz)
#define	ACCEL_XOUT_H	0x3B
#define	ACCEL_XOUT_L	0x3C
#define	ACCEL_YOUT_H	0x3D
#define	ACCEL_YOUT_L	0x3E
#define	ACCEL_ZOUT_H	0x3F
#define	ACCEL_ZOUT_L	0x40
#define	TEMP_OUT_H		0x41
#define	TEMP_OUT_L		0x42
#define	GYRO_XOUT_H		0x43
#define	GYRO_XOUT_L		0x44
#define	GYRO_YOUT_H		0x45
#define	GYRO_YOUT_L		0x46
#define	GYRO_ZOUT_H		0x47
#define	GYRO_ZOUT_L		0x48
#define	PWR_MGMT_1		0x6B	//电源管理,典型值:0x00(正常启用)
#define	WHO_AM_I		0x75	//IIC地址寄存器(默认数值0x68,只读)
#define	SlaveAddress	0x68	//MPU6050-I2C地址

int major = 11;					//主设备号
int minor = 0;					//次设备号
int char_num = 1;				//设备号数量

struct mpu6050_dev
{
	struct cdev mydev;//说明是一个字符设备
	struct i2c_client *pclt;
	/*存储了MPU6050设备在I2C总线上的相关信息,如设备的I2C地址、适配器(即I2C主设备)、传输速率等。
	通过pclt指针,驱动程序可以访问MPU6050设备在I2C总线上的配置信息,并执行数据的读写操作。*/
};
struct mpu6050_dev *pgmydev = NULL;

int mpu6050_read_byte(struct i2c_client *pclt,unsigned char reg)
{
	int ret = 0;
	char txbuf[1] = {reg};
	char rxbuf[1] = {0};

	struct i2c_msg msg[2] = 
	{
		{pclt->addr,0,1,txbuf},
		{pclt->addr,I2C_M_RD,1,rxbuf}
	};
	//利用i2c_transfer将msg里面两个数组发送给mpu6050,其中第一个数组是告诉mpu6050要读取哪个寄存器吗?然后mpu6050将该寄存器地址里面的数据通过msg[1]的rxbuf返回
	ret = i2c_transfer(pclt->adapter,msg,ARRAY_SIZE(msg));
	if(ret < 0)
	{
		printk("ret = %d,in mpu6050_read_byte\n",ret);
		return ret;
	}

	return rxbuf[0];
}

int mpu6050_write_byte(struct i2c_client *pclt,unsigned char reg,unsigned char val)
{
	int ret = 0;
	char txbuf[2] = {reg,val};

	struct i2c_msg msg[1] = 
	{
		{pclt->addr,0,2,txbuf},
	};

	ret = i2c_transfer(pclt->adapter,msg,ARRAY_SIZE(msg));
	if(ret < 0)
	{
		printk("ret = %d,in mpu6050_write_byte\n",ret);
		return ret;
	}

	return 0;
}

int mpu6050_open (struct inode *pnode, struct file *pfile)//打开设备
{
	pfile->private_data = (void *) (container_of(pnode->i_cdev, struct mpu6050_dev,mydev));
	
	return 0;
}

int mpu6050_close(struct inode *pnode, struct file *pfile)//关闭设备
{
	
	return 0;
}

long mpu6050_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
	struct mpu6050_dev *pmydev = (struct mpu6050_dev *)pfile->private_data;
	union mpu6050_data data;
	
	switch(cmd) 
	{
		case GET_ACCEL:
			data.accel.x = mpu6050_read_byte(pmydev->pclt,ACCEL_XOUT_L);
			data.accel.x = mpu6050_read_byte(pmydev->pclt,ACCEL_XOUT_H) << 8;
			
			data.accel.y = mpu6050_read_byte(pmydev->pclt,ACCEL_YOUT_L);
			data.accel.y = mpu6050_read_byte(pmydev->pclt,ACCEL_YOUT_H) << 8;

			data.accel.z = mpu6050_read_byte(pmydev->pclt,ACCEL_ZOUT_L);
			data.accel.z = mpu6050_read_byte(pmydev->pclt,ACCEL_ZOUT_H) << 8;
			break;
		case GET_GYRO:
			data.gyro.x = mpu6050_read_byte(pmydev->pclt,GYRO_XOUT_L);
			data.gyro.x = mpu6050_read_byte(pmydev->pclt,GYRO_XOUT_H) << 8;
			
			data.gyro.y = mpu6050_read_byte(pmydev->pclt,GYRO_YOUT_L);
			data.gyro.y = mpu6050_read_byte(pmydev->pclt,GYRO_YOUT_H) << 8;

			data.gyro.z = mpu6050_read_byte(pmydev->pclt,GYRO_ZOUT_L);
			data.gyro.z = mpu6050_read_byte(pmydev->pclt,GYRO_ZOUT_H) << 8;
			break;
		case GET_TEMP:
			data.temp = mpu6050_read_byte(pmydev->pclt,TEMP_OUT_L);
			data.temp = mpu6050_read_byte(pmydev->pclt,TEMP_OUT_H) << 8;
			break;
		default:
			return -EINVAL;
	}
	
	if(copy_to_user((void *)arg,&data,sizeof(data)))//用于将数据从内核空间安全地复制到用户空间
	{
		return -EFAULT;
	}
	return sizeof(data);
}

void init_mpu6050(struct i2c_client *pclt)
{
	mpu6050_write_byte(pclt,PWR_MGMT_1,0x00);
	mpu6050_write_byte(pclt,SMPLRT_DIV,0x07);
	mpu6050_write_byte(pclt,CONFIG,0x06);
	mpu6050_write_byte(pclt,GYRO_CONFIG,0xF8);
	mpu6050_write_byte(pclt,ACCEL_CONFIG,0x19);
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mpu6050_open,
	.release = mpu6050_close,
	.unlocked_ioctl = mpu6050_ioctl,
};


static int mpu6050_probe(struct i2c_client *pclt,const struct i2c_device_id *pid)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 手动申请设备号 */
	ret = register_chrdev_region(devno, char_num, "mpu6050");
	if (ret) {
		/* 动态申请设备号 */
		ret = alloc_chrdev_region(&devno, minor, char_num, "mpu6050");
		if(ret){
			printk("get devno failed\n");
			return -1;
		}
		/*申请成功 更新设备号*/
		major = MAJOR(devno);
	}
	
	pgmydev = (struct mpu6050_dev *)kmalloc(sizeof(struct mpu6050_dev), GFP_KERNEL);
	if(NULL == pgmydev) 
	{
		unregister_chrdev_region(devno, char_num);
		printk("kmalloc for 'struct mpu6050_dev' failed\n");
		return -1;
	}
	memset(pgmydev, 0, sizeof(struct mpu6050_dev));
	
	pgmydev->pclt = pclt;
	
	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&pgmydev->mydev, &myops);

	/* 将struct cdev对象添加到内核对应的数据结构中 */
	pgmydev->mydev.owner = THIS_MODULE;
	cdev_add(&pgmydev->mydev, devno, char_num);

	init_mpu6050(pgmydev->pclt);
	return 0;
}

static int mpu6050_remove(struct i2c_client *pclt)
{

	dev_t devno = MKDEV(major, minor);
	
	/* 从内核中移除一个字符设备 */
	cdev_del(&pgmydev->mydev);

	/* 回收设备号 */
	unregister_chrdev_region(devno, char_num);
	
	/* 释放内存 */
	kfree(pgmydev);
	pgmydev = NULL;
	
	return 0;
}

/*名称匹配时定义struct i2c_device_id数组*/
struct i2c_device_id mpu6050_ids[] = 
{
	{"mpu6050",0},
	{}
};

struct i2c_driver mpu6050_driver = {
	.driver = {
		.name = "mpu6050",
		.owner = THIS_MODULE,
	},
	.probe = mpu6050_probe,
	.remove = mpu6050_remove,
	.id_table = mpu6050_ids,
};

#if 0
	int __init mpu6050_driver_init(void)
	{
		i2c_add_driver(&mpu6050_driver);
	}
	
	void __exit mpu6050_driver_exit(void)
	{
		i2c_del_driver(&mpu6050_driver);
	}
	module_init(mpu6050_driver_init);
	module_exit(mpu6050_driver_exit);
#else
	module_i2c_driver(mpu6050_driver);
#endif


MODULE_LICENSE("GPL");

testapp.c:

cs 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>


#include <stdio.h>

#include "mpu6050.h"

int main(int argc,char *argv[])
{
	int fd = -1;
	union mpu6050_data data;

	if(argc < 2)
	{
		printf("The argument is too few\n");
		return 1;
	}

	fd = open(argv[1],O_RDONLY);
	if(fd < 0)
	{
		printf("open %s failed \n",argv[1]);
		return 2;
	}

	while(1)
	{
		sleep(2);

		ioctl(fd,GET_ACCEL,&data);
		printf("Accel-x=0x%x\n",data.accel.x);
		printf("Accel-y=0x%x\n",data.accel.y);
		printf("Accel-z=0x%x\n",data.accel.z);

		ioctl(fd,GET_GYRO,&data);
		printf("Gyro-x=0x%x\n",data.gyro.x);
		printf("Gyro-y=0x%x\n",data.gyro.y);
		printf("Gyro-z=0x%x\n",data.gyro.z);

		ioctl(fd,GET_TEMP,&data);
		printf("Temp=0x%x\n",data.temp);

		printf("\n");
	}


	close(fd);
	fd = -1;
	return 0;
}

实验结果:

2. i2c_new_probed_device:不明确二级外设地址

i2c二级外设client框架:不明确二级外设地址,但是知道是可能几个值之一的情况下可用

将外设程序改为如下程序:(其他代码不变即可)

mpu6050_client_probed.c:

cs 复制代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>

//数组列出了MPU6050可能的I2C地址(0x68和0x69),并以I2C_CLIENT_END作为结束标记。
static unsigned short mpu6050_addr_list[] = 
{
	0x68,
	0x69,
	I2C_CLIENT_END
};

//用于存储找到的MPU6050设备的I2C客户端信息
static struct i2c_client *gpmpu6050_client = NULL;

static int __init mpu6050_client_init(void)
{
	struct i2c_adapter *padp = NULL;
	struct i2c_board_info mpu6050_info = {""};


	strcpy(mpu6050_info.type,"mpu6050");
	padp = i2c_get_adapter(5);

    //尝试在指定的I2C总线上找到与mpu6050_info和mpu6050_addr_list匹配的设备。
	gpmpu6050_client = i2c_new_probed_device(padp,&mpu6050_info,mpu6050_addr_list,NULL);
	i2c_put_adapter(padp);

	if(gpmpu6050_client != NULL)
	{
		return 0;
	}
	else
	{
		return -ENODEV;
	}
}

static void  mpu6050_client_exit(void)
{
	i2c_unregister_device(gpmpu6050_client);
}

module_init(mpu6050_client_init);
module_exit(mpu6050_client_exit);
MODULE_LICENSE("GPL");

6.I2C总线二级外设驱动开发之设备树匹配

将驱动程序改为:

cs 复制代码
//设备树
struct of_device_id mpu6050_dt[] = 
{
	{.compatible = "invensense,mpu6050"},
	{}
};

struct i2c_device_id mpu6050_ids[] = 
{
	{"mpu6050",0},
	{}
};

struct i2c_driver mpu6050_driver = {
	.driver = {
		.name = "mpu6050",
		.owner = THIS_MODULE,
		.of_match_table = mpu6050_dt,
	},
	.probe = mpu6050_probe,
	.remove = mpu6050_remove,
	.id_table = mpu6050_ids,
};

实验结果:

相关推荐
sukalot10 小时前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(二)
驱动开发
zwhSunday11 小时前
Linux驱动开发(1)概念、环境与代码框架
linux·运维·驱动开发
sukalot1 天前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(三)
驱动开发
sukalot1 天前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(一)
驱动开发
cxr8282 天前
基于Claude Code的 规范驱动开发(SDD)指南
人工智能·hive·驱动开发·敏捷流程·智能体
zwhSunday3 天前
Linux驱动开发(2)进一步理解驱动
linux·驱动开发
被遗忘的旋律.3 天前
Linux驱动开发笔记(十)——中断
linux·驱动开发·笔记
路溪非溪3 天前
Linux驱动如何向应用层提供sysfs操作接口
linux·arm开发·驱动开发
sukalot4 天前
window显示驱动开发—监视筛选器驱动程序(三)
驱动开发
墨染天姬4 天前
【android 驱动开发九】生产者-消费者模型
android·驱动开发