RK3568笔记四十三:MPU6050驱动开发(硬件I2C_3)

avatar
作者
筋斗云
阅读量:0

若该文为原创文章,转载请注明原文出处。

正点原子提供的I2C有测试ap3216c,SH3001等传感器,根据手册操作可以实现效果。

这里记录使用I2C3驱动MPU6050.

记录原因是前面有模拟I2C,但硬件如何使用,有点不是很清楚,所以使用MPU6050测试,验证自己所想。

一、I2C介绍

i2c支持一主多从,各设备地址独立,标准模式传输速率为100kbit/s,快速模式为400kbit/s。总线通过上拉电阻接到电源。 当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。

I2C物理总线使用两条总线线路,SCL和SDA。

  • SCL: 时钟线,数据收发同步

  • SDA: 数据线,传输具体数据

二、MPU6050数据读写

1、写操作

对MPU6050进行写操作时,主设备发出开始标志(S)和写地址(地址位加一个R/W位,0为写)。 MPU6050产生应答信号。然后主设备开始传送寄存器地址(RA),接到应答后,开始传送寄存器数据, 然后仍然要有应答信号,连续写入多字节时依次类推。

2、读操作

对MPU6050进行读操作时,主设备发出开始标志(S)和读地址(地址位加一个R/W位,1为读)。 等待MPU6050产生应答信号。然后发送寄存器地址,告诉MPU6050读哪一个寄存器。 紧接着,收到应答信号后,主设备再发一个开始信号,然后发送从设备读地址。 MPU6050产生应答信号并开始发送寄存器数据。通信以主设备产生的拒绝应答信号(NACK)和结束标志(P)结束。

三、驱动实验编程思路

1、分析硬件原理图

2、修改设备树

3、编写 mpu6050 驱动程序

4、编写简单测试应用程序

和以前的基本类似。

四、硬件介绍

RK3568有 6 个片上 I2C 控制器,各个 I2C 的使用情况如下表:

根据ATK-DLRK3568原理图:

使用I2C3即GPIO1_A0和GPIO1_A1两个引脚

接线如下:

MPU6050 引脚引脚
VCC3.3V 电源
GDNGND
SCLI2C3_SCL_M0  GPIO1_A1
SDAI2C3_SDA_M0  GPIO1_A0

MPU 芯片手册我们可以知道,MPU6050 的 slave 地址为 b110100X,七位字长,最低有效位 X 由 AD0 管脚上的逻辑电平决定。AD0 接地,则地址为 b1101000,也就是0x68,另外,中断引脚“int”没有使用。这里AD0悬空,所以默认地址也是0x68.

五、设备树

1、设备树节点

修改/home/alientek/rk3568_linux_sdk/kernel/arch/arm64/boot/dts/rockchip/目录下的rk3568-atk-evb1-ddr4-v10.dtsi文件,找到I2C3,添加下面代码:

    pinctrl-names = "default";         pinctrl-0 = <&i2c3m0_xfer>;     //使用GPIO0_A1和GPIO0_A0复用为I2C3引脚 	    mpu6050@68 {  		compatible = "yifeng,i2c_mpu6050";  		reg = <0x68>; 		status = "okay";  	}; 

打开i2c3节点,i2c3m0 作为 i2c3 的引脚,设置 MPU6050 子节点属性为”yifeng,i2c_mpu6050”,和驱动保持一致即可。这里的0x68是MPU6050的器件地址

2、创建设备的 pinctrl 节点

前面GPIO需要修改/home/alientek/rk3568_linux_sdk/kernel/arch/arm64/boot/dts/rockchip/目录下的rk3568-pinctrl.dtsi文件,但文件已经写好了,所以不用修改

设备树修改完成以后使用“/build.sh kernel”重新编译一下,然后重新烧写boot.img 启动Linux内核。

在/sys/bus/i2c/devices 目录下存放着所有 I2C 设备,可以查到注册的节点。

六、驱动编写

由于 rockchip 官方已经写好了 i2c 的总线驱动,mpu6050 这个设备驱动就变得很简单。

1、i2c_mpu6050.c

#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/uaccess.h> #include <linux/i2c.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_gpio.h> #include <asm/io.h> #include <linux/device.h>  #include <linux/platform_device.h>  #include "i2c_mpu6050.h"  /*------------------字符设备内容----------------------*/ #define DEV_NAME "I2C3_mpu6050" #define DEV_CNT (1)  /*定义 led 资源结构体,保存获取得到的节点信息以及转换后的虚拟寄存器地址*/ static dev_t mpu6050_devno;				 //定义字符设备的设备号 static struct cdev mpu6050_chr_dev;		 //定义字符设备结构体chr_dev struct class *class_mpu6050;			 //保存创建的类 struct device *device_mpu6050;			 // 保存创建的设备 struct device_node *mpu6050_device_node; //rgb_led的设备树节点结构体  /*------------------IIC设备内容----------------------*/ struct i2c_client *mpu6050_client = NULL; //保存mpu6050设备对应的i2c_client结构体,匹配成功后由.prob函数带回。  /*通过i2c 向mpu6050写入数据 *mpu6050_client:mpu6050的i2c_client结构体。 *address, 数据要写入的地址, *data, 要写入的数据 *返回值,错误,-1。成功,0   */ static int i2c_write_mpu6050(struct i2c_client *mpu6050_client, u8 address, u8 data) { 	int error = 0; 	u8 write_data[2]; 	struct i2c_msg send_msg; //要发送的数据结构体  	/*设置要发送的数据*/ 	write_data[0] = address; 	write_data[1] = data;  	/*发送 iic要写入的地址 reg*/ 	send_msg.addr = mpu6050_client->addr; //mpu6050在 iic 总线上的地址 	send_msg.flags = 0;					  //标记为发送数据 	send_msg.buf = write_data;			  //写入的首地址 	send_msg.len = 2;					  //reg长度  	/*执行发送*/ 	error = i2c_transfer(mpu6050_client->adapter, &send_msg, 1); 	if (error != 1) 	{ 		printk(KERN_DEBUG "\n i2c_transfer error \n"); 		return -1; 	} 	return 0; }  /*通过i2c 向mpu6050写入数据 *mpu6050_client:mpu6050的i2c_client结构体。 *address, 要读取的地址, *data,保存读取得到的数据 *length,读长度 *返回值,错误,-1。成功,0 */ static int i2c_read_mpu6050(struct i2c_client *mpu6050_client, u8 address, void *data, u32 length) { 	int error = 0; 	u8 address_data = address; 	struct i2c_msg mpu6050_msg[2]; 	/*设置读取位置msg*/ 	mpu6050_msg[0].addr = mpu6050_client->addr; //mpu6050在 iic 总线上的地址 	mpu6050_msg[0].flags = 0;					//标记为发送数据 	mpu6050_msg[0].buf = &address_data;			//写入的首地址 	mpu6050_msg[0].len = 1;						//写入长度  	/*设置读取位置msg*/ 	mpu6050_msg[1].addr = mpu6050_client->addr; //mpu6050在 iic 总线上的地址 	mpu6050_msg[1].flags = I2C_M_RD;			//标记为读取数据 	mpu6050_msg[1].buf = data;					//读取得到的数据保存位置 	mpu6050_msg[1].len = length;				//读取长度  	error = i2c_transfer(mpu6050_client->adapter, mpu6050_msg, 2);  	if (error != 2) 	{ 		printk(KERN_DEBUG "\n i2c_read_mpu6050 error \n"); 		return -1; 	} 	return 0; }  /*初始化i2c *返回值,成功,返回0。失败,返回 -1 */ static int mpu6050_init(void) { 	int error = 0; 	/*配置mpu6050*/ 	error += i2c_write_mpu6050(mpu6050_client, PWR_MGMT_1, 0X00); 	error += i2c_write_mpu6050(mpu6050_client, SMPLRT_DIV, 0X07); 	error += i2c_write_mpu6050(mpu6050_client, CONFIG, 0X06); 	error += i2c_write_mpu6050(mpu6050_client, ACCEL_CONFIG, 0X01);  	if (error < 0) 	{ 		/*初始化错误*/ 		printk(KERN_DEBUG "\n mpu6050_init error \n"); 		return -1; 	} 	return 0; }  /*字符设备操作函数集,open函数实现*/ static int mpu6050_open(struct inode *inode, struct file *filp) { 	// printk("\n mpu6050_open \n");  	/*向 mpu6050 发送配置数据,让mpu6050处于正常工作状态*/ 	mpu6050_init(); 	return 0; }  /*字符设备操作函数集,.read函数实现*/ static ssize_t mpu6050_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off) { 	char data_H; 	char data_L; 	int error; 	short mpu6050_result[6]; //保存mpu6050转换得到的原始数据  	// printk("\n mpu6050_read \n"); 	 	i2c_read_mpu6050(mpu6050_client, ACCEL_XOUT_H, &data_H, 1); 	i2c_read_mpu6050(mpu6050_client, ACCEL_XOUT_L, &data_L, 1); 	mpu6050_result[0] = data_H << 8; 	mpu6050_result[0] += data_L;  	i2c_read_mpu6050(mpu6050_client, ACCEL_YOUT_H, &data_H, 1); 	i2c_read_mpu6050(mpu6050_client, ACCEL_YOUT_L, &data_L, 1); 	mpu6050_result[1] = data_H << 8;     mpu6050_result[1] += data_L;  	i2c_read_mpu6050(mpu6050_client, ACCEL_ZOUT_H, &data_H, 1); 	i2c_read_mpu6050(mpu6050_client, ACCEL_ZOUT_L, &data_L, 1); 	mpu6050_result[2] = data_H << 8; 	mpu6050_result[2] += data_L;  	i2c_read_mpu6050(mpu6050_client, GYRO_XOUT_H, &data_H, 1); 	i2c_read_mpu6050(mpu6050_client, GYRO_XOUT_L, &data_L, 1); 	mpu6050_result[3] = data_H << 8; 	mpu6050_result[3] += data_L;  	i2c_read_mpu6050(mpu6050_client, GYRO_YOUT_H, &data_H, 1); 	i2c_read_mpu6050(mpu6050_client, GYRO_YOUT_L, &data_L, 1); 	mpu6050_result[4] = data_H << 8; 	mpu6050_result[4] += data_L;  	i2c_read_mpu6050(mpu6050_client, GYRO_ZOUT_H, &data_H, 1); 	i2c_read_mpu6050(mpu6050_client, GYRO_ZOUT_L, &data_L, 1); 	mpu6050_result[5] = data_H << 8; 	mpu6050_result[5] += data_L;   	// printk("AX=%d, AY=%d, AZ=%d \n",(int)mpu6050_result[0],(int)mpu6050_result[1],(int)mpu6050_result[2]); 	// printk("GX=%d, GY=%d, GZ=%d \n \n",(int)mpu6050_result[3],(int)mpu6050_result[4],(int)mpu6050_result[5]);  	/*将读取得到的数据拷贝到用户空间*/ 	error = copy_to_user(buf, mpu6050_result, cnt);  	if(error != 0) 	{ 		printk("copy_to_user error!"); 		return -1; 	} 	return 0; }  /*字符设备操作函数集,.release函数实现*/ static int mpu6050_release(struct inode *inode, struct file *filp) { 	// printk("\n mpu6050_release \n"); 	 	/*向mpu6050发送命令,使mpu6050进入关机状态*/ 	return 0; }  /*字符设备操作函数集*/ static struct file_operations mpu6050_chr_dev_fops = 	{ 		.owner = THIS_MODULE, 		.open = mpu6050_open, 		.read = mpu6050_read, 		.release = mpu6050_release, };  /*----------------平台驱动函数集-----------------*/ static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id) {  	int ret = -1; //保存错误状态码  	printk(KERN_EMERG "\t  match successed  \n"); 	/*---------------------注册 字符设备部分-----------------*/  	//采用动态分配的方式,获取设备编号,次设备号为0, 	//设备名称为rgb-leds,可通过命令cat  /proc/devices查看 	//DEV_CNT为1,当前只申请一个设备编号 	ret = alloc_chrdev_region(&mpu6050_devno, 0, DEV_CNT, DEV_NAME); 	if (ret < 0) 	{ 		printk("fail to alloc mpu6050_devno\n"); 		goto alloc_err; 	}  	//关联字符设备结构体cdev与文件操作结构体file_operations 	mpu6050_chr_dev.owner = THIS_MODULE; 	cdev_init(&mpu6050_chr_dev, &mpu6050_chr_dev_fops);  	// 添加设备至cdev_map散列表中 	ret = cdev_add(&mpu6050_chr_dev, mpu6050_devno, DEV_CNT); 	if (ret < 0) 	{ 		printk("fail to add cdev\n"); 		goto add_err; 	}  	/*创建类 */ 	class_mpu6050 = class_create(THIS_MODULE, DEV_NAME);  	/*创建设备 DEV_NAME 指定设备名,*/ 	device_mpu6050 = device_create(class_mpu6050, NULL, mpu6050_devno, NULL, DEV_NAME); 	mpu6050_client = client; 	return 0;  add_err: 	// 添加设备失败时,需要注销设备号 	unregister_chrdev_region(mpu6050_devno, DEV_CNT); 	printk("\n error! \n"); alloc_err:  	return -1; }   static int mpu6050_remove(struct i2c_client *client) { 	/*删除设备*/ 	device_destroy(class_mpu6050, mpu6050_devno);	  //清除设备 	class_destroy(class_mpu6050);					  //清除类 	cdev_del(&mpu6050_chr_dev);						  //清除设备号 	unregister_chrdev_region(mpu6050_devno, DEV_CNT); //取消注册字符设备 	return 0; }    /*定义ID 匹配表*/ static const struct i2c_device_id gtp_device_id[] = { 	{"yifeng,i2c_mpu6050", 0}, 	{}};  /*定义设备树匹配表*/ static const struct of_device_id mpu6050_of_match_table[] = { 	{.compatible = "yifeng,i2c_mpu6050"}, 	{/* sentinel */}};  /*定义i2c总线设备结构体*/ struct i2c_driver mpu6050_driver = { 	.probe = mpu6050_probe, 	.remove = mpu6050_remove, 	.id_table = gtp_device_id, 	.driver = { 		.name = "yifeng,i2c_mpu6050", 		.owner = THIS_MODULE, 		.of_match_table = mpu6050_of_match_table, 	}, };  /* *驱动初始化函数 */ static int __init mpu6050_driver_init(void) { 	int ret; 	pr_info("mpu6050_driver_init\n"); 	ret = i2c_add_driver(&mpu6050_driver); 	return ret; }  /* *驱动注销函数 */ static void __exit mpu6050_driver_exit(void) { 	pr_info("mpu6050_driver_exit\n"); 	i2c_del_driver(&mpu6050_driver); }  module_init(mpu6050_driver_init); module_exit(mpu6050_driver_exit);  MODULE_LICENSE("GPL"); 

2、i2c_mpu6050.h

 #ifndef I2C_MPU6050_H #define I2C_MPU6050_H   //宏定义 #define SMPLRT_DIV                                  0x19 #define CONFIG                                      0x1A #define GYRO_CONFIG                                 0x1B #define ACCEL_CONFIG                                0x1C #define ACCEL_XOUT_H                                0x3B #define ACCEL_XOUT_L                                0x3C #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 #define TEMP_OUT_L                                  0x42 #define GYRO_XOUT_H                                 0x43 #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 #define WHO_AM_I                                    0x75 #define SlaveAddress                                0xD0 #define Address                                     0x68                  //MPU6050地址 #define I2C_RETRIES                                 0x0701 #define I2C_TIMEOUT                                 0x0702 #define I2C_SLAVE                                   0x0703       //IIC从器件的地址设置 #define I2C_BUS_MODE                                0x0780   #endif

3、makefile

KERNELDIR := /home/alientek/rk3568_linux_sdk/kernel ARCH=arm64 CROSS_COMPILE=/opt/atk-dlrk356x-toolchain/usr/bin/aarch64-buildroot-linux-gnu-  export  ARCH  CROSS_COMPILE  CURRENT_PATH := $(shell pwd) obj-m := i2c_mpu6050.o  build: kernel_modules  kernel_modules: 	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules clean: 	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean 

七、应用程序编写

1、mpu6050App.c

#include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <stdlib.h> int main(int argc, char *argv[]) {      short resive_data[6];  //保存收到的 mpu6050转换结果数据,依次为 AX(x轴角度), AY, AZ 。GX(x轴加速度), GY ,GZ 	int error = 0; 	     /*打开文件*/     int fd = open("/dev/I2C3_mpu6050", O_RDWR);     if(fd < 0)     { 		printf("open file : %s failed !\n", argv[0]); 		return -1; 	}  	while(1) 	{ 		/*读取数据*/ 		error = read(fd,resive_data,12); 		if(error < 0) 		{ 			printf("write file error! \n"); 			close(fd); 			/*判断是否关闭成功*/ 		}  		/*打印数据*/ 		printf("AX=%d, AY=%d, AZ=%d ",(int)resive_data[0],(int)resive_data[1],(int)resive_data[2]); 		printf("    GX=%d, GY=%d, GZ=%d \n \n",(int)resive_data[3],(int)resive_data[4],(int)resive_data[5]); 		 		sleep(3); 	}      /*关闭文件*/     error = close(fd);     if(error < 0)     {         printf("close file error! \n");     }          return 0; }

功能比较检测,每3秒读取一次原始数据。

编译

/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc mpu6050App.c -o mpu6050App

2、测试

把i2c_mpu6050.ko文件和mpu6050App文件拷贝到开发板上测试。

加载驱动

insmod i2c_mpu6050.ko

测试

 

./mpu6050App 

这里打印的是原始数据,所以波动较大是正常的。

八、总结

I2C 适配器驱动 SOC 厂商已经替我们编写好了,我们需要做的就是编写具体的设备驱动,

先修改设备树,配置节点和引脚,然后编写驱动就可以了。

如有侵权,或需要完整代码,请及时联系博主。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!