14.IIC核心函数与万能驱动

这篇具有很好参考价值的文章主要介绍了14.IIC核心函数与万能驱动。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

IIC核心函数

i2c_add_adapter()

i2c_add_driver()宏

i2c_transfer()

i2c_master_send()

i2c_master_recv()

i2c_transfer_buffer_flags()

万能的i2c驱动(i2c-dev.c文件)

i2c_dev_init()

i2cdev_attach_adapter()

i2cdev_fops结构体

i2cdev_open()

i2cdev_read()

i2cdev_write()

IIC驱动实验:读取mpu6050数据

mpu6050

添加设备树iomuxc子节点

I2C1子节点

设备树插入编译加载

i2c_mpu6050.c文件

App.c文件

Makefile文件

执行过程


IIC核心函数

i2c_add_adapter()

该函数存放在内核/drivers/i2c/i2c-core-base.c文件。注册一个i2c适配器

/*
 * 注册一个i2c适配器
 * adapter->nr:适配器的编号
 * adapter:i2c物理控制器对应的适配器
 */

// 由kernel分配适配器的编号
int i2c_add_adapter(struct i2c_adapter *adapter)
// 自己指定适配器的编号
int i2c_add_numbered_adapter(struct i2c_adapter *adapter)

/*
 * 返回值:
 *    成功:0
 *    失败:负值
 */

i2c_add_driver()宏

该函数存放在内核/include/linux/i2c.h文件。注册一个i2c驱动

// struct i2c_driver
#define i2c_add_driver(driver) \ i2c_register_driver(THIS_MODULE, driver)

/*
 * 存放在内核/drivers/i2c/i2c-core-base.c文件
 * 注册一个i2c驱动
 * owner:一般为THIS_MODULE
 * driver:要注册的i2c_driver,对应的驱动结构体由我们自己来实现
 */
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
/*
 * 返回值:
 *    成功:0
 *    失败:负值
 */

i2c_transfer()

该函数存放在内核/drivers/i2c/i2c-core-base.c文件。进行iic消息收发(可收可发)

该函数的核心是:adap → algo → master_xfer(adap, msgs, num),也就是适配器的具体通信方法。

/*
 * 收发消息都要用到i2c_adapter适配器
 * adap:所使用的的I2C适配器,i2c_client会保存其对应的i2c_adapter
 * msgs:i2c要发生的一个或多个消息,每个消息对应一个消息结构体
 * num:消息数量(即msgs的数量)
 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
/*
 * 返回值:
 *    成功:发送的msgs的数量
 *    失败:负值
 */

/* 该结构体存放在内核/include/uapi/linux/i2c.h文件 */
struct i2c_msg
{
	__u16 addr;	    // 从机地址,发送到哪个从设备地址
	__u16 flags;    // 读写等其他特性,见下面的宏

	// 此宏表示读,0表示写
	#define I2C_M_RD		    0x0001	/* read data, from slave to master */
					                    /* I2C_M_RD is guaranteed to be 0x0001! */
	#define I2C_M_TEN		    0x0010	/* this is a ten bit chip address */
	#define I2C_M_DMA_SAFE		0x0200	/* the buffer of this message is DMA safe */
					                    /* makes only sense in kernelspace */
					                    /* userspace buffers are copied anyway */
	#define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */
	#define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_NOSTART */
	#define I2C_M_STOP		    0x8000	/* if I2C_FUNC_PROTOCOL_MANGLING */
	
	__u16 len;		// 消息数据的长度
	__u8 *buf;		// 发送或接收的消息缓冲区
};

i2c_master_send()

该函数存放在内核/include/linux/i2c.h文件。发送一个i2c消息(只能是一个)。其实最终调用的还是i2c_transfer()。

static inline int i2c_master_send(const struct i2c_client *client,
				  const char *buf, int count)
{
	return i2c_transfer_buffer_flags(client, (char *)buf, count, 0);
};

i2c_master_recv()

该函数存放在内核/include/linux/i2c.h文件。接收一个i2c消息(只能是一个)。其实最终调用的还是i2c_transfer()。

static inline int i2c_master_recv(const struct i2c_client *client,
				  char *buf, int count)
{
	return i2c_transfer_buffer_flags(client, buf, count, I2C_M_RD);
};

i2c_transfer_buffer_flags()

该函数存放在内核/drivers/i2c/i2c-core-base.c文件。发送一个i2c消息。其实最终调用的还是i2c_transfer()。

int i2c_transfer_buffer_flags(const struct i2c_client *client, char *buf, int count, u16 flags)
{
	int ret;
	// 根据传入参数构建消息结构体,此结构体定义详见上
	struct i2c_msg msg =
	{
		.addr = client->addr,
		.flags = flags | (client->flags & I2C_M_TEN),
		.len = count,
		.buf = buf,
	};

	ret = i2c_transfer(client->adapter, &msg, 1);

	/*
	 * If everything went ok (i.e. 1 msg transferred), return #bytes
	 * transferred, else error code.
	 */
	return (ret == 1) ? count : ret;
}

万能的i2c驱动(i2c-dev.c文件)

文件存放于内核/drivers/i2c/。

内核集成i2c_dev驱动模块,开机自动加载

为每个i2c_adapter生成一个设备文件,通过该设备文件间接使用i2c核心函数收发数据

注册i2c总线的通知函数,解决加载顺序问题

i2c_dev_init()

#define I2C_MAJOR	89		/* Device major number		*/
#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)
#define I2C_MINORS	MINORMASK

static int __init i2c_dev_init(void)
{
	int res;

	printk(KERN_INFO "i2c /dev entries driver\n");
	// 申请设备号,I2C_MAJOR为89,次设备号为0,I2C_MINORS为1<<20-1,表示次设备号的数量。
	// 就是把这个主设备号对应的次设备号都申请了。
	res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
	if (res)
		goto out;
	// 创建一个同名类,在 /sys/class中可以看到
	i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
	if (IS_ERR(i2c_dev_class)) {
		res = PTR_ERR(i2c_dev_class);
		goto out_unreg_chrdev;
	}
	...
	// 注册i2c总线的通知函数
	// 参数2详见下
	res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
	if (res)
		goto out_unreg_class;

	/* Bind to already existing adapters right away */
	// 遍历i2c总线上的所有设备,每次都执行第二个参数对应的函数
	i2c_for_each_dev(NULL, i2cdev_attach_adapter);

	return 0;

out_unreg_class:
	class_destroy(i2c_dev_class);
out_unreg_chrdev:
	unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
out:
	printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
	return res;
}

static struct notifier_block i2cdev_notifier =
{
	// 详见下
	.notifier_call = i2cdev_notifier_call,
};

static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action,
			 void *data)
{
	struct device *dev = data;
	// 发生的事件类型
	switch (action) {
	// 此i2c总线下发生添加设备事件
	case BUS_NOTIFY_ADD_DEVICE:
		// 创建设备文件之类的操作
		return i2cdev_attach_adapter(dev, NULL);
	// 此i2c总线下发生删除设备事件
	case BUS_NOTIFY_DEL_DEVICE:
		return i2cdev_detach_adapter(dev, NULL);
	}

	return 0;
}

int i2c_for_each_dev(void *data, int (*fn)(struct device *, void *))
{
	int res;

	mutex_lock(&core_lock);
	res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn);
	mutex_unlock(&core_lock);

	return res;
}

int bus_for_each_dev(struct bus_type *bus, struct device *start,
		     void *data, int (*fn)(struct device *, void *))
{
	struct klist_iter i;
	struct device *dev;
	int error = 0;

	if (!bus || !bus->p)
		return -EINVAL;

	klist_iter_init_node(&bus->p->klist_devices, &i,
			     (start ? &start->p->knode_bus : NULL));
	while (!error && (dev = next_device(&i)))
		error = fn(dev, data);
	klist_iter_exit(&i);
	return error;
}

i2cdev_attach_adapter()

i2c总线添加设备后回调执行的函数

static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
	struct i2c_adapter *adap;
	struct i2c_dev *i2c_dev;
	int res;
	// 若设备类型不是i2c适配器,直接返回
	// 也有可能是 i2c_client 设备
	if (dev->type != &i2c_adapter_type)
		return 0;
	// 从i2c设备结构体中获取i2c适配器结构体
	adap = to_i2c_adapter(dev);
	// 分配内存
	i2c_dev = get_free_i2c_dev(adap);
	if (IS_ERR(i2c_dev))
		return PTR_ERR(i2c_dev);
	// 设置文件操作接口
	cdev_init(&i2c_dev->cdev, &i2cdev_fops);
	i2c_dev->cdev.owner = THIS_MODULE;
	// 注意次设备号为适配器对应的编号,可以自己指定,也可以有系统分配
	// 参数3为次设备号的数量,此fops只对应次设备号的文件
	res = cdev_add(&i2c_dev->cdev, MKDEV(I2C_MAJOR, adap->nr), 1);
	if (res)
		goto error_cdev;

	/* register this i2c device with the driver core */
	// 在/sys下,在i2c_dev_class类(参数1)下创建一个新的目录, "i2c-适配器编号"就是目录名
	// 然后会通知用户空间的uevent守护进程,此守护进程会创建一个同参数4名的设备文件
	i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
				     MKDEV(I2C_MAJOR, adap->nr), NULL,
				     "i2c-%d", adap->nr);
	if (IS_ERR(i2c_dev->dev)) {
		res = PTR_ERR(i2c_dev->dev);
		goto error;
	}

	pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
		 adap->name, adap->nr);
	return 0;
error:
	cdev_del(&i2c_dev->cdev);
error_cdev:
	put_i2c_dev(i2c_dev);
	return res;
}

i2cdev_fops结构体

static const struct file_operations i2cdev_fops =
{
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,

	.read		= i2cdev_read,
	.write		= i2cdev_write,
 
	.unlocked_ioctl	= i2cdev_ioctl,
	.compat_ioctl	= compat_i2cdev_ioctl,

	.open		= i2cdev_open,
	.release	= i2cdev_release,
};

i2cdev_open()

static int i2cdev_open(struct inode *inode, struct file *file)
{
	// 从inode获取次设备号,就是适配器的编号
	unsigned int minor = iminor(inode);
	struct i2c_client *client;
	struct i2c_adapter *adap;

	// 根据次设备号从i2c总线获取对应的适配器
	adap = i2c_get_adapter(minor);
	if (!adap)
		return -ENODEV;

	// client表示某个具体的i2c设备,为其分配内存
	client = kzalloc(sizeof(*client), GFP_KERNEL);
	if (!client) {
		i2c_put_adapter(adap);
		return -ENOMEM;
	}

	// 设置具体的i2c设备的名字
	snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
	// 设置具体的i2c设备归属的适配器
	client->adapter = adap;
	// 以后可以通过file的此成员获取client指针
	file->private_data = client;

	return 0;
}

i2cdev_read()

static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
		loff_t *offset)
{
	char *tmp;
	int ret;

	struct i2c_client *client = file->private_data;

	if (count > 8192)
		count = 8192;

	// 分配内存,用于接收消息
	tmp = kmalloc(count, GFP_KERNEL);
	if (tmp == NULL)
		return -ENOMEM;

	pr_debug("i2c-dev: i2c-%d reading %zu bytes.\n", iminor(file_inode(file)), count);
	
    // 核心函数,接受一个i2c消息
	ret = i2c_master_recv(client, tmp, count);
	if (ret >= 0)
		ret = copy_to_user(buf, tmp, count) ? -EFAULT : ret;

	kfree(tmp);
	return ret;
}

i2cdev_write()

static ssize_t i2cdev_write(struct file *file, const char __user *buf,
		size_t count, loff_t *offset)
{
	int ret;
	char *tmp;
	struct i2c_client *client = file->private_data;

	if (count > 8192)
		count = 8192;

	把用户空间的buf拷贝内核空间的tmp
	tmp = memdup_user(buf, count);
	if (IS_ERR(tmp))
		return PTR_ERR(tmp);

	pr_debug("i2c-dev: i2c-%d writing %zu bytes.\n", iminor(file_inode(file)), count);
	
    // 核心函数,发送一个i2c消息
	ret = i2c_master_send(client, tmp, count);

	kfree(tmp);
	return ret;
}

IIC驱动实验:读取mpu6050数据

mpu6050

空间运动传感器芯片:3轴加速度、3轴角速度。

添加设备树iomuxc子节点

采用传统设备树的方法,不使用插件设备树。设备树文件中添加新的引脚组。

pinctrl_i2c1: i2c1grp {
                // 添加i2c控制器的两个引脚,对应的pin复用为i2c1控制器的相关引脚
                fsl,pins = < 
                        MX6UL_PAD_UART4_TX_DATA__I2C1_SCL       0x4001b8b0
                        MX6UL_PAD_UART4_RX_DATA__I2C1_SDA       0x4001b8b0
                >;
        };  

I2C1子节点

&i2c1
{
        // 设置i2c1控制器的时钟频率,100k
        clock-frequency = <100000>;
        // 声明设备节点所使用的的引脚状态(pinctrl-0),使用pinctrl_i2c1引脚组
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_i2c1>;
        status = "okay";
        // i2c1节点下的每个子节点,在kernel加载时都会被解析成一个i2c_client结构体
        i2c_mpu6050@68
        {
                compatible = "fire,i2c_mpu6050";
                // 会被转换成i2c设备地址
                 reg = <0x68>;
                status = "okay";
         };
};

设备树插入编译加载

虚拟机:

重新编译设备树:make ARCH=arm -j4 CROSS_COMPILE=arm-linux-gnueabihf- dtbs

然后拷贝到共享文件夹:sudo cp 内核/arch/arm/boot/dts/imx6ull-mmc-npi.dtb /home/couvrir/桌面/sharedir

开发板:

替代旧的二进制设备树:sudo cp /mnt/imx6ull-mmc-npi.dtb /usr/lib/linux-image-4.19.35-imx6/imx6ull-mmc-npi.dtb

同步缓冲区:sync

重启设备:sudo reboot

i2c_mpu6050.c文件

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

#include <linux/i2c.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/platform_device.h>

#include <asm/mach/map.h>
#include <asm/io.h>

#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>

#define SMPLRT_DIV      0X19    /* Sample Rate Divider:采样速率分频器 */
#define CONFIG          0X1A    /* 配置:EXT_SYNC_SET(外部帧同步)+DLPF_CFG(数字低通滤波器) */
#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    /* Power Management:电源管理 */
#define WHO_AM_I        0X75    /* 7位I2C地址的高6位 */

#define DEV_NAME        "I2C1_mpu6050"
#define DEV_CNT         (1)

static dev_t mpu6050_devno;
static struct cdev mpu6050_chrdev;
struct class *class_mpu6050;
struct device *device_mpu6050;
struct device_node *mpu6050_device_node;

/* 保存mpu6050设备对应的i2c_client结构体,匹配成功后由.probe函数传入参数 */
struct i2c_client *mpu6050_client = NULL;

/*
 * 通过i2c向mpu6050写入数据
 * mpu6050_client:mpu6050的i2c_client结构体
 * address:数据要写入的地址
 * data:要写入的数据
 */
static int i2c_write_mpu6050(struct i2c_client *mpu6050_client, u8 address, u8 data)
{
        int error = 0;
        u8 write_data[2];
        struct i2c_msg send_msg;                //要发送的数据结构体

        /* 地址 + 数据 */
        write_data[0] = address;
        write_data[1] = data;

        send_msg.addr = mpu6050_client->addr;   //mpu6050在i2c总线上的地址
        send_msg.flags = 0;                     //标记为发送数据
        send_msg.buf = write_data;              //写入的首地址
        send_msg.len = 2;                       //reg长度

        error = i2c_transfer(mpu6050_client->adapter, &send_msg, 1);
        if(error != 1){
                printk(KERN_DEBUG "i2c_transfer error\n");
                return -1;
        }

        return 0;
}

/*
 * 通过i2c向mpu6050读取数据
 * mpu6050_client:mpu6050的i2c_client结构体
 * address:数据要读取的地址
 * data:保存读取的数据
 * length:读取到的数据长度
 */
static int i2c_read_mpu6050(struct i2c_client *mpu6050_client, u8 address, void *data, u32 length)
{
        int error = 0;
        u8 address_data = address;
        struct i2c_msg send_msg[2];

        send_msg[0].addr = mpu6050_client->addr;        //mpu6050在i2c总线上的地址
        send_msg[0].flags = 0;                          //标记为发送数据
        send_msg[0].buf = &address_data;                //写入的首地址
        send_msg[0].len = 1;                            //写入长度

        send_msg[1].addr = mpu6050_client->addr;        //mpu6050在i2c总线上的地址
        send_msg[1].flags = I2C_M_RD;                   //标记为读取数据
        send_msg[1].buf = data;                         //保存读取数据
        send_msg[1].len = length;                       //读取长度

        error = i2c_transfer(mpu6050_client->adapter, send_msg, 2);
        if(error != 2){
                printk(KERN_DEBUG "i2c_transfer error\n");
                return -1;
        }

        return 0;
}

static int mpu6050_init(void)
{
        int error = 0;

        /* 配置mpu6050电源管理,0x00, 解除休眠状态 */
        error += i2c_write_mpu6050(mpu6050_client, PWR_MGMT_1, 0X00);

        /* 设置mpu6050的采样率:1k/(1+7)= 125kz */
        error += i2c_write_mpu6050(mpu6050_client, SMPLRT_DIV, 0X07);

        /* 设置数字低通滤波器频率5hz和帧同步引脚采样 */
        error += i2c_write_mpu6050(mpu6050_client, CONFIG, 0X06);

        /* 加速度计不自检,输出满量程范围:±2g */
        error += i2c_write_mpu6050(mpu6050_client, ACCEL_CONFIG, 0X01);

        if(error < 0){
                printk(KERN_DEBUG "mpu6050_init error\n");
                return -1;
        }

        return 0;
}

static int mpu6050_open(struct inode *inode, struct file *filp)
{
        /* 向mpu6050发送配置数据,让mpu6050处于正常状态 */
        mpu6050_init();
        return 0;
}

static ssize_t mpu6050_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
        char data_H, data_L;
        int error;
        short mpu6050_result[6];        //保存mpu6050转换的原始数据

        /* 读取3轴加速度原始值 */
        i2c_read_mpu6050(mpu6050_client, ACCEL_XOUT_H, &data_H, 1);
        i2c_read_mpu6050(mpu6050_client, ACCEL_XOUT_L, &data_L, 1);
        mpu6050_result[0] = data_H << 8;
        mpu6050_result[0] += data_L;

        i2c_read_mpu6050(mpu6050_client, ACCEL_YOUT_H, &data_H, 1);
        i2c_read_mpu6050(mpu6050_client, ACCEL_YOUT_L, &data_L, 1);
        mpu6050_result[1] = data_H << 8;
        mpu6050_result[1] += data_L;

        i2c_read_mpu6050(mpu6050_client, ACCEL_ZOUT_H, &data_H, 1);
        i2c_read_mpu6050(mpu6050_client, ACCEL_ZOUT_L, &data_L, 1);
        mpu6050_result[2] = data_H << 8;
        mpu6050_result[2] += data_L;

        /* 读取3轴角速度原始值 */
        i2c_read_mpu6050(mpu6050_client, GYRO_XOUT_H, &data_H, 1);
        i2c_read_mpu6050(mpu6050_client, GYRO_XOUT_L, &data_L, 1);
        mpu6050_result[3] = data_H << 8;
        mpu6050_result[3] += data_L;

        i2c_read_mpu6050(mpu6050_client, GYRO_YOUT_H, &data_H, 1);
        i2c_read_mpu6050(mpu6050_client, GYRO_YOUT_L, &data_L, 1);
        mpu6050_result[4] = data_H << 8;
        mpu6050_result[4] += data_L;

        i2c_read_mpu6050(mpu6050_client, GYRO_ZOUT_H, &data_H, 1);
        i2c_read_mpu6050(mpu6050_client, GYRO_ZOUT_L, &data_L, 1);
        mpu6050_result[5] = data_H << 8;
        mpu6050_result[5] += data_L;

		/* 将读取的数据拷贝到用户空间 */
        error = copy_to_user(buf, mpu6050_result, cnt);
        if(error != 0){
                printk("copy_to_user error!\n");
                return -1;
        }

        return 0;
}

static int mpu6050_release(struct inode *inode, struct file *filp)
{
        return 0;
}

static struct file_operations mpu6050_chrdev_fops = {
        .owner = THIS_MODULE,
        .open = mpu6050_open,
        .read = mpu6050_read,
        .release = mpu6050_release,
};

static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
        int ret = -1;   //保存错误状态码

        printk(KERN_EMERG "match successed!\n");

        /***********************************注册字符设备部分**************************************/

        // 采用动态分配的方式,获取设备编号,次设备号为0,设备名称为rgb-leds,可通过命令cat /proc/devices查看
        // DEV_CNT为1,当前只申请一个设备编号
        ret = alloc_chrdev_region(&mpu6050_devno, 0, DEV_CNT, DEV_NAME);
        if(ret < 0){
                printk("fail to alloc mpu6050_devno\n");
                goto alloc_err;
        }

        // 关联字符设备结构体cdev与文件操作结构体file_operations
        mpu6050_chrdev.owner = THIS_MODULE;
        cdev_init(&mpu6050_chrdev, &mpu6050_chrdev_fops);

		// 添加设备到cdev_map哈希表中
        ret = cdev_add(&mpu6050_chrdev, mpu6050_devno, DEV_CNT);
        if(ret < 0){
                printk("fail to add cdev\n");
                goto add_err;
        }

        // 创建类
        class_mpu6050 = class_create(THIS_MODULE, DEV_NAME);

        // 创建设备DEV_NAME指定设备名
        device_mpu6050 = device_create(class_mpu6050, NULL, mpu6050_devno, NULL, DEV_NAME);
        mpu6050_client = client;
        return 0;

alloc_err:
        return -1;
add_err:
        //添加设备失败时,需要注销设备号
        unregister_chrdev_region(mpu6050_devno, DEV_CNT);
}

static int mpu6050_remove(struct i2c_client *client)
{
        device_destroy(class_mpu6050, mpu6050_devno);
        class_destroy(class_mpu6050);
        cdev_del(&mpu6050_chrdev);
        unregister_chrdev_region(mpu6050_devno, DEV_CNT);

        return 0;
}

/* 定义ID匹配表 */
static const struct i2c_device_id gtp_device_id[] = {
        {"fire,i2c_mpu6050", 0},
        {/* sentinel */},
};

/* 定义设备树匹配表 */
static const struct of_device_id mpu6050_of_match_table[] = {
        {.compatible = "fire,i2c_mpu6050"},
        {/* sentinel */},
};

struct i2c_driver mpu6050_driver ={
        .probe = mpu6050_probe,
        .remove = mpu6050_remove,
        .id_table = gtp_device_id,
        .driver = {
                /*      根据.name去和设备树的i2c1节点匹配,
                        匹配成功后设备树的i2c1节点会自动转化为i2c_client结构体,
                        然后i2c_client结构体和i2c_driver结构体进行配对
                */
                .name = "fire,i2c_mpu6050",
                .owner = THIS_MODULE,
                .of_match_table = mpu6050_of_match_table,
        },
};

static int __init mpu6050_driver_init(void)
{
        int ret;
        pr_info("mpu6050_driver_init!\n");
        ret = i2c_add_driver(&mpu6050_driver);

        return ret;
}

static void __exit mpu6050_driver_exit(void){
        pr_info("mpu6050_driver_exit!\n");
        i2c_del_driver(&mpu6050_driver);
}

module_init(mpu6050_driver_init);
module_exit(mpu6050_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("couvrir");
MODULE_DESCRIPTION("led module");
MODULE_ALIAS("led module");

App.c文件

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
 
int main(int argc, char *argv[])
{
		/* 保存接收到的mpu6050转换结构数据,依次是Ax、Ay、Az、Gx、Gy、Gz */
        short receive_data[6];
 
        int fd = open("/dev/I2C1_mpu6050", O_RDWR);
        if(fd < 0){
                printf("open file:%s failed!!!\n", argv[0]);
                return -1;
        }
 
        int error = read(fd, receive_data, 12);
        if(error < 0){
                printf("read file error!\n");
                close(fd);
        }
 
		printf("AX=%d, AY=%d, AZ=%d ", (int)receive_data[0], (int)receive_data[1], (int)receive_data[2]);
		printf(" GX=%d, GY=%d, GZ=%d\n\n", (int)receive_data[3], (int)receive_data[4], (int)receive_data[5]);
 
        error = close(fd);
        if(error < 0){
                printf("close file error!\n");
        }
 
        return 0;
}

Makefile文件

照旧

执行过程

虚拟机:

执行makemake copy。生成.ko文件。

debian系统里面默认加载了一部分插件(使用到i2c控制器),为了不干扰实验结果,我们屏蔽掉iic的插件。#屏蔽掉iic内容的dtbo,包括i2c1、i2c2、lcd5(i2c触摸芯片)、lcd43(i2c触摸芯片)。

sudo vim /boot/uEnv.txt 

sudo reboot

开发板(在挂载目录下执行):

sudo insmod i2c_mpu6050.ko

ls /dev/I2C1_mpu6050

sudo ./App

sudo rmmod i2c_mpu6050.ko

14.IIC核心函数与万能驱动,# 野火i.mx 6ull内核驱动进阶,linux​​​​​​​文章来源地址https://www.toymoban.com/news/detail-683833.html

到了这里,关于14.IIC核心函数与万能驱动的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 016——DHT11驱动开发(基于I.MX6uLL)

    016——DHT11驱动开发(基于I.MX6uLL)

    目录 一、 模块介绍 1.1 简介 1.2 电路描述 1.3 通信协议 二、 驱动程序 三、 应用程序 四、 上机实验         DHT11 是一款可测量温度和湿度的传感器。比如市面上一些空气加湿器,会测量空气中湿度,再根据测量结果决定是否继续加湿。DHT11 数字温湿度传感器是一款含有已

    2024年04月16日
    浏览(9)
  • linux驱动开发 ST7789 LCD驱动移植(I.MX6ULL平台)

    linux驱动开发 ST7789 LCD驱动移植(I.MX6ULL平台)

    前言 I.MX6ULL的板子未选配RGB的屏幕,无法在板子上进行GUI的开发调试,不过手头上有块控制器为ST7789V3的LCD屏幕(1.3inch),通过简易接线后可以很方便进行驱动的移植 如有异议,欢迎留言指正 ST7789 LCD控制器 ST7789是一款单芯片TFT-LCD控制器,支持并口与SPI通信方式 特性 控制器支

    2023年04月09日
    浏览(12)
  • I.MX6ULL_Linux_驱动篇(57)linux Regmap API驱动

    I.MX6ULL_Linux_驱动篇(57)linux Regmap API驱动

    我们在前面学习 I2C 和 SPI 驱动的时候,针对 I2C 和 SPI 设备寄存器的操作都是通过相关的 API 函数进行操作的。这样 Linux 内核中就会充斥着大量的重复、冗余代码,但是这些本质上都是对寄存器的操作,所以为了方便内核开发人员统一访问 I2C/SPI 设备的时候,为此引入了 Reg

    2024年03月26日
    浏览(13)
  • I.MX6ULL_Linux_驱动篇(42)设备树与platform设备驱动

    上一章我们详细的讲解了 Linux 下的驱动分离与分层,以及总线、设备和驱动这样的驱动框架。基于总线、设备和驱动这样的驱动框架, Linux 内核提出来 platform 这个虚拟总线,相应的也有 platform 设备和 platform 驱动。上一章我们讲解了传统的、未采用设备树的 platform 设备和驱

    2024年02月14日
    浏览(9)
  • 018——红外遥控模块驱动开发(基于HS0038和I.MX6uLL)

    018——红外遥控模块驱动开发(基于HS0038和I.MX6uLL)

    目录 一、 模块介绍 1.1 简介 1.2 协议 二、 驱动代码 三、 应用代码 四、 实验 五、 程序优化         红外遥控被广泛应用于家用电器、工业控制和智能仪器系统中,像我们熟知的有电视机盒子遥控器、空调遥控器。红外遥控器系统分为发送端和接收端,如图下图所示。

    2024年04月16日
    浏览(10)
  • 017——DS18B20驱动开发(基于I.MX6uLL)

    017——DS18B20驱动开发(基于I.MX6uLL)

    目录 一、 模块介绍 1.1 简介 1.2 主要特点 1.3 存储器介绍 1.4 时序 1.5 命令 1.5.1 命令大全    1.5.2 命令使用 1.5.3 使用示例 1.6 原理图 二、 驱动程序 三、 应用程序 四、 测试         DS18B20 温度传感器具有线路简单、体积小的特点,用来测量温度非常简单,在一根通信线上

    2024年04月12日
    浏览(11)
  • i.MX6ULL驱动开发 | 27 - 使用WM8960 CODEC播放音频

    i.MX6ULL驱动开发 | 27 - 使用WM8960 CODEC播放音频

    WM8960是欧胜公司(wolfson)的一款低功耗、高质量的立体声音频编解码芯片。 其内部集成D类喇叭功放,每个通道可以驱动一个1W喇叭(8Ω),内部集成3个立体声输入源,可以灵活配置,拥有一路完整的麦克风接口。 WM8960内部ADC和DAC都为24位,主要特性如下: DAC的SNR(信噪比)

    2024年02月02日
    浏览(10)
  • NXP i.MX 6ULL工业核心板硬件说明书( ARM Cortex-A7,主频792MHz)

    NXP i.MX 6ULL工业核心板硬件说明书( ARM Cortex-A7,主频792MHz)

    创龙科技SOM-TLIMX6U是一款基于NXP i.MX 6ULL的ARM Cortex-A7高性能低功耗处理器设计的低成本工业级核心板,主频792MHz,通过邮票孔连接方式引出Ethernet、UART、CAN、LCD、USB等接口。核心板经过专业的PCB Layout和高低温测试验证,稳定可靠,可满足各种工业应用环境。 SOM-TLIMX6U核心板板

    2024年02月16日
    浏览(9)
  • I.MX6ULL_Linux_驱动篇(45)linux INPUT子系统

    I.MX6ULL_Linux_驱动篇(45)linux INPUT子系统

    按键、鼠标、键盘、触摸屏等都属于输入(input)设备, Linux 内核为此专门做了一个叫做 input子系统的框架来处理输入事件。输入设备本质上还是字符设备,只是在此基础上套上了 input 框 架,用户只需要负责上报输入事件,比如按键值、坐标等信息, input 核心层负责处理这些

    2024年02月14日
    浏览(25)
  • I.MX6ULL_Linux_驱动篇(39) 阻塞和非阻塞IO

    I.MX6ULL_Linux_驱动篇(39) 阻塞和非阻塞IO

    阻塞和非阻塞 IO 是 Linux 驱动开发里面很常见的两种设备访问模式,在编写驱动的时候一定要考虑到阻塞和非阻塞。本章我们就来学习一下阻塞和非阻塞 IO,以及如何在驱动程序中处理阻塞与非阻塞,如何在驱动程序使用等待队列和 poll 机制。 阻塞和非阻塞简介 这里的“IO”

    2024年02月12日
    浏览(30)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包