[milkv] 2. Add and test mpu6050 driver

Foreword

This chapter introduces the driver addition and testing of mpu6050.

The driver does not use the driver provided by the sdk. On the one hand, you need to configure the irq. On the other hand, you can learn how to add the driver through the ko method.

1. Reference articles

Driver and test file compilation process:

https://community.milkv.io/t/risc-v-milk-v-lsm6dsr-i2c-module/284

Code source: https://blog.csdn.net/m0_58844968/article/details/124994041

2. Circuit diagram

add is grounded, i2c address is 0x68

3. Add dts

duo-buildroot-sdk\build\boards\cv180x\cv1800b_milkv_duo_sd\dts_riscv\cv1800b_milkv_duo_sd.dts

mpu6050:mpu6050@68 {
    compatible = "invensense,mpu6050";
    reg = h;
    status = "okay";
    //interrupt-parent = < & amp;gpio26>;
    //interrupts = <0 IRQ_TYPE_LEVEL_LOW>;
};

4. Add driver

Code source: https://blog.csdn.net/m0_58844968/article/details/124994041

Note that the driver mpu6050.c must be consistent with the compatible in dts.

#ifndef MPU6050REG_H
#define MPU6050REG_H
 
 
typedef unsigned char uint8_t;
  
/* register define */
#define MPU6050_SMPLRT_DIV 0x19 /*Gyroscope sampling rate, typical value: 0x07 (125Hz) */
#define MPU6050_CONFIG 0x1A /*Low pass filter frequency, typical value: 0x06(5Hz)*/
#define MPU6050_GYRO_CONFIG 0x1B /*Gyroscope self-test and measurement range, typical value: 0x18 (no self-test, 2000deg/s)*/
#define MPU6050_ACCEL_CONFIG 0x1C /*Accelerometer self-test, measurement range and high-pass filter frequency, typical value: 0x01 (no self-test, 2G, 5Hz)*/
#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 management, typical value: 0x00 (normally enabled) */
#define MPU6050_WHO_AM_I 0x75 /* IIC address register (default value 0x68, read-only) */
#define MPU6050_SlaveAddress 0xD0 /*IIC address byte data when writing, + 1 for reading*/
#define MPU6050_IIC_ADDR 0x68 /*MPU6050 IIC device address*/
 
/* Interrupt status register*/
#define MPU6050_INT_STATUS 0x3A
#define MPU6050_INT_ENABLE 0x38
#define MPU6050_INT_PIN_CFG 0x37
 
 
 
struct mpu6050_accel {
    short x;
    short y;
    short z;
};
 
struct mpu6050_gyro {
    short x;
    short y;
    short z;
};
 
struct mpu6050_data {
    struct mpu6050_accel accel;
    struct mpu6050_gyro gyro;
};
 
 
 
#endif // __MPU6050REG_H
#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.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
// #include <asm/mach/map.h>
// #include <asm/uaccess.h>
// #include <asm/io.h>
// #include <linux/mach/map.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/i2c.h>
#include "mpu6050reg.h"
 
#define mpu6050dev_CNT 1
#define mpu6050dev_NAME "mpu6050"
#define MPU6050_USE_INT 0 /* Do not use interrupts */
 
struct mpu6050_dev{
int major; /* major device number */
int minor; /* minor device number */
struct cdev cdev; /* cdev */
struct class *class; /* class */
struct device *device; /* device */
dev_t devid; /* device number */
    struct i2c_client *client;
    void *private_data; /* Private data */
    int16_t acceleration[3], gyro[3], temp; /* mpu6050 data acceleration: acceleration data gyro: gyroscope data temp: temperature index data */
 
};
 
static struct mpu6050_dev mpu6050dev;
 
/*
 * @description: Read multiple register data from mpu6050
 * [url=home.php?mod=space & amp;uid=3142012]@param[/url] - dev: mpu6050 device
 * @param - reg: the first address of the register to be read
 * @param - val: read data
 * @param - len: length of data to be read
 * [url=home.php?mod=space & amp;uid=1141835]@Return[/url] : Operation result
 */
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] is the first address to be read */
msg[0].addr = client->addr; /* mpu6050 address */
msg[0].flags = 0; /* Mark to send data */
msg[0].buf = & amp;reg; /* The first address to be read */
msg[0].len = 1; /* reg length*/
 
/* msg[1] read data */
msg[1].addr = client->addr; /* mpu6050 address */
msg[1].flags = I2C_M_RD; /* Marked for reading data*/
msg[1].buf = val; /* Read data buffer */
msg[1].len = len; /* length of data to be read*/
 
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
printk("i2c read failed=%d reg= x len=%d\\
",ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
 
/*
 * @description: Write data to multiple registers of mpu6050
 * @param - dev: mpu6050 device
 * @param - reg: the first address of the register to be written
 * @param - val: the data buffer to be written
 * @param - len: length of data to be written
 * @return: operation result
 */
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;
\t
b[0] = reg; /* Register first address */
memcpy( & amp;b[1],buf,len); /* Copy the data to be written to array b */
\t\t
msg.addr = client->addr; /* mpu6050 address */
msg.flags = 0; /* Mark for writing data */
 
msg.buf = b; /* Data buffer to be written */
msg.len = len + 1; /* length of data to be written */
 
return i2c_transfer(client->adapter, & amp;msg, 1);
}
 
/*
 * @description: Read the specified register value of mpu6050, read a register
 * @param - dev: mpu6050 device
 * @param - reg: the register to be read
 * @return: the read register value
 */
static unsigned char mpu6050_read_reg(struct mpu6050_dev *dev, u8 reg)
{
u8 data = 0;
 
mpu6050_read_regs(dev, reg, & amp;data, 1);
return data;
}
 
/*
 * @description: Write the specified value to the specified register of mpu6050, write a register
 * @param - dev: mpu6050 device
 * @param - reg: the register to be written
 * @param - data: value to be written
 * @return: None
 */
static void mpu6050_write_reg(struct mpu6050_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
mpu6050_write_regs(dev, reg, & amp;buf, 1);
}
 
 
/*MPU6050 device initialization */
static void mpu6050_reset(void) {
mpu6050_write_reg( & amp;mpu6050dev,MPU6050_PWR_MGMT_1, 0x00); //Release sleep state
mpu6050_write_reg( & amp;mpu6050dev,MPU6050_SMPLRT_DIV, 0x07);
mpu6050_write_reg( & amp;mpu6050dev,MPU6050_CONFIG, 0x06);
mpu6050_write_reg( & amp;mpu6050dev,MPU6050_GYRO_CONFIG, 0x18);
mpu6050_write_reg( & amp;mpu6050dev,MPU6050_ACCEL_CONFIG, 0x01);
}
/*
 * @description: Read the data of mpu6050 and read the original data
 * @param - dev: mpu6050 device
 * @return: None.
 */
void mpu6050_readdata(struct mpu6050_dev *dev)
{
unsigned char i =0;
    unsigned char buf[6];
unsigned char val = 0x3B;
/*
Read 6 bytes of data starting from the acceleration register (0x3B)
Start reading acceleration registers from register 0x3B for 6 bytes
*/
mpu6050_read_regs(dev, val, buf, 6);
for(i = 0; i < 3; i + + )
{
dev->acceleration[i] = (buf[i * 2] << 8 | buf[(i * 2) + 1]);
}
\t
/*
Read 6 bytes of data from the gyro instrument data register (0x43)
The register is auto incrementing on each read
Now gyro data from reg 0x43 for 6 bytes
     The register is auto incrementing on each read
*/
val = 0x43;
mpu6050_read_regs(dev, val, buf, 6);
for(i = 0; i < 3; i + + )
{
dev->gyro[i] = (buf[i * 2] << 8 | buf[(i * 2) + 1]);
}
/*
Read 2 bytes of data from the temperature register (0x41)
Register automatically increments on each read
Now temperature from reg 0x41 for 2 bytes
    The register is auto incrementing on each read
*/
val = 0x41;
mpu6050_read_regs(dev, val, buf, 2);
for(i = 0; i < 3; i + + )
{
dev->temp = buf[0] << 8 | buf[1];
}
}
 
 
/*
 * @description: open the device
 * @param - inode: the inode passed to the driver
 * @param - filp: device file, the file structure has a member variable called private_data
 * Generally, private_data points to the device structure when opening.
 * @return: 0 success; others failure
 */
static int mpu6050_open(struct inode *inode,struct file *filp)
{
 
filp->private_data = & amp;mpu6050dev;
printk("mpu6050-Drive_open\r\\
");
    return 0;
}
 
 
/*
 * @description: close/release the device
 * @param - filp: the device file (file descriptor) to be closed
 * @return: 0 success; others failure
 */
static int mpu6050_release(struct inode *inode,struct file *filp)
{
 
return 0;
}
 
/*
 * @description: Read data from the device
 * @param - filp: the device file (file descriptor) to be opened
 * @param - buf: data buffer returned to user space
 * @param - cnt: length of data to be read
 * @param - offt: offset relative to the first address of the file
 * @return: The number of bytes read. If it is a negative value, it means the read failed.
 */
static ssize_t mpu6050_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int16_t MPU6050_data[7];
long err = 0;
 
struct mpu6050_dev *dev = (struct mpu6050_dev *)filp->private_data;
\t
mpu6050_readdata(dev);
 
MPU6050_data[0] = dev->acceleration[0];
MPU6050_data[1] = dev->acceleration[1];
MPU6050_data[2] = dev->acceleration[2];
MPU6050_data[3] = dev->gyro[0];
MPU6050_data[4] = dev->gyro[1];
MPU6050_data[5] = dev->gyro[2];
MPU6050_data[6] = dev->temp;
err = copy_to_user(buf, MPU6050_data, sizeof(MPU6050_data));
return 0;
 
}
 
/*Character operation set*/
static const struct file_operations mpu6050_fops = {
.open = mpu6050_open,
.release = mpu6050_release,
.read = mpu6050_read,
.owner = THIS_MODULE,
};
 
 
/*
 * @description: The remove function of the i2c driver. This function will be executed when the i2c driver is removed.
 * @param - client: i2c device
 * @return: 0, success; other negative values, failure
 */
static int mpu6050_remove(struct i2c_client *client)
{
    /* Uninstall character device driver */
cdev_del( &mpu6050dev.cdev);
unregister_chrdev_region(mpu6050dev.devid,mpu6050dev_CNT);
device_destroy(mpu6050dev.class, mpu6050dev.devid);
class_destroy(mpu6050dev.class);
 
printk("mpu6050-Drive_EXIT!\r\\
");
return 0;
}
 
/*When the I2C device and I2C driver are successfully matched, the probe function will be executed*/
 /*
  * @description: probe function of i2c driver, when the driver and
  * This function will be executed after the device is matched.
  * @param - client: i2c device
  * @param - id: i2c device ID
  * @return: 0, success; other negative values, failure
  */
static int mpu6050_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
printk("mpu6050_probe\r\\
");
/*Build character device framework*/
/* 1. Register character device driver */
mpu6050dev.major = 0;
if(mpu6050dev.major){
mpu6050dev.devid = MKDEV(mpu6050dev.major,0);
register_chrdev_region(mpu6050dev.devid,mpu6050dev_CNT,mpu6050dev_NAME);
}else{
alloc_chrdev_region(&mpu6050dev.devid,0,mpu6050dev_CNT,mpu6050dev_NAME);
mpu6050dev.major = MAJOR(mpu6050dev.devid);
mpu6050dev.minor = MINOR(mpu6050dev.devid);
}
/* 2.Initialize cdev */
mpu6050dev.cdev.owner = THIS_MODULE;
cdev_init( & amp;mpu6050dev.cdev, & amp;mpu6050_fops);
 
/* 3. Add cdev */
cdev_add(&mpu6050dev.cdev,mpu6050dev.devid,mpu6050dev_CNT);
 
/* 4. Create class */
mpu6050dev.class = class_create(THIS_MODULE,mpu6050dev_NAME);
if (IS_ERR(mpu6050dev.class)) {
return PTR_ERR(mpu6050dev.class);
}
/* 5. Create device */
mpu6050dev.device = device_create(mpu6050dev.class, NULL, mpu6050dev.devid, NULL,mpu6050dev_NAME);
if (IS_ERR(mpu6050dev.device)) {
return PTR_ERR(mpu6050dev.device);
}
 
    mpu6050dev.private_data = client;
    mpu6050_reset();
printk("mpu6050_reset\\
");
 
return 0;
}
/* Traditional matching table, matching ID table when there is no device tree */
static struct i2c_device_id mpu6050_id[] = {
{"invensense,mpu6050",0},
{}
};
 
/*Device matching table*/
static struct of_device_id mpu6050_of_match[] ={
{ .compatible = "invensense,mpu6050" },
{}
};
 
/*i2c_driver*/
static struct i2c_driver mpu6050_driver = {
.driver = {
.name = "mpu6050",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(mpu6050_of_match),
},
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.id_table = mpu6050_id,
};
 
static int __init mpu6050dev_init(void){
int ret = 0;
ret = i2c_add_driver( & amp;mpu6050_driver);
 
return 0;
 
}
 
static void __exit mpu6050dev_exit(void){
 
i2c_del_driver( & amp;mpu6050_driver);
}
 
module_init(mpu6050dev_init);
module_exit(mpu6050dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("XXXX");

5. Write makefile

SDK_DIR = /home/milkv/duo_buildroot_sdk/duo-buildroot-sdk
KERN_DIR = $(SDK_DIR)/linux_5.10/build/cv1800b_milkv_duo_sd

all:
make -C $(KERN_DIR) M=$(PWD) modules
$(CROSS_COMPILE)gcc mpu6050-app.c -o mpu6050-app -Wall -pthread -O2

clean:
make -C $(KERN_DIR) M=$(PWD) modules clean
rm -rf modules.order
rm -rf mpu6050-app

obj-m + = mpu6050.o

6. Write test files

//https://blog.csdn.net/m0_58844968/article/details/124994041
#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>
 
 
/*
 * @description: main main program
 * @param - argc: the number of elements in the argv array
 * @param - argv: specific parameters
 * @return: 0 success; others failure
 */
int main(int argc, char *argv[])
{
int fd;
char *filename;
int16_t databuf[7];
int16_t gyro_x_adc, gyro_y_adc, gyro_z_adc;
int16_t accel_x_adc, accel_y_adc, accel_z_adc;
int16_t temp_adc;
 
int ret = 0;
 
filename = "/dev/mpu6050";
fd = open(filename, O_RDWR);
if(fd < 0) {
printf("can't open file %s\r\\
", filename);
return -1;
}
 
while (1) {
ret = read(fd, databuf, sizeof(databuf));
if(ret == 0) { /* Data read successfully */
accel_x_adc = databuf[0];
accel_y_adc = databuf[1];
accel_z_adc = databuf[2];
gyro_x_adc = databuf[3];
gyro_y_adc = databuf[4];
gyro_z_adc = databuf[5];
temp_adc = databuf[6];
 
printf("Acc.
printf("Gyro. X = %d, Y = %d, Z = %d\\
", gyro_x_adc, gyro_y_adc, gyro_z_adc);
// Temperature is simple so use the datasheet calculation to get deg C.
// Note this is chip temperature.
printf("Temp. = %f\\
", (temp_adc / 340.0) + 36.53);
}
usleep(1000000); /*1000ms */
}
close(fd); /* Close the file */
return 0;
}

7. Compilation

make
file mpu6050-app
modinfo mpu6050.ko

8. Install ko

insmod mpu6050.ko
lsmod
dmesg|tail
ls /dev

9. Test