若该文为原创文章,转载请注明原文出处。
正点原子提供的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 引脚 | 引脚 |
VCC | 3.3V 电源 |
GDN | GND |
SCL | I2C3_SCL_M0 GPIO1_A1 |
SDA | I2C3_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 厂商已经替我们编写好了,我们需要做的就是编写具体的设备驱动,
先修改设备树,配置节点和引脚,然后编写驱动就可以了。
如有侵权,或需要完整代码,请及时联系博主。