天天看点

linux I2C子系统(及相关程序设计MPU6050)

文章目录

    • linux I2C子系统框架
    • 在设备树中添加从设备信息,mpu5060
    • I2C driver 程序的编写
    • mpu6050 I2C程序具体实现

linux I2C子系统框架

在之前的一篇文章中记录了 I2C协议相关内容,这里记录一下I2C在linux系统上I2C子系统框架,经过总结,I2C子系统框架可以总结为如下一张图

五层(其实是三层,不算应用层和硬件层)

  • 应用层
  • i2c driver层:从设备驱动层

    需要和应用层交互

    封包数据,不知道数据是如何写入到硬件

  • i2c 核心层:drivers/i2c/i2c-core.c

    维护ic总线,包括i2c driver,i2c client链表

  • i2c adapter层:drivers/i2c/busses/i2c-s3c2410.c i2c 控制层,初始化 i2c 控制器

    完成将数据写入或读取 ---------从设备硬件

    不知道数据具体是什么,但是知道如何操作从设备

  • 硬件层

    确保 i2c core 和 i2c adapter 侧面和必须编译进内核:

    linux I2C子系统(及相关程序设计MPU6050)
linux I2C子系统(及相关程序设计MPU6050)

在设备树中添加从设备信息,mpu5060

  • I2C子系统中涉及到的设备树文件

    i2c 控制器地址:

0x1386_0000,
0x1387_0000,
0x1388_0000,
0x1389_0000,
0x138A_0000,
0x138B_0000,   ------MPU5060
0x138C_0000,
0x138D_0000,
0x138E_0000,

其中MPU5060:从设备地址是 0x68
soc  GPB_3 ---I2C_SCL5
	 GPB_3 --- I2C_SCL5
	 GPX_3 --- GYRO_INT
           
  • 模板:

    控制器中对应的设备树:arch/arm/boot/dts/exynos4.dtsi

  • 新增加 i2c 从设备,/arch/arm/boot/dts/exynos4412-fs4412.dts 增加 i2c 控制和它包含的从设备
i2c@138B0000 {
		#address-cells = <1>'
		#size-cells = <0>;
		samsung,i2c-sda-delay = <100>;
		samsung,i2c-max-bus = <20000>;
		pinctrl-0 = <&i2c5_bus>;
		pinctrl-names = "default";
		status = "okay";

		mpu6050@68 {
			compatible = "invensense,mpu6050";
			reg = <0x68>;
		};

	};
           

保存之后 make dtbs

I2C driver 程序的编写

  • I2C driver 程序的编写
  1. 添加 i2c client 的信息,必须在控制器对应节点中
  2. 直接编写 i2c driver

    (1)构建 i2c driver,并注册到 i2c 总线

    (2)实现 probe方法:

    a.申请设备号

    b.创建设备文件

    c.通过 i2c 的接口去初始化 i2c 从设备

  • 几个常用的对象
struct i2c_driver  //表示是一个从设备的驱动对象
{
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);
	struct device_driver driver;  //继承了父类
			|
			const struct of_device_if *of_match_table;
		
	const struct i2c_device_id *id_table;   //用于作比对,非设备树情况 与i2c_client
};
//注册和注销i2c_driver
//注册
int i2c_add_driver( struct i2c_driver *driver);
//删除
void i2c_del_driver(struct i2c_driver *);


struct i2c_client   //描述一个从设备的信息,不需要在代码中创建,因为是由i2c_adapter构建
{
	unsigned short flags;		/* div., see below		*/
	unsigned short addr;		//从设备地址,来自于设备树中的 <reg>
		
	char name[I2C_NAME_SIZE];  //用于和i2c_driver进行匹配,来自于设备树中的 compatible
	struct i2c_adapter *adapter;	//指向当前从设备存在的i2c_adapter
	struct device dev;		  //继承了父类
	int irq;		
	struct list_head detected;
};
//创建i2c_client 的函数
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);


struct i2c_adapter   //描述一个i2c控制器,也不是我们构建,原厂代码会帮我们构建
{
	const struct i2c_algorithm *algo;   //算法
		|
		int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);
		
	struct device dev;	//继承了父类,也会被加入到i2c_bus
	int nr;   //编号,类似i2c_5
};
//注册和注销 i2c_adapter   
int i2c_add_adapter(struct i2c_adapter *);
void i2c_del_adapter(struct i2c_adapter *);


struct i2c_msg   //描述了一个从设备要发送的数据的数据包
{
	__u16 addr;  //设备地址,发送给哪个从设备
	__u16 flags;  //读 1 写 0
	__u16 len;    //发送数据的长度
	__u8 *buf;   //指向数据的指针
};
//写从设备
int i2c_master_send(const struct i2c_client *client, const char *buf,int count);
//读从设备
int i2c_master_recv(const struct i2c_client *client, char *buf,int count);
//以上两个函数都调用了:i2c_transfer
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
           

mpu6050 I2C程序具体实现

  • 头文件mpu6050.h
#ifndef __MPU6050_H__
#define __MPU6050_H__
union mpu6050_data
{
	//加速度
	struct 
	{
		short x;
		short y;
		short z;
	}accel;

	//角度
	struct 
	{
		short x;
		short y;
		short z;
	}gyro;

	//温度
	short temp;
};

#define IOC_GET_ACCEL _IOR('M',0x34,mpu6050_data)
#define IOC_GET_GYRO _IOR('M',0x35,mpu6050_data)
#define IOC_GET_TEMP _IOR('M',0x36,mpu6050_data)

#endif


           
  • mpu6050 (陀螺仪)驱动程序设计
#include <linux/init.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/i2c.h>
#include <linux/slab.h>

#include <asm/uaccess.h>
#include <asm/io.h>
#include "mpu6050.h"


#define SMPLRT_DIV 0x19  //采样频率寄存器 - 25 典型值:0x07(125HZ)
#define CONFIG 0x1A  //配置寄存器 -26 典型值:0x06(5HZ)
#define GYRO_CONFIG  0x1B  //陀螺仪配置-27      典型值:0x18(不自检。2000deg/s)

#define ACCEL_CONFIG 0x1C  //加速度配置-28  典型值:0x01(不自检,2G,5HZ)
#define ACCEL_XOUT_H 0x3B  //59-65,加速度计测量值 XOUT_H
#define ACCEL_XOUT_L 0x3C   //XOUT_L
#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  //温度测量值 -65
#define TEMP_OUT_L 0x42

#define GYRO_XOUT_H 0x43  //陀螺仪-67  ,采样频率(由寄存器25定义)写入到这些寄存器
#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(正常启用)

//设计一个全局的设备对象
struct mpu_senor
{
	int dev_major;
	struct device *dev;
	struct class *cls;
	struct i2c_client *client;  //记录probe中的client
};

struct mpu_senor *mpu_dev;


//mpu6050  i2c写数据
int mpu6050_write_bytes(struct i2c_client *client,char *buf,int count)
{
	int ret;
	
	struct i2c_adapter *adapter = client->adapter;
	struct i2c_msg msg;

	msg.addr = client->addr;
	msg.flags = 0;
	msg.len = count;
	msg.buf = buf;
	
	ret = i2c_transfer(adapter,&msg,1);

	return ret == 1 ? count : ret;
}


//mpu6050  i2c读数据
int mpu6050_read_bytes(struct i2c_client *client,char *buf,int count)
{
	int ret;
	
	struct i2c_adapter *adapter = client->adapter;
	struct i2c_msg msg;

	msg.addr = client->addr;
	msg.flags |= I2C_M_RD;
	msg.len = count;
	msg.buf = buf;
	
	ret = i2c_transfer(adapter,&msg,1);

	return ret == 1 ? count : ret;
}

//读取某个特定寄存器地址,然后返回值
int mpu6050_read_reg_byte(struct i2c_client *client,char reg)
{
	//先写寄存器的地址,然后再读寄存器的值
	int ret;
	
	struct i2c_adapter *adapter = client->adapter;
	struct i2c_msg msg[2];

	char rxbuf[1];
	
	msg[0].addr = client->addr;
	msg[0].flags = 0;
	msg[0].len = 1;
	msg[0].buf = &reg;

	
	msg[1].addr = client->addr;
	msg[1].flags = 1;
	msg[1].len = 1;
	msg[1].buf = rxbuf;
	
	ret = i2c_transfer(adapter,msg,2);
	if(ret < 0)
	{
		printk("i2c_transfer read error\n");
		return ret;
	}

	return rxbuf[0];
}


int mpu6050_drv_open(struct inode *inode,struct file *filp)
{
	return 0;
}


int mpu6050_drv_release(struct inode *inode,struct file *filp )
{
	return 0;
}

/*应用程序:ioctl给驱动发送不同的指令
	ioctl(fd,cmd,args)

如何定义命令:
	1,直接定义一个数字
		#define IOC_GET_ACCEL 0x9999
	2,通过系统的接口
		_IO(x,y)
		_IOR(x,y,z)
		_IOW(x,y,z)
		参数1:表示magic,字符
		参数2:区分不同命令,整数
		参数3:传给驱动数据类型
	
*/
long mpu6050_drv_ioctl(struct file *filp,unsigned int cmd,unsigned long args)
{
	union mpu6050_data data;

	switch(cmd)
	{
		case IOC_GET_ACCEL:
			//读数据
			data.accel.x = mpu6050_read_reg_byte(mpu_dev->client, ACCEL_XOUT_L);
			data.accel.x |= mpu6050_read_reg_byte(mpu_dev->client, ACCEL_XOUT_H) << 8;

			data.accel.y = mpu6050_read_reg_byte(mpu_dev->client, ACCEL_YOUT_L);
			data.accel.y |= mpu6050_read_reg_byte(mpu_dev->client, ACCEL_YOUT_H) << 8;

			data.accel.z = mpu6050_read_reg_byte(mpu_dev->client, ACCEL_ZOUT_L);
			data.accel.z |= mpu6050_read_reg_byte(mpu_dev->client, ACCEL_ZOUT_H) << 8;
			break;
			
		case IOC_GET_GYRO:
			
			data.gyro.x = mpu6050_read_reg_byte(mpu_dev->client, GYRO_XOUT_L);
			data.gyro.x |= mpu6050_read_reg_byte(mpu_dev->client, GYRO_XOUT_H) << 8;

			data.gyro.y = mpu6050_read_reg_byte(mpu_dev->client, GYRO_YOUT_L);
			data.gyro.y |= mpu6050_read_reg_byte(mpu_dev->client, GYRO_YOUT_H) << 8;

			data.gyro.z = mpu6050_read_reg_byte(mpu_dev->client, GYRO_ZOUT_L);
			data.gyro.z |= mpu6050_read_reg_byte(mpu_dev->client, GYRO_ZOUT_H) << 8;
			break;
			
		case IOC_GET_TEMP:
			data.temp = mpu6050_read_reg_byte(mpu_dev->client, TEMP_OUT_L);
			data.temp |= mpu6050_read_reg_byte(mpu_dev->client, TEMP_OUT_H) << 8;
			break;
			
		default:
			printk("invalid cmd\n");
			return -EINVAL;
	}

	//将所有数据交给用户
	if(copy_to_user((void __user *)args,&data,sizeof(data))>0)
	{
		return -EFAULT;
	}

	return 0;
}

const struct file_operations mpu6050_fops = {
	.open = mpu6050_drv_open,
	.release = mpu6050_drv_release,
	.unlocked_ioctl = mpu6050_drv_ioctl,
	
};



int mpu6050_drv_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	/*实现 probe方法:
		a.申请设备号
		b.创建设备文件
	c.通过 i2c 的接口去初始化 i2c 从设备
	*/
	//分配空间
	mpu_dev = kzalloc(sizeof(struct mpu_senor),GFP_KERNEL);

	//记录下client
	mpu_dev->client = client;

	//申请设备号
	mpu_dev->dev_major = register_chrdev(0,"mpu_drv",mpu6050_fops);

	//创建文件
	mpu_dev->cls = class_create(THIS_MODULE,"mpu_cls");

	mpu_dev->dev = device_create(mpu_dev->cls,NULL,MKDEV(mpu_dev->dev_major,0),NULL,"mpu_sensor");

	//初始化各种寄存器
	char buf1[2] = {PWR_MGMT_1,0x0};	
	mpu6050_write_bytes(mpu_dev->client,buf1,2);

	char buf2[2] = {SMPLRT_DIV,0x07};
	mpu6050_write_bytes(mpu_dev->client,buf2,2);
	
	char buf3[2] = {CONFIG,0x06};
	mpu6050_write_bytes(mpu_dev->client,buf3,2);

	char buf4[2] = {GYRO_CONFIG,0x18};
	mpu6050_write_bytes(mpu_dev->client,buf4,2);

	char buf5[2] = {ACCEL_CONFIG,0x01};
	mpu6050_write_bytes(mpu_dev->client,buf5,2);

	
	//i2c_master_send(client, const char * buf, int count);
	
}



int mpu6050_drv_remove(struct i2c_client *client)
{
	//释放各种资源
	class_destroy(mpu_dev->cls);
	unregister_chrdev(mpu_dev->dev_major,"mpu_drv");
	device_destroy(mpu_dev->cls,MKDEV(mpu_dev->dev_major,0));
	kfree(mpu_dev);
}


const struct of_device_id of_mpu6050_id[] = {
	{
		.compatible = "invensense,mpu6050",
	},
	{/*nothing to be done*/}
};

const struct i2c_device_id mpu_id_table[] = {
	{"mpu6050_drv",0x1111},
	{/*nothing to be done*/}
};   

struct i2c_driver mpu6050_drv = {
	.probe = mpu6050_drv_probe,
	.remove = mpu6050_drv_remove,
	.driver = {
		.name = "mpu6050_drv"; //随便写, /sys/bus/i2c/driver/
		.of_match_table = of_match_ptr(of_mpu6050_id);
	},
	.id_table = mpu_id_table;  //用于非设备树情况下的匹配,设备树模式下不使用
};

static int __init mpu6050_drv_init(void)

{
	//1.构建 i2c driver,并注册到 i2c 总线
	return i2c_add_driver(&mpu6050_drv);
}


static void __exit  mpu6050_drv_exit(void)
{
	i2c_del_driver(&mpu6050_drv);
}
module_init(mpu6050_drv_init);
module_exit(mpu6050_drv_exit);
MODULE_LICENSE("GPL");

           
  • mpu6050 (陀螺仪)应用程序设计
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/input.h>
#include <unistd.h>
#include <sys/ioctl.h>

#include "mpu6050.h"

int main(int argc,char *argv[])
{

	union mpu6050_data data;

	int fd;
	fd = open("/dev/mpu_sensor",O_RDWR);
	if(fd < 0)
	{
		perror("open");
		exit(1);
	}
	while(1)
	{
		ioctl(fd,IOC_GET_ACCEL,&data);
		printf("accel data : x=%d,y=%d,z=%d\n",data.accel.x,data.accel.y,data.accel.z);

		ioctl(fd,IOC_GET_GYRO,&data);
		printf("gyro data : x=%d,y=%d,z=%d\n",data.gyro.x,data.gyro.y,data.gyro.z);
		sleep(1);
	}

	close(fd);
	return 0;
}