跳至主要内容

【转】I2C 24LC02 C读写例程(PIC单片机)

程序匠人 发表于 2008-10-28 14:06:00  阅读全文(332) | 回复(0) | 引用通告(0) | 编辑

I2C 24LC02 C读写例程(PIC单片机)

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

2   I2C总线工作原理
I2C总线上的数据稳定规则,SCL为高电平时SDA上的数据保持稳定,SCL为低电平时允许SDA变化。如果SCL处于高电平时,SDA上产生下降沿,则认为是起始位,SDA上的上升沿认为是停止位。通信速率分为常规模式(时钟频率100kHz)和快速模式(时钟频率400kHz)。同一总线上可以连接多个带有I2C接口的器件,每个器件都有一个唯一的地址,既可以是单接收的器件,也可以是能够接收发送的器件。

 

每次数据传输都是以一个起始位开始,而以停止位结束。传输的字节数没有限制。最高有效位将首先被传输,接收方收到第8位数据后会发出应答位。数据传输通常分为两种:主设备发送从设备接收和从设备发送主设备接收。这两种模式都需要主机发送起始位和停止位,应答位由接收方产生。从设备地址一般是1或2个字节,用于区分连接在同一I2C上的不同器件。

I2C总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。
   开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
   结束信号:SCL为低电平时,SDA由低电平向高电平跳变,结束传送数据。
   应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。

在I2C总线中只有主发送和主接收两种操作方式。在系统初始化时,由指令控制CPU送出相关的数据,经接口送到I2C寄存器内。通过初始化这些寄存器,可以实现I2C总线的主模式控制,以及实现I2C总线上的从设备读写。

      当主设备和其中的一个从设备交换数据时,主设备首先发出一个启动Start信号,这个信号被所有的从设备接收。即从设备准备接收CPU的信号,然后主设备再发出它要通信的从设备地址。接下来,所有的从设备将收到的这个地址和它们自己的地址进行比较。

如果收到的地址和它们自己的地址不同,则什么都不做,只是等待主设备发出停止stop信号;如果收到的地址和它自己的地址相同,它就发出一个信号给主设备,这个信号称为应答Acknowledge信号。当主设备收到应答信号后,它就开始向从设备发送数据或者从从设备接收数据。当所有操作都进行完毕时,主设备发出一个Stop信号,通信完毕,释放I2C总线;然后所有的从设备都等待下一次Start信号的到来。

3   总线基本操作
   I2C规程运用主/从双向通讯。器件发送数据到总线上,则定义为发送器,器件接收数据则定义为接收器。主器件和从器件都可以工作于接收和发送状态。 总线必须由主器件(通常为微控制器)控制,主器件产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。SDA线上的数据状态仅在SCL为低电平的期间才能改变,SCL为高电平的期间,SDA状态的改变被用来表示起始和停止条件。
3.1 控制字节
   在起始条件之后,必须是器件的控制字节,其中高四位为器件类型识别符(不同的芯片类型有不同的定义,EEPROM一般应为1010),接着三位为片选,最后一位为读写位,当为1时为读操作,为0时为写操作。

1.写过程

(1)上电后等待一个延时(1ms)。

(2)器件寻址,给一个起始信号(SCL为高电平时SDA给一个下降沿)。发送从器件地址,高5位为10110,然后根据A1/A0(如果和器件的地址相同则那个器件会应答)进行读/写控制(O为读)。

(3)应答,器件在SCL的第9个周期时SDA给出一个低电平,作为应答信号。

(4)开始写有两种模式:字节写模式和页写模式。

・字节模式:给出A15~A8应答,给出A7~A0应答;然后给出DATA和停止信号     (SCL为高电平时,SDA给出一个上升沿),接着要等待一个擦写时间。

・页写模式:给出地址以后连续给出64个数据。如果多于64个数据,则地址计数器自动翻转。(如果少于64昵,估计是没有问题的,但是需要实验验证。)

(5)判断擦写操作是否完毕的一个方法(应答查询),如果器件还处于擦写状态,则不会应答器件寻址;如果有应答,则说明擦写完毕。

2.读过程

(1)上电以后等待一个延时(lms)。

(2)器件寻址。

(3)应答。

(4)开始读有三种模式:立即当前地址读、选择/随机读、连续读。

・立即当前地址读:如果上次读/写的操作地址为N,则现在是N+1。不需要ACK,但是需要Stop信号。

・选择/随机读:先伪写(用于给出一个地址),然后再次启动,读取数据。

・连续读:读取一个以后给一个应答,这样器件会再给出下一个地址的数据内容。

(5)开始数据传输Start后、停止数据传输Stop前,SCL高电平期间,SDA上为有效数据。

/*******************************************************************
一、程序说明:
1, 24LC02器件地址是1010000R/W.
2, 数组写入24LC02采取页写方式.
3, 数组code从24LC02读出时采取自由读方式.
4, 采用4.00M晶体。
5,采用软件I2C。

二、硬件连接:
1, SDA------->23 pin.(当然你可以任意选择脚位)
2, SCL------->18 Pin.(当然你可以任意选择脚位)
3, PORTD----->外接8个LED,显示读出的数据,在这里,读出的刚好是一个闪动的流水灯状态。

*******************************************************************/
#i nclude "pic.h"

#define uchar unsigned char
#define nop() asm("nop"
#define SCL TRISC3
#define SDA TRISC4

void start_i2c();
void stop_i2c();
void send_byte(uchar c);
uchar receive_byte();
void I_send_str(uchar sla,uchar suba,uchar *s,uchar no);
void delay_250ms();
void i2c_error ();

uchar code[]={0x00,0x01,0x03,0x07,0x0f,0x1f,0x3f,0x7f,0xff};
uchar no,ack,c,data;


void main(void)
{
uchar i;
TRISC=0Xff; //C口设为输入 RC3为SCL线,RC4为SDA线。
PORTC=0X00;
TRISD=0X00; //D口为输出,显示IC24LC02中读出的内容
PORTD=0X00; //初始显示全亮

I_send_str(0xa0,0x00,code,9); //页写入code数组到24LC02,器件地址为0Xa0,子地址为0X00,共9个数。

delay_250ms();


///////////开始读出到D口进行显示,根据Random read时序图。
while (1)
{
for (i=0x00;i<0x09;i++)
{
start_i2c();
send_byte(0xa0); //发送器件地址,即DEVICE ADDRESS。
if (ack==0) i2c_error(); //如果24LC02无应答。则进入I2C ERROR错误指示。
send_byte(i); //发送字地址,即WORD ADDRESS。D口显示数组。
if (ack==0) i2c_error();
start_i2c(); //重新启动总线。
send_byte(0xa1); //发送读命令和器件地址DEVICE ADDRESS。
if (ack==0) i2c_error();
data=receive_byte();
stop_i2c();
PORTD=data;
delay_250ms();
}
}
}


/*******************************************************************
起动总线函数
函数原型: void start_i2c();
Function: start on the I2C bus
*******************************************************************/
void start_i2c()
{
SDA=1; //发送启始条件的数据信号
nop();
SCL=1;
nop();nop();nop();nop();nop(); //24LC02要求建立时间大于4,7S
SDA=0; //发送起始信号
nop();nop();nop();nop();nop();
SCL=0; //钳住I2C总线,准备发送数据或接收数据
nop();nop();
}


/*******************************************************************
停止总线函数
函数原型: void stop_i2c();
Function: stop the I2C bus
*******************************************************************/
void stop_i2c()
{

SDA=0; //发送结束条件的数据信号
nop();
SCL=1;
nop();nop();nop();nop();nop();
SDA=1;
nop();nop();nop();nop();
}

/*=================================================================
字节数据传送函数
函数原型: void send_byte(uchar c);
Function: 将数据C发送出去,可以是地址,也可以是数据,发完后等待回应,并对此状态
位进行操作(不应答或非应答都使ack=0 ),发送数据正常,ack=1;ack=0
表示被控器无应答或损坏。
==================================================================*/
void send_byte(uchar c)
{
uchar bit_count;
for (bit_count=0;bit_count<8;bit_count++)
{
if ((c<<bit_count)&0x80) {SDA=1;}
else {SDA=0;}
nop();
SCL=1;
nop();nop();nop();nop();nop();
SCL=0;
}
nop();nop();
SDA=1;
nop();nop();
SCL=1;
nop();nop();nop();
if (RC4==1) ack=0;
else ack=1; //用ASK=1为有应答信号
SCL=0;
nop();nop();
}

/*==================================================================
字节数据接收函数
函数原型:uchar receive_byte();
FUNCTION: 用来接收从器件传来的数据,并判断总线错误(不发应答信号),
发完后请用应答函数。
===================================================================*/
uchar receive_byte()
{
uchar retc,bit_count;
retc=0;
SDA=1;
for (bit_count=0;bit_count<8;bit_count++)
{
nop();
SCL=0;
nop();nop();nop();nop();nop();
SCL=1;
nop();nop();
retc=retc<<1;
if (RC4==1) retc=retc+1;
nop();nop();
}
SCL=0;
nop();nop();
return (retc);
}


/*================================================================
向有子地址器件发送多字节数据函数
函数原型: bit I_send_str(uchar sla,uchar suba,uchar *s,uchar no);
Function: 从启动总线到发送地址,数据,结束总线的全过程,从器件地址sla。如果
返回1表示操作成功,否则操作有误。
=================================================================*/
void I_send_str(uchar sla,uchar suba,uchar *s,uchar no)
{
uchar i;
start_i2c();
send_byte(sla);
if (ack==0) i2c_error();
send_byte(suba);
if (ack==0) i2c_error();
for (i=0;i<no;i++)
{
send_byte(*s);
if (ack==0) i2c_error();
s++;
}
stop_i2c();
// return(1);
}

/*****************************************************************
延时函数
函数原型: void delay_250ms();
FUNCTION: 延明250ms
*****************************************************************/
void delay_250ms()
{
unsigned int d=24999;
while (--d);
}

/*****************************************************************
总线错误函数
函数原型: void i2c_error();
Function: 通过RD7闪动8次表示总线操作失败一次报警。
*****************************************************************/
void i2c_error ()
{
uchar i;
for (i=0;i<8;i++)
{
RD7=0;
delay_250ms();
RD7=1;
delay_250ms();
}

}
/**********END**************/

评论

此博客中的热门博文

【转】AMBA、AHB、APB总线简介

AMBA 简介 随着深亚微米工艺技术日益成熟,集成电路芯片的规模越来越大。数字IC从基于时序驱动的设计方法,发展到基于IP复用的设计方法,并在SOC设计中得到了广泛应用。在基于IP复用的SoC设计中,片上总线设计是最关键的问题。为此,业界出现了很多片上总线标准。其中,由ARM公司推出的AMBA片上总线受到了广大IP开发商和SoC系统集成者的青睐,已成为一种流行的工业标准片上结构。AMBA规范主要包括了AHB(Advanced High performance Bus)系统总线和APB(Advanced Peripheral Bus)外围总线。   AMBA 片上总线        AMBA 2.0 规范包括四个部分:AHB、ASB、APB和Test Methodology。AHB的相互连接采用了传统的带有主模块和从模块的共享总线,接口与互连功能分离,这对芯片上模块之间的互连具有重要意义。AMBA已不仅是一种总线,更是一种带有接口模块的互连体系。下面将简要介绍比较重要的AHB和APB总线。 基于 AMBA 的片上系统        一个典型的基于AMBA总线的系统框图如图3所示。        大多数挂在总线上的模块(包括处理器)只是单一属性的功能模块:主模块或者从模块。主模块是向从模块发出读写操作的模块,如CPU,DSP等;从模块是接受命令并做出反应的模块,如片上的RAM,AHB/APB 桥等。另外,还有一些模块同时具有两种属性,例如直接存储器存取(DMA)在被编程时是从模块,但在系统读传输数据时必须是主模块。如果总线上存在多个主模块,就需要仲裁器来决定如何控制各种主模块对总线的访问。虽然仲裁规范是AMBA总线规范中的一部分,但具体使用的算法由RTL设计工程师决定,其中两个最常用的算法是固定优先级算法和循环制算法。AHB总线上最多可以有16个主模块和任意多个从模块,如果主模块数目大于16,则需再加一层结构(具体参阅ARM公司推出的Multi-layer AHB规范)。APB 桥既是APB总线上唯一的主模块,也是AHB系统总线上的从模块。其主要功能是锁存来自AHB系统总线的地址、数据和控制信号,并提供二级译码以产生APB外围设备的选择信号,从而实现AHB协议到APB协议的转换。 ====================================

【转】GPIO编程模拟I2C入门

ARM编程:ARM普通GPIO口线模拟I2C  请教个问题: 因为需要很多EEPROM进行点对点控制,所以我现在要用ARM的GPIO模拟I2C,管脚方向我设 置的是向外的。我用网上的RW24C08的万能程序修改了一下,先进行两根线的模拟,SDA6, SCL6,但是读出来的数不对。我做了一个简单的实验,模拟SDA6,SCL6输出方波,在示波 器上看到正确方波,也就是说,我的输出控制是没问题的。 哪位大哥能指点一下,是否在接收时管脚方向要设为向内?(不过IOPIN不管什么方向都可 以读出当前状态值的阿) 附修改的RW24C08()程序: #define  SomeNOP() delay(300); /**/ /* *********************************  RW24C08   **************************************** */ /**/ /* ----------------------------------------------------------------------------- ---  调用方式:void I2CInit(void)   函数说明:私有函数,I2C专用 ------------------------------------------------------------------------------- -- */ void  I2CInit( void ) ... {  IO0CLR  =  SCL6;      // 初始状态关闭总线  SomeNOP();  // 延时   I2CStop();  // 确保初始化,此时数据线是高电平 }   /**/ /* ---------------------------------------------------------------------------- ----  调用方式:void I2CStart(void)   函数说明:私有函数,I2C专用 ------------------------------------------------------------------------------- -- */ void  I2CStart( void )

【转】cs8900网卡的移植至基于linux2.6内核的s3c2410平台

cs8900网卡的移植至基于linux2.6内核的s3c2410平台(转) 2008-03-11 20:58 硬件环境:SBC-2410X开发板(CPU:S3C2410X) 内核版本:2.6.11.1 运行环境:Debian2.6.8 交叉编译环境:gcc-3.3.4-glibc-2.3.3 第一部分 网卡CS8900A驱动程序的移植 一、从网上将Linux内核源代码下载到本机上,并将其解压: #tar jxf linux-2.6.11.1.tar.bz2 二、打开内核顶层目录中的Makefile文件,这个文件中需要修改的内容包括以下两个方面。 (1)指定目标平台。 移植前:         ARCH?= $(SUBARCH) 移植后: ARCH            :=arm (2)指定交叉编译器。 移植前: CROSS_COMPILE ?= 移植后: CROSS_COMPILE   :=/opt/crosstool/arm-s3c2410-linux-gnu/gcc-3.3.4-glibc-2.3.3/bin/arm-s3c2410-linux-gnu- 注:这里假设编译器就放在本机的那个目录下。 三、添加驱动程序源代码,这涉及到以下几个方面。(1)、从网上下载了cs8900.c和cs8900.h两个针对2.6.7的内核的驱动程序源代码,将其放在drivers/net/arm/目录下面。 #cp cs8900.c ./drivers/net/arm/ #cp cs8900.h ./drivers/net/arm/ 并在cs8900_probe()函数中,memset (&priv,0,sizeof (cs8900_t));函数之后添加如下两条语句: __raw_writel(0x2211d110,S3C2410_BWSCON); __raw_writel(0x1f7c,S3C2410_BANKCON3); 注:其原因在"第二部分"解释。 (2)、修改drivers/net/arm/目录下的Kconfig文件,在最后添加如下内容: Config ARM_CS8900    tristate "CS8900 support" depends on NET_ETHERNET && A