IIC专题(一)-Smart210的I2C操作使用I2C通用驱动撰写用户态驱动程序

首先先来了解一下,I2C的相关知识


首先我们知道IIC总线是采用两线式串行通讯接口,分别为SDA数据线以及SCL时钟信号

其中每个I2C设备有个7位的从设备地址,7位的地址中前4位是设备识别地址,这个需要看实际操作的外围器件手册来确认,

后面3位为电路上决定的(A0~A2),最低位为读写标示位,整体8位来描述一个设备,也就是说相同的器件(前四位相同)最多只能有7个,

接下来我们看一下Smart210的原理图可以找到如下图


所以可以确定A0~A2都是0,再来找到AT24LC04的手册如下图


所以从设备地址的最前面为 0b1010XXXX后面A2~A0因为原理图上都接地所以也为0 所以前七位就是 0b1010000X

以及


这样看出来读的时候最后一位是1写的时候最后一位是0

所以我们可以确定 Smart210板载的AT24LC04的从设备写地址为 0xA0(0b10100000)、读地址为0xA1(0x10100001)

时序部分的说明在最上面的笔记上自己阅读,在Linux下的I2C子系统架构如下图


可以看出来在Linux下可以用两种方式来操作i2c,一种是透过/dev 下的设备文件直接透过i2c设备驱动来操作,

另外一种是透过用户态驱动程序使用通用驱动 i2c-dev来操作,所以加上裸机驱动总共为大家介绍以下三种方式


方法一:使用内核提供的I2C通用设备驱动来编写用户态的驱动代码

方法二:使用内核原代码提供的at24.c驱动来操作

方法三:使用裸机代码操作

本篇主要介绍方法一--使用内核提供的I2C通用设备驱动来编写用户态的驱动代码


因为使用内核中 i2c-dev.c通用设备接口所以我们需要先了解一下是怎么实现的,因为我们主要使用ioctl来操作所以重点先看ioctl的处理

因为Linux下的驱动都是采用设备文件的方式来操作,所以我们需要先了解设备文件在哪

在i2c-dev.c文件中设备初始化入口中可以看到以下片段

static int __init i2c_dev_init(void)
{
	int res;

	printk(KERN_INFO "i2c /dev entries driver\n");

	res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
	if (res)
		goto out;

	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;
	}

	/* Keep track of adapters which will be added or removed later */
	res = bus_register_notifier(&i2c_

这边发现设备是以字符设备来描述的,并且设备名为i2c,

再来了解ioctl的参数部分

static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct i2c_client *client = file->private_data;
	unsigned long funcs;

	dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
		cmd, arg);

	switch (cmd) {
....省略一部分代码.....
	case I2C_RDWR:
		return i2cdev_ioctl_rdrw(client, arg);
<pre name="code" class="cpp">....省略一部分代码.....
return 0;}
 

因为我们要读写操作所以我们可以知道第一件事我们需要传 I2C_RDWR标示,并且透过 i2cdev_ioctl_rdrw函数来操作

static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client,
		unsigned long arg)
{
	struct i2c_rdwr_ioctl_data rdwr_arg;
	struct i2c_msg *rdwr_pa;
	u8 __user **data_ptrs;
	int i, res;

	if (copy_from_user(&rdwr_arg,
			   (struct i2c_rdwr_ioctl_data __user *)arg,
			   sizeof(rdwr_arg)))
		return -EFAULT;

	/* Put an arbitrary limit on the number of messages that can
	 * be sent at once */
	if (rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)
		return -EINVAL;

	rdwr_pa = kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg), GFP_KERNEL);
	if (!rdwr_pa)
		return -ENOMEM;
我们传入的操作参数 arg 直接就被作为i2c_rdwr_ioctl_data的结构体指针地址了解一下i2c_rdwr_ioctl_data结构

struct i2c_rdwr_ioctl_data {
	struct i2c_msg __user *msgs;	/* pointers to i2c_msgs */
	__u32 nmsgs;			/* number of i2c_msgs */
};

发现这边需要使用这种消息的结构体



透过代码追踪发现在地址的处理上有点不同,

在最终的实际操作部分有下面这样的代码片段

struct i2c_client {
	unsigned short flags;		/* div., see below		*/
	unsigned short addr;		/* chip address - NOTE: 7bit	*/
					/* addresses are stored in the	*/
					/* _LOWER_ 7 bits		*/
	char name[I2C_NAME_SIZE];
	struct i2c_adapter *adapter;	/* the adapter we sit on	*/
	struct i2c_driver *driver;	/* and our access routines	*/
	struct device dev;		/* the device structure		*/
	int irq;			/* irq issued by device		*/
	struct list_head detected;
};

这边关于addr的描述是只要传低7位而下面实际操作地址时如下面代码片断这样处理

static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,
				      struct i2c_msg *msg)
{
	unsigned int addr = (msg->addr & 0x7f) << 1;
	unsigned long stat;
	unsigned long iiccon;

	stat = 0;
	stat |=  S3C2410_IICSTAT_TXRXEN;

	if (msg->flags & I2C_M_RD) {
		stat |= S3C2410_IICSTAT_MASTER_RX;
		addr |= 1;
	} else
		stat |= S3C2410_IICSTAT_MASTER_TX;

	if (msg->flags & I2C_M_REV_DIR_ADDR)
		addr ^= 1;

所以我们知道这个地址最后要实现发给从设备的地址读为0xA1写为0xA0所以我们知道我们应该先把最后一位的读写标示放到flag中,

而前7位的地址我们需要先向右移 也就是说以读来说最终要发送 0xA1,所以把最后一位的 1 放到flag中,而把高7位右移变成 0x50作为addr

结合写入之后再读出看看是否写成功所以实现以下代码

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

#define I2C_RDWR	0x0707	/* Combined R/W transfer (one STOP only) */

#define I2C_SLAVE_ADDR 0x50
#define I2C_WRITE_FLAG 0
#define I2C_READ_FLAG 1
#define READ_COUNT 256
struct i2c_msg {
	unsigned short addr;	/* slave address			*/
	unsigned short flags;
	unsigned short len;		/* msg length				*/
	unsigned char *buf;		/* pointer to msg data			*/
};

struct i2c_rdwr_ioctl_data {
	struct i2c_msg *msgs;	/* pointers to i2c_msgs */
	unsigned int nmsgs;			/* number of i2c_msgs */
};

void delay(int i)
{
	int j;
	for (;i>0 ;i--)
		for(j=0;j<1000;j++)
			;
}

int main()
{
	int fd;
	int i;
	struct i2c_rdwr_ioctl_data e2prom_data;
	//打开通用设备文件
	fd = open("/dev/i2c-0",O_RDWR);
	
	
	e2prom_data.msgs = (struct i2c_msg *)malloc(2*sizeof(struct i2c_msg));
	
	//构造写入EEPROM的消息
	e2prom_data.nmsgs =1;
	e2prom_data.msgs[0].len = 2;
	e2prom_data.msgs[0].addr = I2C_SLAVE_ADDR;
	e2prom_data.msgs[0].flags = I2C_WRITE_FLAG;
	e2prom_data.msgs[0].buf = (unsigned char *)malloc(2);
	e2prom_data.msgs[0].buf[0] = 0x10;
	e2prom_data.msgs[0].buf[1] = 0x96;
	//使用ioctl写入数据
	ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);

	delay(1000);
	
	//构造读取EEPROM的消息
	e2prom_data.nmsgs =2;
	e2prom_data.msgs[0].len = 1;
	e2prom_data.msgs[0].addr = I2C_SLAVE_ADDR;
	e2prom_data.msgs[0].flags = I2C_WRITE_FLAG;
	e2prom_data.msgs[0].buf[0] = 0x00;
	
	e2prom_data.msgs[1].len = READ_COUNT;
	e2prom_data.msgs[1].addr = I2C_SLAVE_ADDR;
	e2prom_data.msgs[1].flags = I2C_READ_FLAG;
	e2prom_data.msgs[1].buf = (unsigned char *)malloc(READ_COUNT);
	e2prom_data.msgs[1].buf[0] = 0x00;
	
	//使用ioctl读出数据
	ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);
	
	//打印数据
	for (i=0;i<READ_COUNT;i++)
	{
		printf("%2x ",e2prom_data.msgs[1].buf[i]);
		if ((i+1)%16 ==0)
			printf("\r\n");
	}
	printf("\r\n");
	//关闭设备
	close(fd);
}

其中必须要注意的是操作完写操作之后,需要做个延迟,这个延迟时间还不能短了,主要I2C本身就不是高速设备,210的处理速度太快,

编译之后作为应用程序执行,好使......


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值