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号总线。 

 以及一些必要的宏设置:

#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样式或其他标准驱动框架,为二级外设驱动搭建驱动框架。

//其它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");
上述代码框架解释:
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结构体注释:

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·后,需要使用该函数减少引用计数。(如果你的适配器驱动不需要卸载,可以不使用)*/

 具体实现代码(部分):

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.
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函数注释:

/*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:读写数据的数据长度
*/

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

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对象

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:

#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:

#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:

#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 0int __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);
#elsemodule_i2c_driver(mpu6050_driver);
#endifMODULE_LICENSE("GPL");

testapp.c:

#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:

#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总线二级外设驱动开发之设备树匹配

将驱动程序改为:

//设备树
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,
};

 实验结果:

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

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

相关文章

实验四 FPGA 使用Verilog HDL设计电机运动控制程序

实验目的 1.掌握使用GPIO控制直流电机的原理。 2.掌握使用Verilog HDL设计电机运动控制程序的方法。 实验要求 采用Verilog HDL语言设计直流电机运动控制程序&#xff0c;实现直流电机的运动控制&#xff0c;并通过数码管显示当前输出的PWM波的占空比。通过按键或拔位开关可…

ArcGIS Pro不能编辑ArcGIS10.X的注记的解决办法

​ 点击下方全系列课程学习 点击学习—>ArcGIS全系列实战视频教程——9个单一课程组合系列直播回放 点击学习——>遥感影像综合处理4大遥感软件ArcGISENVIErdaseCognition 一、问题 我们利用ArcGIS Pro编辑ArcGIS10.X系列软件生成的注记要素类的时候&#xff0c;会提示不…

Apache POI-Excel入门与实战

目录 一、了解Apache POI 1.1 什么是Apache POI 1.2 为什么要使用ApaChe POI 1.3 Apache POI应用场景 1.4 Apache POI 依赖 二、Apache POI-Excel 入门案例 2.1 写入Excel文件 2.2 读取文件 四、Apache POI实战 4.1 创建一个获取天气的API 4.2高德天气请求API与响应…

怎样使用 Juicer tools 的 dump 命令将.hic文件转换为交互矩阵matrix计数文件 (Windows)

创作日志&#xff1a; 万恶的生信…一个scHiC数据集没有提供处理好的计数文件&#xff0c;需要从.hic转换。Github一个个好长的文档看了好久才定位到 juicer tools 的dump命令&#xff0c;使用起来比想象中简单。 一、下载Juicer tools 注意&#xff1a;使用Juicer tools的前提…

邮件安全篇:邮件反垃圾系统运作机制简介

1. 什么是邮件反垃圾系统&#xff1f; 邮件反垃圾系统是一种专门设计用于检测、过滤和阻止垃圾邮件的技术解决方案。用于保护用户的邮箱免受未经请求的商业广告、诈骗信息、恶意软件、钓鱼攻击和其他非用户意愿接收的电子邮件的侵扰。 反垃圾系统的常见部署形式 2. 邮件反垃圾…

day6 io线程

获取终端输入的字符

深入探究 Golang 反射:功能与原理及应用

Go 出于通用性的考量&#xff0c;提供了反射这一功能。借助反射功能&#xff0c;我们可以实现通用性更强的函数&#xff0c;传入任意的参数&#xff0c;在函数内通过反射动态调用参数对象的方法并访问它的属性。举例来说&#xff0c;下面的bridge接口为了支持灵活调用任意函数&…

python一维表转二维表

一维表转二维表 import pandas as pd # 读取数据 product_df pd.read_csv(rD:\excelFile\practice\物品属性值一维表.csv,encodingutf-8) # print(product_df)# 将一维表转变二维 s pd.Series(list(product_df[属性值]),index[product_df[物品编号],product_df[属性名]]) …

GMSSL2.x编译鸿蒙静态库和动态库及使用

一、编译环境准备 1.1 开发工具 DevEco-Studio下载。 1.2 SDK下载 ​ 下载编译第三方库的SDK有两种方式&#xff0c;第一种方式从官方渠道根据电脑系统选择对应的SDK版本&#xff0c;第二种方式通过DevEco-Studio下载SDK。本文只介绍通过DevEco-Studio下载SDK的方式。 安装…

centos中zabbix安装、卸载及遇到的问题

目录 Zabbix简介Zabbix5.0和Zabbix7.0的区别监控能力方面模板和 API 方面性能、速度方面 centos7安装Zabbix(5.0)安装zabbix遇到的问题卸载Zabbix Zabbix简介 Zabbix 是一个基于 WEB 界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。zabbix 能监视各种网络参…

大数据架构体系演进

传统离线大数据架构 ​ 21世纪初随着互联网时代的到来&#xff0c;数据量暴增&#xff0c;大数据时代到来。Hadoop生态群及衍生技术慢慢走向“舞台”&#xff0c;Hadoop是以HDFS为核心存储&#xff0c;以MapReduce&#xff08;简称MR&#xff09;为基本计算模型的批量数据处理…

MATLAB实验五:MATLAB数据分析

1. 某线路上不同时间对应的电压如下表所示&#xff1a; 1&#xff09;用 3 次多项式拟合(polyfit)该实验曲线&#xff0c;要求绘制 2 原始采样 点&#xff0c;并在 1~8 范围内&#xff0c;使用时间间隔为 0.2 的数据绘制拟合曲线。 建立一个脚本文件&#xff1a;text5_1.m 如下…

黑马JavaWeb企业级开发(知识清单)01——前端介绍,HTML实现标题:排版

文章目录 前言一、认识web前端、HTML、CSS二、VS Code开发工具&#xff08;插件弃用问题&#xff09;三、HTML结构标签介绍1. 标签页标题< title >2. 图片标签< img >1) 常见属性2) src路径书写方式 3. 标题标签< h >4. 水平分页线标签< hr > 四、用Vs…

安全的备忘录工具有哪些 安全好用的备忘录

在这个数字化的时代&#xff0c;我们的生活中充斥着各种各样的信息&#xff0c;从工作计划到个人琐事&#xff0c;从账号密码到重要日期&#xff0c;这些信息都需要我们牢记。然而&#xff0c;人的记忆毕竟有限&#xff0c;于是&#xff0c;备忘录工具成为了我们日常生活中不可…

运行 npm install 报错-4048

我在已经开发中的项目&#xff0c;执行 npm install 命令时&#xff0c;出现报错&#xff1a; 并且之前在帖子中提到的报错类型还不一样&#xff08;帖子内容如下&#xff09;&#xff1a; 运行 npm run dev 总报错_运行npm run dev报错-CSDN博客 该报错内容主要为权限导致的&…

C# 编程机器人

右边写代码&#xff0c;控制左边机器人移动 冯腾飞/编程机器人 - Gitee.com

SpringBoot框架学习笔记(五):静态资源访问、Rest风格请求处理、配置视图解析器、接收参数的相关注解详解

1 WEB开发-静态资源访问 1.1 基本介绍 &#xff08;1&#xff09;只要静态资源放在类路径的以下目录&#xff1a;/static、/public、/resources、/META-INF/resources 可以被直接访问。maven项目的类路径即为main/resources目录--对应SpringBoot源码为WebProperties.java类 …

基于STM32的PM2.5监测系统设计

目录 1、设计要求 2、系统功能 3、演示视频和实物 4、系统设计框图 5、软件设计流程图 6、原理图 7、主程序 8、总结 &#x1f91e;大家好&#xff0c;这里是5132单片机毕业设计&#xff0c;今天给大家分享的是《基于STM32的PM2.5监测系统设计》。 设备的详细功能见网…

Nginx 怎样处理请求的重试机制?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01; 文章目录 Nginx 怎样处理请求的重试机制&#xff1f;一、为何需要重试机制&#xff1f;二、Nginx 中的重试机制原理三、Nginx 重试机制的配置参数四、Nginx 重试机制的实际…

GPT盘新增容量后如何扩容?

场景&#xff1a;一块5T的GPT盘&#xff0c;现有需求再加10T&#xff0c; 在虚拟化平台加10T盘后&#xff0c;机器不重启&#xff0c;执行命令 echo 1 > /sys/block/sdb/device/rescan刷新磁盘容量&#xff0c;可看到容量已刷出。 但执行fdisk /dev/sdb时&#xff0c;发现创…