开发板:Nvidia Jetson Nano b01
模块:MPU6050
I2C设备
接线
vcc — 3.3v(17)
gnd — GND(25)
scl — I2C0_SCL(28) -> pJ1
sda — I2C0_SDA(27)
//AD0:从机地址设置引脚 :1.接地或悬空时, 地址为0x68;2.接VCC时,地址为0x69
//INT: 中断输出引脚 (暂未用)
设备树移植
- 我的目录在 ~/kernel-4.9/hardware/nvidia/platform/t210/porg/kernel-dts/tegra210-p3448-0000-p3449-0000-b00.dts
- kernel为软连接,kernel-dts已经在kernel-4.9下创建软连接
- kernel-dts -> /home/yhai/kernel-4.9/hardware/nvidia/platform/t210/porg/kernel-dts
//tegra210-p3448-0000-p3449-0000-b00.dts
i2c@7000c000 { //i2c设备mp6050,要挂载到的i2c-0控制器,对应 I2C0_SCL(28),同名节点内容会叠加mpu6050-3-axi@68 {compatible = "invensense,mpu6050";reg = <0x68>; //从机地址来源于MPU6050手册PS-MPU-6000A-00v3.4.pdf i2c slave address };
};
驱动程序
//mpu6050.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/delay.h>#include <asm/uaccess.h>#include "mpu6050.h"MODULE_LICENSE("GPL");#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 MPU6050_MAJOR 500
#define MPU6050_MINOR 5struct mpu6050_device { //定义mpu6050设备结构体,便于描述struct cdev cdev; //继承于字符设备,因为要使用ioctl,open等字符设备中的文件操作struct i2c_client *client;//继承于i2c设备
};
struct mpu6050_device *mpu6050; /*这里的代码经过测试有问题
static int mpu6050_read_byte(struct i2c_client *client, unsigned char reg)
{int ret;char txbuf[1] = { reg };char rxbuf[1];struct i2c_msg msg[2] = { //设定 I2C消息格式{client->addr, 0, 1, txbuf}, //0 指定i2c设备里的寄存器地址 txbuf 存放其地址{client->addr, I2C_M_RD, 1, rxbuf} //I2C_M_RD 表示读i2c设备里该寄存器的数据};ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); //传输I2C消息if (ret < 0) {printk("ret = %d\n", ret);return ret;}return rxbuf[0];
}
*///后面发现是mpu6050_write_byte函数有问题,应改为
static int mpu6050_write_byte(struct i2c_client *client, uint8_t reg_addr, uint8_t data)
{int ret = 0;uint8_t w_buf[2];struct i2c_msg msg;w_buf[0] = reg_addr;w_buf[1] = data;msg.addr = client->addr;msg.buf = w_buf;msg.flags = 0; // I2C direction write msg.len = sizeof(w_buf);ret = i2c_transfer(client->adapter, &msg, 1);if (ret < 0){printk("i2c transfer err\n");return ret;}else if (ret != 1){printk("i2c transfer err -EIO\n");return -EIO;}//printk("i2c transfer ok\n");return 0;
}static int mpu6050_write_byte(struct i2c_client *client, unsigned char reg, unsigned char val)
{char txbuf[2] = {reg, val};struct i2c_msg msg[2] = {{client->addr, 0, 2, txbuf},};printk("i2c write!\n");i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));printk("i2c write 2!\n");return 0;
}static int mpu6050_open(struct inode *inode, struct file *file)
{return 0;
}static int mpu6050_release(struct inode *inode, struct file *file)
{return 0;
}static long mpu6050_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{union mpu6050_data data;struct i2c_client *client = mpu6050->client;switch(cmd) {case GET_ACCEL:data.accel.x = mpu6050_read_byte(client, ACCEL_XOUT_L);data.accel.x |= mpu6050_read_byte(client, ACCEL_XOUT_H) << 8;data.accel.y = mpu6050_read_byte(client, ACCEL_YOUT_L);data.accel.y |= mpu6050_read_byte(client, ACCEL_YOUT_H) << 8;data.accel.z = mpu6050_read_byte(client, ACCEL_ZOUT_L);data.accel.z |= mpu6050_read_byte(client, ACCEL_ZOUT_H) << 8;break;case GET_GYRO:data.gyro.x = mpu6050_read_byte(client, GYRO_XOUT_L);data.gyro.x |= mpu6050_read_byte(client, GYRO_XOUT_H) << 8;data.gyro.y = mpu6050_read_byte(client, GYRO_YOUT_L);data.gyro.y |= mpu6050_read_byte(client, GYRO_YOUT_H) << 8;data.gyro.z = mpu6050_read_byte(client, GYRO_ZOUT_L);data.gyro.z |= mpu6050_read_byte(client, GYRO_ZOUT_H) << 8;break;case GET_TEMP: data.temp = mpu6050_read_byte(client, TEMP_OUT_L);data.temp |= mpu6050_read_byte(client, TEMP_OUT_H) << 8;break;default:printk("invalid argument\n");return -EINVAL;}if (copy_to_user((void *)arg, &data, sizeof(data)))return -EFAULT;return sizeof(data);
}struct file_operations mpu6050_fops = {.owner = THIS_MODULE,.open = mpu6050_open,.release = mpu6050_release,.unlocked_ioctl = mpu6050_ioctl,
};static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)//probe//在probe中使得mpu6050指向具体实体
{int ret;dev_t devno = MKDEV(MPU6050_MAJOR, MPU6050_MINOR);//合并设备号printk("match OK!\n");mpu6050 = kzalloc(sizeof(*mpu6050), GFP_KERNEL);//分配康健if (mpu6050 == NULL) {return -ENOMEM;}mpu6050->client = client;ret = register_chrdev_region(devno, 1, "mpu6050");if (ret < 0) {printk("failed to register char device region!\n");goto err1;}cdev_init(&mpu6050->cdev, &mpu6050_fops);mpu6050->cdev.owner = THIS_MODULE;ret = cdev_add(&mpu6050->cdev, devno, 1);if (ret < 0) {printk("failed to add device\n");goto err2;}mpu6050_write_byte(client, PWR_MGMT_1, 0x00);mpu6050_write_byte(client, SMPLRT_DIV, 0x07);mpu6050_write_byte(client, CONFIG, 0x06);mpu6050_write_byte(client, GYRO_CONFIG, 0xF8);mpu6050_write_byte(client, ACCEL_CONFIG, 0x19);return 0;
err2:unregister_chrdev_region(devno, 1);
err1:kfree(mpu6050);return ret;
}static int mpu6050_remove(struct i2c_client *client)
{dev_t devno = MKDEV(MPU6050_MAJOR, MPU6050_MINOR);cdev_del(&mpu6050->cdev);unregister_chrdev_region(devno, 1);kfree(mpu6050);return 0;
}static const struct i2c_device_id mpu6050_id[] = {{ "mpu6050", 0},{}
}; static struct of_device_id mpu6050_dt_match[] = {{.compatible = "invensense,mpu6050" }, //id和设备树一致{/*northing to be done*/},
};struct i2c_driver mpu6050_driver = {.driver = {.name = "mpu6050",.owner = THIS_MODULE,.of_match_table = of_match_ptr(mpu6050_dt_match),},.probe = mpu6050_probe,//insmod设备后触发probe,进入probe.remove = mpu6050_remove,.id_table = mpu6050_id,
};module_i2c_driver(mpu6050_driver);
应用程序
//test_mpu6050.c#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>#include "mpu6050.h"int main(int argc, const char *argv[])
{int fd;union mpu6050_data data; fd = open("/dev/mpu6050", O_RDWR);if (fd < 0) {perror("open");exit(1);}while(1) {//三轴加速度ioctl(fd, GET_ACCEL, &data);printf("1 acceleration data: x = %04x, y = %04x, z = %04x\n", data.accel.x, data.accel.y, data.accel.z);//三轴陀螺仪(三轴角速度)ioctl(fd, GET_GYRO, &data);printf("2 gyroscope data: x = %04x, y = %04x, z = %04x\n", data.accel.x, data.accel.y, data.accel.z);//温度值 ioctl(fd, GET_TEMP, &data);printf("3 temp data: = %x \n", data.temp);sleep(1);}close(fd);return 0;
}
头文件
//mpu6050.h
#ifndef MPU6050_HHHH
#define MPU6050_HHHH#define MPU6050_MAGIC 'K'union mpu6050_data
{struct {unsigned short x;unsigned short y;unsigned short z;}accel; //三轴加速度struct {unsigned short x;unsigned short y;unsigned short z;}gyro; //三轴陀螺仪(三轴角速度)unsigned short temp; //温度值
};#define GET_ACCEL _IOR(MPU6050_MAGIC, 0, union mpu6050_data)
#define GET_GYRO _IOR(MPU6050_MAGIC, 1, union mpu6050_data)
#define GET_TEMP _IOR(MPU6050_MAGIC, 2, union mpu6050_data)#endif
Makefile
//Makefile
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= ~/kernel-4.9 #这里为内核目录
PWD := $(shell pwd)all:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesclean:rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module* modules* a.out elseobj-m := mpu6050.o
endif
验证测试-linux虚拟机端
$ cd ~/kernel-4.9 #修改完设备树文件后进入内核目录
$ make dtbs #编译设备树
$ cp arch/arm64/boot/dts/tegra210-p3448-0000-p3449-0000-b00.dtb /tftpboot/ #拷贝设备树文件到tftpboot
$ cd mpu6050 #进到mpu6050文件夹下,文件夹下应该由驱动程序、头文件、应用程序、Makefile
$ make #编译驱动程序,生成ko模块文件
$ cp mpu6050.ko /nfs/rootfs/_install #拷贝ko模块到nfs目录下,此处为我的目录
$ aarch64-linux-gnu-gcc test_mpu6050.c #编译应用文件,注意是交叉编译应用文件
$ cp a.out /nfs/rootfs #编译完成生成的.out文件拷贝到nfs目录下,传到板子上
验证测试-板子端
insmod mpu6050.ko #加载设备
mknod /dev/mpu6050 c 500 5 #创建设备 内核自带的模块只需要配置载入就可以,不用创建设备
./a.out #执行应用程序
rmmod mpu6050.ko #卸载设备,若要重新加载必须先卸载
#能成功读到下面值,摇晃mpu6050 值会相应变化
出现问题(需要注意细节)的总结
1、cp kernel-4.9/arch/arm64/boot/dts/tegra210-p3448-0000-p3449-0000-b00.dtb /tftpboot/
dtbs为设备树源码,dtb为make之后的二进制文件;在kernel-4.9目录下make dtbs之后,拷贝该文件夹下面生成的dtb设备树文 件到tftp,我的猜测就是make dtbs之后生成的dtb就是在这个arch/arm64/boot/dts/指定文件夹下面。
2、aarch64-linux-gnu-gcc test_mpu6050.c
交叉编译的是测试文件(应用文件),生成a.out文件
3、板子上电自动tftp下载的uboot设置
# setenv bootargs root=/dev/nfs rw nfsroot=192.168.9.119:/nfs/rootfs/_install,v3 console=ttyS0,115200 init=/linuxrc ip=192.168.9.9
# setenv nfsboot pci enum \; pci \; ext4load mmc 1:1 0x84000000 /home/bjbd/Image \; tftp 83100000 tegra210-p3448-0000-p3449-0000-b00.dtb \; booti 0x84000000 - 83100000
# saveenv //保存环境变量
# run nfsboot //手动运行# setenv bootcmd run nfsboot //测试成功后,可设为自启动命令,避免每次都手动运行
# saveenv