Driver for MPU6050 on RK3588S

  • 1. Main Registers of MPU6050
    • 1.1 Initialization Registers and Their Settings
    • 1.2 Data Registers
  • 2. Hardware Connections
  • 3. Device Tree Configuration
  • 4. Writing Driver Code
    • 4.1 i2c_mpu6050.h
    • 4.2 mpu6050.c
  • 5. Timing Observations of Initialization Functions in Probe
    • 5.1 Overall Timing
    • 5.2 CLK
    • 5.3 Host Writing One Byte to Slave
    • 5.4 Host Reading One Byte from Slave

1. Main Registers of MPU6050

1.1 Initialization Registers and Their Settings

Register Name Address Setting Value Function Overview
<span>PWR_MGMT_1</span> 0x6B 0x00 Wake up the device, use internal clock
<span>SMPLRT_DIV</span> 0x19 0x07 Set sample rate to 125Hz
<span>CONFIG</span> 0x1A 0x06 Set low-pass filter, bandwidth of 5Hz
<span>ACCEL_CONFIG</span> 0x1C 0x01 Set accelerometer range to ±4g

1.2 Data Registers

The following are the registers that store the raw data from the accelerometer and gyroscope:

Register Name Address Function
<span>ACCEL_XOUT_H</span> <span>0x3B</span> X-axis high 8 bits
<span>ACCEL_XOUT_L</span> <span>0x3C</span> X-axis low 8 bits
<span>ACCEL_YOUT_H</span> <span>0x3D</span> Y-axis high 8 bits
<span>ACCEL_YOUT_L</span> <span>0x3E</span> Y-axis low 8 bits
<span>ACCEL_ZOUT_H</span> <span>0x3F</span> Z-axis high 8 bits
<span>ACCEL_ZOUT_L</span> <span>0x40</span> Z-axis low 8 bits

2. Hardware Connections

Driver for MPU6050 on RK3588S

3. Device Tree Configuration

Add the following content to the main device tree:

&i2c5 {
    status = "okay";  // Enable i2c5
    pinctrl-names = "default";
    pinctrl-0 = <&i2c5m3_xfer>;  // Reuse m3 pins
    #address-cells = <1>;  // Set reg content as address
    #size-cells = <0>;
    mpu6050:mpu6050@68 {
        compatible = "invensense,mpu6050";
        reg = <0x68>;
        status = "okay";
    };
};

Compile the device tree and copy it to the development board, then check the status of i2c5 and the node information under i2c5:

cat@lubancat:/proc/device-tree$ grep -r "mpu"
grep: __symbols__/mpu6050: matches binary file
grep: i2c@fead0000/mpu6050@68/compatible: matches binary file
grep: i2c@fead0000/mpu6050@68/name: matches binary file
cat@lubancat:/proc/device-tree$ cd i2c@fead0000/
cat@lubancat:/proc/device-tree/i2c@fead0000$ ls
'#address-cells'   compatible         dcphy1-ov13850@10   dcphy1-ov8858@36   name        pinctrl-names   resets
    clock-names       dcphy1-dw9714@c    dcphy1-ov5647@36    interrupts         phandle     reg            '#size-cells'
    clocks            dcphy1-imx415@1a   dcphy1-ov5648@36    mpu6050@68         pinctrl-0   reset-names     status
cat@lubancat:/proc/device-tree/i2c@fead0000$ cat mpu6050@68/compatible
invensense,mpu6050

4. Writing Driver Code

The main functions used are<span>i2c_master_send</span> and <span>i2c_master_recv</span>.

4.1 i2c_mpu6050.h

#ifndef I2C_MPU6050_H
#define I2C_MPU6050_H

// Macro Definitions
#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 WHO_AM_I                                    0x75
#define SlaveAddress                                0xD0
#define Address                                     0x68                  // MPU6050 address
#define I2C_RETRIES                                 0x0701
#define I2C_TIMEOUT                                 0x0702
#define I2C_SLAVE                                   0x0703       // IIC slave device address setting
#define I2C_BUS_MODE                                0x0780

#endif

4.2 mpu6050.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h> // Add this header file
#include <linux/delay.h>
#include <linux/i2c.h>

#include "i2c_mpu6050.h"

struct mpu6050_private{
    int dev_num; 
    struct cdev mpu6050_cdev;  
    struct class *mpu6050_class;
    struct device *mpu6050_device;
    char *cdev_name;
    char *class_name;
    char *device_name;
};

static struct mpu6050_private mpu6050={
    .cdev_name="mpu6050_cdev",
    .class_name="mpu6050_class",
    .device_name="mpu6050_device",
};

static struct i2c_client *mpu6050_client; 

int mpu6050_open(struct inode *node, struct file *file)
{
    printk(KERN_EMERG"Enter mpu6050_open function\n");
    return 0;
}

ssize_t mpu6050_read(struct file *file, char __user *userBuf, size_t size, loff_t *offset)
{
    char tx_buf[1];
    char high,low;
    char rx_buf[6];
    // First send address, then read high and low bytes
    tx_buf[0]=ACCEL_XOUT_H;
    if(i2c_master_send(mpu6050_client,tx_buf,1)>0) 
        i2c_master_recv(mpu6050_client,&high,1);
    else
        printk(KERN_EMERG"ACCEL_XOUT_H read error\n");
    tx_buf[0]=ACCEL_XOUT_L;
    if(i2c_master_send(mpu6050_client,tx_buf,1)>0) i2c_master_recv(mpu6050_client,&low,1);    
    rx_buf[0]=high<<8|low;

    tx_buf[0]=ACCEL_YOUT_H;
    if(i2c_master_send(mpu6050_client,tx_buf,1)>0) i2c_master_recv(mpu6050_client,&high,1);
    tx_buf[0]=ACCEL_YOUT_L;
    if(i2c_master_send(mpu6050_client,tx_buf,1)>0) i2c_master_recv(mpu6050_client,&low,1);    
    rx_buf[1]=high<<8|low;

    tx_buf[0]=ACCEL_ZOUT_H;
    if(i2c_master_send(mpu6050_client,tx_buf,1)>0) i2c_master_recv(mpu6050_client,&high,1);
    tx_buf[0]=ACCEL_ZOUT_L;
    if(i2c_master_send(mpu6050_client,tx_buf,1)>0) i2c_master_recv(mpu6050_client,&low,1);    
    rx_buf[2]=high<<8|low;

    tx_buf[0]=GYRO_XOUT_H;
    if(i2c_master_send(mpu6050_client,tx_buf,1)>0) i2c_master_recv(mpu6050_client,&high,1);
    tx_buf[0]=GYRO_XOUT_L;
    if(i2c_master_send(mpu6050_client,tx_buf,1)>0) i2c_master_recv(mpu6050_client,&low,1);    
    rx_buf[3]=high<<8|low;

    tx_buf[0]=GYRO_YOUT_H;
    if(i2c_master_send(mpu6050_client,tx_buf,1)>0) i2c_master_recv(mpu6050_client,&high,1);
    tx_buf[0]=GYRO_YOUT_L;
    if(i2c_master_send(mpu6050_client,tx_buf,1)>0) i2c_master_recv(mpu6050_client,&low,1);    
    rx_buf[4]=high<<8|low;

    tx_buf[0]=GYRO_ZOUT_H;
    if(i2c_master_send(mpu6050_client,tx_buf,1)>0) i2c_master_recv(mpu6050_client,&high,1);
    tx_buf[0]=GYRO_ZOUT_L;
    if(i2c_master_send(mpu6050_client,tx_buf,1)>0) i2c_master_recv(mpu6050_client,&low,1);    
    rx_buf[5]=high<<8|low;

    if(copy_to_user(userBuf,rx_buf,sizeof(rx_buf)))
    {
        printk(KERN_EMERG"copy_to_user error\n");
        return -1;
    }

    return 0;
}
ssize_t mpu6050_write(struct file *file, const char __user *userBuf, size_t size, loff_t *offset)
{
    return size;
}
static struct file_operations mpu6050_ops={
    .open=mpu6050_open,
    .read=mpu6050_read,
    .write=mpu6050_write,
};

void mpu6050_conf(void)
{
    int error = 0;
    char buf[2]; // Address to write + data to write
    buf[0]=PWR_MGMT_1;
    buf[1]=0X00;
    error+=i2c_master_send(mpu6050_client,buf, 2);
    buf[0]=SMPLRT_DIV;
    buf[1]=0X07;
    error+=i2c_master_send(mpu6050_client,buf, 2);
    buf[0]=CONFIG;
    buf[1]=0X06;
    error+=i2c_master_send(mpu6050_client,buf, 2);
    buf[0]=ACCEL_CONFIG;
    buf[1]=0X01;
    error+=i2c_master_send(mpu6050_client,buf, 2);

 if (error < 0)
 {
  /* Initialization error */
  printk(KERN_DEBUG "mpu6050_init error \n");
 }
}

int mpu6050_probe(struct i2c_client *client)
{   
    int ret;
    printk(KERN_EMERG"Enter mpu6050_probe function\n");

    mpu6050_client=client;
    // Allocate character device number
    ret = alloc_chrdev_region(&mpu6050.dev_num, 0, 1, mpu6050.cdev_name);
    if (ret < 0)
    {
        printk(KERN_EMERG"alloc_chrdev_region error\n");
        return -EAGAIN;
    }
    // Initialize character device
    cdev_init(&mpu6050.mpu6050_cdev, &mpu6050_ops);
    mpu6050.mpu6050_cdev.owner=THIS_MODULE;
    cdev_add(&mpu6050.mpu6050_cdev, mpu6050.dev_num, 1);

    // Create device class
    mpu6050.mpu6050_class=class_create(THIS_MODULE,mpu6050.class_name);
    if (IS_ERR(mpu6050.mpu6050_class))
    {
        printk(KERN_EMERG"class_create error\n");
        return -EAGAIN;
    }

    // Create device
    mpu6050.mpu6050_device= device_create(mpu6050.mpu6050_class, NULL, mpu6050.dev_num, NULL, mpu6050.device_name);
    if (IS_ERR(mpu6050.mpu6050_device))
    {
        printk(KERN_EMERG"device_create error\n");
        return -EAGAIN;
    }

    // Initialize mpu6050
    mpu6050_conf();
    return 0;
}
void mpu6050_remove(struct i2c_client *client)
{
}
struct of_device_id mpu6050_match_table[]={
    {.compatible="invensense,mpu6050"},
    {},
};
const struct i2c_device_id mpu6050_id_table[]={
    {"mpu6050",0},
    {}
};

static struct i2c_driver mpu6050_driver={
    .id_table=mpu6050_id_table,
    .probe_new=mpu6050_probe,
    .remove=mpu6050_remove,
    .driver={
        .owner=THIS_MODULE,
        .name="mpu6050",
        .of_match_table=mpu6050_match_table,
    },
};

static int mpu6050_init(void)
{
    int ret;
    printk(KERN_EMERG"Enter mpu6050_init function\n");
    ret=i2c_register_driver(THIS_MODULE,&mpu6050_driver);

    if(ret<0)
    {
        printk(KERN_EMERG"i2c_register_driver error\n");
        return -1;
    }
    return 0;
}
static void mpu6050_exit(void)
{
    device_destroy(mpu6050.mpu6050_class,mpu6050.dev_num);
    class_destroy(mpu6050.mpu6050_class);
    cdev_del(&mpu6050.mpu6050_cdev);
    unregister_chrdev_region(mpu6050.dev_num,1);
    i2c_del_driver(&mpu6050_driver);
}


module_init(mpu6050_init);
module_exit(mpu6050_exit);

MODULE_AUTHOR("Fle");
MODULE_LICENSE("GPL v2");

5. Timing Observations of Initialization Functions in Probe

5.1 Overall Timing

void mpu6050_conf(void)
{
    int error = 0;
    char buf[2]; // Address to write + data to write
    buf[0]=PWR_MGMT_1;
    buf[1]=0X00;
    error+=i2c_master_send(mpu6050_client,buf, 2);
    buf[0]=SMPLRT_DIV;
    buf[1]=0X07;
    error+=i2c_master_send(mpu6050_client,buf, 2);
    buf[0]=CONFIG;
    buf[1]=0X06;
    error+=i2c_master_send(mpu6050_client,buf, 2);
    buf[0]=ACCEL_CONFIG;
    buf[1]=0X01;
    error+=i2c_master_send(mpu6050_client,buf, 2);

 if (error < 0)
 {
  /* Initialization error */
  printk(KERN_DEBUG "mpu6050_init error \n");
 }
}

The timing corresponding to the above code is shown below:Driver for MPU6050 on RK3588S

5.2 CLK

Driver for MPU6050 on RK3588S

One CLK cycle is 10us, which corresponds to a transmission rate of 100kHz.

5.3 Host Writing One Byte to Slave

buf[0]=PWR_MGMT_1; //0x6B
buf[1]=0X00;
error+=i2c_master_send(mpu6050_client,buf, 2);

The timing corresponding to the above code is as follows:Driver for MPU6050 on RK3588S

From the figure, it can be seen that the transmitted 7 bits are 0x1101000=0x01101000=0x68, which is the address of the MPU6050.

The 8th bit is 0, indicating a write operation, as the host needs to send data to the slave.

At the 9th clock, the host has released the SDA bus, and the slave pulls it low, indicating ACK.

It can be seen that after executing the <span>i2c_master_send</span> function, the CLK goes high, and the SDA shows a rising edge, which is the stop signal sent by the host.

5.4 Host Reading One Byte from Slave

// First send address, then read high and low bytes
tx_buf[0]=ACCEL_XOUT_H; //0x3B
if(i2c_master_send(mpu6050_client,tx_buf,1)>0) 
    i2c_master_recv(mpu6050_client,&high,1);
else
    printk(KERN_EMERG"ACCEL_XOUT_H read error\n");

The timing for the above code is as follows:

Driver for MPU6050 on RK3588S

Before reading, it is necessary to first determine the device address and the register address to read, so in fact, the <span>i2c_master_send</span> function sends two bytes of data consecutively, one is 0x68 write, and the other is 0x3B write, then 0x68 read, and then the SDA bus is handed over to the slave, which transmits data 0xF9. It is worth noting that the last is a NAK signal, which is sent by the host to inform the slave that the reading is complete.

Leave a Comment