13-I2C驱动

从内核中最简单的驱动程序入手,描述Linux驱动开发,主要文章目录如下(持续更新中):
01 - 第一个内核模块程序
02 - 注册字符设备驱动
03 - open & close 函数的应用
04 - read & write 函数的应用
05 - ioctl 的应用
06 - ioctl LED灯硬件分析
07 - ioctl 控制LED软件实现(寄存器操作)
08 - ioctl 控制LED软件实现(库函数操作)
09 - 注册字符设备的另一种方法(常用)
10 - 一个cdev实现对多个设备的支持
11 - 四个cdev控制四个LED设备
12 - 虚拟串口驱动
13 - I2C驱动
14 - SPI协议及驱动讲解
15 - SPI Linux驱动代码实现
16 - 非阻塞型I/O
17 - 阻塞型I/O
18 - I/O多路复用之 select
19 - I/O多路复用之 poll
20 - I/O多路复用之 epoll
21 - 异步通知


文章以MPU6050三轴加速度计为例,描述I2C驱动的编写

示例代码

设备树

参考文档 Documentation/i2c/instantiating-devices 对设备树进行添加,修改文件 arch/arm/boot/dts/am335x-evmsk.dts,添加以下代码。

mpu6050: mpu6050@68 {
	compatible = "ti,mpu6050";
	reg = <0x68>;
};

驱动 drv.c

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/mod_devicetable.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/device.h>

#include <uapi/asm-generic/errno-base.h>

#include "mpu6050.h"

#define MPU6050_MAJOR 255
#define MPU6050_MINOR 0
#define MPU6050_NAME "mpu6050_cdev"

#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

struct mpu6050_dev{
	struct i2c_client *client;
	struct cdev cdev;
};

struct class  *cls = NULL;
struct device *dev = NULL;

static int mpu6050_open(struct inode *inode, struct file *filp)
{
	struct mpu6050_dev *mpu6050 = container_of(inode->i_cdev, struct mpu6050_dev, cdev);
	filp->private_data = mpu6050;

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	return 0;
}

static int mpu6050_release(struct inode *inode, struct file *filp)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	return 0;
}

static ssize_t mpu6050_read(struct file *filp, char __user *userbuf, size_t size, loff_t *offset)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	return 0;
}

static ssize_t mpu6050_write(struct file *filp, const char __user *userbuf, size_t size, loff_t *offset)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	return 0;
}

static long mpu6050_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
{
	struct mpu6050_dev *mpu6050 = filp->private_data;
	struct atg_val val;

	if ( _IOC_TYPE(cmd) != MPU6050_MAGIC)
	{
		return -ENOTTY;	/* not a typewriter */
	}

	switch(cmd)
	{
	case MPU6050_GET_VAL:
		val.accelx = i2c_smbus_read_word_data(mpu6050->client, ACCEL_XOUT_H);
		val.accely = i2c_smbus_read_word_data(mpu6050->client, ACCEL_YOUT_H);
		val.accelz = i2c_smbus_read_word_data(mpu6050->client, ACCEL_ZOUT_H);
		val.temp   = i2c_smbus_read_word_data(mpu6050->client, TEMP_OUT_H);
		val.gyrox  = i2c_smbus_read_word_data(mpu6050->client, GYRO_XOUT_H);
		val.gyroy  = i2c_smbus_read_word_data(mpu6050->client, GYRO_YOUT_H);
		val.gyroz  = i2c_smbus_read_word_data(mpu6050->client, GYRO_ZOUT_H);
		val.accelx = be16_to_cpu(val.accelx);
		val.accely = be16_to_cpu(val.accely);
		val.accelz = be16_to_cpu(val.accelz);
		val.temp   = be16_to_cpu(val.temp);
		val.gyrox  = be16_to_cpu(val.gyrox);
		val.gyroy  = be16_to_cpu(val.gyroy);
		val.gyroz  = be16_to_cpu(val.gyroz);
		if (copy_to_user((struct atg_val __user *)args, &val, sizeof(struct atg_val)))
			return -EFAULT;
		break;
	default:
		printk("cmd error.\n");
	}

	return 0;
}

const struct file_operations mpu6050_ops = {
	.open = mpu6050_open,
	.release = mpu6050_release,
	.read = mpu6050_read,
	.write = mpu6050_write,
	.unlocked_ioctl = mpu6050_ioctl,
};

static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	dev_t dev_no;
	int ret;
	struct mpu6050_dev *mpu6050;

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);
	printk("%p.\n", client);

	dev_no = MKDEV(MPU6050_MAJOR, MPU6050_MINOR);
	ret = register_chrdev_region(dev_no, 1, MPU6050_NAME);
	if ( ret )
	{
		printk("register_chrdev_region failed.\n");
		goto reg_err;
	}

	mpu6050 = kzalloc(sizeof(struct mpu6050_dev), GFP_KERNEL);	/* GFP_KERNEL = 0 */
	if ( IS_ERR(mpu6050) )		/* 判断指针是否有误 */
	{
		printk("kzalloc failed.\n");
		ret = -PTR_ERR(mpu6050);	/* 返回错误码 ,在include/uapi/asm-generic/errno-base.h中定义 */
		goto kzalloc_err;
	}
	i2c_set_clientdata(client, mpu6050);	 /* 把数据保存到 client->dev.driver_data */
	mpu6050->client = client;

	cdev_init(&mpu6050->cdev, &mpu6050_ops);

	ret = cdev_add(&mpu6050->cdev, dev_no, 1);
	if ( ret )
	{
		printk("cdev_add failed.\n");
		goto add_err;
	}

	cls = class_create(THIS_MODULE, "mpu6050_cls");
	if ( IS_ERR(cls) )
	{
		printk("class_create failed.\n");
		ret = PTR_ERR(cls);
		goto cls_err;
	}

	dev = device_create(cls, NULL, dev_no, NULL, "mpu6050dev%d", 0);
	if ( IS_ERR(dev) )
	{
		printk("device_create failed.\n");
		ret = PTR_ERR(dev);
		goto dev_err;
	}

	i2c_smbus_write_byte_data(client, PWR_MGMT_1, 0x80);
	msleep(200);
	i2c_smbus_write_byte_data(client, PWR_MGMT_1, 0x40);
	i2c_smbus_write_byte_data(client, PWR_MGMT_1, 0x00);

	i2c_smbus_write_byte_data(client, SMPLRT_DIV,   0x7);
	i2c_smbus_write_byte_data(client, CONFIG,       0x6);
	i2c_smbus_write_byte_data(client, GYRO_CONFIG,  0x3 << 3); 
	i2c_smbus_write_byte_data(client, ACCEL_CONFIG, 0x3 << 3);

	return 0;

dev_err:
	class_destroy(cls);
cls_err:
	cdev_del(&mpu6050->cdev);
add_err:
	kfree(mpu6050);
kzalloc_err:
	unregister_chrdev_region(dev_no, 1);
reg_err:
	return ret;
}

static int mpu6050_remove(struct i2c_client *client)
{
	dev_t dev;
	struct mpu6050_dev *mpu6050;

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);
	printk("%p.\n", client);

	dev = MKDEV(MPU6050_MAJOR, MPU6050_MINOR);
	mpu6050 = i2c_get_clientdata(client);	/* client->dev.driver_data */

	device_destroy(cls, dev);
	class_destroy(cls);
	cdev_del(&mpu6050->cdev);
	kfree(mpu6050);
	unregister_chrdev_region(dev, 1);

	return 0;
}

struct i2c_device_id mpu6050_id[] = {
	{.name = "mpu6050"},
	{}
};

struct i2c_driver mpu6050_driver = {
	.probe  = mpu6050_probe,
	.remove = mpu6050_remove,
	.driver = {
		.name  = "my_i2cdrv",
		.owner = THIS_MODULE, 
	},
	.id_table = mpu6050_id,
};

static int __init i2c_init(void)
{
	return i2c_add_driver(&mpu6050_driver);
}

static void __exit i2c_exit(void)
{
	i2c_del_driver(&mpu6050_driver);
}

module_init(i2c_init);
module_exit(i2c_exit);
/* 
	不加GPL协议会报错        
	drv: module license 'unspecified' taints kernel.   drv:模块许可证“未指定”污染内核。
	Disabling lock debugging due to kernel taint       由于内核污染而禁用锁调试。
*/
MODULE_LICENSE("GPL");	

test.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>

#include "mpu6050.h"

#define READ_COUNT 2

const char *mpu6050_pathname = "/dev/mpu6050dev0";

int main()
{
	int fd, i;
	struct atg_val val;

	fd = open(mpu6050_pathname, O_RDWR, 0666);
	if (fd < 0)
	{
		printf("open failed\n");
		return -1;
	}

	for (i=0; i<READ_COUNT; i++)
	{
		ioctl(fd, MPU6050_GET_VAL, &val);
		printf("accelx: %.2f\n", val.accelx / 2048.0);
		printf("accely: %.2f\n", val.accely / 2048.0);
		printf("accelz: %.2f\n", val.accelz / 2048.0);
		printf("temp: %.2f\n", val.temp / 340.0 + 36.53); 
		printf("gyrox: %.2f\n", val.gyrox / 16.4);
		printf("gyroy: %.2f\n", val.gyroy / 16.4);
		printf("gyroz: %.2f\n", val.gyroz / 16.4);
		sleep(3);
	}

	close(fd);

	return 0;
}

自定义头文件 mpu6050.h

#ifndef _MPU6050_H
#define _MPU6050_H

struct atg_val {
	short accelx;
	short accely;
	short accelz;
	short temp;
	short gyrox;
	short gyroy;
	short gyroz;
};

#define MPU6050_MAGIC	'm'

#define MPU6050_GET_VAL	_IOR(MPU6050_MAGIC, 0, struct atg_val)

#endif	/* mpu6050.h */

Makefile

KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-05.02.00.10/board-support/linux-4.14.79/
PWD := $(shell pwd)

all:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
	arm-linux-gnueabihf-gcc test.c -o app
install:
	sudo cp *.ko  app /tftpboot
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app
clean:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app

obj-m += drv.o

测试结果

root@am335x-evm:~# insmod drv.ko 
[   67.654517] mpu6050_probe -- 131.
[   67.657897] cf628000.
root@am335x-evm:~# ./app
[   70.139433] mpu6050_open -- 54.
accelx: -15.00
accely: -15.00
accelz: -15.00
temp: -53.83
gyrox: -1873.23
gyroy: -1873.23
gyroz: -1873.23
accelx: -15.00
accely: -15.00
accelz: -15.00
temp: -53.83
gyrox: -1873.23
gyroy: -1873.23
gyroz: -1873.23
[   76.147381] mpu6050_release -- 61.
root@am335x-evm:~# rmmod drv.ko 
[   85.926126] mpu6050_remove -- 206.
[   85.929656] cf628000.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值