闲来无事想写写一个驱动教学,想到之前做过stm32,esp32平台的,就想在linux平台也做一个教学与记录。
首先说明,芯片采用i.mx6ull,mpu就是用的图片这一款。
进入快乐的驱动时间。
来看设备树
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
mag3110@0e {
compatible = "fsl,mag3110";
reg = <0x0e>;
position = <2>;
};
ap3216c@1e {
compatible = "ap3216c";
reg = <0x1e>;
};
mpu6050@68 {
compatible = "jiaojiao,mpu6050";
status = "okay";
reg = <0x68>;
};
};
在i2c1节点下,增加一个mpu6050节点,查阅数据手册可知,地址0x68
已经有了mpu节点
然后写驱动代码,先把寄存器列出来
mpu6050.h
#ifndef MPU6050_H
#define MPU6050_H
/* register define */
#define MPU6050_SMPLRT_DIV 0x19 /* */
#define MPU6050_CONFIG 0x1A
#define MPU6050_GYRO_CONFIG 0x1B
#define MPU6050_ACCEL_CONFIG 0x1C
#define MPU6050_ACCEL_XOUT_H 0x3B
#define MPU6050_ACCEL_XOUT_L 0x3C
#define MPU6050_ACCEL_YOUT_H 0x3D
#define MPU6050_ACCEL_YOUT_L 0x3E
#define MPU6050_ACCEL_ZOUT_H 0x3F
#define MPU6050_ACCEL_ZOUT_L 0x40
#define MPU6050_TEMP_OUT_H 0x41
#define MPU6050_TEMP_OUT_L 0x42
#define MPU6050_GYRO_XOUT_H 0x43
#define MPU6050_GYRO_XOUT_L 0x44
#define MPU6050_GYRO_YOUT_H 0x45
#define MPU6050_GYRO_YOUT_L 0x46
#define MPU6050_GYRO_ZOUT_H 0x47
#define MPU6050_GYRO_ZOUT_L 0x48
#define MPU6050_PWR_MGMT_1 0x6B /* power managemnt register */
#define MPU6050_PWR_MGMT_2 0X6C
#define MPU6050_WHO_AM_I 0x75 /* default value : IIC addr 0x68 */
#define MPU_FIFO_EN_REG 0X23
/* interrupt status register */
#define MPU6050_INT_STATUS 0x3A
#define MPU6050_INT_ENABLE 0x38
#define MPU6050_INT_PIN_CFG 0x37
#endif
然后是写一个设备结构体,除去基本的东西,用a[3],g[3],tem三个short表示其传感器数据
struct mpu6050_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int major; /* 主设备号 */
void *private_data; /* 私有数据 */
short a[3], g[3], tem; /* 三个传感器数据 */
u8 name;
};
然后写一下初始化代码
static int mpu6050_open(struct inode *inode, struct file *filp)
{
u8 value;
filp->private_data = &mpu6050dev;
/* 初始化mpu6050 */
mpu6050_write_reg(&mpu6050dev,MPU6050_PWR_MGMT_1, 0x80);
mdelay(100);
mpu6050_write_reg(&mpu6050dev,MPU6050_PWR_MGMT_1, 0x00);
mpu6050_write_reg(&mpu6050dev,MPU6050_SMPLRT_DIV, 0x07);
mpu6050_write_reg(&mpu6050dev,MPU6050_CONFIG, 0x06);
mpu6050_write_reg(&mpu6050dev,MPU6050_ACCEL_CONFIG, 0x08);//+-4g
mpu6050_write_reg(&mpu6050dev,MPU6050_GYRO_CONFIG, 0x08);//+-500/s
mpu6050_write_reg(&mpu6050dev,MPU_FIFO_EN_REG, 0x00);
mpu6050_write_reg(&mpu6050dev,MPU6050_INT_ENABLE, 0x00);
mpu6050_write_reg(&mpu6050dev,MPU6050_PWR_MGMT_1, 0x01);
mpu6050_write_reg(&mpu6050dev,MPU6050_PWR_MGMT_2, 0x00);
value = mpu6050_read_reg(&mpu6050dev, MPU6050_WHO_AM_I);
printk("mpu6050 ID = %#X\r\n", value);
return 0;
}
量程设置的是4g和500°
读数据也很简单,mpu几个传感器地址连在一起的,直接读取14个字节就行
void mpu6050_readdata(struct mpu6050_dev *dev)
{
unsigned char buf[14],name[1];
mpu6050_read_regs(dev,MPU6050_ACCEL_XOUT_H, buf,14);
dev->a[0] = (short)(buf[0]<<8 | buf[1]);
dev->a[1] = (short)(buf[2]<<8 | buf[3]);
dev->a[2] = (short)(buf[4]<<8 | buf[5]);
dev->tem = (short)(buf[6]<<8 | buf[7]);
dev->g[0] = (short)(buf[8]<<8 | buf[9]);
dev->g[1] = (short)(buf[10]<<8 | buf[11]);
dev->g[2] = (short)(buf[12]<<8 | buf[13]);
mpu6050_read_regs(dev,MPU6050_WHO_AM_I, name,1);
dev->name = name[0];
}
贴一下驱动完整代码
mpu6050.c
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "mpu6050.h"
#define MPU6050_CNT 1
#define MPU6050_NAME "mpu6050"
struct mpu6050_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int major; /* 主设备号 */
void *private_data; /* 私有数据 */
short a[3], g[3], tem; /* 三个传感器数据 */
u8 name;
};
static struct mpu6050_dev mpu6050dev;
/*
* @description : 从mpu6050读取多个寄存器数据
* @param - dev: mpu6050设备
* @param - reg: 要读取的寄存器首地址
* @param - val: 读取到的数据
* @param - len: 要读取的数据长度
* @return : 操作结果
*/
static int mpu6050_read_regs(struct mpu6050_dev *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->private_data;
/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr; /* mpu6050地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg长度*/
/* msg[1]读取数据 */
msg[1].addr = client->addr; /* mpu6050地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据*/
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度*/
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
}
else {
printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
/*
* @description : 向mpu6050多个寄存器写入数据
* @param - dev: mpu6050设备
* @param - reg: 要写入的寄存器首地址
* @param - val: 要写入的数据缓冲区
* @param - len: 要写入的数据长度
* @return : 操作结果
*/
static s32 mpu6050_write_regs(struct mpu6050_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->private_data;
b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */
msg.addr = client->addr; /* mpu6050地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 1; /* 要写入的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
/*
* @description : 读取mpu6050指定寄存器值,读取一个寄存器
* @param - dev: mpu6050设备
* @param - reg: 要读取的寄存器
* @return : 读取到的寄存器值
*/
static unsigned char mpu6050_read_reg(struct mpu6050_dev *dev, u8 reg)
{
u8 data = 0;
mpu6050_read_regs(dev, reg, &data, 1);
return data;
#if 0
struct i2c_client *client = (struct i2c_client *)dev->private_data;
return i2c_smbus_read_byte_data(client, reg);
#endif
}
/*
* @description : 向mpu6050指定寄存器写入指定的值,写一个寄存器
* @param - dev: mpu6050设备
* @param - reg: 要写的寄存器
* @param - data: 要写入的值
* @return : 无
*/
static void mpu6050_write_reg(struct mpu6050_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
mpu6050_write_regs(dev, reg, &buf, 1);
}
/*
* @description : 读取mpu6050的数据,读取原始数据, 注意!
* @return : 无。
*/
void mpu6050_readdata(struct mpu6050_dev *dev)
{
unsigned char buf[14],name[1];
mpu6050_read_regs(dev,MPU6050_ACCEL_XOUT_H, buf,14);
dev->a[0] = (short)(buf[0]<<8 | buf[1]);
dev->a[1] = (short)(buf[2]<<8 | buf[3]);
dev->a[2] = (short)(buf[4]<<8 | buf[5]);
dev->tem = (short)(buf[6]<<8 | buf[7]);
dev->g[0] = (short)(buf[8]<<8 | buf[9]);
dev->g[1] = (short)(buf[10]<<8 | buf[11]);
dev->g[2] = (short)(buf[12]<<8 | buf[13]);
mpu6050_read_regs(dev,MPU6050_WHO_AM_I, name,1);
dev->name = name[0];
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int mpu6050_open(struct inode *inode, struct file *filp)
{
u8 value;
filp->private_data = &mpu6050dev;
/* 初始化mpu6050 */
mpu6050_write_reg(&mpu6050dev,MPU6050_PWR_MGMT_1, 0x80);
mdelay(100);
mpu6050_write_reg(&mpu6050dev,MPU6050_PWR_MGMT_1, 0x00);
mpu6050_write_reg(&mpu6050dev,MPU6050_SMPLRT_DIV, 0x07);
mpu6050_write_reg(&mpu6050dev,MPU6050_CONFIG, 0x06);
mpu6050_write_reg(&mpu6050dev,MPU6050_ACCEL_CONFIG, 0x08);//+-4g
mpu6050_write_reg(&mpu6050dev,MPU6050_GYRO_CONFIG, 0x08);//+-500/s
mpu6050_write_reg(&mpu6050dev,MPU_FIFO_EN_REG, 0x00);
mpu6050_write_reg(&mpu6050dev,MPU6050_INT_ENABLE, 0x00);
mpu6050_write_reg(&mpu6050dev,MPU6050_PWR_MGMT_1, 0x01);
mpu6050_write_reg(&mpu6050dev,MPU6050_PWR_MGMT_2, 0x00);
value = mpu6050_read_reg(&mpu6050dev, MPU6050_WHO_AM_I);
printk("mpu6050 ID = %#X\r\n", value);
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t mpu6050_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
short data[7];
long err = 0;
u8 name[1];
struct mpu6050_dev *dev = (struct mpu6050_dev *)filp->private_data;
mpu6050_readdata(dev);
data[0] = dev->a[0];
data[1] = dev->a[1];
data[2] = dev->a[2];
data[3] = dev->tem;
data[4] = dev->g[0];
data[5] = dev->g[1];
data[6] = dev->g[2];
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int mpu6050_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* mpu6050操作函数 */
static const struct file_operations mpu6050_ops = {
.owner = THIS_MODULE,
.open = mpu6050_open,
.read = mpu6050_read,
.release = mpu6050_release,
};
/*
* @description : i2c驱动的probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - client : i2c设备
* @param - id : i2c设备ID
* @return : 0,成功;其他负值,失败
*/
static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
/* 1、构建设备号 */
if (mpu6050dev.major) {
mpu6050dev.devid = MKDEV(mpu6050dev.major, 0);
register_chrdev_region(mpu6050dev.devid, MPU6050_CNT, MPU6050_NAME);
} else {
alloc_chrdev_region(&mpu6050dev.devid, 0, MPU6050_CNT, MPU6050_NAME);
mpu6050dev.major = MAJOR(mpu6050dev.devid);
}
/* 2、注册设备 */
cdev_init(&mpu6050dev.cdev, &mpu6050_ops);
cdev_add(&mpu6050dev.cdev, mpu6050dev.devid, MPU6050_CNT);
/* 3、创建类 */
mpu6050dev.class = class_create(THIS_MODULE, MPU6050_NAME);
if (IS_ERR(mpu6050dev.class)) {
return PTR_ERR(mpu6050dev.class);
}
/* 4、创建设备 */
mpu6050dev.device = device_create(mpu6050dev.class, NULL, mpu6050dev.devid, NULL, MPU6050_NAME);
if (IS_ERR(mpu6050dev.device)) {
return PTR_ERR(mpu6050dev.device);
}
mpu6050dev.private_data = client;
return 0;
}
/*
* @description : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
* @param - client : i2c设备
* @return : 0,成功;其他负值,失败
*/
static int mpu6050_remove(struct i2c_client *client)
{
/* 删除设备 */
cdev_del(&mpu6050dev.cdev);
unregister_chrdev_region(mpu6050dev.devid, MPU6050_CNT);
/* 注销掉类和设备 */
device_destroy(mpu6050dev.class, mpu6050dev.devid);
class_destroy(mpu6050dev.class);
return 0;
}
/* 传统匹配方式ID列表 */
static const struct i2c_device_id mpu6050_id[] = {
{"jiaojiao,mpu6050", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id mpu6050_of_match[] = {
{ .compatible = "jiaojiao,mpu6050" },
{ /* Sentinel */ }
};
/* i2c驱动结构体 */
static struct i2c_driver mpu6050_driver = {
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.driver = {
.owner = THIS_MODULE,
.name = "mpu6050",
.of_match_table = mpu6050_of_match,
},
.id_table = mpu6050_id,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init mpu6050_init(void)
{
int ret = 0;
u8 value;
ret = i2c_add_driver(&mpu6050_driver);
return ret;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit mpu6050_exit(void)
{
i2c_del_driver(&mpu6050_driver);
}
module_init(mpu6050_init);
module_exit(mpu6050_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jiaojiao");
然后写一个测试app
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#define m 0.0002663
#define n 0.0001211
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd;
char *filename;
short databuf[7];
short a[3], g[3], tem;
unsigned char name,bu[1];
float temperature;
int ret = 0;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0) {
printf("can't open file %s\r\n", filename);
return -1;
}
while (1) {
ret = read(fd, databuf, sizeof(databuf));
if(ret == 0) {
a[0] = databuf[0];
a[1] = databuf[1];
a[2] = databuf[2];
tem = databuf[3];
temperature = 36.53+((int)tem)/340;
g[0] = databuf[4];
g[1] = databuf[5];
g[2] = databuf[6];
printf("ax=%4fg ay=%4fg az=%4fg\n",(float)(a[0]*n),(float)(a[1]*n),(float)(a[2]*n));
printf("gx=%4fr/s gy=%4fr/s gz=%4fr/s\n",(float)(g[0]*m),(float)(g[1]*m),(float)(g[2]*m));
printf("temprature=%f\n",temperature);
}
usleep(200000); /*100ms */
}
close(fd); /* 关闭文件 */
return 0;
}
对于原始数据处理的问题上,用加速度计打个比方,量程是+-4g,对应寄存器是16位的,有正负的数据,所以读取到的是-32767~32767,对应过后就可以得出实际的加速度大小,用原始数据x4除以32767就行。
来看看效果
可以看到还是很准的,平放时,z轴方向正好有一个1g大小左右的重力加速度