天天看點

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;
}



           

繼續閱讀