04 - I2C总线

1,i2c协议和时序

在这里插入图片描述

//=================================================================================================================================
[1] >> I2C子系统
-->><1>打开Linux内核I2C子系统功能
[
make menuconfig 	#内核
		Device Drivers  --->
			[] I2C support  ---> //I2C-core层编译进内核 ;启动关开发板后可以在/sys/bus中找到i2c总线 
				I2C Hardware Bus support  --->
					[] S3C2410 I2C Driver  
					//I2C-adatper层编译进内核 == i2c-s3c2410.c编译进内核;
					//由于设备树中只写了i2c0的设备树节点;所以启动开发板可以在/sys/bus/i2c总线的devices中发现第1组i2c --> i2c-0 
]
-->><2>


//=================================================================================================================================

2,i2c子系统软件框架

===========一个soc芯片有多组i2c=============
	i2c0  -->  0x1386_0000, 
	i2c1  -->  0x1387_0000,
	i2c2  -->  0x1388_0000, 
	i2c3  -->  0x1389_0000,
	i2c4  -->  0x138A_0000,
	i2c5  -->  0x138B_0000, ------ MPU6050(7位i2c设备地址 = 0x68 = 1101000) (我们需要用到的i2c)
	i2c6  -->  0x138C_0000, 
	i2c7  -->  0x138D_0000, 
	i2c8  -->  0x138E_0000

====================编写i2c5设备树节点=====================
	查看原理图 --> MPU6050模块接入的是i2c5,但是i2c总线中没有找到i2c5的设备;
	我们发现设备树只有i2c0节点,所以我们需要自己仿照i2c0写i2c5节点 ;
	然后重新编译文件树,并替换开发板的设备树文件
  arch/arm/boot/dts/exynos4412-fs4412.dts中
    i2c@138B0000 {/*i2c5 设备树节点 ; 138B0000是i2c5的基地址*/
            #address-cells = <1>;
            #size-cells = <0>;
            samsung,i2c-sda-delay = <100>;
            samsung,i2c-max-bus-freq = <20000>;
            pinctrl-0 = <&i2c5_bus>;
            pinctrl-names = "default";
            status = "okay";

            mpu6050@68 { /*i2c client信息 == i2c5下的子节点信息*/
                    compatible = "invensense,mpu6050";
                    reg = <0x68>;   //0x68 = 设备的i2c7位地址 = 1101000
			};
    };

====================i2c_driver 结构体=====================
struct i2c_driver {//表示是一个从设备的驱动对象
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);  //probe接口
	int (*remove)(struct i2c_client *);             //remove接口
	struct device_driver driver; //继承了父类
				const struct of_device_id	*of_match_table;
	const struct i2c_device_id *id_table;//用于做比对,非设备树的情况
}

====================i2c_client 结构体=====================
struct i2c_client {					//描述的是I2C从设备
	unsigned short addr;		//i2c从设备地址,来自于设备树中<reg>
	char name[I2C_NAME_SIZE]; //用于和i2c driver进行匹配,来自于设备树中compatible
	struct i2c_adapter *adapter;     //指向当前从设备所存在的i2c adapter
	struct device dev;	               	// 继承了父类
};

====================i2c_adapter 结构体=====================
	struct i2c_adapter {          //描述一个i2c主设备/soc信息,也不是我们要构建,原厂的代码会帮我们构建
	.....
	}

====================i2c_msg  结构体=====================
struct i2c_msg {//描述一个从设备要发送的数据的数据包
	__u16 addr;	 //从设备地址
	__u16 flags; //读1还是写0
	__u16 len;	//发送数据的长度
	__u8 *buf;	//发送的数据
};


=========================陀螺仪的基础知识============================
陀螺仪原理:小时候玩过陀螺,如果给它一定的旋转速度,陀螺会竖立旋转起来而不会倒
陀螺仪能测量3维层面(X,Y,Z轴)的加速度数据,角度数据


=========================================i2c系统架构如下=======================================
==============================================================================================
							app 								level
==============================================================================================
							i2c 			driver				level 				[程序员编写]
>>i2c_msg对象
>>完成fops
>>创建i2c_driver对象
>>与i2c_client匹配后进入probe()
==============================================================================================
						   i2c 		 	     core		        level     [i2c-core.c 内核提供]
维护i2c总线的 driver链表,device链表
//需要将i2c core level 代码i2c_core.c 编译进内核 -->	 /sys/bus/i2c/
==============================================================================================
						   i2c 				 adapt 				level   [i2c-s3c2410.c 芯片商提供]
>>i2c_adapt  -->  i2c主设备对象
>>i2c_client -->  i2c从设备对象
问:如何确定将i2c adapt level 层的代码 i2c-s3c2410.c编译进内核了?
答:可以找到/sys/bus/i2c/devices/i2c-0  
(为什么只有一组i2c呢,由于设备树文件中只写了i2c0(第一组i2c)的设备树节点,我们需要写i2c5的设备树节点才会有i2c5)

问:为什么是4412的开发板为什么用i2c-s3c2410.c呢?
答:因为 i2c-s3c2410.cs不仅作为i2c总线的device,还作为platform bus的device(平台总线是用于升级的,所以4412平台也能用)
==============================================================================================

mpu6050_i2c_driver.c

/*********头文件********/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/i2c.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include "mpu6050.h"

/*******************MPU6050 I2C 内部 寄存器地址****************/
#define SMPLRT_DIV		0x19 //采样频率寄存器 典型值:0x07(125Hz)
#define CONFIG			0x1A //配置寄存器     典型值:0x06(5Hz)
#define GYRO_CONFIG		0x1B //陀螺仪配置     配置自检和满量程范围  典型值:0x18(不自检,2000deg/s)
#define ACCEL_CONFIG	0x1C //加速度配置     可以配置自检和满量程范围及高通滤波频率 典型值:0x01(不自检,2G,5Hz)
#define ACCEL_XOUT_H	0x3B //加速度计X轴测量值高八位
#define ACCEL_XOUT_L	0x3C //加速度计X轴测量值低八位
#define ACCEL_YOUT_H	0x3D //加速度计Y轴测量值高八位
#define ACCEL_YOUT_L	0x3E //加速度计Y轴测量值低八位
#define ACCEL_ZOUT_H	0x3F //加速度计Z轴测量值高八位
#define ACCEL_ZOUT_L	0x40 //加速度计Z轴测量值低八位
#define TEMP_OUT_H		0x41 //温度测量值高八位
#define TEMP_OUT_L		0x42 //温度测量值低八位
#define GYRO_XOUT_H		0x43 //陀螺仪X轴测量值高八位
#define GYRO_XOUT_L		0x44 //陀螺仪X轴测量值低八位
#define GYRO_YOUT_H		0x45 //陀螺仪Y轴测量值高八位
#define GYRO_YOUT_L		0x46 //陀螺仪Y轴测量值低八位
#define GYRO_ZOUT_H		0x47 //陀螺仪Z轴测量值高八位
#define GYRO_ZOUT_L		0x48 //陀螺仪Z轴测量值低八位
#define PWR_MGMT_1		0x6B //电源管理 典型值:0x00(正常启用)

//设计一个全局的设备对象
struct mpu6050{
	int dmajor; //主设备号
	struct device *dev; 
	struct class *cls;  //类
	struct i2c_client *client;//记录probe中client
};
struct mpu6050 *mpu6050_dev;

//把数据写入i2c从设备  <--> i2c_client 存储i2c从设备
int mpu6050_write_reg_bytes(struct i2c_client *client, char *buf, int count)
{
	int ret;
	//应用层的数据 --> linux内核 --> soc芯片/i2c主设备 --> i2c从设备
	struct i2c_adapter *adapter = client->adapter;  //获取i2c主设备
	
	struct i2c_msg msg[2]; //封装的数据包
	msg[0].addr = client->addr;  //从设备地址
	msg[0].flags = 0;    /* write data, from slave to master */
	msg[0].len = count;  //长度
	msg[0].buf = buf;    //被写入的寄存器地址
	
	msg[1].addr = client->addr;  //从设备地址
	msg[1].flags = 0;    /* write data, from slave to master */
	msg[1].len = count;  //长度
	msg[1].buf = buf+1;    //被写入的寄存器地址的数据
	
	//数据由soc芯片/i2c主设备 --> i2c从设备
	ret = i2c_transfer(adapter, &msg,  2); 
	return ret==1?count:ret;
}

//读取i2c从设备的特定寄存器的地址,然后返回值
int mpu6050_read_reg_byte(struct i2c_client *client, char reg)
{
		// 先写寄存器的地址, 然后在读寄存器的值
		int ret;
		struct i2c_adapter *adapter = client->adapter;
		struct i2c_msg msg[2]; 		//封装的数据包;两个i2c_msg数据

		char rxbuf[1];
		
		//先发地址
		msg[0].addr = client->addr;  //i2c从设备地址
		msg[0].flags = 0; /* write data, from slave to master */
		msg[0].len = 1;
		msg[0].buf = &reg; //发送寄存器地址

		//再读数据
		msg[1].addr = client->addr;
		msg[1].flags = 1; /* read data, from slave to master */
		msg[1].len = 1;
		msg[1].buf = rxbuf;  //存放读到的数据
		
		ret = i2c_transfer(adapter, msg,  2);  //发送2个i2c_msg数据吧
		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_close(struct inode *inode, struct file *filp)
{
	return 0;
}

long mpu6050_drv_ioctl (struct file *filp, unsigned int cmd, unsigned long args)
{
	union mpu6050_data data;  //union对象
	switch(cmd){
		case IOC_GET_ACCEL:  //获取加速度数据并封装在union对象
			data.accel.x = mpu6050_read_reg_byte(mpu6050_dev->client, ACCEL_XOUT_L);  //读取ACCEL_XOUT_L寄存器的值
			data.accel.x |= mpu6050_read_reg_byte(mpu6050_dev->client, ACCEL_XOUT_H) << 8;  //读取ACCEL_XOUT_H寄存器的值

			data.accel.y = mpu6050_read_reg_byte(mpu6050_dev->client, ACCEL_YOUT_L);//读取ACCEL_YOUT_L寄存器的值
			data.accel.y |= mpu6050_read_reg_byte(mpu6050_dev->client, ACCEL_YOUT_H) << 8;//读取ACCEL_YOUT_H寄存器的值

			data.accel.z = mpu6050_read_reg_byte(mpu6050_dev->client, ACCEL_ZOUT_L);//读取ACCEL_ZOUT_L寄存器的值
			data.accel.z |= mpu6050_read_reg_byte(mpu6050_dev->client, ACCEL_ZOUT_H) << 8;//读取ACCEL_ZOUT_H寄存器的值
			break;
		case IOC_GET_GYRO:  //获取陀螺仪角度数据并封装union对象
			data.gyro.x = mpu6050_read_reg_byte(mpu6050_dev->client, GYRO_XOUT_L);
			data.gyro.x |= mpu6050_read_reg_byte(mpu6050_dev->client, GYRO_XOUT_H) << 8;

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

			data.gyro.z= mpu6050_read_reg_byte(mpu6050_dev->client, GYRO_ZOUT_L);
			data.gyro.z |= mpu6050_read_reg_byte(mpu6050_dev->client, GYRO_ZOUT_H) << 8;
			break;
		case IOC_GET_TEMP:  //获取温度数据并封装在union对象
			data.temp = mpu6050_read_reg_byte(mpu6050_dev->client, TEMP_OUT_L);
			data.temp |= mpu6050_read_reg_byte(mpu6050_dev->client, TEMP_OUT_H) << 8;
			break;
		default:
			printk("invalid cmd\n");
			return -EINVAL;
	}

	//将所有的数据封装在union对象中 --> 交给用户
	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_close,
	.unlocked_ioctl = mpu6050_drv_ioctl,
};


int mpu6050_drv_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	printk("-----%s----\n", __FUNCTION__);
	//为全局设备对象分配堆栈内存
	mpu6050_dev = kzalloc(sizeof(struct mpu6050), GFP_KERNEL);
	
	mpu6050_dev->client = client;
	//自动分配主设备号
	mpu6050_dev->major = register_chrdev(0,"mpu_drv", &mpu6050_fops);
	//创建类"mpu_cls"
	mpu6050_dev->cls = class_create(THIS_MODULE, "mpu_cls");
	//在mpu_cls类中创建mpu6050设备节点
	mpu6050_dev->dev = device_create(mpu6050_dev->cls, NULL, MKDEV(mpu6050_dev->major, 0),
				NULL, "mpu6050");
	
	/**************************配置功能寄存器****************************/
	char buf1[2] = {PWR_MGMT_1, 0x0};
	mpu6050_write_reg_bytes(mpu6050_dev->client, buf1, 2);

	char buf2[2] = {SMPLRT_DIV, 0x07};
	mpu6050_write_reg_bytes(mpu6050_dev->client, buf2, 2);

	char buf3[2] = {CONFIG, 0x06};
	mpu6050_write_reg_bytes(mpu6050_dev->client, buf3, 2);

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

	char buf5[2] = {ACCEL_CONFIG, 0x01};
	mpu6050_write_reg_bytes(mpu6050_dev->client, buf5, 2);
	return 0;
}


int mpu5060_drv_remove(struct i2c_client *client)
{
	printk("-----%s----\n", __FUNCTION__);
	//设备节点销毁
	device_destroy(mpu6050_dev->cls, MKDEV(mpu6050_dev->major, 0));
	//类销毁
	class_destroy(mpu6050_dev->cls);
	//设备注销
	unregister_chrdev(mpu6050_dev->major, "mpu_drv");
	//释放全局对象
	kfree(mpu6050_dev);
	return 0;
}


const struct of_device_id  of_mpu6050_id[] = {
		{
			.compatible = "invensense,mpu6050",  //与设备树中compatible一致
		},
};
	
struct i2c_driver mpu6050_drv = { //i2c_driver对象
	.probe = mpu6050_drv_probe,
	.remove = mpu5060_drv_remove,
	.driver = {
		.name = "mpu6050_drv",//随便写,/sys/bus/i2c/driver/mpu6050_drv
		.of_match_table = of_match_ptr(of_mpu6050_id),  //用于匹配i2c_client
	},	
};


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);  //注销i2c_driver对象
}
module_init(mpu6050_drv_init);
module_exit(mpu6050_drv_exit);
MODULE_LICENSE("GPL");

mpu6050.h


#define IOC_GET_ACCEL 0 //获取加速度 --> 指令0
#define IOC_GET_GYRO  1 //获取陀螺仪角度 --> 指令1
#define IOC_GET_TEMP  2 //获取温度 --> 指令2
#ifndef __MPU6050_H__
#define __MPU6050_H__

union mpu6050_data{  //union对象
	struct{	
		short x;
		short y;
		short z;
	}accel;

	struct{	
		short x;
		short y;
		short z;
	}gyro;

	short temp;
};


#endif

mpu6050_text.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "mpu6050.h"     //<----------自定义
int main(int argc, char *argv[])
{
	int fd;
	union mpu6050_data data;  //union对象
	
	fd = open("/dev/mpu_sensor", O_RDWR);
	if(fd < 0)
	{
		perror("open");
		exit(1);
	}

	while(1)
	{
		ioctl(fd, IOC_GET_ACCEL, &data);   //ioctl能发送指令来获取特定类型的数据,这就是不用read的原因
		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;
}

Makefile文件

obj-m += $(MODULE_NAME).o

ROOTFS_DIR = /opt/4412/rootfs
MODULE_NAME = mpu6050_i2c_drv
APP_NAME = mpu6050_test
CROSS_COMPILE = /home/george/Linux_4412/toolchain/gcc-4.6.4/bin/arm-none-linux-gnueabi-
CC = $(CROSS_COMPILE)gcc
KERNEL_DIR = /home/george/Linux_4412/kernel/linux-3.14
CUR_DIR = $(shell pwd)

all :
	make -C  $(KERNEL_DIR) M=$(CUR_DIR) modules
	$(CC) $(APP_NAME).c  -o $(APP_NAME)

install:
	cp -raf *.ko $(APP_NAME)   $(ROOTFS_DIR)/drv_module
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值