当前时区为 UTC + 8 小时



发表新帖 回复这个主题  [ 1 篇帖子 ] 
作者 内容
1 楼 
 文章标题 : linux下IIC驱动开发分析及编写
帖子发表于 : 2010-05-21 9:14 
头像

注册: 2009-08-12 0:39
帖子: 46
送出感谢: 0 次
接收感谢: 0 次
转载自:
http://blog.chinaunix.net/u3/102275/sho ... 13547.html
http://blog.chinaunix.net/u/3063/showart_217716.html

1. IIC规范
IIC(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。IIC总线产生于在80年代,最初为音频和视频设备开发,如今主要在服务器管理中使用,其中包括单个组件状态的通信。例如管理员可对各个组件进行查询,以管理系统的配置或掌握组件的功能状态,如电源和系统风扇。可随时监控内存、硬盘、网络、系统温度等多个参数,增加了系统的安全性,方便了管理。

2.1 IIC总线的特点
IIC总线最主要的优点是其简单性和有效性。由于接口直接在组件之上,因此IIC总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件。IIC总线的另一个优点是,它支持多主控(multimastering), 其中任何能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。

2.2 IIC总线工作原理
2.2.1 总线构成及信号类型
IIC总线是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,最高传送速率100kbps。各种被控制电路均并联在这条总线上,但就像电话机一样只有拨通各自的号码才能工作,所以每个电路和模块都有唯一的地址,在信息的传输过程中,IIC总线上并接的每一模块电路既是主控器(或被控器),又是发送器(或接收器),这取决于它所要完成的功能。

CPU发出的控制信号分为地址码和控制量两部分:

Ø 地址码用来选址,即接通需要控制的电路,确定控制的种类;

Ø 控制量决定该调整的类别(如对比度、亮度等)及需要调整的量。

这样,各控制电路虽然挂在同一条总线上,却彼此独立,互不相关。

IIC总线在传送数据过程中共有三种类型信号:

Ø 开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。

Ø 结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。



Ø 数据传输信号:在开始条件以后,时钟信号SCL的高电平周期期问,当数据线稳定时,数据线SDA的状态表示数据有效,即数据可以被读走,开始进行读操作。在时钟信号SCL的低电平周期期间,数据线上数据才允许改变。每位数据需要一个时钟脉冲。


Ø 应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。


目前有很多半导体集成电路上都集成了IIC接口。带有IIC接口的单片机有:CYGNAL的 C8051F0XX系列,PHILIPSP87LPC7XX系列,MICROCHIP的PIC16C6XX系列等。很多外围器件如存储器、监控芯片等也提供IIC接口。

2.3 总线基本操作
IIC规程运用主/从双向通讯。器件发送数据到总线上,则定义为发送器,器件接收数据则定义为接收器。主器件和从器件都可以工作于接收和发送状态。 总线必须由主器件(通常为微控制器)控制,主器件产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。SDA线上的数据状态仅在SCL为低电平的期间才能改变,SCL为高电平的期间,SDA状态的改变被用来表示起始和停止条件。

2.3.1 控制字节
在起始条件之后,必须是器件的控制字节,其中高四位为器件类型识别符(不同的芯片类型有不同的定义,EEPROM一般应为1010),接着三位为片选,最后一位为读写位,当为1时为读操作,为0时为写操作。


2.3.2 写操作
写操作分为字节写和页面写两种操作,对于页面写根据芯片的一次装载的字节不同有所不同。


2.3.3 读操作
读操作有三种基本操作:当前地址读、随机读和顺序读。图4给出的是顺序读的时序图。应当注意的是:最后一个读操作的第9个时钟周期不是“不关心”。为了结束读操作,主机必须在第9个周期间发出停止条件或者在第9个时钟周期内保持SDA为高电平、然后发出停止条件。


2.3.4 总线仲裁
主机只能在总线空闲的时候启动传输。两个或多个主机可能在起始条件的最小持续内产生一个起始条件,结果在总线上产生一个规定的起始条件。

当SCL线是高电平时,仲裁在SDA线发生:这样,在其他主机发送低电平时,发送高电平的主机将断开它的数据输出级,因为总线上的电平和它自己的电平不同。

仲裁可以持续多位。从地址位开始,同一个器件的话接着就是数据位(如果主机-发送器),或者比较相应位(如果主机-接收器)。IIC总线的地址和数据信息由赢得仲裁的主机决定,在这个过程中不会丢失信息。


仲裁不能在下面情况之间进行:

Ø 重复起始条件和数据位;

Ø 停止条件和数据位;

Ø 重复起始条件和停止条件。

2.4 特性总结
Ø IIC肯定是2线的(不算地线)IIC协议确实很科学,比3/4线的SPI要好,当然线多通讯速率相对就快了

Ø IIC的原则是

l 在SCL=1(高电平)时,SDA千万别忽悠!!!否则,SDA下跳则"判罚"为"起始信号S",SDA上跳则"判罚"为"停止信号P".

l 在SCL=0(低电平)时,SDA随便忽悠!!!(可别忽悠过火到SCL跳高)

Ø 每个字节后应该由对方回送一个应答信号ACK做为对方在线的标志.非应答信号一般在所有字节的最后一个字节后.一般要由双方协议签定.

Ø SCL必须由主机发送,否则天下大乱

Ø 首字节是"片选信号",即7位从机地址加1位方向(读写)控制.从机收到(听到)自己的地址才能发送应答信号(必须应答!!!)表示自己在线.其他地址的从机不允许忽悠!!!(当然群呼可以忽悠但只能听不许说话)

Ø 读写是站在主机的立场上定义的."读"是主机接收从机数据,"写"是主机发送数据给从机.

Ø 重复位主要用于主机从发送模式到接收模式的转换"信号",由于只有2线,所以收发转换肯定要比SPI复杂,因为SPI可用不同的边沿来收发数据,而IIC不行.

Ø 在硬件IIC模块,特别是MCU/ARM/DSP等每个阶段都会得到一个准确的状态码,根据这个状态码可以很容易知道现在在什么状态和什么出错信息.

Ø 7位IIC总线可以挂接127个不同地址的IIC设备,0号"设备"作为群呼地址.10位IIC总线可以挂接更多的10位IIC设备.





linux2.6的IIC驱动编写




1.四种模式的IIC驱动编写介绍

2.一个完整的IIC驱动(从器件接收模式,并且是裸写驱动)




1.1 开启从器件接收模式的示例

R_IICCON = 0xE2; // 使能ACK,使能中断

R_IICADD = 0xAA; // 从器件地址

R_IICSTAT = 0x10; // 设置从器件接收模式

进入中断处理,读收数据:

unit8_t ch = R_IICDS & 0xff; // 读取数据寄存器

R_IICCON &= 0xEF; // 清除IICCON[4]恢复中断响应

RET;




1.2 开启从器件发送模式的示例

R_IICCON = 0xE2; // 使能ACK,使能中断

R_IICADD = 0xAA; // 从器件地址

R_IICSTAT = 0x50; // 设置从器件接收模式

进入中断处理,发送数据:

unit8_t ch = extern_buffer[i]; // 取得待发送到从器件的数据

R_IICDS = ch; // 写入数据到IICDS

R_IICCON &= 0xEF; // 清除IICCON[4]恢复中断响应

RET;

以切换模式的方式结束发送。




1.3 开启主器件发送模式的示例

R_IICCON = 0xE2; // 使能ACK,使能中断

R_IICSTAT = 0xD0; // 设置主器件发送模式

R_IICDS = 0xD0; // 写入从器件地址到IICDS寄存器

R_IICSTAT = 0xF0; // 送出IICDS中的数据

ACK并进入中断处理:

uint8_t ch = extern_buffer[i]; // 取得待发送到从器件的数据

R_IICDS = ch; // 再次写入数据到IICDS

R_IICCON &= 0xEF; // 清除IICCON[4]恢复中断响应

结束发送:

R_IICSTAT = 0xD0; // 置IICSTAT[5]为0,产生停止条件




1.4 开启主器件接收模式的示例

R_IICCON = 0xE2; // 使能ACK,使能中断

R_IICSTAT = 0x90; // 设置主器件接收模式

R_IICDS = 0xD0; // 写入从器件地址到 IICDS

R_IICSTAT = 0xB0; // 送出IICDS中的数据

ACK并进入中断处理:

uint8_t ch = R_IICDS & 0xFF; // 读取一个八位序列

R_IICCON &= 0xEF; // 清除IICCON[4]恢复中断响应

结束接收:

R_IICSTAT = 0x90; // 置IICSTAT[5]为0,产生停止条件




2.1 驱动源代码

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/poll.h>
#include <linux/wait.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <asm/arch/irqs.h>

#define DEVICE "TEST_IIC"
#define DATA_LEN 6

int major = 233;
int minor = 0;
void *R_GPECON,*R_GPEUP,*R_IICCON,*R_IICSTAT,*R_IICADD,*R_IICDS;

int IIC_open(struct inode *, struct file *);
int IIC_release(struct inode *, struct file *);
ssize_t IIC_read(struct file *file, char* buf, size_t count, loff_t *f_pos);
unsigned int IIC_poll(struct file* file, poll_table* wait);
irqreturn_t interrupt_handle( int irq, void* dev_id, struct pt_regs* regs );

static void address_map(void)
{
#define IIC_BASE (0x54000000)
#define IIC_GPECON ( IIC_BASE + 0x40 )
#define IIC_GPEUP ( IIC_BASE + 0x48 )
#define IIC_CON ( IIC_BASE + 0x0 )
#define IIC_STAT ( IIC_BASE + 0x4 )
#define IIC_ADDR ( IIC_BASE + 0x8 )
#define IIC_DS ( IIC_BASE + 0xC )
R_GPECON = ioremap(IIC_GPECON,4);
R_GPEUP = ioremap(IIC_GPEUP ,4);
R_IICCON = ioremap(IIC_CON ,4);
R_IICSTAT = ioremap(IIC_STAT ,4);
R_IICADD = ioremap(IIC_ADDR ,4);
R_IICDS = ioremap(IIC_DS ,4);
}

static void address_unmap(void)
{
iounmap( R_GPECON );
iounmap( R_GPEUP );
iounmap( R_IICCON );
iounmap( R_IICSTAT );
iounmap( R_IICADD );
iounmap( R_IICDS );
}

static struct file_operations fops = {
owner : THIS_MODULE,
read: IIC_read,
open: IIC_open,
release: IIC_release,
poll: IIC_poll,
};

struct IIC_dev{
wait_queue_head_t rq; // 读取等待队列
uint8_t *buffer;
uint32_t size;
uint32_t index;
struct semaphore sem;
struct cdev cdev;
};
struct IIC_dev *my_dev;

static int __init IIC_init(void)
{
// 1. 分配主设备号
dev_t devno = MKDEV( major, minor );
int ret = register_chrdev_region( devno, 1, DEVICE );
if( ret < 0 )
{
printk(KERN_DEBUG "register major number failed with %d\n", ret);
return ret;
}
printk(KERN_DEBUG "%s:register major number OK\n",DEVICE);

// 2. 注册设备
my_dev = kmalloc(sizeof(struct IIC_dev), GFP_KERNEL);
memset( my_dev, 0, sizeof(struct IIC_dev) );
cdev_init( &my_dev->cdev, &fops );
my_dev->cdev.ops = &fops;
my_dev->cdev.owner = THIS_MODULE;
ret = cdev_add( &my_dev->cdev, devno, 1 );
if( ret < 0 )
{
printk(KERN_DEBUG "register device failed with %d\n", ret);
return ret;
}
printk(KERN_DEBUG "%s:register device OK\n",DEVICE);

// 3. 分配本驱动要使用的内存
my_dev->index = 0;
my_dev->size = 128;
my_dev->buffer = kmalloc( my_dev->size, GFP_KERNEL );
if( NULL == my_dev->buffer )
{
printk(KERN_DEBUG "kmalloc failed\n");
return -ENOMEM;
}
printk(KERN_DEBUG "%s:kmalloc buffer OK\n",DEVICE);

// 4. 初始化信号量
init_MUTEX( &(my_dev->sem) );
printk(KERN_DEBUG "%s:init semaphore OK\n",DEVICE);

// 5. 初始化等待队列头
init_waitqueue_head(&my_dev->rq);

// 6. Remap IIC 寄存器
address_map();

// 7. 设置 s3c2410 IIC
unsigned int tmp = ioread32( R_GPEUP );
tmp |= 0xc000; //Pull-up disable
iowrite32( tmp, R_GPEUP );

tmp = ioread32( R_GPECON );
tmp |= 0xa0000000; //GPE15:IICSDA , GPE14:IICSCL
iowrite32( tmp, R_GPECON );

return 0;
}


static void __exit IIC_exit(void)
{
dev_t devno = MKDEV( major, minor );
// 以相反的顺序清除
address_unmap();
kfree( my_dev->buffer );
cdev_del( &my_dev->cdev );
kfree( my_dev );
printk(KERN_DEBUG "%s:kfree OK\n",DEVICE);
unregister_chrdev_region( devno, 1 );
printk(KERN_DEBUG "%s:unregister device OK\n",DEVICE);
}

static void set_slave_recv_mode(void)
{
iowrite8( 0xE2, R_IICCON ); // 使能ACK,使能中断
iowrite8( 0xAA, R_IICADD ); // 从器件地址
iowrite8( 0x10, R_IICSTAT); // 设置从器件接收模式
barrier(); // 强制写入寄存器
}

int IIC_open(struct inode *inode, struct file *file)
{
struct IIC_dev *dev = container_of(inode->i_cdev, struct IIC_dev, cdev);
file->private_data = dev;

if( down_interruptible(&dev->sem) )
return -ERESTARTSYS;

set_slave_recv_mode();

int ret = request_irq( IRQ_IIC, interrupt_handle,
SA_INTERRUPT, DEVICE, (void*)dev );
if( ret )
{
printk( KERN_INFO "I2C: can't get assigned irq %d\n", IRQ_IIC );
}
return 0;
}

int IIC_read(struct file *file, char* buf, size_t count, loff_t *f_pos)
{
struct IIC_dev *dev = file->private_data;
size_t val = DATA_LEN;

while( dev->index < val )
{
if( file->f_flags & O_NONBLOCK )
return -EAGAIN;
// 在这里准备睡眠,等待条件为真
if( wait_event_interruptible(dev->rq, (dev->index >= val)) )
return -ERESTARTSYS; // 返回非0表示被信号中断
}
if( copy_to_user(buf, dev->buffer, val) )
return -EFAULT;
memset( dev->buffer, 0, dev->size );
dev->index = 0;

set_slave_recv_mode();
return val;
}

int IIC_release(struct inode *inode, struct file *file)
{
struct IIC_dev *dev = file->private_data;

iowrite8( 0x0, R_IICCON );
iowrite8( 0x0, R_IICADD );
iowrite8( 0x0, R_IICSTAT);
barrier(); // 强制写入寄存器

memset( dev->buffer, 0, dev->size );
dev->index = 0;

free_irq( IRQ_IIC, NULL );

up(&dev->sem);
return 0;
}

unsigned int IIC_poll(struct file* file, poll_table* wait)
{
struct IIC_dev *dev = file->private_data;
unsigned int mask = 0, val = DATA_LEN;

poll_wait(file,&dev->rq,wait);
if( dev->index >= val )
mask |= POLLIN | POLLRDNORM;
return mask;
}

irqreturn_t interrupt_handle( int irq, void* dev_id, struct pt_regs* regs )
{
struct IIC_dev *dev = dev_id;
int val = DATA_LEN;

uint8_t ch = ioread8( R_IICDS );
if( dev->index == 0 && ch == 0xAA )
goto ret;
dev->buffer[dev->index++] = ch;
if( dev->index >= val )
{
wake_up_interruptible( &dev->rq );
// 直接退出 Slave Receiver 模式
return IRQ_HANDLED;
}
ret:
iowrite8( 0xEF, R_IICCON );
return IRQ_HANDLED;
}

module_init(IIC_init);
module_exit(IIC_exit);

MODULE_AUTHOR("kf701.ye AT gmail.com");
MODULE_DESCRIPTION("Study");
MODULE_SUPPORTED_DEVICE(DEVICE);
MODULE_LICENSE("GPL");


页首
 用户资料  
 
显示帖子 :  排序  
发表新帖 回复这个主题  [ 1 篇帖子 ] 

当前时区为 UTC + 8 小时


在线用户

正在浏览此版面的用户:没有注册用户 和 2 位游客


不能 在这个版面发表主题
不能 在这个版面回复主题
不能 在这个版面编辑帖子
不能 在这个版面删除帖子
不能 在这个版面提交附件

前往 :  
本站点为公益性站点,用于推广开源自由软件,由 DiaHosting VPSBudgetVM VPS 提供服务。
我们认为:软件应可免费取得,软件工具在各种语言环境下皆可使用,且不会有任何功能上的差异;
人们应有定制和修改软件的自由,且方式不受限制,只要他们自认为合适。

Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
简体中文语系由 王笑宇 翻译