用的mini板,没有icm20608,只能写个mpu6050了,刚好视频没讲I2C的IIO。
上一篇写太长了,重新开一篇写mpu6050
1、不带iio的普通mpu6050驱动详见IIC(AP3216C驱动+MPU6050驱动),本章代码也是在也是在这篇的基础上进行修改。
2、MPU6050相关资料需要在ATK-MPU6050 --- 正点原子资料下载中心下载,主要是"4参考资料"里面的《Register Map and Descriptions.pdf》《MPU6050-英文原版数据手册.pdf》有用
3、IIO相关的结构和使用详见上一篇IIO + icm20608驱动,本章只有mpu6050代码
1、文件结构
27_IIO_MPU6050 (工作区)
├── .vscode
│ ├── c_cpp_properties.json
│ └── settings.json
├── 27_iio_mpu6050.code-workspace
├── Makefile
├── mpu6050reg.h
├── mpu6050.c
└── mpu6050APP.c
2、 Makefile
bash
CFLAGS_MODULE += -w
KERNELDIR := /home/...../linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek # 内核路径
# KERNELDIR改成自己的 linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek文件路径(这个文件从正点原子"01、例程源码"中直接搜,cp到虚拟机里面)
CURRENT_PATH := $(shell pwd) # 当前路径
obj-m := mpu6050.o # 编译文件
build: kernel_modules # 编译模块
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
3、设备树
3.1 复用引脚
先看看MPU6050需要哪些脚:

(上图来自mpu6050驱动实验 --- [野火])
在imx6ull-alientek-emmc.dts中,ic21配置如下:
cpp
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
mag3110@0e {
compatible = "fsl,mag3110";
reg = <0x0e>;
position = <2>;
};
fxls8471@1e {
compatible = "fsl,fxls8471";
reg = <0x1e>;
position = <0>;
interrupt-parent = <&gpio5>;
interrupts = <0 8>;
};
};
时钟频率、name、status都不用改,只需要看看pinctrl-0 = <&pinctrl_i2c1>是否符合要求。

由上图可得,现在需要去配置&i2c1中的pinctrl-0,将UART4_RXD/TXD复用为I2C1_SDA/SCL。
pinctrl_i2c1的内容如下:
cpp
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0 // 定义在arch/arm/boot/dts/imx6ul-pinfunc.h
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
巧了,刚好配置的就是UART4复用为I2C1,就不需要改了。
检查一下dts文件中有没有其他位置复用了UART4、I2C1,有的话注释掉。
3.2 添加设备节点
在(正点原子资料给的网盘资料中的)《MPU-6000 and MPU-6050 Product Specification.pdf》的"9.2 I2C Interface"部分可以看到MPU6050的从机地址:
The slave address of the MPU-60X0 is b110100X which is 7 bits long. The LSB bit of the 7 bit address is determined by the logic level on pin AD0. This allows two MPU-60X0s to be connected to the same I2C bus. When used in this configuration, the address of the one of the devices should be b1101000 (pin AD0 is logic low) and the address of the other should be b1101001 (pin AD0 is logic high).
翻译就是:AD0拉低,从机地址为b1101000,即0x68;AD0拉高,从机地址为b1101001,即0x69。图中可以看出,AD0接了下拉电阻,即默认从机地址为0x68。
将下面这段代码写到imx6ull-alientek-emmc.dts的&i2c1里面:
cpp
// 新增 2025/11/5 MPU6050节点
mpu6050@68 {
compatible = "alientek,mpu6050"; // 因为是自己实验,随便写一个就行了,写驱动时保持一致即可
reg = <0x68>; // 器件地址
};

3.3 测试
保存dts文件,编译、复制:
bash
cd ~/....../linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
make dtbs
sudo cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb /.../tftpboot/
现在打开开发板,看看是否正确注册了0x68:
bash
ls /sys/bus/i2c/devices/ # 应能看到0-0068
cd /sys/bus/i2c/devices/0-0068/
cat name # 应能看到mpu6050
4、寄存器头文件mpu6050reg.h
参考正点原子资料里面的《MPU-6000 and MPU-6050 Register Map and Descriptions.pdf》,第6页的Register Map可以找到所有的寄存器地址。我们需要加速度计和陀螺仪三个轴的寄存器(高低位共2*3*2=12个)、温度传感器的值(高低位共2个)、两个CONFIG。但是mpu6050似乎没有OFFSET和CALIBBIAS。



cpp
#define MPU6050_SLAVE_ADDRESS (0x68<<1) // 读地址
//加速度数据寄存器
#define ACCEL_XOUT_H 0x3B // X轴
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D // Y轴
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F // Z轴
#define ACCEL_ZOUT_L 0x40
// 温度传感器
#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42
// 计算公式: TEMP_OUT/340 + 36.53 = 摄氏度
//角速度数据寄存器
#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 // Power Management
#define CONFIG 0x1A // 控制陀螺仪、加速计的带宽,设置陀螺仪输出率(↓间接控制全芯片采样率),设置数字低通滤波
#define SMPLRT_DIV 0x19 // 全芯片的采样率 = 陀螺仪输出速率 ÷ (该寄存器的值+1)
#define ACCEL_CONFIG 0x1C // 设置加速计 量程,设备自检,数字高通滤波
#define GYRO_CONFIG 0x1B
5、驱动代码
IIO相关函数和使用详见IIO + icm20608驱动
本代码在IIC(AP3216C驱动+MPU6050驱动)的MPU6050基础上修改:
有了iio,就可以:
删掉操作集以及对应的read、write等函数
删掉probe中申请设备号、cdev、class、device等操作
删掉remove中注销设备号、cdev、class、device等操作
删掉全局声明的设备结构体变量,之后会挂载iio后面,需要时通过iio获取
增加probe和remove里面对应的iio的申请、注册、注销、释放;
增加通道iio_chan_spec相关的函数
增加iio_info及对应的read_raw、write_raw、write_raw_get_fmt函数
增加regmap操作(regmap写这篇时只改了icm20608,没改mpu6050,这里补上)
5.1 设备结构体 & probe & remove
还是先从最简单的probe & remove开始:
5.1.1 定义设备结构体:
cpp
struct mpu6050_dev_struct {
struct i2c_client *client;
struct regmap *regmap;
struct regmap_config regmap_config;
struct mutex lock;
};
5.1.2 probe函数:
申请iio和设备结构体 -> 配置regmap_config -> 初始化锁mutex -> 配置iio_dev成员 -> 最后再注册iio ->进行设备的初始化
cpp
static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id){
printk(" == probe ==\r\n");
struct mpu6050_dev_struct *dev;
struct iio_dev *indio_dev;
int ret;
// 申请iio和设备结构体
indio_dev = iio_device_alloc(sizeof(*dev));
if(!indio_dev){
ret = -ENOMEM;
pr_err("iio alloc failed!\r\n");
goto fail_iio_alloc;
}
dev = iio_priv(indio_dev);
dev->client = client;
i2c_set_clientdata(dev->client, indio_dev);
// 配置regmap_config
dev->regmap_config.reg_bits = 8; // 寄存器地址位数
dev->regmap_config.val_bits = 8; // 寄存器数据位数
dev->regmap_config.read_flag_mask = 0;
// 【新增】必须关闭缓存,否则传感器数据无法实时更新
dev->regmap_config.cache_type = REGCACHE_NONE;
dev->regmap = regmap_init_i2c(client, &dev->regmap_config);
if(IS_ERR(dev->regmap)){
ret = PTR_ERR(dev->regmap);
pr_err("regmap init failed!\r\n");
goto fail_regmap_init;
}
// 初始化锁
mutex_init(&dev->lock);
// 初始化iio & 注册
indio_dev->dev.parent = &client->dev;
indio_dev->channels = mpu6050_channels;
indio_dev->num_channels = ARRAY_SIZE(mpu6050_channels); // 7个通道
indio_dev->name = MPU6050_NAME;
indio_dev->modes = INDIO_DIRECT_MODE; // 提供sysfs接口
indio_dev->info = &mpu6050_info;
ret = iio_device_register(indio_dev);
if(ret < 0){
pr_err("iio register failed!\r\n");
goto fail_iio_register;
}
mpu6050_device_init(dev); // 设备初始化
return 0;
// iio_device_unregister(indio_dev);
fail_iio_register:
regmap_exit(dev->regmap);
mutex_destroy(&dev->lock);
fail_regmap_init:
iio_device_free(indio_dev);
fail_iio_alloc:
return ret;
}
可以看到代码中的mpu6050_channels、mpu6050_info还未定义,需要之后完成。
5.1.3 remove:
cpp
static void mpu6050_remove(struct i2c_client *client){
struct iio_dev *indio_dev = i2c_get_clientdata(client);
struct mpu6050_dev_struct *dev = iio_priv(indio_dev);
iio_device_unregister(indio_dev);
regmap_exit(client, dev->regmap_config);
mutex_destroy(&dev->lock);
iio_device_free(indio_dev);
}
5.2 MPU6050寄存器读写函数
现在使用了regmap,也把mpu6050读写函数改成regmap的:
cpp
// i2c读函数
static int mpu6050_read_regs(struct mpu6050_dev_struct *dev, u8 reg, void *buf, int len){
int ret = 0;
ret = regmap_bulk_read(dev->regmap, reg, buf, len);
if(ret)pr_err("mpu6050: i2c_read_regs 0x%x failed %d\n", reg, ret);
return ret;
}
static int mpu6050_read_one_reg(struct mpu6050_dev_struct *dev, u8 reg, u8 *data){
int ret = 0;
unsigned int val = 0;
ret = regmap_read(dev->regmap, reg, &val); // 这里不直接把data传进去,因为regmap_read会返回一个u int类型,
// 如果把data指针直接传进去,32位数据会把u8之后的24位也覆盖掉
if(ret){
pr_err("mpu6050: i2c_read_one_reg 0x%x failed %d\n", reg, ret);
return ret;
}
*data = (u8)val;
return 0;
}
// i2c写函数
static int mpu6050_write_regs(struct mpu6050_dev_struct *dev, u8 reg, u8 *buf, int len){
int ret = 0;
ret = regmap_bulk_write(dev->regmap, reg, buf, len);
if (ret < 0)pr_err("mpu6050: i2c_write_regs 0x%x failed %d\n", reg, ret);
return ret;
}
static int mpu6050_write_one_reg(struct mpu6050_dev_struct *dev, u8 reg, u8 value){
int ret = 0;
ret = regmap_write(dev->regmap, reg, value);
if (ret < 0)pr_err("mpu6050: i2c_write_one_reg 0x%x failed: %d\n", reg, ret);
return ret;
}
5.3 配置通道iio_chan_spec
这部分代码和上一篇的icm20608对应部分代码基本一致。
首先为了方便管理和以后添加,使用枚举列出所有scan_index:
cpp
// MPU6050的扫描元素 用于确定缓冲区顺序
enum inv_mpu6050_scan {
INV_MPU6050_SCAN_ACCEL_X, // 0
INV_MPU6050_SCAN_ACCEL_Y, // 1
INV_MPU6050_SCAN_ACCEL_Z, // ......
INV_MPU6050_SCAN_TEMP,
INV_MPU6050_SCAN_GYRO_X,
INV_MPU6050_SCAN_GYRO_Y,
INV_MPU6050_SCAN_GYRO_Z,
INV_MPU6050_SCAN_TIMESTAMP,
};
定义mpu6050的iio_chan_spec结构体数组:
cpp
static const struct iio_chan_spec mpu6050_channels[] = {
{
.type = IIO_TEMP,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
| BIT(IIO_CHAN_INFO_SCALE)
| BIT(IIO_CHAN_INFO_OFFSET),
.scan_index = INV_MPU6050_SCAN_TEMP,
.scan_type = {
.sign = 's', // signed有符号数(补码形式)
.realbits = 16, // 真实数据16位
.storagebits = 16, // 存储数据16位
.shift = 0, // 数据不需要移位,低位对齐
.endianness = IIO_BE,// Big Endian大端模式
},
},
MPU6050_CHAN(IIO_ACCEL,IIO_MOD_X,INV_MPU6050_SCAN_ACCEL_X),
MPU6050_CHAN(IIO_ACCEL,IIO_MOD_Y,INV_MPU6050_SCAN_ACCEL_Y),
MPU6050_CHAN(IIO_ACCEL,IIO_MOD_Z,INV_MPU6050_SCAN_ACCEL_Z),
MPU6050_CHAN(IIO_ANGL_VEL,IIO_MOD_X,INV_MPU6050_SCAN_GYRO_X),
MPU6050_CHAN(IIO_ANGL_VEL,IIO_MOD_Y,INV_MPU6050_SCAN_GYRO_Y),
MPU6050_CHAN(IIO_ANGL_VEL,IIO_MOD_Z,INV_MPU6050_SCAN_GYRO_Z),
};
因为加速度、角度的三个轴基本都是一样的。为了提高复用,使用了MPU6050_CHAN宏定义来定义这两个通道的iio_chan_spec数组元素:
cpp
#define MPU6050_CHAN(_type, _channel2, _index){ \
.modified = 1, \
.channel2 = _channel2, \
.type = _type, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) /*独立*/ \
| BIT(IIO_CHAN_INFO_CALIBBIAS), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),/*相同通道共享scale,如陀螺仪通道下的xyz共享一个scale*/ \
.scan_index = _index, \
.scan_type = { \
.sign = 's', /*signed有符号数(补码形式)*/ \
.realbits = 16, /*真实数据16位*/ \
.storagebits = 16, /*存储数据16位*/ \
.shift = 0, /*数据不需要移位,低位对齐*/ \
.endianness = IIO_BE,/*Big Endian大端模式*/ \
} \
}
5.4 IIO_info
5.4.1 分辨率数组
在正点原子资料的《MPU6050-英文原版数据手册.pdf》第6部分可以找到:




可以发现加速度计和陀螺仪的参数和icm20608一样,所以分辨率/比例尺数组可以直接拿来用。但是温度计不一样,icm20608是326.8,而mpu6050是340,需要修改。
这个数组里的数怎么算的可以详见IIO + icm20608驱动的附录B。
cpp
#define MPU6050_TEMP_OFFSET 36.53
#define MPU6050_TEMP_SCALE 340000000// 温度传感器的比例:340 LSB/°C
// 温度传感器量程只有一个,所以只有一个比例尺
// 计算公式: RAW/340 + 36.53 = 真实温度
/* mpu6050 陀螺仪分辨率,对应 250、500、1000、2000,计算方法:
* 以正负 250 度量程为例,500/2^16=0.007629,扩大 1000000 倍,就是 7629
*/
static const int gyro_scale_mpu6050[] = {7629, 15258, 30517, 61035};
/* mpu6050 加速度计分辨率,对应量程±2、±4、±8、±16。计算方法:
* 以正负 2g 量程为例,4/2^16=0.000061035,扩大 1000000000 倍,就是 61035
* 也对应1÷(16384LSB\g) = 0.000061035,也就是一个LSB值对应0.000061035 g
*/
static const int accel_scale_mpu6050[] = {61035, 122070, 244140, 488281};
5.4.2 read_raw函数
基本和icm20608一样,换个名字和寄存器地址即可。但是在《Register Map and Descriptions.pdf》里面我没有找到OFFSET和CALIBBIAS的寄存器,这里先不写case CALIBBIAS了:
cpp
// 从mpu6050读取数据。角度、加速度、温度
// reg:寄存器首地址
// axis:通道,比如xyz
// val:存储读取到的值
static int mpu6050_sensor_show(struct mpu6050_dev_struct *dev, int reg,int axis, int *val){
int ind, result;
short d;
ind = (axis - IIO_MOD_X) * 2; // ind是偏移量
// mpu6050每个寄存器8位,一个数据要16位
// 而对于角度、加速度来说,都是以X轴高8位寄存器地址为起始
// 所以当axis=IIO_MOD_X,ind=0;axis=IIO_MOD_Y,ind=2;axis=IIO_MOD_Z,ind=4;
result = regmap_bulk_read(dev->regmap, reg + ind, (u8 *)&d, 2); // 以reg+ind为首地址,连续读取2个寄存器,数据存到d
if(result)return -EINVAL;
*val = (short)be16_to_cpup(&d);// 一个宏。将d的大端格式转换成小端
return 0;
}
// 读取iio mpu6050生成的文件,就会调用这个函数
// 比如在终端使用cat读取,就会执行
static int mpu6050_read_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan, int *val, int *val2, long mask){
int ret=0;
struct mpu6050_dev_struct *dev = iio_priv(indio_dev);
unsigned char regdata = 0;
switch(mask){
case IIO_CHAN_INFO_RAW: // RAW:有温度、加速度、角度共3个
mutex_lock(&dev->lock); // 防止多进程同时读取
switch(chan->type){
case IIO_TEMP:
ret = mpu6050_sensor_show(dev, TEMP_OUT_H, IIO_MOD_X, val); // 只有一个,就输入IIO_MOD_X骗过mpu6050_sensor_show的逻辑
break;
case IIO_ANGL_VEL:
ret = mpu6050_sensor_show(dev, GYRO_XOUT_H, chan->channel2, val);
break;
case IIO_ACCEL:
ret = mpu6050_sensor_show(dev, ACCEL_XOUT_H, chan->channel2, val);
break;
default:
ret = -EINVAL;
}
mutex_unlock(&dev->lock);
if(!ret) return IIO_VAL_INT;
return ret;
case IIO_CHAN_INFO_SCALE: // SCAL:有温度、加速度、角度共3个
switch(chan->type){
case IIO_TEMP:
*val = MPU6050_TEMP_SCALE/ 1000000;
*val2 = MPU6050_TEMP_SCALE % 1000000;
return IIO_VAL_INT_PLUS_MICRO; /* val + val2/1000000 */
case IIO_ANGL_VEL:
mutex_lock(&dev->lock);
ret = mpu6050_read_one_reg(dev, GYRO_CONFIG, ®data); // GYRO_CONFIG是陀螺仪配置寄存器
if (ret) {
mutex_unlock(&dev->lock);
return ret;
}
regdata = (regdata & 0X18) >> 3; // bit3、bit4表示量程选择
*val = 0;
*val2 = gyro_scale_mpu6050[regdata];
mutex_unlock(&dev->lock);
return IIO_VAL_INT_PLUS_MICRO; /* val + val2/1000000 */
case IIO_ACCEL:
mutex_lock(&dev->lock);
ret = mpu6050_read_one_reg(dev, ACCEL_CONFIG, ®data); // GYRO_CONFIG是陀螺仪配置寄存器
if (ret) {
mutex_unlock(&dev->lock);
return ret;
}
regdata = (regdata & 0X18) >> 3; // bit3、bit4表示量程选择
*val = 0;
*val2 = accel_scale_mpu6050[regdata];
mutex_unlock(&dev->lock);
return IIO_VAL_INT_PLUS_NANO;/* val + val2/1000000000 */
default:
return -EINVAL;
}
case IIO_CHAN_INFO_OFFSET:// 偏移值:1个
switch (chan->type){
case IIO_TEMP:
*val = MPU6050_TEMP_OFFSET;
return IIO_VAL_INT;
default:
return -EINVAL;
}
default:
return -EINVAL;
}
}
5.4.3 write_raw
5.4.3.1 修改陀螺仪量程
cpp
static int mpu6050_write_gyro_scale(struct mpu6050_dev_struct *dev,int val){
int i, ret;
for(i=0;i<ARRAY_SIZE(gyro_scale_mpu6050);i++){
if(val == gyro_scale_mpu6050[i]){
ret = regmap_update_bits(dev->regmap, GYRO_CONFIG, BIT(3)|BIT(4), (i<<3));
if(ret)return ret; // 错误
return 0;
}
}
return -EINVAL; // 没有对应量程
}
其中,i << 3的意思是:(这部分内容和上一篇icm20608对应部分一样)
GYRO_CONFIG[4:3]负责配置陀螺仪量程,
GYRO_CONFIG[4:3]=00时量程为±250°/s
GYRO_CONFIG[4:3]=01时量程为±500°/s
GYRO_CONFIG[4:3]=10时量程为±1000°/s
GYRO_CONFIG[4:3]=11时量程为±2000°/s
当val = 7629 = gyro_scale_mpu6050[0] -> i == 0b00 -> 250°/s
当val = 15258= gyro_scale_mpu6050[1] -> i == 0b01 -> 500°/s
当val = 30517= gyro_scale_mpu6050[2] -> i == 0b10 -> 1000°/s
当val = 61035= gyro_scale_mpu6050[3] -> i == 0b11 -> 2000°/s
这样,将i << 3覆盖到[4:3]位上(FS_SEL)再写回即可完成对应的量程修改。
5.4.3.2 修改加速度计量程
和5.4.3.1的代码基本一样,甚至寄存器也是[4:3]配置量程,只是0b00~0b11分别对应±2/4/8/16的量程。
那么函数就是:
cpp
// 配置加速度计量程
static int mpu6050_write_accel_scale(struct mpu6050_dev_struct *dev,int val){
int i, ret;
for(i=0;i<ARRAY_SIZE(accel_scale_mpu6050);i++){
if(val == accel_scale_mpu6050[i]){
ret = regmap_update_bits(dev->regmap, ACCEL_CONFIG, BIT(3)|BIT(4), (i<<3));
if(ret)return ret; // 错误
return 0;
}
}
return -EINVAL; // 没有对应量程
}
5.4.3.3 write_raw
mpu6050没有OFFSET、CALIBBIAS的XYZ寄存器,write_raw只需要修改量程就够了,所以write_raw比icm20608的简短很多。
cpp
static int mpu6050_write_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int val, int val2, long mask){
struct mpu6050_dev_struct *dev = iio_priv(indio_dev);
int ret = 0;
switch (mask){
case IIO_CHAN_INFO_SCALE: // 配置陀螺仪、加速度计的scale
switch (chan->type){ // 陀螺仪
case IIO_ANGL_VEL:
mutex_lock(&dev->lock);
ret = mpu6050_write_gyro_scale(dev, val2); // scale都是小于1的小数,所以用val2,不需要val
mutex_unlock(&dev->lock);
return ret;
case IIO_ACCEL: // 加速度
mutex_lock(&dev->lock);
ret = mpu6050_write_accel_scale(dev, val2); // scale都是小于1的小数,所以用val2,不需要val
mutex_unlock(&dev->lock);
return ret;
default: return -EINVAL;
}
default: return -EINVAL;
}
}
5.4.4 write_raw_get_fmt
write_raw_get_fmt函数决定了wtite_raw函数中val和val2的意义。
比如要在应用程序 中设置加速度计的量程为±8g,那么分辨率就是|8-(-8)| / 2^16 = 16 / 65536 ≈ 0.000244,我们在write_raw_get_fmt函数里面设置加速度计的数据格式为 IIO_VAL_INT_PLUS_MICRO。那么应用程序 向指定的文件写入0.000244以后,最终传递给内核驱动的就是0.000244*1000000=244。也就是write_raw函数的val为0,val2为244。
从前面read_raw的case IIO_CHAN_INFO_SCALE部分的return值可以看到,加速度计是return IIO_VAL_INT_PLUS_NANO,温度计和陀螺仪是return IIO_VAL_INT_PLUS_MICRO,因此write_raw_get_fmt可以写为:
cpp
static int mpu6050_write_raw_get_fmt(struct iio_dev *indio_dev,struct iio_chan_spec const *chan, long mask){
switch(mask){
case IIO_CHAN_INFO_SCALE: // SCAL:有温度、加速度、角度共3个
switch(chan->type){
case IIO_ANGL_VEL: return IIO_VAL_INT_PLUS_MICRO; // 陀螺仪
default :return IIO_VAL_INT_PLUS_NANO; // 加速度、温度
}
default: return IIO_VAL_INT_PLUS_MICRO;
}
return -EINVAL;
}
write_raw_get_fmt函数会在调用write_raw之前被调用。
如果现在用户想修改温度传感器 的scale:
①首先调用iwrite_raw_get_fmt函数,返回IIO_VAL_INT_PLUS_NANO
②然后进入write_raw函数。在write_raw函数中,发现并没有对应的case IIO_TEMP,即表示温度传感器的scale只读,不准改,就会返回-EINVAL。
③然后用户就会得到一句Write Error: Invalid Argument。
5.4.5 iio_info
组成iio_info:
cpp
static const struct iio_info mpu6050_info = {
.read_raw = mpu6050_read_raw,
.write_raw = mpu6050_write_raw,
.write_raw_get_fmt = mpu6050_write_raw_get_fmt,
.driver_module = THIS_MODULE,
};
5.4.5 完整驱动代码
还有一些代码与IIC(AP3216C驱动+MPU6050驱动)中的一致,但和iio没什么关系,比如匹配表、mpu6050初始化等等,不再单独写出,直接放到完整驱动代码中:
cpp
#include<linux/module.h>
#include<linux/input.h>
#include<linux/i2c.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/string.h>
#include<linux/delay.h>
#include<linux/regmap.h>
#include <linux/iio/iio.h>
#include"mpu6050reg.h"
#define MPU6050_CNT 1
#define MPU6050_NAME "mpu6050"
#define MPU6050_TEMP_OFFSET 36.53
#define MPU6050_TEMP_SCALE 340000000// 温度传感器的比例:340 LSB/°C
// 温度传感器量程只有一个,所以只有一个比例尺
// 计算公式: RAW/340 + 36.53 = 真实温度
/* mpu6050 陀螺仪分辨率,对应 250、500、1000、2000,计算方法:
* 以正负 250 度量程为例,500/2^16=0.007629,扩大 1000000 倍,就是 7629
*/
static const int gyro_scale_mpu6050[] = {7629, 15258, 30517, 61035};
/* mpu6050 加速度计分辨率,对应量程±2、±4、±8、±16。计算方法:
* 以正负 2g 量程为例,4/2^16=0.000061035,扩大 1000000000 倍,就是 61035
* 也对应1÷(16384LSB\g) = 0.000061035,也就是一个LSB值对应0.000061035 g
*/
static const int accel_scale_mpu6050[] = {61035, 122070, 244140, 488281};
// MPU6050的扫描元素 用于确定缓冲区顺序
enum inv_mpu6050_scan {
INV_MPU6050_SCAN_ACCEL_X, // 0
INV_MPU6050_SCAN_ACCEL_Y, // 1
INV_MPU6050_SCAN_ACCEL_Z, // ......
INV_MPU6050_SCAN_TEMP,
INV_MPU6050_SCAN_GYRO_X,
INV_MPU6050_SCAN_GYRO_Y,
INV_MPU6050_SCAN_GYRO_Z,
INV_MPU6050_SCAN_TIMESTAMP,
};
// 设备结构体 ===================================================
struct mpu6050_dev_struct {
struct i2c_client *client;
struct regmap *regmap;
struct regmap_config regmap_config;
struct mutex lock;
};
// mpu6050寄存器读写函数 ===================================================
static int mpu6050_read_regs(struct mpu6050_dev_struct *dev, u8 reg, void *buf, int len){
int ret = 0;
ret = regmap_bulk_read(dev->regmap, reg, buf, len);
if(ret)pr_err("mpu6050: i2c_read_regs 0x%x failed %d\n", reg, ret);
return ret;
}
static int mpu6050_read_one_reg(struct mpu6050_dev_struct *dev, u8 reg, u8 *data){
int ret = 0;
unsigned int val = 0;
ret = regmap_read(dev->regmap, reg, &val); // 这里不直接把data传进去,因为regmap_read会返回一个u int类型,
// 如果把data指针直接传进去,32位数据会把u8之后的24位也覆盖掉
if(ret){
pr_err("mpu6050: i2c_read_one_reg 0x%x failed %d\n", reg, ret);
return ret;
}
*data = (u8)val;
return 0;
}
static int mpu6050_write_regs(struct mpu6050_dev_struct *dev, u8 reg, u8 *buf, int len){
int ret = 0;
ret = regmap_bulk_write(dev->regmap, reg, buf, len);
if (ret < 0)pr_err("mpu6050: i2c_write_regs 0x%x failed %d\n", reg, ret);
return ret;
}
static int mpu6050_write_one_reg(struct mpu6050_dev_struct *dev, u8 reg, u8 value){
int ret = 0;
ret = regmap_write(dev->regmap, reg, value);
if (ret < 0)pr_err("mpu6050: i2c_write_one_reg 0x%x failed: %d\n", reg, ret);
return ret;
}
// MPU6050设备初始化
// 要和MPU6050驱动初始化mpu6050_init区分
static int mpu6050_device_init(struct mpu6050_dev_struct *dev){
int err = 0;
short val;
struct i2c_client *client = (struct i2c_client *)dev->client;
val = 0x00;
err += mpu6050_write_regs(dev, PWR_MGMT_1, &val,2); // 启动8MHz振荡器,关闭休眠,关闭睡眠和循环模式,不禁用温度计
val = 0x06;
err += mpu6050_write_regs(dev, CONFIG, &val,2); // 加速度/陀螺带宽为5Hz,陀螺输出率为1kHz;关闭FSYNC采样
val = 0x07;
err += mpu6050_write_regs(dev, SMPLRT_DIV, &val,2); // 全芯片采样率 = 陀螺仪输出速率 ÷ (该寄存器的值+1) = 1k ÷ 8 = 125Hz
val = 0x00;
err += mpu6050_write_regs(dev, ACCEL_CONFIG, &val,2); // 加速度计量程设为±2g,打开加速度计高通滤波
if (err < 0){
printk("mpu6050_device_init fail\n");
return -1;
}
printk("mpu6050_device_init finished!\r\n");
return 0;
}
#define MPU6050_CHAN(_type, _channel2, _index){ \
.modified = 1, \
.channel2 = _channel2, \
.type = _type, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) /*独立*/ \
| BIT(IIO_CHAN_INFO_CALIBBIAS), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),/*相同通道共享scale,如陀螺仪通道下的xyz共享一个scale*/ \
.scan_index = _index, \
.scan_type = { \
.sign = 's', /*signed有符号数(补码形式)*/ \
.realbits = 16, /*真实数据16位*/ \
.storagebits = 16, /*存储数据16位*/ \
.shift = 0, /*数据不需要移位,低位对齐*/ \
.endianness = IIO_BE,/*Big Endian大端模式*/ \
} \
}
static const struct iio_chan_spec mpu6050_channels[] = {
{
.type = IIO_TEMP,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
| BIT(IIO_CHAN_INFO_SCALE)
| BIT(IIO_CHAN_INFO_OFFSET),
.scan_index = INV_MPU6050_SCAN_TEMP,
.scan_type = {
.sign = 's', // signed有符号数(补码形式)
.realbits = 16, // 真实数据16位
.storagebits = 16, // 存储数据16位
.shift = 0, // 数据不需要移位,低位对齐
.endianness = IIO_BE,// Big Endian大端模式
},
},
MPU6050_CHAN(IIO_ACCEL,IIO_MOD_X,INV_MPU6050_SCAN_ACCEL_X),
MPU6050_CHAN(IIO_ACCEL,IIO_MOD_Y,INV_MPU6050_SCAN_ACCEL_Y),
MPU6050_CHAN(IIO_ACCEL,IIO_MOD_Z,INV_MPU6050_SCAN_ACCEL_Z),
MPU6050_CHAN(IIO_ANGL_VEL,IIO_MOD_X,INV_MPU6050_SCAN_GYRO_X),
MPU6050_CHAN(IIO_ANGL_VEL,IIO_MOD_Y,INV_MPU6050_SCAN_GYRO_Y),
MPU6050_CHAN(IIO_ANGL_VEL,IIO_MOD_Z,INV_MPU6050_SCAN_GYRO_Z),
};
// read_raw 相关函数 ========================================================
// 从mpu6050读取数据。角度、加速度、温度
// reg:寄存器首地址
// axis:通道,比如xyz
// val:存储读取到的值
static int mpu6050_sensor_show(struct mpu6050_dev_struct *dev, int reg,int axis, int *val){
int ind, result;
short d;
ind = (axis - IIO_MOD_X) * 2; // ind是偏移量
// mpu6050每个寄存器8位,一个数据要16位
// 而对于角度、加速度来说,都是以X轴高8位寄存器地址为起始
// 所以当axis=IIO_MOD_X,ind=0;axis=IIO_MOD_Y,ind=2;axis=IIO_MOD_Z,ind=4;
result = regmap_bulk_read(dev->regmap, reg + ind, (u8 *)&d, 2); // 以reg+ind为首地址,连续读取2个寄存器,数据存到d
if(result)return -EINVAL;
*val = (short)be16_to_cpup(&d);// 一个宏。将d的大端格式转换成小端
return 0;
}
// 读取iio mpu6050生成的文件,就会调用这个函数
// 比如在终端使用cat读取,就会执行
static int mpu6050_read_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan, int *val, int *val2, long mask){
int ret=0;
struct mpu6050_dev_struct *dev = iio_priv(indio_dev);
unsigned char regdata = 0;
switch(mask){
case IIO_CHAN_INFO_RAW: // RAW:有温度、加速度、角度共3个
mutex_lock(&dev->lock); // 防止多进程同时读取
switch(chan->type){
case IIO_TEMP:
ret = mpu6050_sensor_show(dev, TEMP_OUT_H, IIO_MOD_X, val); // 只有一个,就输入IIO_MOD_X骗过mpu6050_sensor_show的逻辑
break;
case IIO_ANGL_VEL:
ret = mpu6050_sensor_show(dev, GYRO_XOUT_H, chan->channel2, val);
break;
case IIO_ACCEL:
ret = mpu6050_sensor_show(dev, ACCEL_XOUT_H, chan->channel2, val);
break;
default:
ret = -EINVAL;
}
mutex_unlock(&dev->lock);
if(!ret) return IIO_VAL_INT;
return ret;
case IIO_CHAN_INFO_SCALE: // SCAL:有温度、加速度、角度共3个
switch(chan->type){
case IIO_TEMP:
*val = MPU6050_TEMP_SCALE/ 1000000;
*val2 = MPU6050_TEMP_SCALE % 1000000;
return IIO_VAL_INT_PLUS_MICRO; /* val + val2/1000000 */
case IIO_ANGL_VEL:
mutex_lock(&dev->lock);
ret = mpu6050_read_one_reg(dev, GYRO_CONFIG, ®data); // GYRO_CONFIG是陀螺仪配置寄存器
if (ret) {
mutex_unlock(&dev->lock);
return ret;
}
regdata = (regdata & 0X18) >> 3; // bit3、bit4表示量程选择
*val = 0;
*val2 = gyro_scale_mpu6050[regdata];
mutex_unlock(&dev->lock);
return IIO_VAL_INT_PLUS_MICRO; /* val + val2/1000000 */
case IIO_ACCEL:
mutex_lock(&dev->lock);
ret = mpu6050_read_one_reg(dev, ACCEL_CONFIG, ®data); // GYRO_CONFIG是陀螺仪配置寄存器
if (ret) {
mutex_unlock(&dev->lock);
return ret;
}
regdata = (regdata & 0X18) >> 3; // bit3、bit4表示量程选择
*val = 0;
*val2 = accel_scale_mpu6050[regdata];
mutex_unlock(&dev->lock);
return IIO_VAL_INT_PLUS_NANO;/* val + val2/1000000000 */
default:
return -EINVAL;
}
case IIO_CHAN_INFO_OFFSET:// 偏移值:1个
switch (chan->type){
case IIO_TEMP:
*val = MPU6050_TEMP_OFFSET;
return IIO_VAL_INT;
default:
return -EINVAL;
}
default:
return -EINVAL;
}
}
// write_raw相关函数 ========================================================
static int mpu6050_write_gyro_scale(struct mpu6050_dev_struct *dev,int val){
int i, ret;
for(i=0;i<ARRAY_SIZE(gyro_scale_mpu6050);i++){
if(val == gyro_scale_mpu6050[i]){
ret = regmap_update_bits(dev->regmap, GYRO_CONFIG, BIT(3)|BIT(4), (i<<3));
if(ret)return ret; // 错误
return 0;
}
}
return -EINVAL; // 没有对应量程
}
// 配置加速度计量程
static int mpu6050_write_accel_scale(struct mpu6050_dev_struct *dev,int val){
int i, ret;
for(i=0;i<ARRAY_SIZE(accel_scale_mpu6050);i++){
if(val == accel_scale_mpu6050[i]){
ret = regmap_update_bits(dev->regmap, ACCEL_CONFIG, BIT(3)|BIT(4), (i<<3));
if(ret)return ret; // 错误
return 0;
}
}
return -EINVAL; // 没有对应量程
}
static int mpu6050_write_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int val, int val2, long mask){
struct mpu6050_dev_struct *dev = iio_priv(indio_dev);
int ret = 0;
switch (mask){
case IIO_CHAN_INFO_SCALE: // 配置陀螺仪、加速度计的scale
switch (chan->type){ // 陀螺仪
case IIO_ANGL_VEL:
mutex_lock(&dev->lock);
ret = mpu6050_write_gyro_scale(dev, val2); // scale都是小于1的小数,所以用val2,不需要val
mutex_unlock(&dev->lock);
return ret;
case IIO_ACCEL: // 加速度
mutex_lock(&dev->lock);
ret = mpu6050_write_accel_scale(dev, val2); // scale都是小于1的小数,所以用val2,不需要val
mutex_unlock(&dev->lock);
return ret;
default: return -EINVAL;
}
default: return -EINVAL;
}
}
static int mpu6050_write_raw_get_fmt(struct iio_dev *indio_dev,struct iio_chan_spec const *chan, long mask){
switch(mask){
case IIO_CHAN_INFO_SCALE: // SCAL:有温度、加速度、角度共3个
switch(chan->type){
case IIO_ANGL_VEL: return IIO_VAL_INT_PLUS_MICRO; // 陀螺仪
default :return IIO_VAL_INT_PLUS_NANO; // 加速度、温度
}
default: return IIO_VAL_INT_PLUS_MICRO;
}
return -EINVAL;
}
static const struct iio_info mpu6050_info = {
.read_raw = mpu6050_read_raw,
.write_raw = mpu6050_write_raw,
.write_raw_get_fmt = mpu6050_write_raw_get_fmt,
.driver_module = THIS_MODULE,
};
// 匹配表 ===================================================
static struct i2c_device_id mpu6050_id[] = {
{"alientek,mpu6050"},
{/*sentinel*/},
};
static struct of_device_id mpu6050_of_match[] = {
{.compatible = "alientek,mpu6050"},
{/*sentinel*/},
};
// 总线设备结构体 ===================================================
static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id){
printk(" == probe ==\r\n");
struct mpu6050_dev_struct *dev;
struct iio_dev *indio_dev;
int ret;
// 申请iio和设备结构体
indio_dev = iio_device_alloc(sizeof(*dev));
if(!indio_dev){
ret = -ENOMEM;
pr_err("iio alloc failed!\r\n");
goto fail_iio_alloc;
}
dev = iio_priv(indio_dev);
dev->client = client;
i2c_set_clientdata(dev->client, indio_dev);
// 配置regmap_config
dev->regmap_config.reg_bits = 8; // 寄存器地址位数
dev->regmap_config.val_bits = 8; // 寄存器数据位数
dev->regmap_config.read_flag_mask = 0;
// 【新增】必须关闭缓存,否则传感器数据无法实时更新
dev->regmap_config.cache_type = REGCACHE_NONE;
dev->regmap = regmap_init_i2c(client, &dev->regmap_config);
if(IS_ERR(dev->regmap)){
ret = PTR_ERR(dev->regmap);
pr_err("regmap init failed!\r\n");
goto fail_regmap_init;
}
// 初始化锁
mutex_init(&dev->lock);
// 初始化iio & 注册
indio_dev->dev.parent = &client->dev;
indio_dev->channels = mpu6050_channels;
indio_dev->num_channels = ARRAY_SIZE(mpu6050_channels); // 7个通道
indio_dev->name = MPU6050_NAME;
indio_dev->modes = INDIO_DIRECT_MODE; // 提供sysfs接口
indio_dev->info = &mpu6050_info;
ret = iio_device_register(indio_dev);
if(ret < 0){
pr_err("iio register failed!\r\n");
goto fail_iio_register;
}
mpu6050_device_init(dev); // 设备初始化
return 0;
// iio_device_unregister(indio_dev);
fail_iio_register:
regmap_exit(dev->regmap);
mutex_destroy(&dev->lock);
fail_regmap_init:
iio_device_free(indio_dev);
fail_iio_alloc:
return ret;
}
static void mpu6050_remove(struct i2c_client *client){
struct iio_dev *indio_dev = i2c_get_clientdata(client);
struct mpu6050_dev_struct *dev = iio_priv(indio_dev);
iio_device_unregister(indio_dev);
regmap_exit(dev->regmap);
mutex_destroy(&dev->lock);
iio_device_free(indio_dev);
}
static struct i2c_driver mpu6050_driver = {
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.driver = {
.owner = THIS_MODULE,
.name = "mpu6050",
.of_match_table = of_match_ptr(mpu6050_of_match),
},
.id_table = mpu6050_id,
};
// 驱动入口/出口 ===================================================
static int __init mpu6050_init(void){
i2c_add_driver(&mpu6050_driver);
return 0;
}
static void __exit mpu6050_exit(void){
i2c_del_driver(&mpu6050_driver);
}
module_init(mpu6050_init);
module_exit(mpu6050_exit);
MODULE_LICENSE("GPL");
5.5 应用程序
这部分代码直接在【正点原子】阿尔法Linux开发板(A盘)-基础资料\01、例程源码\02、Linux驱动例程\27_iio\spi的基础上改改,把icm20608改成mpu6050,注释掉calibbias相关的部分即可。
代码主要是以下几个部分。
5.5.1 从iio生成的文件中读取数据
从文件中读到的都是字符串形式:
cpp
/*
* @description : 读取指定文件内容
* @param - filename : 要读取的文件路径
* @param - str : 读取到的文件字符串
* @return : 0 成功;其他 失败
*/
static int file_data_read(char *filename, char *str)
{
int ret = 0;
FILE *data_stream;
data_stream = fopen(filename, "r"); /* 只读打开 */
if(data_stream == NULL) {
printf("can't open file %s\r\n", filename);
return -1;
}
ret = fscanf(data_stream, "%s", str);
if(!ret) {
printf("file read error!\r\n");
} else if(ret == EOF) {
/* 读到文件末尾的话将文件指针重新调整到文件头 */
fseek(data_stream, 0, SEEK_SET);
}
fclose(data_stream); /* 关闭文件 */
return 0;
}
5.5.2 字符串转浮点数/整数
将file_data_read从文件中读到的字符串转为数字,用于后续计算。
cpp
/* 字符串转数字,将浮点小数字符串转换为浮点数数值 */
#define SENSOR_FLOAT_DATA_GET(ret, index, str, member)\
ret = file_data_read(file_path[index], str);\
dev->member = atof(str);\
/* 字符串转数字,将整数字符串转换为整数数值 */
#define SENSOR_INT_DATA_GET(ret, index, str, member)\
ret = file_data_read(file_path[index], str);\
dev->member = atoi(str);\
5.5.3 预定义文件路径
可以发现上面的两个函数用了file_path[index]来找文件的路径。因为iio生成的文件名称都是固定的,所以可以直接写好存到数组里,并配合使用枚举作为索引:
(这里注意,自己的设备可能并不是device0,需要提前确认一下)
cpp
/* mpu6050 iio框架对应的文件路径 */
static char *file_path[] = {
"/sys/bus/iio/devices/iio:device0/in_accel_scale",
// "/sys/bus/iio/devices/iio:device0/in_accel_x_calibbias",
"/sys/bus/iio/devices/iio:device0/in_accel_x_raw",
// "/sys/bus/iio/devices/iio:device0/in_accel_y_calibbias",
"/sys/bus/iio/devices/iio:device0/in_accel_y_raw",
// "/sys/bus/iio/devices/iio:device0/in_accel_z_calibbias",
"/sys/bus/iio/devices/iio:device0/in_accel_z_raw",
"/sys/bus/iio/devices/iio:device0/in_anglvel_scale",
// "/sys/bus/iio/devices/iio:device0/in_anglvel_x_calibbias",
"/sys/bus/iio/devices/iio:device0/in_anglvel_x_raw",
// "/sys/bus/iio/devices/iio:device0/in_anglvel_y_calibbias",
"/sys/bus/iio/devices/iio:device0/in_anglvel_y_raw",
// "/sys/bus/iio/devices/iio:device0/in_anglvel_z_calibbias",
"/sys/bus/iio/devices/iio:device0/in_anglvel_z_raw",
"/sys/bus/iio/devices/iio:device0/in_temp_offset",
"/sys/bus/iio/devices/iio:device0/in_temp_raw",
"/sys/bus/iio/devices/iio:device0/in_temp_scale",
};
/* 文件路径索引,要和file_path里面的文件顺序对应 */
enum path_index {
IN_ACCEL_SCALE = 0,
// IN_ACCEL_X_CALIBBIAS,
IN_ACCEL_X_RAW,
// IN_ACCEL_Y_CALIBBIAS,
IN_ACCEL_Y_RAW,
// IN_ACCEL_Z_CALIBBIAS,
IN_ACCEL_Z_RAW,
IN_ANGLVEL_SCALE,
// IN_ANGLVEL_X_CALIBBIAS,
IN_ANGLVEL_X_RAW,
// IN_ANGLVEL_Y_CALIBBIAS,
IN_ANGLVEL_Y_RAW,
// IN_ANGLVEL_Z_CALIBBIAS,
IN_ANGLVEL_Z_RAW,
IN_TEMP_OFFSET,
IN_TEMP_RAW,
IN_TEMP_SCALE,
};
5.5.4 设备结构体
这里列出了所有可能需要读取的数据:
cpp
/*
* 数据设备结构体
*/
struct mpu6050_dev{
// int accel_x_calibbias, accel_y_calibbias, accel_z_calibbias;
int accel_x_raw, accel_y_raw, accel_z_raw;
// int gyro_x_calibbias, gyro_y_calibbias, gyro_z_calibbias;
int gyro_x_raw, gyro_y_raw, gyro_z_raw;
int temp_offset, temp_raw;
float accel_scale, gyro_scale, temp_scale;
float gyro_x_act, gyro_y_act, gyro_z_act; // 实际值
float accel_x_act, accel_y_act, accel_z_act;
float temp_act;
};
struct mpu6050_dev mpu6050; // 顺便定义一下变量
5.5.5 读取函数
调用上面的一堆函数,读物icm20608的三个传感器的所有原始数据,并计算出真实值:
cpp
/*
* @description : 获取mpu6050数据
* @param - dev : 设备结构体
* @return : 0 成功;其他 失败
*/
static int sensor_read(struct mpu6050_dev *dev)
{
int ret = 0;
char str[50];
/* 1、获取陀螺仪原始数据 */
SENSOR_FLOAT_DATA_GET(ret, IN_ANGLVEL_SCALE, str, gyro_scale);
SENSOR_INT_DATA_GET(ret, IN_ANGLVEL_X_RAW, str, gyro_x_raw);
SENSOR_INT_DATA_GET(ret, IN_ANGLVEL_Y_RAW, str, gyro_y_raw);
SENSOR_INT_DATA_GET(ret, IN_ANGLVEL_Z_RAW, str, gyro_z_raw);
/* 2、获取加速度计原始数据 */
SENSOR_FLOAT_DATA_GET(ret, IN_ACCEL_SCALE, str, accel_scale);
SENSOR_INT_DATA_GET(ret, IN_ACCEL_X_RAW, str, accel_x_raw);
SENSOR_INT_DATA_GET(ret, IN_ACCEL_Y_RAW, str, accel_y_raw);
SENSOR_INT_DATA_GET(ret, IN_ACCEL_Z_RAW, str, accel_z_raw);
/* 3、获取温度值 */
SENSOR_FLOAT_DATA_GET(ret, IN_TEMP_SCALE, str, temp_scale);
SENSOR_INT_DATA_GET(ret, IN_TEMP_OFFSET, str, temp_offset);
SENSOR_INT_DATA_GET(ret, IN_TEMP_RAW, str, temp_raw);
/* 3、转换为实际数值 */
dev->accel_x_act = dev->accel_x_raw * dev->accel_scale;
dev->accel_y_act = dev->accel_y_raw * dev->accel_scale;
dev->accel_z_act = dev->accel_z_raw * dev->accel_scale;
dev->gyro_x_act = dev->gyro_x_raw * dev->gyro_scale;
dev->gyro_y_act = dev->gyro_y_raw * dev->gyro_scale;
dev->gyro_z_act = dev->gyro_z_raw * dev->gyro_scale;
dev->temp_act = ((dev->temp_raw - dev->temp_offset) / dev->temp_scale) + 25;
return ret;
}
5.5.6 主函数
cpp
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int ret = 0;
if (argc != 1) {
printf("Error Usage!\r\n");
return -1;
}
while (1) {
// \033[H : 把光标移动到屏幕左上角 (Home)
// \033[J : 清除光标之后的所有内容 (Clear to End of Screen)
// printf("\033[H\033[J"); // 如果嫌刷新数据太抖了,可以用这个
ret = sensor_read(&mpu6050);
if(ret == 0) { /* 数据读取成功 */
printf("\r\n原始值:\r\n");
printf("gx = %d, gy = %d, gz = %d\r\n", mpu6050.gyro_x_raw, mpu6050.gyro_y_raw, mpu6050.gyro_z_raw);
printf("ax = %d, ay = %d, az = %d\r\n", mpu6050.accel_x_raw, mpu6050.accel_y_raw, mpu6050.accel_z_raw);
printf("temp = %d\r\n", mpu6050.temp_raw);
printf("实际值:");
printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", mpu6050.gyro_x_act, mpu6050.gyro_y_act, mpu6050.gyro_z_act);
printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", mpu6050.accel_x_act, mpu6050.accel_y_act, mpu6050.accel_z_act);
printf("act temp = %.2f°C\r\n", mpu6050.temp_act);
}
usleep(100000); /*100ms */
}
return 0;
}
5.6 测试
编译、放到nfs中:
bash
make
arm-linux-gnueabihf-gcc mpu6050APP.c -o mpu6050APP
sudo cp mpu6050APP mpu6050.ko /home/....../nfs/rootfs/lib/modules/4.1.15/
probe成功后,查看/sys/bus/iio/devices下面,发现多了一个device0。查看其下面的内容,可以看到生成的各种文件:

突然发现生成了calibbias文件,才想起来MPU6050_CHAN里面写上了calibbias:

那就试着读一下这个calibbias,发现报错了,因为我们在read中就没有写case calibbias,直接default -EINVAL了:

再看看其他值:

1÷0.000061035≈16384,说明此时量成为±2g

将mpu6050平放,z轴加速度RAW为16440,实际值=16440÷16385≈1.00341796875,差不多是1g,说明数值是正确的。
接下来测试应用程序。直接执行./mpu6050APP,就能看到数据了:


