首先先来了解一下,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通用设备驱动来编写用户态的驱动代码
方法三:使用裸机代码操作
本篇主要介绍方法一--使用内核提供的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的处理速度太快,
编译之后作为应用程序执行,好使......