嵌入式内核及驱动开发-09IIC子系统框架使用(I2C协议和时序,I2C驱动框架,I2C从设备驱动开发,MPU6050硬件连接,MPU6050数据读取,MPU6050从设备驱动编写)

文章目录

  • I2c协议和时序
    • I2c介绍
    • I2c硬件连接
    • I2c总线的信号
    • I2c总线写时序
    • I2c总线读时序
    • I2c驱动框架
    • I2C子系统软件框架
    • 常用的对象
      • 设备驱动对象
      • i2c_client—挂在I2C总线上的I2C从设备
      • adapter i2c 控制器对象
      • 数据包对象
      • 读写数据
  • 设备树中添加MPU6050信息
    • 内核选配
    • 添加设备树节点
  • 编写驱动程序 mpu6050_i2c_drv.c
    • mpu6050_i2c_drv.c
    • mpu6050.h
    • mpu6050_test.c
    • 终端显示
  • 参考

I2c协议和时序

I2c介绍

I2C(Inter-integrated Circuit)总线支持设备之间的短距离通信,用于处理器和一些外围设备之间的接口,它只需要两根信号线来完成信息交换。I2C最早是飞利浦在1982年开发设计并用于自己的芯片上,一开始只允许100kHz、7-bit标准地址。1992年,I2C的第一个公共规范发行,增加了400kHz的快速模式以及10-bit扩展地址。在I2C的基础上,1995年Intel提出了“System Management Bus” (SMBus),用于低速设备通信,SMBus 把时钟频率限制在10kHz100kHz,但I2C可以支持0kHz5MHz的设备:普通模式(100kHz即100kbps)、快速模式(400kHz)、快速模式+(1MHz)、高速模式(3.4MHz)和超高速模式(5MHz)。

I2c硬件连接

嵌入式内核及驱动开发-09IIC子系统框架使用(I2C协议和时序,I2C驱动框架,I2C从设备驱动开发,MPU6050硬件连接,MPU6050数据读取,MPU6050从设备驱动编写)_第1张图片

  • sda 数据线
  • scl 时钟线

I2c总线的信号

嵌入式内核及驱动开发-09IIC子系统框架使用(I2C协议和时序,I2C驱动框架,I2C从设备驱动开发,MPU6050硬件连接,MPU6050数据读取,MPU6050从设备驱动编写)_第2张图片

起始信号:scl为高电平 sda从高到低跳变
停止信号:scl为高电平 sda从低到高跳变
应答信号:从机将sda设置为低电平,主机读取数据

I2c总线写时序

嵌入式内核及驱动开发-09IIC子系统框架使用(I2C协议和时序,I2C驱动框架,I2C从设备驱动开发,MPU6050硬件连接,MPU6050数据读取,MPU6050从设备驱动编写)_第3张图片

start+7位从机地址 写+ack+8寄存器地址+ack+data+ack+stop

I2c总线读时序

嵌入式内核及驱动开发-09IIC子系统框架使用(I2C协议和时序,I2C驱动框架,I2C从设备驱动开发,MPU6050硬件连接,MPU6050数据读取,MPU6050从设备驱动编写)_第4张图片

start+7位从机地址 写+ack+8寄存器地址+start+7位从机地址 读+ack+data+NO ack+stop

I2c驱动框架

1 2
嵌入式内核及驱动开发-09IIC子系统框架使用(I2C协议和时序,I2C驱动框架,I2C从设备驱动开发,MPU6050硬件连接,MPU6050数据读取,MPU6050从设备驱动编写)_第5张图片 嵌入式内核及驱动开发-09IIC子系统框架使用(I2C协议和时序,I2C驱动框架,I2C从设备驱动开发,MPU6050硬件连接,MPU6050数据读取,MPU6050从设备驱动编写)_第6张图片

I2C子系统软件框架

    app:  open   read  write  close
----------------------------------------------
	kernel |设备驱动:驱动工程师实现的
		   |根据用户的读写需求(字符设备驱动),完成数据的封装和数据的发送
		   |封装好的数据会发给控制驱动,最终操作硬件
           |--------------------------------------
           |核心层(i2c-core.c):由内核工程师实现的
           |完成设备驱动和总线驱动匹配的过程,提供两者的注册注销的方法
           |---------------------------------------
		   |控制器驱动(总线驱动):由控制器厂商实现的
		   |完成控制器的初始化,以及提供读写的时序 i2c-s3c2410.c 
	----------------------------------------------
    	hardware
  • I2C核心(i2c_core)

I2C核心维护了i2c_bus结构体,提供了I2C总线驱动和设备驱动的注册、注销方法,维护了I2C总线的驱动、设备链表,实现了设备、驱动的匹配探测。此部分代码由Linux内核提供。

  • I2C总线驱动
    I2C总线驱动维护了I2C适配器数据结构(i2c_adapter)和适配器的通信方法数据结构(i2c_algorithm)。所以I2C总线驱动可控制I2C适配器产生start、stop、ACK等。此部分代码由具体的芯片厂商提供,比如Samsung、高通。

  • I2C设备驱动
    I2C设备驱动主要维护两个结构体:i2c_driver和i2c_client,实现和用户交互的文件操作集合fops、cdev等。此部分代码就是驱动开发者需要完成的
    嵌入式内核及驱动开发-09IIC子系统框架使用(I2C协议和时序,I2C驱动框架,I2C从设备驱动开发,MPU6050硬件连接,MPU6050数据读取,MPU6050从设备驱动编写)_第7张图片

常用的对象

设备驱动对象

用于管理I2C的驱动程序和i2c设备(client)的匹配探测,实现与应用层交互的文件操作集合fops、cdev等。

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_id	*of_match_table;
		const struct i2c_device_id *id_table;//用于做比对,非设备树的情况
	}

注册和注销

int i2c_add_driver( struct i2c_driver *driver);
void i2c_del_driver(struct i2c_driver *);

i2c_client—挂在I2C总线上的I2C从设备

每一个i2c从设备都需要用一个i2c_client结构体来描述,i2c_client对应真实的i2c物理设备device。

struct i2c_client {//描述一个从设备的信息,不需要在代码中创建,因为是由i2c adapter帮我们创建
    unsigned short addr;        //从设备地址,来自于设备树中
    char name[I2C_NAME_SIZE]; //用于和i2c driver进行匹配,来自于设备树中compatible
    struct i2c_adapter *adapter;//指向当前从设备所存在的i2c adapter
    struct device dev;        // 继承了父类
};

创建i2c client的函数

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)

adapter i2c 控制器对象

I2C总线适配器,即soc中的I2C总线控制器,硬件上每一对I2C总线都对应一个适配器来控制它。在Linux内核代码中,每一个adapter提供了一个描述它的结构(struct
i2c_adapter),再通过i2c core层将i2c设备与i2c
adapter关联起来。主要用来完成i2c总线控制器相关的数据通信,此结构体在芯片厂商提供的代码中维护。

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; //编号
}

注册和注销:

int i2c_add_adapter(struct i2c_adapter * adapter);
void i2c_del_adapter(struct i2c_adapter * adap);

数据包对象

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)
以上两个函数都调用了:
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

设备树中添加MPU6050信息

内核选配

确保i2c core和i2c adatper层必须编译进内核

make menuconfig
	Device Drivers  --->
		-*- I2C support  ---> //编译i2c-core.c
			I2C Hardware Bus support  --->
				<*> S3C2410 I2C Driver // i2c-s3c2410.c

嵌入式内核及驱动开发-09IIC子系统框架使用(I2C协议和时序,I2C驱动框架,I2C从设备驱动开发,MPU6050硬件连接,MPU6050数据读取,MPU6050从设备驱动编写)_第8张图片

[root@farsight i2c-0]# cd /sys/bus/i2c/devices/i2c-0/ 
[root@farsight i2c-0]# cat name
s3c2410-i2c

添加设备树节点

硬件原理图
嵌入式内核及驱动开发-09IIC子系统框架使用(I2C协议和时序,I2C驱动框架,I2C从设备驱动开发,MPU6050硬件连接,MPU6050数据读取,MPU6050从设备驱动编写)_第9张图片
根据结合芯片手册,芯片原理图,开发板与原理图的查询,将i2c控制器和从设备的硬件描述添加到设备树节点中(node-value)。

(1)控制器对应的设备树模板信息:arch/arm/boot/dts/exynos4.dtsi。参考模板,将描述从设备信息的设备树节点添加到文件 arch/arm/boot/dts/exynos4412-origen.dts

    i2c@138B0000 {/*i2c adapter5信息*/
            #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信息*/
                    compatible = "invensense,mpu6050";
                    reg = <0x68>;
			};
  };

(2) 编译设备树,cp到tftp根文件目录
在这里插入图片描述
(3)运行开发板 ,添加设备树节点成功
嵌入式内核及驱动开发-09IIC子系统框架使用(I2C协议和时序,I2C驱动框架,I2C从设备驱动开发,MPU6050硬件连接,MPU6050数据读取,MPU6050从设备驱动编写)_第10张图片

编写驱动程序 mpu6050_i2c_drv.c

  1. 构建i2c driver,并注册到i2c总线
  2. 实现probe方法和remove方法。
    申请设备号,实现fops
    创建设备文件
    通过i2c的接口去初始化i2c从设备
  3. 实现fpos接口

最终实现读取从设备(mpu6050)的数据,实际上就是对模块的寄存器操作。由于mpu6050模块支持iic总线,主控制器通过读写iic的方式对mpu6050模块进行读写操作。

首先驱动创建并注册从设备驱动对象到核心层链表,通过设备树节点的compatible属性匹配到设备对象client,然后可以调用i2c_transfer接口对从设备进行读写操作,这里先进行初始化mpu6050模块。然后对上(应用层)提供接口ioctl,让应用程序可以通过ioctl获取mpu6050的三轴角速度和加速度的原始数据。

mpu6050_i2c_drv.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 

#include "mpu6050.h"


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

MPU_SENSOR_T *mpu_dev;



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;

}

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 = I2C_M_RD;
	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)
{
	printk("-----%s------------\n", __FUNCTION__);
	return 0;
}
int mpu6050_drv_close(struct inode *inode, struct file *filp)
{
	printk("-----%s------------\n", __FUNCTION__);
	return 0;
}
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_H) << 8) | (mpu6050_read_reg_byte(mpu_dev->client, ACCEL_XOUT_L));
			data.accel.y = (mpu6050_read_reg_byte(mpu_dev->client, ACCEL_YOUT_H) << 8) | (mpu6050_read_reg_byte(mpu_dev->client, ACCEL_YOUT_L));
			data.accel.z = (mpu6050_read_reg_byte(mpu_dev->client, ACCEL_ZOUT_H) << 8) | (mpu6050_read_reg_byte(mpu_dev->client, ACCEL_ZOUT_L));
			break;
		case IOC_GET_GYRO:
		
			data.gyro.x = (mpu6050_read_reg_byte(mpu_dev->client, GYRO_XOUT_H) << 8) | (mpu6050_read_reg_byte(mpu_dev->client, GYRO_XOUT_L));
			data.gyro.y = (mpu6050_read_reg_byte(mpu_dev->client, GYRO_YOUT_H) << 8) |
 (mpu6050_read_reg_byte(mpu_dev->client, GYRO_YOUT_L));
			data.gyro.z = (mpu6050_read_reg_byte(mpu_dev->client, GYRO_ZOUT_H) << 8) | (mpu6050_read_reg_byte(mpu_dev->client, GYRO_ZOUT_L));
			break;
		case IOC_GET_TEMP:
			data.temp = (mpu6050_read_reg_byte(mpu_dev->client, TEMP_OUT_H) << 8) | ( mpu6050_read_reg_byte(mpu_dev->client, TEMP_OUT_L));
			break;
		default:
			printk("invalid cmd\n");
			return -EINVAL;
	}

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


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)
{

	/*
		申请设备号,实现fops
			创建设备文件
			通过i2c的接口去初始化i2c从设备
	*/

	char buf[2] = {0x0, 0x0};
	int ret;
	printk("-----%s------------\n", __FUNCTION__);

	mpu_dev = kzalloc(sizeof(MPU_SENSOR_T), GFP_KERNEL);	
	if(NULL == mpu_dev)
	{
		printk(KERN_ERR "malloc error\n");		
		ret = -ENOMEM;
		goto malloc_err;
	}

	if(client != NULL)
		mpu_dev->client = client;

	mpu_dev->dev_major = register_chrdev(0,"mpu_drv", &mpu6050_fops);
	if(mpu_dev->dev_major < 0)
	{
		printk(KERN_ERR "register_chrdev error\n");
		ret = -ENOMEM;
		goto reg_err;
	}
	else
	{
		printk("register_chrdev ok\n");
	}

	mpu_dev->cls = class_create(THIS_MODULE, "mpu_cls");
	if(IS_ERR(mpu_dev->cls))
	{
		printk(KERN_ERR "class_create error\n");
		ret = PTR_ERR(mpu_dev->cls);
		goto class_err;
	}

	mpu_dev->dev = device_create(mpu_dev->cls, NULL, MKDEV(mpu_dev->dev_major, 0),
				NULL, "mpu_sensor");
	if(IS_ERR(mpu_dev->dev))
	{
		printk(KERN_ERR "device_create error\n");
		ret = PTR_ERR(mpu_dev->dev);
		goto device_err;
	}

	buf[0] = PWR_MGMT_1;
	buf[1] = 0x0;
	mpu6050_write_bytes(mpu_dev->client, buf, 2);

	buf[0] = SMPLRT_DIV;
	buf[1] = 0x07;
	mpu6050_write_bytes(mpu_dev->client, buf, 2);

	buf[0] = CONFIG;
	buf[1] = 0x06;
	mpu6050_write_bytes(mpu_dev->client, buf, 2);

	buf[0] = GYRO_CONFIG;
	buf[1] = 0x18;
	mpu6050_write_bytes(mpu_dev->client, buf, 2);

	buf[0] = ACCEL_CONFIG;
	buf[1] = 0x01;
	mpu6050_write_bytes(mpu_dev->client, buf, 2);
	
	return 0;

device_err:
	class_destroy(mpu_dev->cls);	
class_err:
	unregister_chrdev(mpu_dev->dev_major, "mpu_drv");		
reg_err:
	kfree(mpu_dev);	
malloc_err:
	
	return ret;
	
}

int mpu5060_drv_remove(struct i2c_client *client)
{
	printk("-----%s------------\n", __FUNCTION__);
	device_destroy(mpu_dev->cls, MKDEV(mpu_dev->dev_major, 0));
	class_destroy(mpu_dev->cls);
	unregister_chrdev(mpu_dev->dev_major, "mpu_drv");
	kfree(mpu_dev);
}

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

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

struct i2c_driver mpu6050_drv = {
	.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),
	},
	
	.id_table = mpu_id_table,//非设备树情况下的匹配,在设备树的模式下不需要使用
};


static int __init mpu6050_drv_init(void)
{
	
	printk("-----%s------------\n", __FUNCTION__);

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

	return 0;
}

static void __exit mpu6050_drv_exit(void)
{
	printk("-----%s------------\n", __FUNCTION__);
	i2c_del_driver(&mpu6050_drv);
}


module_init(mpu6050_drv_init);
module_exit(mpu6050_drv_exit);


MODULE_LICENSE("GPL");

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,union mpu6050_data)
#define IOC_GET_GYRO  _IOR('M', 0x35,union mpu6050_data)
#define IOC_GET_TEMP  _IOR('M', 0x36,union mpu6050_data)


#define SMPLRT_DIV		0x19 //采样频率寄存器-25 典型值:0x07(125Hz)
									//寄存器集合里的数据根据采样频率更新
#define CONFIG			0x1A	//配置寄存器-26-典型值:0x06(5Hz)
										//DLPF is disabled(DLPF_CFG=0 or 7)
#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  //YOUT_H
#define ACCEL_YOUT_L	0x3E  //YOUT_L
#define ACCEL_ZOUT_H	0x3F  //ZOUT_H
#define ACCEL_ZOUT_L	0x40 //ZOUT_L---64
#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 //陀螺仪值--72
#define PWR_MGMT_1		0x6B //电源管理 典型值:0x00(正常启用)


#endif

mpu6050_test.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#include "mpu6050.h"


int main(int argc, char *argv[])
{
	int fd;
	
	union mpu6050_data data;
	
	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;

}

终端显示

嵌入式内核及驱动开发-09IIC子系统框架使用(I2C协议和时序,I2C驱动框架,I2C从设备驱动开发,MPU6050硬件连接,MPU6050数据读取,MPU6050从设备驱动编写)_第11张图片

参考

https://blog.csdn.net/feit2417/article/details/84564391

你可能感兴趣的:(驱动开发)