文章目錄
-
- 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 側面和必須編譯進核心:
在裝置樹中添加從裝置資訊,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 程式的編寫
- 添加 i2c client 的資訊,必須在控制器對應節點中
-
直接編寫 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 = ®
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;
}