总结一下编程步骤。
我们只需要编写drv的代码即调用层的代码。
再调用层做的事情就是,把drv注册到iIC bus里面,然后实现按照iic的总线通信协议,进行数据交互。
具体步骤。
1)编写设备树文件,根据芯片手册,找到我们的硬件连接在第几组iic总线,以及地址,中断等信息。
2)实例化i2c_driver结构体并把它注册到drv总线里面去。
注册成功后,匹配完成就会调用i2c_driver结构体的probe方法
我们需要在probe方法中:
3)申请设备号,以及实现file_operation
4)创建设备节点
这样我们一个设备就创建好了。
然后我们还需要通过i2c的接口去初始化i2c从设备
数据的发送的接收依赖于iic_transfor这个接口。按照ii2总线的通信协议步骤去实现数据的发送和接收。
iic _transform的原理就是——在probe方法中拿到iic_client,然后通过iic_client找到iic_adepter,再把自己里面存储数据的iic_msg的信息结构体,给到iic_adepter。这样iic_adepter就可以根据提供的信息,利用自己结构体里面的一个算法alogitim以及控制iic通信的master_xop这个函数,来实现通信。
5)所以我们还要实现iic_msg,并把iic_msg给到iic _transform
6)把初始化的值给到寄存器。
7)读取寄存器的值。
如果利用read的文件接口我们就不能指定读什么了,只能写死的数据。比如我用个一个FIFO来接受数据,那数据的类型,只能是我怎么写的它就怎么读,但是,然后我是mpu6050我只想读加速度,或者都想都,这就不能同时实现
所以我们引入了 ioctl 这个文件操作。和read类似,但是可以根据设定的值来确定想读的数据。
自此我们要编写的驱动模块完成
———————————————————————代码—————————————————————————
#include <linux/init.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/i2c.h>
#include <asm/io.h>
#include <asm/uaccess.h>
/************************mpu6050寄存器************************************/
#define SMPLRT_DIV 0x19 //采样频率寄存器-25 典型值:0x07(125Hz)
//寄存器集合里的数据根据采样频率更新
#define CONFIG 0x1A //配置寄存器-26-典型值:0x06(5Hz)
//DLPF is disabled(DLPF_CFG=0 or 7)
#define GYRO_CONFIG 0x1B//陀螺仪配置-27,可以配置自检和满量程范围
//典型值:x180(不自检,2000deg/s)
#define ACCEL_CONFIG 0x1C //加速度配置-28 可以配置自检和满量程范围及高通滤波频率
//典型值:0x01(不自检,2G,5Hz)
#define ACCEL_XOUT_H 0x3B //59-65,加速度计测量值 XOUT_H
#define ACCEL_XOUT_L 0x3C // XOUT_L
#define ACCEL_YOUT_H 0x3D //YOUT_H
#define ACCEL_YOUT_L 0x3E //YOUT_L
#define ACCEL_ZOUT_H 0x3F //ZOUT_H
#define ACCEL_ZOUT_L 0x40 //ZOUT_L---64
#define TEMP_OUT_H 0x41 //温度测量值--65
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43 //陀螺仪值--67,采样频率(由寄存器 25 定义)写入到这些寄存器
#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 //陀螺仪值--72
#define PWR_MGMT_1 0x6B //电源管理 典型值:0x00(正常启用)
/************************************************************************/
//数据共用体
union mpu6050_data{
struct{
short x;
short y;
short z;
}accel;
struct{
short x;
short y;
short z;
}gyro;
short temp;
};
#define IOC_GET_ACCEL _IOR('M', 0x01,union mpu6050_data) //系统接口定义命令1
#define IOC_GET_GYRO _IOR('M', 0x02,union mpu6050_data) //参数1 魔幻数 参数二 命令值 参数三数据类型
#define IOC_GET_TEMP _IOR('M', 0x03,union mpu6050_data)
//设备信息结构体
struct mpu_sensor {
unsigned int major;
struct class *cls;
struct device *dev;
struct i2c_client *client;
};
struct mpu_sensor * mpu_dev;
//我们自己写一个和i2c_master_send一样的函数
int mpu_write_bytes (const struct i2c_client * client, const char * buf, int count){
// printk("---------%s---------\n",__FUNCTION__);
int ret;
//i2c_adapter * adap
struct i2c_adapter *adapter = client->adapter; //client是由adapter创建,它里面也有一个adapter指针
// struct i2c_msg * msgs
struct i2c_msg msg;
msg.addr = client->addr;
msg.flags = 0; //写标志为 1为写 0 为读
msg.len = count; //数据大小
msg.buf = buf;
//这个函数就是要实现这个接口去发送数据。
ret = i2c_transfer(adapter, &msg, 1); //发送一个数据包
//返回值:如果失败返回负数,如果成功返回发送的数据个数
return ret ==1?count:ret;
}
int mpu_read_bytes (const struct i2c_client * client, const char * buf, int count){
int ret;
struct i2c_adapter *adapter = client->adapter;
struct i2c_msg msg;
msg.addr = client->addr;
msg.flags = I2C_M_RD; //读写标志为 1为写 0 为读
msg.len = count; //数据大小
msg.buf = buf;
ret = i2c_transfer(adapter, &msg, 1); //发送一个数据包
return ret ==1?count:ret;
}
//读取某个寄存器的值,时序是先把把地址写进行,然后在读
int mpu_read_reg_bytes (const struct i2c_client * client, char reg){
int ret;
struct i2c_adapter *adapter = client->adapter;
struct i2c_msg msg[2];
char rbuf[1];
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].len = 1;
msg[0].buf = ®
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = 1;
msg[1].buf = rbuf; //把寄存器里面的值存储到rxbuf里面
ret = i2c_transfer(adapter, msg, 2);
if(ret < 0)
{
printk("i2c_transfer read error\n");
return ret;
}
//返回的是读到的值
return rbuf[0];
}
int mpu_fops_open (struct inode *inode, struct file *filp){
return 0;
}
int mpu_fops_release (struct inode *inode, struct file *filp){
return 0;
}
long mpu_fops_ioctl (struct file *filp, unsigned int cmd, unsigned long args){
union mpu6050_data data;
switch(cmd){
case IOC_GET_ACCEL:
data.accel.x = mpu_read_reg_bytes(mpu_dev->client,ACCEL_XOUT_L);
data.accel.x |= mpu_read_reg_bytes(mpu_dev->client,ACCEL_XOUT_H) << 8;
data.accel.y = mpu_read_reg_bytes(mpu_dev->client,ACCEL_YOUT_L);
data.accel.y |= mpu_read_reg_bytes(mpu_dev->client,ACCEL_YOUT_H) << 8;
data.accel.z = mpu_read_reg_bytes(mpu_dev->client,ACCEL_ZOUT_L);
data.accel.z |= mpu_read_reg_bytes(mpu_dev->client,ACCEL_ZOUT_H) << 8;
break;
case IOC_GET_GYRO:
data.gyro.x = mpu_read_reg_bytes(mpu_dev->client,GYRO_XOUT_L);
data.gyro.x |= mpu_read_reg_bytes(mpu_dev->client,GYRO_XOUT_H) << 8;
data.gyro.y = mpu_read_reg_bytes(mpu_dev->client,GYRO_YOUT_L);
data.gyro.y |= mpu_read_reg_bytes(mpu_dev->client,GYRO_YOUT_H) << 8;
data.gyro.z = mpu_read_reg_bytes(mpu_dev->client,GYRO_ZOUT_L);
data.gyro.z |= mpu_read_reg_bytes(mpu_dev->client,GYRO_ZOUT_H) << 8;
break;
case IOC_GET_TEMP:
data.temp = mpu_read_reg_bytes(mpu_dev->client,TEMP_OUT_L);
data.temp |= mpu_read_reg_bytes(mpu_dev->client,TEMP_OUT_H) << 8;
break;
default:
printk("invalid cmd\n");
return -EFAULT;
}
if(copy_to_user((void __user * )args, &data, sizeof(data)) > 0)
return -EFAULT;
return 0;
}
const struct file_operations mpu_fops = {
.open = mpu_fops_open,
.release = mpu_fops_release,
.unlocked_ioctl = mpu_fops_ioctl,
};
int mpu_drv_probe(struct i2c_client *client , const struct i2c_device_id *id){
// printk("---------%s---------\n",__FUNCTION__);
/*匹配完成了就会调用这个proble方法。
2,实现probe:
|
申请设备号,实现fops
创建设备文件
通过i2c的接口去初始化i2c从设备 */
//记录,和drv匹配的client 由系统传参传过来
//1.申请设备号,实现fops 我们需要一个设备信息结构体
mpu_dev = kzalloc(sizeof(struct mpu_sensor), GFP_KERNEL);
if(mpu_dev == NULL){
printk("kzalloc error\n");
return -ENOMEM;
}
mpu_dev->major = register_chrdev(0, "mpu_drv", &mpu_fops); //proc/device
if(mpu_dev->major < 0){
printk("register_chrdev\n");
goto err0;
}
//记录,和drv匹配的client 由系统传参传过来
mpu_dev->client = client;
//创建设备文件
mpu_dev->cls = class_create(THIS_MODULE, "mpu_cls");
// printk("----class_create---\n");
if(mpu_dev->cls == NULL){
printk("class_create\n");
goto err1;
}
mpu_dev->dev = device_create(mpu_dev->cls,NULL,MKDEV(mpu_dev->major,0),NULL, "MPU6050");
if(mpu_dev->dev == NULL){
printk("device_creat\n");
goto err2;
}
// printk("----device_create---\n");
//初始化通过i2c的接口去初始化i2c从设备。根据iic的通信协议去写
//我们的目的是读三轴角速度和加速度的值,我们要先写mpu6050的一些设定标准的寄存器的值
//i2c_master_send(const struct i2c_client * client, const char * buf, int count)
//这个是系统自带的接口,把buf送到client里面
/* char buf[4][1]= {{PWR_MGMT_1,0x00},{SMPLRT_DIV ,0x07 },{CONFIG,0x06},
{GYRO_CONFIG,0x180},{ACCEL_CONFIG,0x01}};
mpu_write_bytes (mpu_dev->client, buf[0], 2);
mpu_write_bytes (mpu_dev->client, buf[1], 2);
mpu_write_bytes (mpu_dev->client, buf[2], 2);
mpu_write_bytes (mpu_dev->client, buf[3], 2);
mpu_write_bytes (mpu_dev->client, buf[4], 2);
*/
char buf1[2] = {PWR_MGMT_1, 0x0};
mpu_write_bytes(mpu_dev->client, buf1, 2);
char buf2[2] = {SMPLRT_DIV, 0x07};
mpu_write_bytes(mpu_dev->client, buf2, 2);
char buf3[2] = {CONFIG, 0x06};
mpu_write_bytes(mpu_dev->client, buf3, 2);
char buf4[2] ={GYRO_CONFIG, 0x18};
mpu_write_bytes(mpu_dev->client, buf4, 2);
char buf5[2] = {ACCEL_CONFIG, 0x01};
mpu_write_bytes(mpu_dev->client, buf5, 2);
return 0;
err2:
class_destroy(mpu_dev->cls);
err1:
unregister_chrdev(mpu_dev->major, "mpu_6050");
err0:
kfree(mpu_dev);
return -EFAULT;
}
int mpu_drv_remove(struct i2c_client *client){
device_destroy(mpu_dev->cls, MKDEV(mpu_dev->major, 0));
unregister_chrdev(mpu_dev->major, "mpu_6050");
class_destroy(mpu_dev->cls);
kfree(mpu_dev);
}
const struct of_device_id of_mpu6050_id[] ={
//信息要和client里面的信息一样,即和设备树的信息一样
{.compatible = "invensense,mpu6050",},
{/*northing to be done*/},
};
const struct i2c_device_id mpu_id_table[] ={
{"mpu6050_drv", 0x1111},
{/*northing to be done*/},
};
struct i2c_driver mpu6050_drv = {
.probe = mpu_drv_probe,
.remove = mpu_drv_remove,
.driver = {
.name = "mpu6050_drv", //这个自定义 ,会在/sys/bus/i2c/driver显示
.of_match_table = of_match_ptr(of_mpu6050_id), //用于与client里面的信息比对进行匹配
},
.id_table = mpu_id_table, //非设备树环境下用这个匹配,但是我这里用了设备树,所以不填也可以
};
static int __init mpu6050_drv_init(void){
printk("---------%s---------\n",__FUNCTION__);
//把设备直接注册到i2c bus中去,然后就会调用probe方法
return i2c_add_driver(&mpu6050_drv); //sys/bus/i2c/drives
}
static void __exit mpu6050_drv_exit(void){
i2c_del_driver(&mpu6050_drv);
}
module_init(mpu6050_drv_init);
module_exit(mpu6050_drv_exit);
MODULE_LICENSE("GPL");
————————————————————应用程序读取数据——————————————————————
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "mpu6050.h"
int main(int argc, char *argv[]){
int fd;
union mpu6050_data data;
fd = open("/dev/MPU6050",O_RDWR);
if(fd < 0){
perror("open");
exit(0);
}
while(1){
ioctl(fd, IOC_GET_ACCEL,&data);
printf("a: x=%2d,y=%2d,z=%2d\n",data.accel.x,data.accel.y,data.accel.z);
ioctl(fd, IOC_GET_GYRO,&data);
printf("w: x=%2d,y=%2d,z=%2d\n",data.gyro.x,data.gyro.y,data.gyro.y);
ioctl(fd, IOC_GET_TEMP,&data);
printf("T=%2d F\n",data.temp);
sleep(1);
}
close(fd);
}