28.成功解决i2c_transfer返回-6的问题并linux驱动mpu6050(适合一切linux学习者)
开发板:正点原子STM32MP157
环境:ubuntu
驱动目标:mpu6050
解决思路:就是换等长的杜邦线就行!
正点原子的STM32MP157板子上并没有mpu6050的模块!
所以只能外接!
涉及总代码会放在后面!
对于代码的编写不会的宝子可以按照我之前的文章看看I2C的驱动知识和编程,评论区或者后台问我,会一一回答!
1、电路原理图
这块板子只提供I2C4的外设接口并引脚引出:看上图就是I2C4 SCL PZ4和I2C4 SDA PZ5。
看过我之前的同学就知道:我的pcf8563也是这个总线接口!但不影响!
所以我们驱动mpu6050只能利用I2C4总线来驱动。
2、修改设备树
设备树里面几乎不用改,比如:arch/arm/boot/dts/stm32mp151.dtsi、arch/arm/boot/dts/stm32mp15-pinctrl.dtsi不用改,除了不一样的板子,那么就要改一些接口重复的。
打开arch/arm/boot/dts/stm32mp157d-atk.dts文件
提供给大家复制:
&i2c4 {clock-frequency = <100000>;pinctrl-names = "default", "sleep";pinctrl-0 = <&i2c4_pins_a>;pinctrl-1 = <&i2c4_pins_sleep_a>;status = "okay";#address-cells = <1>;#size-cells = <0>;mpu6050@68{compatible = "alientek,mpu6050";reg = <0x68>;status = "okay"; };
};
同时也提供stm32mp15-pinctrl.dtsi和stm32mp151.dtsi关于I2C4的代码:
stm32mp151.dtsi
i2c4: i2c@5c002000 {compatible = "st,stm32mp15-i2c";reg = <0x5c002000 0x400>;interrupt-names = "event", "error";interrupts-extended = <&exti 24 IRQ_TYPE_LEVEL_HIGH>,<&intc GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>;clocks = <&scmi0_clk CK_SCMI0_I2C4>;resets = <&scmi0_reset RST_SCMI0_I2C4>;#address-cells = <1>;#size-cells = <0>;dmas = <&mdma1 36 0x0 0x40008 0x0 0x0 0>,<&mdma1 37 0x0 0x40002 0x0 0x0 0>;dma-names = "rx", "tx";power-domains = <&pd_core>;st,syscfg-fmp = <&syscfg 0x4 0x8>;wakeup-source;status = "disabled";};
stm32mp15-pinctrl.dtsi
i2c4_pins_a: i2c4-0 {pins {pinmux = <STM32_PINMUX('Z', 4, AF6)>, /* I2C4_SCL */<STM32_PINMUX('Z', 5, AF6)>; /* I2C4_SDA */bias-disable;drive-open-drain;slew-rate = <0>;};};i2c4_pins_sleep_a: i2c4-1 {pins {pinmux = <STM32_PINMUX('Z', 4, ANALOG)>, /* I2C4_SCL */<STM32_PINMUX('Z', 5, ANALOG)>; /* I2C4_SDA */};};
当然板子不一样,涉及的很多配置都不一样,大家可以看网上的资料!
3、驱动代码会放在后面!
当我改好设备树和驱动代码后,烧录启动开发板:
发现modprobe mpu6050.ko后发现:
总是返回的是-6;这个是由i2c_transfer传输失败返回的!
我继续查找原因!
发现:
1、在sys/bus/i2c/devices下有68,也就是mpu6050
这里没有i2c-3不知道为什么?如果有知道的同学可以在评论区回复!
2、在dev下也有mpu6050
3、在proc/device-tree/soc/i2c@5c002000也有mpu6050@68
正如设备树的配置:
同时我们看看文件里的内容:
都是okay:说明i2c4开启了!
这都是正常的:
最后我们查查I2C总线上有没有68这个地址:
我们利用:i2cdetect -y 或者i2cdetect -r -y来测试通信:
还是没有68!
其中0和1是之前看到的:
这里我也不知道为什么是通过I2C1和I2C2来。但是i2cdetect -r -y 3或4都没用。
之后只能换杜邦线了!
我换的是:
然后就行了!
出现68了!
同时只能用开发板上的3.3v和5V还有GND才可以。
如果不换杜邦线,用零散的会发生抢占总线等异常等现象!
比如:这是用零散的杜邦线!
结果测试:
返回-110或-16!
所以我们还是用等长的杜邦线!
结果可以了!
测试:
随便截一段数据:
当我把手放在芯片上温度就上来!
mpu6050.c
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include "mpu6050reg.h"
#include <linux/gpio.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>#define MPU6050_CNT 1
#define MPU6050_NAME "mpu6050"
#define MPU_SELF_TEST_X 0x0D
#define MPU_SELF_TEST_Y 0x0E
#define MPU_SELF_TEST_Z 0x0F
#define MPU_SELF_TEST_A 0x10/* mpu6050设备结构体 */
struct mpu6050_dev {dev_t devid; /* 设备号 */int major; /* 主设备号 */int minor; /* 次设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */struct i2c_client *client; /* i2c设备 */signed int gyro_x_adc; /* 陀螺仪X轴原始值 */signed int gyro_y_adc; /* 陀螺仪Y轴原始值 */signed int gyro_z_adc; /* 陀螺仪Z轴原始值 */signed int accel_x_adc; /* 加速度计X轴原始值 */signed int accel_y_adc; /* 加速度计Y轴原始值 */signed int accel_z_adc; /* 加速度计Z轴原始值 */signed int temp_adc; /* 温度原始值 */
};/* 从mpu6050读取多个寄存器数据 */
static int mpu6050_read_regs(struct mpu6050_dev *dev, u8 reg, void *val, int len)
{int ret=-1;struct i2c_msg msg[2];struct i2c_client *client = dev->client;/* 第一个i2c_msg结构体,发送要读取的寄存器首地址 */msg[0].addr = client->addr; /* i2c设备地址 */msg[0].flags = 0; /* 标记为发送数据 */msg[0].buf = ® /* 要发送的数据 */msg[0].len = 1; /* 要发送的数据长度 *//* 第二个i2c_msg结构体,读取数据 */msg[1].addr = client->addr; /* i2c设备地址 */msg[1].flags = I2C_M_RD; /* 标记为读取数据 */msg[1].buf = val; /* 读取数据缓冲区 */msg[1].len = len; /* 要读取的数据长度 *///printk("msg[0].addr=%#x,msg[1].addr=%#x,reg_addr=%#x\r\n",msg[0].addr//,msg[1].addr,reg);printk("msg[0].addr=%#x\r\n",msg[0].addr);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;
}/* 向mpu6050多个寄存器写入数据 */
static int mpu6050_write_regs(struct mpu6050_dev *dev, u8 reg, u8 *buf, u8 len)
{int ret=-1;u8 b[256];struct i2c_msg msg;struct i2c_client *client = dev->client;b[0] = reg; /* 寄存器首地址 */memcpy(&b[1], buf, len); /* 将要写入的数据拷贝到数组b里面 */msg.addr = client->addr; /* i2c设备地址 */msg.flags = 0; /* 标记为写入数据 */msg.buf = b; /* 要写入的数据缓冲区 */msg.len = len + 1; /* 要写入的数据长度 */ret = i2c_transfer(client->adapter, &msg, 1);if(ret == 1) {ret = 0;} else {printk("i2c write failed=%d reg=%06x len=%d\n",ret, reg, len);ret = -EREMOTEIO;}return ret;
}/* 读取mpu6050指定寄存器值,读取一个寄存器 */
static unsigned char mpu6050_read_reg(struct mpu6050_dev *dev, u8 reg)
{u8 data = 0;mpu6050_read_regs(dev,reg,&data, 1);return data;
}/* 向mpu6050指定寄存器写入指定的值,写一个寄存器 */
static void mpu6050_write_reg(struct mpu6050_dev *dev, u8 reg, u8 value)
{u8 buf=0;buf = value;mpu6050_write_regs(dev, reg, &buf, 1);
}/* 读取MPU6050的数据,读取原始数据,包括三轴陀螺仪、三轴加速度计和内部温度 */
void mpu6050_read_data(struct mpu6050_dev *dev)
{unsigned char data[14];mpu6050_read_regs(dev, MPU_ACCEL_XOUT_H, data, 14);dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]); dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]); dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]); dev->temp_adc = (signed short)((data[6] << 8) | data[7]);dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]);dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]);dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]);
}/* MPU6050内部寄存器初始化函数 */
void mpu6050_self_test(struct mpu6050_dev *dev) {u8 self_test[4];signed int self_test_results[4];int i=0;// 读取自检寄存器mpu6050_read_regs(dev, MPU_SELF_TEST_X, self_test, 4);// 计算自检结果for ( i=0 ; i < 4; i++) {self_test_results[i] = (self_test[i] - 1) * 100 / 255; // 示例计算}// 打印自检结果printk("Self Test Results: X=%d, Y=%d, Z=%d, A=%d\n",self_test_results[0], self_test_results[1],self_test_results[2], self_test_results[3]);
}void mpu6050_reginit(struct mpu6050_dev *dev)
{u8 value = 0;/* 复位设备 */mpu6050_write_reg(dev, MPU_PWR_MGMT_1, 0x80);mdelay(50);/* 唤醒设备 */mpu6050_write_reg(dev, MPU_PWR_MGMT_1, 0x00);mdelay(50);mpu6050_write_reg(dev, MPU_PWR_MGMT_2, 0x00);mdelay(50);value = mpu6050_read_reg(dev,MPU_WHO_AM_I);printk("MPU6050 ID = %#X\r\n", value);mpu6050_write_reg(dev, MPU_SMPLRT_DIV, 0x09); /* 采样率分频,1000/(1+7)=125Hz */mpu6050_write_reg(dev, MPU_CONFIG, 0x06); /* 数字低通滤波器,截止频率5Hz */mpu6050_write_reg(dev, MPU_GYRO_CONFIG, 0x18); /* 陀螺仪量程,±2000°/s */mpu6050_write_reg(dev, MPU_ACCEL_CONFIG, 0x18); /* 加速度计量程,±16g */mpu6050_write_reg(dev, 0x38, 0x00); //关闭所有中断mpu6050_write_reg(dev, 0x6A, 0x00); //I2C主模式关闭mpu6050_write_reg(dev, 0x23, 0x00); //关闭FIFOmpu6050_write_reg(dev, MPU_PWR_MGMT_1, 0x01);mpu6050_write_reg(dev, MPU_PWR_MGMT_2, 0x00);// 进行自检mpu6050_self_test(dev);
}static int mpu6050_open(struct inode *inode, struct file *filp)
{filp->private_data = container_of(inode->i_cdev, struct mpu6050_dev, cdev);return 0;
}static ssize_t mpu6050_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{signed int data[7];long err = 0;struct mpu6050_dev *dev = filp->private_data;mpu6050_read_data(dev);data[0] = dev->gyro_x_adc;data[1] = dev->gyro_y_adc;data[2] = dev->gyro_z_adc;data[3] = dev->accel_x_adc;data[4] = dev->accel_y_adc;data[5] = dev->accel_z_adc;data[6] = dev->temp_adc;err = copy_to_user(buf, data, sizeof(data));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,
};/* i2c驱动的probe函数 */
static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{int ret;struct mpu6050_dev *mpu6050dev;/* 分配内存 */mpu6050dev = devm_kzalloc(&client->dev, sizeof(*mpu6050dev), GFP_KERNEL);if (!mpu6050dev)return -ENOMEM;/* 注册字符设备驱动 *//* 1、申请设备号 */mpu6050dev->major = 0;if (mpu6050dev->major) {mpu6050dev->devid = MKDEV(mpu6050dev->major, 0);ret = register_chrdev_region(mpu6050dev->devid, MPU6050_CNT, MPU6050_NAME);} else {ret = alloc_chrdev_region(&mpu6050dev->devid, 0, MPU6050_CNT, MPU6050_NAME);mpu6050dev->major = MAJOR(mpu6050dev->devid);mpu6050dev->minor = MINOR(mpu6050dev->devid);}if (ret < 0) {goto fail_devid;}printk("major=%d,minor=%d\r\n",mpu6050dev->major, mpu6050dev->minor); /* 2、初始化cdev */mpu6050dev->cdev.owner = THIS_MODULE;cdev_init(&mpu6050dev->cdev, &mpu6050_ops);/* 3、添加cdev */ret = cdev_add(&mpu6050dev->cdev, mpu6050dev->devid, MPU6050_CNT);if (ret < 0)goto fail_cdev;/* 4、创建类 */mpu6050dev->class = class_create(THIS_MODULE, MPU6050_NAME);if (IS_ERR(mpu6050dev->class)) {ret = PTR_ERR(mpu6050dev->class);goto fail_class;}/* 5、创建设备 */mpu6050dev->device = device_create(mpu6050dev->class, NULL, mpu6050dev->devid, NULL, MPU6050_NAME);if (IS_ERR(mpu6050dev->device)) {ret = PTR_ERR(mpu6050dev->device);goto fail_device;}/* 6、保存i2c_client */mpu6050dev->client = client;/* 7、保存mpu6050dev结构体 */i2c_set_clientdata(client, mpu6050dev);/* 8、初始化MPU6050内部寄存器 */mpu6050_reginit(mpu6050dev);return 0;fail_device:class_destroy(mpu6050dev->class);
fail_class:cdev_del(&mpu6050dev->cdev);
fail_cdev:unregister_chrdev_region(mpu6050dev->devid, MPU6050_CNT);
fail_devid:return ret;
}/* i2c驱动的remove函数 */
static int mpu6050_remove(struct i2c_client *client)
{struct mpu6050_dev *mpu6050dev = i2c_get_clientdata(client);/* 注销设备 */device_destroy(mpu6050dev->class, mpu6050dev->devid);/* 注销类 */class_destroy(mpu6050dev->class);/* 删除cdev */cdev_del(&mpu6050dev->cdev);/* 注销设备号 */unregister_chrdev_region(mpu6050dev->devid, MPU6050_CNT);return 0;
}/* 传统匹配方式ID列表 */
static const struct i2c_device_id mpu6050_id[] = {{"alientek,mpu6050", 0}, {}
};/* 设备树匹配列表 */
static const struct of_device_id mpu6050_of_match[] = {{ .compatible = "alientek,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,
};/* 驱动注册与卸载 */
module_i2c_driver(mpu6050_driver);MODULE_LICENSE("GPL");
MODULE_AUTHOR("chensir");
MODULE_INFO(intree, "Y");
mpu6050reg.h
#ifndef MPU6050_H
#define MPU6050_H#define MPU6050_ID 0x68 /* ID值 *//* MPU6050寄存器 *复位后所有寄存器地址都为0,除了*Register 107(0X6B) Power Management 1 = 0x40*Register 117(0X75) WHO_AM_I = 0x68*/
/* 陀螺仪和加速度自测 */
#define MPU_SELF_TEST_X 0x0D
#define MPU_SELF_TEST_Y 0x0E
#define MPU_SELF_TEST_Z 0x0F
#define MPU_SELF_TEST_A 0x10#define MPU_SMPLRT_DIV 0x19 /* 采样率分频,典型值:0x07(125Hz) */
#define MPU_CONFIG 0x1A /* 配置寄存器 */
#define MPU_GYRO_CONFIG 0x1B /* 陀螺仪配置 */
#define MPU_ACCEL_CONFIG 0x1C /* 加速度计配置 *//* 加速度输出 */
#define MPU_ACCEL_XOUT_H 0x3B
#define MPU_ACCEL_XOUT_L 0x3C
#define MPU_ACCEL_YOUT_H 0x3D
#define MPU_ACCEL_YOUT_L 0x3E
#define MPU_ACCEL_ZOUT_H 0x3F
#define MPU_ACCEL_ZOUT_L 0x40/* 温度输出 */
#define MPU_TEMP_OUT_H 0x41
#define MPU_TEMP_OUT_L 0x42/* 陀螺仪输出 */
#define MPU_GYRO_XOUT_H 0x43
#define MPU_GYRO_XOUT_L 0x44
#define MPU_GYRO_YOUT_H 0x45
#define MPU_GYRO_YOUT_L 0x46
#define MPU_GYRO_ZOUT_H 0x47
#define MPU_GYRO_ZOUT_L 0x48/* 电源管理 */
#define MPU_PWR_MGMT_1 0x6B /* 电源管理1 */
#define MPU_PWR_MGMT_2 0x6C /* 电源管理2 */#define MPU_WHO_AM_I 0x75 /* WHO AM I *//* SPI特定设置 */
#define MPU_SPI_READ 0x80 /* SPI读标志 */#endif
mpu6050App.c
#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>int main(int argc, char *argv[])
{int fd;char *filename;signed int databuf[7];signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;signed int accel_x_adc, accel_y_adc, accel_z_adc;signed int temp_adc;float gyro_x_act, gyro_y_act, gyro_z_act;float accel_x_act, accel_y_act, accel_z_act;float temp_act;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) { /* 数据读取成功 */gyro_x_adc = databuf[0];gyro_y_adc = databuf[1];gyro_z_adc = databuf[2];accel_x_adc = databuf[3];accel_y_adc = databuf[4];accel_z_adc = databuf[5];temp_adc = databuf[6];/* 计算实际值 */gyro_x_act = (float)(gyro_x_adc) / 16.4; /* 16.4 LSB/(°/s) */gyro_y_act = (float)(gyro_y_adc) / 16.4;gyro_z_act = (float)(gyro_z_adc) / 16.4;accel_x_act = (float)(accel_x_adc) / 2048; /* 2048 LSB/g */accel_y_act = (float)(accel_y_adc) / 2048;accel_z_act = (float)(accel_z_adc) / 2048;temp_act = ((float)(temp_adc) - 521 ) / 340 + 35; /* 温度转换公式 */printf("\r\n原始值:\r\n");printf("gx = %d, gy = %d, gz = %d\r\n", gyro_x_adc, gyro_y_adc, gyro_z_adc);printf("ax = %d, ay = %d, az = %d\r\n", accel_x_adc, accel_y_adc, accel_z_adc);printf("temp = %d\r\n", temp_adc);printf("实际值:\r\n");printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", gyro_x_act, gyro_y_act, gyro_z_act);printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n",accel_x_act, accel_y_act, accel_z_act);printf("act temp = %.2f°C\r\n", temp_act);}usleep(100000); /*100ms */}close(fd);return 0;
}
makefile
KERNELDIR := /home/chensir/linux/atk-mp1/linux/my_linux/linux-5.4.31
CURRENT_PATH := $(shell pwd)
obj-m := mpu6050.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
对于代码的编写不会的宝子可以按照我之前的文章看看I2C的驱动知识和编程,评论区或者后台问我,会一一回答!