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 = ® //发送寄存器地址 //再读数据 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