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