pic_I2C_slave
I2C总线协议及工作原理
I2C总线协议及工作原理I2C总线协议及工作原理之阿布丰王创作一、概述1、I2C总线只有两根双向信号线.一根是数据线SDA,另一根是时钟线SCL.SCL:上升沿将数据输入到每个EEPROM器件中;下降沿驱动EEPROM器件输出数据.(边缘触发)SDA:双向数据线,为OD门,与其它任意数量的OD与OC门成"线与"关系.I2C总线通过上拉电阻接正电源.当总线空闲时,两根线均为高电平(SDL=1;SCL=1).连到总线上的任一器件输出的低电平,都将使总线的信号变低,即各器件的SDA及SCL都是线“与”关系.2、主设备与从设备系统中的所有外围器件都具有一个7位的"从器件专用地址码",其中高4位为器件类型,由生产厂家制定,低3位为器件引脚界说地址,由使用者界说.主控器件通过地址码建立多机通信的机制,因此I2C总线省去了外围器件的片选线,这样无论总线上挂接几多个器件,其系统仍然为简约的二线结构.终端挂载在总线上,有主端和从端之分,主端必需是带有CPU的逻辑模块,在同一总线上同一时刻使能有一个主端,可以有多个从端,从真个数量受地址空间和总线的最年夜电容 400pF的限制.主端主要用来驱动SCL line;从设备对主设备发生响应;二者都可以传输数据,可是从设备不能发起传输,且传输是受到主设备控制的.二、协议1.空闲状态I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态.此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高.2.起始位与停止位的界说:起始信号:当SCL为高期间,SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平信号.停止信号:当SCL为高期间,SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号.起始和终止信号都是由主机发出的,在起始信号发生后,总线就处于被占用的状态;在终止信号发生后,总线就处于空闲状态.接收器件收到一个完整的数据字节后,有可能需要完成一些其它工作,如处置内部中断服务等,可能无法立刻接收下一个字节,这时接收器件可以将SCL线拉成低电平,从而使主机处于等候状态.直到接收器件准备好接收下一个字节时,再释放SCL线使之为高电平,从而使数据传送可以继续进行.3.ACK发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号. 应答信号为低电平时,规定为有效应答位(ACK简称应答位),暗示接收器已经胜利地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般暗示接收器接收该字节没有胜利.对反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,而且确保在该时钟的高电平期间为稳定的低电平. 如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P.如下图逻辑分析仪的采样结果:释放总线后,如果没有应答信号,sda应该一直继续为高电平,可是如图中蓝色虚线部份所示,它被拉低为低电平,证明收到了应答信号.这里面给我们的两个信息是:1)接收器在SCL的上升沿到来之前的低电平期间拉低SDA;2)应答信号一直坚持到SCL的下降沿结束;正如前文红色标识所指出的那样.4.数据的有效性:I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必需坚持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变动.5.数据的传送:在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据.数据位的传输是边缘触发.工作过程总线上的所有通信都是由主控器引发的.在一次通信中,主控器与被控器总是在饰演着两种分歧的角色.1.主设备向从设备发送数据主设备发送起始位,这会通知总线上的所有设备传输开始了,接下来主机发送设备地址,与这一地址匹配的slave将继续这一传输过程,而其它slave将会忽略接下来的传输并等候下一次传输的开始.主设备寻址到从设备后,发送它所要读取或写入的从设备的内部寄存器地址;之后,发送数据.数据发送完毕后,发送停止位.(这段看不懂就别看了,没什么用)写入过程如下:发送起始位1)发送从设备的地址和读/写选择位;释放总线,比及EEPROM拉低总线进行应答;如果EEPROM接收胜利,则进行应答;若没有握手胜利或者发送的数据毛病时EEPROM不发生应答,此时要求重发或者终止.2)发送想要写入的内部寄存器地址;EEPROM对其发出应答;3)发送数据4)发送停止位.5)EEPROM收到停止信号后,进入到一个内部的写入周期,年夜概需要10ms,其间任何把持都不会被EEPROM响应详细:需要说明的是:①主控器通过发送地址码与对应的被控器建立了通信关系,而挂接在总线上的其它被控器虽然同时也收到了地址码,但因为与其自身的地址不相符合,因此提前退出与主控器的通信;2.主控器读取数据的过程:读的过程比力复杂,在从slave读出数据前,你必需先要告诉它哪个内部寄存器是你想要读取的,因此必需先对其进行写入(dummy write):1)发送起始位;2)发送slave地址+write bit set;3)发送内部寄存器地址;4)重新发送起始位,即restart;5)重新发送slave地址+read bit set;6)读取数据主机接收器在接收到最后一个字节后,也不会发出ACK信号.于是,从机发送器释放SDA线,以允许主机发出P信号结束传输. 7)发送停止位详细:2、数据传送格式(1)字节传送与应答每一个字节必需保证是8位长度.数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必需跟随一位应答位(即一帧共有9位).由于某种原因从机分歧毛病主机寻址信号应答时(如从机正在进行实时性的处置工作而无法接收总线上的数据),它必需将数据线置于高电平,而由主机发生一个终止信号以结束总线的数据传送.如果从机对主机进行了应答,但在数据传送一段时间后无法继续接收更多的数据时,从机可以通过对无法接收的第一个数据字节的“非应答”通知主机,主机则应发出终止信号以结束数据的继续传送.当主机接收数据时,它收到最后一个数据字节后,必需向从机发出一个结束传送的信号.这个信号是由对从机的“非应答”来实现的.然后,从机释放SDA线,以允许主机发生终止信号.(2)数据帧格式I2C总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号.在起始信号后必需传送一个从机的地址(7位),第8位是数据的传送方向位(R/),用“0”暗示主机发送数据(T),“1”暗示主机接收数据(R).每次数据传送总是由主机发生的终止信号结束.可是,若主机希望继续占用总线进行新的数据传送,则可以不发生终止信号,马上再次发出起始信号对另一从机进行寻址.在总线的一次数据传送过程中,可以有以下几种组合方式:a、主机向从机发送数据,数据传送方向在整个传送过程中不变:注:有阴影部份暗示数据由主机向从机传送,无阴影部份则暗示数据由从机向主机传送. A暗示应答, 暗示非应答(高电平).S暗示起始信号,P暗示终止信号.b、主机在第一个字节后,立即由从机读数据c、在传送过程中,当需要改变传送方向时,起始信号和从机地址都被重复发生一次,但两次读/写方向位正好反相.6、总线的寻址I2C总线协议有明确的规定:采纳7位的寻址字节(寻址字节是起始信号后的第一个字节).(1)寻址字节的位界说D7~D1位组成从机的地址.D0位是数据传送方向位,为“0”时暗示主机向从机写数据,为“1”时暗示主机由从机读数据.主机发送地址时,总线上的每个从机都将这7位地址码与自己的地址进行比力,如果相同,则认为自己正被主机寻址,根据R/位将自己确定为发送器或接收器.从机的地址由固定部份和可编程部份组成.在一个系统中可能希望接入多个相同的从机,从机地址中可编程部份决定了可接入总线该类器件的最年夜数目.如一个从机的7位寻址位有4位是固定位,3位是可编程位,这时仅能寻址8个同样的器件,即可以有8个同样的器件接入到该I2C总线系统中.(2)寻址字节中的特殊地址固定地址编号0000和1111已被保管作为特殊用途.起始信号后的第一字节的8位为“0000 0000”时,称为通用呼叫地址.通用呼叫地址的用意在第二字节中加以说明.格式为:第二字节为 06H(0110)时,所有能响应通用呼叫地址的从机器件复位,并由硬件装入从机地址的可编程部份.能响应命令的从机器件复位时不拉低SDA和SCL线,以免梗塞总线.第二字节为 04H(0100)时,所有能响应通用呼叫地址并通过硬件来界说其可编程地址的从机器件将锁定地址中的可编程位,但不进行复位.如果第二字节的方向位B为“1”,则这两个字节命令称为硬件通用呼叫命令.在这第二字节的高7位说明自己的地址.接在总线上的智能器件,如单片机或其他微处置器能识别这个地址,并与之传送数据.硬件主器件作为从机使用时,也用这个地址作为从机地址.格式为:在系统中另一种选择可能是系统复位时硬件主机器件工作在从机接收器方式,这时由系统中的主机先告诉硬件主机器件数据应送往的从机器件地址,当硬件主机器件要发送数据时就可以直接向指定从机器件发送数据了.(3)起始字节起始字节是提供给没有I2C总线接口的单片机查询I2C总线时使用的特殊字节.不具备I2C总线接口的单片机,则必需通过软件不竭地检测总线,以便及时地响应总线的请求.单片机的速度与硬件接口器件的速度就呈现了较年夜的分歧,为此,I2C总线上的数据传送要由一个较长的起始过程加以引导.引导过程由起始信号、起始字节、应答位、重复起始信号(Sr)组成.请求访问总线的主机发出起始信号后,发送起始字节(0000 0001),另一个单片机可以用一个比力低的速率采样SDA线,直到检测到起始字节中的7个“0”中的一个为止.在检测到SDA线上的高电平后,单片机就可以用较高的采样速率,以便寻找作为同步信号使用的第二个起始信号Sr.在起始信号后的应答时钟脉冲仅仅是为了和总线所使用的格式一致,其实不要求器件在这个脉冲期间作应答.总线数据传送的模拟。
PIC单片机的IIC总线 PIC单机24C02储存器
nop();nop();nop();nop();nop();
SCL=0; //钳住I2C总线,准备发送数据或接收数据
nop();nop();
}
/*************************************************************
#i nclude "pic.h"
#define uchar unsigned char
#define nop() asm("nop"
#define SCL TRISC3
#define SDA TRISC4
void start_i2c();
void stop_i2c();
如果收到的地址和它们自己的地址不同,则什么都不做,只是等待主设备发出停止stop信号;如果收到的地址和它自己的地址相同,它就发出一个信号给主设备,这个信号称为应答Acknowledge信号。当主设备收到应答信号后,它就开始向从设备发送数据或者从从设备接收数据。当所有操作都进行完毕时,主设备发出一个Stop信号,通信完毕,释放I2C总线;然后所有的从设备都等待下一次Start信号的到来。
3 总线基本操作
I2C规程运用主/从双向通讯。器件发送数据到总线上,则定义为发送器,器件接收数据则定义为接收器。主器件和从器件都可以工作于接收和发送状态。 总线必须由主器件(通常为微控制器)控制,主器件产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。SDA线上的数据状态仅在SCL为低电平的期间才能改变,SCL为高电平的期间,SDA状态的改变被用来表示起始和停止条件。
PORTD=data;
delay_250ms();
I2C工作原理范文
I2C工作原理范文I2C(Inter-Integrated Circuit)是一种串行通信协议,用于在集成电路之间进行通信。
它由Philips公司(现在是恩智浦半导体公司)于1982年推出,并已广泛应用于各种电子设备和嵌入式系统中。
I2C的工作原理如下:1.总线拓扑结构:I2C使用两根线进行通信,一根是串行数据线(SDA),另一根是串行时钟线(SCL)。
所有I2C设备都连接到同一条总线上,并且每个设备都有一个唯一的7位地址。
2. 主从模式:I2C通信分为主设备(Master)和从设备(Slave)。
主设备是发起通信的一方,负责控制总线上的通信。
从设备则是被动接收和响应来自主设备的命令或数据。
3. 起始信号和停止信号:I2C通信始于主设备发送一个起始信号(Start)和一个从设备地址。
起始信号告诉所有从设备,接下来的通信将是针对一些特定从设备的。
停止信号(Stop)则标志着一次通信的结束。
4.寄存器读写:主设备通过发送一个从设备地址和一个读/写位来指定是读取还是写入数据。
在写入模式下,主设备发送数据字节到从设备;在读取模式下,主设备请求从设备发送数据字节。
5.硬件应答:在每个字节的传输结束后,接收方(主设备或从设备)都会返回一个应答位。
如果接收方成功接收到了字节,则返回一个低电平的应答位;否则,返回一个高电平的非应答位。
6.时钟同步:I2C通信的时钟由主设备控制。
主设备在SCL线上产生时钟信号,而从设备则根据这个信号来同步自己的时钟。
总的来说,I2C通信是通过主设备发起的,它控制总线上的通信流程和时钟信号。
从设备根据主设备发送的命令或数据来执行相应的操作,并通过应答位来确认是否成功接收到数据。
这种通信协议适用于多个设备之间进行简单的数据交换和控制操作。
I2C的优点是可以同时连接多个设备,并且只需要两根线就能实现通信。
这大大减少了总线的复杂性和成本。
同时,I2C还具有可靠性高、速度适中、容错能力强等特点,使得它成为了很多电子设备中主要的串行通信协议之一总之,I2C是一种简单、灵活且可靠的串行通信协议。
PIC单片机之I2C总线
PIC单片机之I2C总线一、I2C总线特点I2C 总线是主从结构,单片机是主器件,存储器是从器件。
一条总线可以带多个从器件( 也可以有多主结构),I2C 总线的SDA 和SCL 是双向的,开路门结构,通过上拉电阻接正电源。
进行数据传输时,SDA 线上的数据必须在时钟的高电平周期保持稳定。
数据线的高或低电平状态只有在SCL 线的时钟信号是低电平时才能改变,如图1 所示。
图1 数据位的有效性规定在SCL 线是高电平时,SDA 线从高电平向低电平切换表示起始条件;当SCL 是高电平时SDA 线由低电平向高电平切换表示停止条件如图2 所示。
图2 起始和停止信号发送到SDA 线上的每个字节必须为8 位。
可以由高位到低位传输多个字节。
每个字节后必须跟一个响应位(ACK)。
响应时钟脉冲由主机产生。
主机释放SDA 线从机将SDA 线拉低,并在时钟脉冲的高电平期间保持稳定。
如图3 示。
当主机接受数据时,它收到最后一个数据字节后,必须向从机发出一个结束传送的信号。
这个信号是由主机对从机的“非应答”来实现的。
然后,从机释放SDA 线,以允许主机产生终止或重复起始信号。
图3 字节格式与应答二、数据帧格式(1)主机向从机发送数据,数据的传送方向在传输过程中不改变,如图4 所示。
图4 主机向从机发送数据注:阴影部分:表示主机向从机发送数据;无阴影部分:表示主机向从机读取数据。
A:表示应答;:表示非应答。
S:起始信号;P :终止信号。
(2)主机在第一个字节后,立即向从机读取数据,如图5 所示。
图5 主机在第一个字节后立即读从机(3)复合格式,如图6 所示。
传输改变方向的时候,起始条件和从机地址都会被重复,但R/ W-位取反。
如果主机接收器发送一个停止或重复起始信号,它之前应该发送了一个不响应信号()。
图6 复合格式由以上格式可见,无论哪种传输方式,起始信号、终止信号和地址均由主机发出(图中阴影部分),数据字节的传送方向则由寻址字节中的方向位规定,每个字节的传送都必须有应答位(A 或)。
I2C架构的详细分析
I2c是philips提出的外设总线.I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL.正因为这样,它方便了工程人员的布线.另外,I2C是一种多主机控制总线.它和USB总线不同,USB是基于master-slave机制,任何设备的通信必须由主机发起才可以.而I2C 是基于multi master机制.一同总线上可允许多个master.关于I2C协议的知识,这里不再赘述.可自行下载spec 阅读即可.二:I2C架构概述在linux中,I2C驱动架构如下所示:如上图所示,每一条I2C对应一个adapter.在kernel中,每一个adapter提供了一个描述的结构(struct i2c_adapter),也定义了adapter支持的操作(struct i2c_adapter).再通过i2c core层将i2c设备与i2c adapter关联起来.这个图只是提供了一个大概的框架.在下面的代码分析中,从下至上的来分析这个框架图.以下的代码分析是基于linux 2.6.26.分析的代码基本位于:linux-2.6.26.3/drivers/i2c/位置.I2C-mt6516.c三:adapter注册在kernel中提供了两个adapter注册接口,分别为i2c_add_adapter()和i2c_add_numbered_adapter().由于在系统中可能存在多个adapter,因为将每一条I2C总线对应一个编号,下文中称为I2C总线号.这个总线号的PCI中的总线号不同.它和硬件无关,只是软件上便于区分而已.对于i2c_add_adapter()而言,它使用的是动态总线号,即由系统给其分析一个总线号,而i2c_add_numbered_adapter()则是自己指定总线号,如果这个总线号非法或者是被占用,就会注册失败.分别来看一下这两个函数的代码:int i2c_add_adapter(struct i2c_adapter *adapter){int id, res = 0;retry:if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)return -ENOMEM;mutex_lock(&core_lock);/* "above" here means "above or equal to", sigh */res = idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id);mutex_unlock(&core_lock);if (res < 0) {if (res == -EAGAIN)goto retry;return res;}adapter->nr = id;return i2c_register_adapter(adapter);}在这里涉及到一个idr结构.idr结构本来是为了配合page cache中的radix tree而设计的.在这里我们只需要知道,它是一种高效的搜索树,且这个树预先存放了一些内存.避免在内存不够的时候出现问题.所在,在往idr中插入结构的时候,首先要调用idr_pre_get()为它预留足够的空闲内存,然后再调用idr_get_new_above()将结构插入idr中,该函数以参数的形式返回一个id.以后凭这个id就可以在idr中找到相对应的结构了.对这个数据结构操作不太理解的可以查阅本站<<linux文件系统之文件的读写>>中有关radix tree的分析.注意一下idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id)的参数的含义,它是将adapter结构插入到i2c_adapter_idr中,存放位置的id必须要大于或者等于__i2c_first_dynamic_bus_num,然后将对应的id号存放在adapter->nr中.调用i2c_register_adapter(adapter)对这个adapter进行进一步注册.看一下另外一人注册函数: i2c_add_numbered_adapter( ),如下所示:int i2c_add_numbered_adapter(struct i2c_adapter *adap){int id;int status;if (adap->nr & ~MAX_ID_MASK)return -EINVAL;retry:if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)return -ENOMEM;mutex_lock(&core_lock);/* "above" here means "above or equal to", sigh;* we need the "equal to" result to force the result*/status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);if (status == 0 && id != adap->nr) {status = -EBUSY;idr_remove(&i2c_adapter_idr, id);}mutex_unlock(&core_lock);if (status == -EAGAIN)goto retry;if (status == 0)status = i2c_register_adapter(adap);return status;}对比一下就知道差别了,在这里它已经指定好了adapter->nr了.如果分配的id不和指定的相等,便返回错误.过一步跟踪i2c_register_adapter().代码如下:static int i2c_register_adapter(struct i2c_adapter *adap){int res = 0, dummy;mutex_init(&adap->bus_lock);mutex_init(&adap->clist_lock);INIT_LIST_HEAD(&adap->clients);mutex_lock(&core_lock);/* Add the adapter to the driver core.* If the parent pointer is not set up,* we add this adapter to the host bus.*/if (adap->dev.parent == NULL) {adap->dev.parent = &platform_bus;pr_debug("I2C adapter driver [%s] forgot to specify ""physical device\n", adap->name);}sprintf(adap->dev.bus_id, "i2c-%d", adap->nr);adap->dev.release = &i2c_adapter_dev_release;adap->dev.class = &i2c_adapter_class;res = device_register(&adap->dev);if (res)goto out_list;dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);/* create pre-declared device nodes for new-style drivers */if (adap->nr < __i2c_first_dynamic_bus_num)i2c_scan_static_board_info(adap);/* let legacy drivers scan this bus for matching devices */dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,i2c_do_add_adapter);out_unlock:mutex_unlock(&core_lock);return res;out_list:idr_remove(&i2c_adapter_idr, adap->nr);goto out_unlock;}首先对adapter和adapter中内嵌的struct device结构进行必须的初始化.之后将adapter内嵌的struct device注册. 在这里注意一下adapter->dev的初始化.它的类别为i2c_adapter_class,如果没有父结点,则将其父结点设为platform_bus.adapter->dev的名字为i2c + 总线号.测试一下:[eric@mochow i2c]$ cd /sys/class/i2c-adapter/[eric@mochow i2c-adapter]$ lsi2c-0可以看到,在我的PC上,有一个I2C adapter,看下详细信息:[eric@mochow i2c-adapter]$ tree.`-- i2c-0|-- device -> ../../../devices/pci0000:00/0000:00:1f.3/i2c-0|-- name|-- subsystem -> ../../../class/i2c-adapter`-- uevent3 directories, 2 files可以看到,该adapter是一个PCI设备.继续往下看:之后,在注释中看到,有两种类型的driver,一种是new-style drivers,另外一种是legacy driversNew-style drivers是在2.6近版的kernel加入的.它们最主要的区别是在adapter和i2c driver的匹配上.3.1: new-style 形式的adapter注册对于第一种,也就是new-style drivers,将相关代码再次列出如下:if (adap->nr < __i2c_first_dynamic_bus_num)i2c_scan_static_board_info(adap);如果adap->nr 小于__i2c_first_dynamic_bus_num的话,就会进入到i2c_scan_static_board_info().结合我们之前分析的adapter的两种注册分式: i2c_add_adapter()所分得的总线号肯会不会小于__i2c_first_dynamic_bus_num.只有i2c_add_numbered_adapter()才有可能满足:(adap->nr < __i2c_first_dynamic_bus_num)mt6516_devs.c而且必须要调用i2c_register_board_info()将板子上的I2C设备信息预先注册时才会更改__i2c_first_dynamic_bus_num的值.在x86上只没有使用i2c_register_board_info()的.因此,x86平台上的分析可以忽略掉new-style driver的方式.不过,还是详细分析这种情况下.s首先看一下i2c_register_board_info(),如下:int __initi2c_register_board_info(int busnum,struct i2c_board_info const *info, unsigned len){int status;mutex_lock(&__i2c_board_lock);/* dynamic bus numbers will be assigned after the last static one */if (busnum >= __i2c_first_dynamic_bus_num)__i2c_first_dynamic_bus_num = busnum + 1;for (status = 0; len; len--, info++) {struct i2c_devinfo *devinfo;devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);if (!devinfo) {pr_debug("i2c-core: can't register boardinfo!\n");status = -ENOMEM;break;}devinfo->busnum = busnum;devinfo->board_info = *info;list_add_tail(&devinfo->list, &__i2c_board_list);}mutex_unlock(&__i2c_board_lock);return status;}这个函数比较简单,struct i2c_board_info用来表示I2C设备的一些情况,比如所在的总线.名称,地址,中断号等.最后,这些信息会被存放到__i2c_board_list链表.跟踪i2c_scan_static_board_info():代码如下:static void i2c_scan_static_board_info(struct i2c_adapter *adapter){struct i2c_devinfo *devinfo;mutex_lock(&__i2c_board_lock);list_for_each_entry(devinfo, &__i2c_board_list, list) {if (devinfo->busnum == adapter->nr&& !i2c_new_device(adapter,&devinfo->board_info))printk(KERN_ERR "i2c-core: can't create i2c%d-%04x\n",i2c_adapter_id(adapter),devinfo->board_info.addr);}mutex_unlock(&__i2c_board_lock);}该函数遍历挂在__i2c_board_list链表上面的i2c设备的信息,也就是我们在启动的时候指出的i2c设备的信息. 如果指定设备是位于adapter所在的I2C总线上,那么,就调用i2c_new_device().代码如下:struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info){struct i2c_client *client;int status;client = kzalloc(sizeof *client, GFP_KERNEL);if (!client)return NULL;client->adapter = adap;client->dev.platform_data = info->platform_data;device_init_wakeup(&client->dev, info->flags & I2C_CLIENT_WAKE);client->flags = info->flags & ~I2C_CLIENT_WAKE;client->addr = info->addr;client->irq = info->irq;strlcpy(client->name, info->type, sizeof(client->name));/* a new style driver may be bound to this device when we* return from this function, or any later moment (e.g. maybe* hotplugging will load the driver module). and the device* refcount model is the standard driver model one.*/status = i2c_attach_client(client);if (status < 0) {kfree(client);client = NULL;}return client;}我们又遇到了一个新的结构:struct i2c_client,不要被这个结构吓倒了,其实它就是一个嵌入struct device的I2C设备的封装.它和我们之前遇到的struct usb_device结构的作用是一样的.首先,在clinet里保存该设备的相关消息.特别的, client->adapter指向了它所在的adapter.特别的,clinet->name为info->name.也是指定好了的.一切初始化完成之后,便会调用i2c_attach_client( ).看这个函数的字面意思,是将clinet关联起来.到底怎么样关联呢?继续往下看:int i2c_attach_client(struct i2c_client *client){struct i2c_adapter *adapter = client->adapter;int res = 0;//初始化client内嵌的dev结构//父结点为所在的adapter,所在bus为i2c_bus_typeclient->dev.parent = &client->adapter->dev;client->dev.bus = &i2c_bus_type;//如果client已经指定了driver,将driver和内嵌的dev关联起来if (client->driver)client->dev.driver = &client->driver->driver;//指定了driver, 但不是newstyle的if (client->driver && !is_newstyle_driver(client->driver)) {client->dev.release = i2c_client_release;client->dev.uevent_suppress = 1;} elseclient->dev.release = i2c_client_dev_release;//clinet->dev的名称snprintf(&client->dev.bus_id[0], sizeof(client->dev.bus_id),"%d-%04x", i2c_adapter_id(adapter), client->addr);//将内嵌的dev注册res = device_register(&client->dev);if (res)goto out_err;//将clinet链到adapter->clients中mutex_lock(&adapter->clist_lock);list_add_tail(&client->list, &adapter->clients);mutex_unlock(&adapter->clist_lock);dev_dbg(&adapter->dev, "client [%s] registered with bus id %s\n",client->name, client->dev.bus_id);//如果adapter->cleinet_reqister存在,就调用它if (adapter->client_register) {if (adapter->client_register(client)) {dev_dbg(&adapter->dev, "client_register ""failed for client [%s] at 0x%02x\n",client->name, client->addr);}}return 0;out_err:dev_err(&adapter->dev, "Failed to attach i2c client %s at 0x%02x ""(%d)\n", client->name, client->addr, res);return res;}参考上面添加的注释,应该很容易理解这段代码了,就不加详细分析了.这个函数的名字不是i2c_attach_client()么?怎么没看到它的关系过程呢?这是因为:在代码中设置了client->dev所在的bus为i2c_bus_type .因为只需要有bus为i2c_bus_type的driver 注册,就会产生probe了.这个过程在后面分析i2c driver的时候再来详细分析.3.2:legacy形式的adapter注册Legacy形式的adapter注册代码片段如下:dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,i2c_do_add_adapter);这段代码遍历挂在i2c_bus_type上的驱动,然后对每一个驱动和adapter调用i2c_do_add_adapter().代码如下:static int i2c_do_add_adapter(struct device_driver *d, void *data){struct i2c_driver *driver = to_i2c_driver(d);struct i2c_adapter *adap = data;if (driver->attach_adapter) {/* We ignore the return code; if it fails, too bad */driver->attach_adapter(adap);}return 0;}该函数很简单,就是调用driver的attach_adapter()接口.到此为止,adapter的注册已经分析完了.四:i2c driver注册在分析i2c driver的时候,有必要先分析一下i2c架构的初始化代码如下:static int __init i2c_init(void){int retval;retval = bus_register(&i2c_bus_type);if (retval)return retval;retval = class_register(&i2c_adapter_class);if (retval)goto bus_err;retval = i2c_add_driver(&dummy_driver);if (retval)goto class_err;return 0;class_err:class_unregister(&i2c_adapter_class);bus_err:bus_unregister(&i2c_bus_type);return retval;}subsys_initcall(i2c_init);很明显,i2c_init()会在系统初始化的时候被调用.在i2c_init中,先注册了i2c_bus_type的bus,i2c_adapter_class的class.然后再调用i2c_add_driver()注册了一个i2c driver.I2c_bus_type结构如下:static struct bus_type i2c_bus_type = {.name = "i2c",.dev_attrs = i2c_dev_attrs,.match = i2c_device_match,.uevent = i2c_device_uevent,.probe = i2c_device_probe,.remove = i2c_device_remove,.shutdown = i2c_device_shutdown,.suspend = i2c_device_suspend,.resume = i2c_device_resume,};这个结构先放在这里吧,以后还会用到里面的信息的.从上面的初始化函数里也看到了,注册i2c driver的接口为i2c_add_driver().代码如下: static inline int i2c_add_driver(struct i2c_driver *driver){return i2c_register_driver(THIS_MODULE, driver);}继续跟踪:int i2c_register_driver(struct module *owner, struct i2c_driver *driver){int res;/* new style driver methods can't mix with legacy ones *///如果是一个newstyle的driver.但又定义了attach_adapter/detach_adapter.非法if (is_newstyle_driver(driver)) {if (driver->attach_adapter || driver->detach_adapter|| driver->detach_client) {printk(KERN_WARNING"i2c-core: driver [%s] is confused\n",driver->);return -EINVAL;}}/* add the driver to the list of i2c drivers in the driver core *///关联到i2c_bus_typesdriver->driver.owner = owner;driver->driver.bus = &i2c_bus_type;/* for new style drivers, when registration returns the driver core* will have called probe() for all matching-but-unbound devices.*///注册内嵌的driverres = driver_register(&driver->driver);if (res)return res;mutex_lock(&core_lock);pr_debug("i2c-core: driver [%s] registered\n", driver->);/* legacy drivers scan i2c busses directly *///遍历所有的adapter,对其都调用driver->attach_adapterif (driver->attach_adapter) {struct i2c_adapter *adapter;down(&i2c_adapter_class.sem);list_for_each_entry(adapter, &i2c_adapter_class.devices,dev.node) {driver->attach_adapter(adapter);}up(&i2c_adapter_class.sem);}mutex_unlock(&core_lock);return 0;}这里也有两种形式的区分,对于第一种,只需要将内嵌的driver注册就可以了,对于legacy的情况,对每一个adapter都调用driver->attach_adapter().现在,我们可以将adapter和i2c driver关联起来考虑一下了:1:如果是news style形式的,在注册adapter的时候,将它上面的i2c 设备转换成了struct client.struct client->dev->bus 又指定了和i2c driver同一个bus.因为,它们可以发生probe.2:如果是legacy形式,就直接找到对应的对象,调用driver->attach_adapter().五:i2c_bus_type的相关操作I2c_bus_type的操作主要存在于new-style形式的驱动中.接下来分析一下对应的probe过程:5.1:match过程分析Match对应的操作函数为i2c_device_match().代码如下static int i2c_device_match(struct device *dev, struct device_driver *drv){struct i2c_client *client = to_i2c_client(dev);struct i2c_driver *driver = to_i2c_driver(drv);/* make legacy i2c drivers bypass driver model probing entirely;* such drivers scan each i2c adapter/bus themselves.*/if (!is_newstyle_driver(driver))return 0;/* match on an id table if there is one */if (driver->id_table)return i2c_match_id(driver->id_table, client) != NULL;return 0;}如果该驱动不是一个new-style形式的.或者driver没有定义匹配的id_table.都会匹配失败.继续跟踪进i2c_match_id():static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,const struct i2c_client *client){while (id->name[0]) {if (strcmp(client->name, id->name) == 0)return id;id++;}return NULL;}由此可见.如果client的名字和driver->id_table[]中的名称匹配即为成功.5.2:probe过程分析Probe对应的函数为:i2c_device_probe()static int i2c_device_probe(struct device *dev){struct i2c_client *client = to_i2c_client(dev);struct i2c_driver *driver = to_i2c_driver(dev->driver);const struct i2c_device_id *id;int status;if (!driver->probe)return -ENODEV;client->driver = driver;dev_dbg(dev, "probe\n");if (driver->id_table)id = i2c_match_id(driver->id_table, client);elseid = NULL;status = driver->probe(client, id);if (status)client->driver = NULL;return status;}这个函数也很简单,就是将probe流程回溯到i2c driver的probe()六:其它的扩展分析完adapter和i2c driver的注册之后,好像整个架构也差不多了,其它,扩展的东西还有很多. 我们举一个legacy形式的例子,这个例子是在kernel中随便搜索出来的:在linux-2.6.26.3/drivers/hwmon/ad7418.c中,初始化函数为:static int __init ad7418_init(void){return i2c_add_driver(&ad7418_driver);}i2c_driver ad7418_driver结构如下:static struct i2c_driver ad7418_driver = {.driver = {.name = "ad7418",},.attach_adapter = ad7418_attach_adapter,.detach_client = ad7418_detach_client,};该结构中没有probe()函数,可以断定是一个legacy形式的驱动.这类驱动注册的时候,会调用driver的attach_adapter函数.在这里也就是ad7418_attach_adapter.这个函数代码如下:static int ad7418_attach_adapter(struct i2c_adapter *adapter){if (!(adapter->class & I2C_CLASS_HWMON))return 0;return i2c_probe(adapter, &addr_data, ad7418_detect);}在这里我们又遇到了一个i2c-core中的函数,i2c_probe().在分析这个函数之前,先来看下addr_data是什么?#define I2C_CLIENT_MODULE_PARM(var,desc) \static unsigned short var[I2C_CLIENT_MAX_OPTS] = I2C_CLIENT_DEFAULTS; \static unsigned int var##_num; \module_param_array(var, short, &var##_num, 0); \MODULE_PARM_DESC(var,desc)#define I2C_CLIENT_MODULE_PARM_FORCE(name) \I2C_CLIENT_MODULE_PARM(force_##name, \"List of adapter,address pairs which are " \"unquestionably assumed to contain a `" \# name "' chip")#define I2C_CLIENT_INSMOD_COMMON \I2C_CLIENT_MODULE_PARM(probe, "List of adapter,address pairs to scan " \"additionally"); \I2C_CLIENT_MODULE_PARM(ignore, "List of adapter,address pairs not to " \"scan"); \static const struct i2c_client_address_data addr_data = { \.normal_i2c = normal_i2c, \.probe = probe, \.ignore = ignore, \.forces = forces, \}#define I2C_CLIENT_FORCE_TEXT \"List of adapter,address pairs to boldly assume to be present"由此可知道,addr_data中的三个成员都是模块参数.在加载模块的时候可以用参数的方式对其赋值.三个模块参数为别为probe,ignore,force.另外需要指出的是normal_i2c不能以模块参数的方式对其赋值,只能在驱动内部静态指定.从模块参数的模述看来, probe是指"List of adapter,address pairs to scan additionally"Ignore是指"List of adapter,address pairs not to scan "Force是指"List of adapter,address pairs to boldly assume to be present"事实上,它们里面的数据都是成对出现的.前面一部份表示所在的总线号,ANY_I2C_BUS表示任一总线.后一部份表示设备的地址.现在可以来跟踪i2c_probe()的代码了.如下:int i2c_probe(struct i2c_adapter *adapter,const struct i2c_client_address_data *address_data,int (*found_proc) (struct i2c_adapter *, int, int)){int i, err;int adap_id = i2c_adapter_id(adapter);/* Force entries are done first, and are not affected by ignoreentries *///先扫描force里面的信息,注意它是一个二级指针.ignore里的信息对它是无效的if (address_data->forces) {const unsigned short * const *forces = address_data->forces;int kind;for (kind = 0; forces[kind]; kind++) {for (i = 0; forces[kind][i] != I2C_CLIENT_END;i += 2) {if (forces[kind][i] == adap_id|| forces[kind][i] == ANY_I2C_BUS) {dev_dbg(&adapter->dev, "found force ""parameter for adapter %d, ""addr 0x%02x, kind %d\n",adap_id, forces[kind][i + 1],kind);err = i2c_probe_address(adapter,forces[kind][i + 1],kind, found_proc);if (err)return err;}}}}/* Stop here if we can't use SMBUS_QUICK *///如果adapter不支持quick.不能够遍历这个adapter上面的设备if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_QUICK)) { if (address_data->probe[0] == I2C_CLIENT_END&& address_data->normal_i2c[0] == I2C_CLIENT_END) return 0;dev_warn(&adapter->dev, "SMBus Quick command not supported, ""can't probe for chips\n");return -1;}/* Probe entries are done second, and are not affected by ignore entries either *///遍历probe上面的信息.ignore上的信息也对它是没有影响的for (i = 0; address_data->probe[i] != I2C_CLIENT_END; i += 2) { if (address_data->probe[i] == adap_id|| address_data->probe[i] == ANY_I2C_BUS) {dev_dbg(&adapter->dev, "found probe parameter for ""adapter %d, addr 0x%02x\n", adap_id,address_data->probe[i + 1]);err = i2c_probe_address(adapter,address_data->probe[i + 1],-1, found_proc);if (err)return err;}}/* Normal entries are done last, unless shadowed by an ignore entry */ //最后遍历normal_i2c上面的信息.它上面的信息不能在ignore中.for (i = 0; address_data->normal_i2c[i] != I2C_CLIENT_END; i += 1) { int j, ignore;ignore = 0;for (j = 0; address_data->ignore[j] != I2C_CLIENT_END;j += 2) {if ((address_data->ignore[j] == adap_id ||address_data->ignore[j] == ANY_I2C_BUS)&& address_data->ignore[j + 1]== address_data->normal_i2c[i]) {dev_dbg(&adapter->dev, "found ignore ""parameter for adapter %d, ""addr 0x%02x\n", adap_id,address_data->ignore[j + 1]);ignore = 1;break;}}if (ignore)continue;dev_dbg(&adapter->dev, "found normal entry for adapter %d, ""addr 0x%02x\n", adap_id,address_data->normal_i2c[i]);err = i2c_probe_address(adapter, address_data->normal_i2c[i],-1, found_proc);if (err)return err;}return 0;}这段代码很简单,结合代码上面添加的注释应该很好理解.如果匹配成功,则会调用i2c_probe_address ().这个函数代码如下: static int i2c_probe_address(struct i2c_adapter *adapter, int addr, int kind,int (*found_proc) (struct i2c_adapter *, int, int)){int err;/* Make sure the address is valid *///地址小于0x03或者大于0x77都是不合法的if (addr < 0x03 || addr > 0x77) {dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",addr);return -EINVAL;}/* Skip if already in use *///adapter上已经有这个设备了if (i2c_check_addr(adapter, addr))return 0;/* Make sure there is something at this address, unless forced *///如果kind小于0.检查adapter上是否有这个设备if (kind < 0) {if (i2c_smbus_xfer(adapter, addr, 0, 0, 0,I2C_SMBUS_QUICK, NULL) < 0)return 0;/* prevent 24RF08 corruption */if ((addr & ~0x0f) == 0x50)i2c_smbus_xfer(adapter, addr, 0, 0, 0,I2C_SMBUS_QUICK, NULL);}/* Finally call the custom detection function *///调用回调函数err = found_proc(adapter, addr, kind);/* -ENODEV can be returned if there is a chip at the given addressbut it isn't supported by this chip driver. We catch it here asthis isn't an error. */if (err == -ENODEV)err = 0;if (err)dev_warn(&adapter->dev, "Client creation failed at 0x%x (%d)\n",addr, err);return err;}首先,对传入的参数进行一系列的合法性检查.另外,如果该adapter上已经有了这个地址的设备了.也会返回失败.所有adapter 下面的设备都是以adapter->dev为父结点的.因此只需要遍历adapter->dev下面的子设备就可以得到当前地址是不是被占用了.如果kind < 0.还得要adapter检查该总线是否有这个地址的设备.方法是向这个地址发送一个Read的Quick请求.如果该地址有应答,则说明这个地址上有这个设备.另外还有一种情况是在24RF08设备的特例.如果adapter上确实有这个设备,就会调用驱动调用时的回调函数.在上面涉及到了IIC的传输方式,有疑问的可以参考intel ICH5手册的有关smbus部份.跟踪i2c_smbus_xfer().代码如下:s32 i2c_smbus_xfer(struct i2c_adapter * adapter, u16 addr, unsigned short flags,char read_write, u8 command, int size,union i2c_smbus_data * data){s32 res;flags &= I2C_M_TEN | I2C_CLIENT_PEC;if (adapter->algo->smbus_xfer) {mutex_lock(&adapter->bus_lock);res = adapter->algo->smbus_xfer(adapter,addr,flags,read_write,command,size,data);mutex_unlock(&adapter->bus_lock);} elseres = i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,command,size,data);return res;}如果adapter有smbus_xfer()函数,则直接调用它发送,否则,也就是在adapter不支持smbus协议的情况下,调用i2c_smbus_xfer_emulated()继续处理.跟进i2c_smbus_xfer_emulated().代码如下:static s32 i2c_smbus_xfer_emulated(struct i2c_adapter * adapter, u16 addr,unsigned short flags,char read_write, u8 command, int size,union i2c_smbus_data * data){/* So we need to generate a series of msgs. In the case of writing, weneed to use only one message; when reading, we need two. We initializemost things with sane defaults, to keep the code below somewhatsimpler. *///写操作只会进行一次交互,而读操作,有时会有两次操作.//因为有时候读操作要先写command,再从总线上读数据//在这里为了代码的简洁.使用了两个缓存区,将两种情况统一起来.unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];//一般来说,读操作要交互两次.例外的情况我们在下面会接着分析int num = read_write == I2C_SMBUS_READ?2:1;//与设备交互的数据,一般在msg[0]存放写入设备的信息,在msb[1]里存放接收到的//信息.不过也有例外的//msg[2]的初始化,默认发送缓存区占一个字节,无接收缓存struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 },{ addr, flags | I2C_M_RD, 0, msgbuf1 }};int i;u8 partial_pec = 0;//将要发送的信息copy到发送缓存区的第一字节msgbuf0[0] = command;switch(size) {//quick类型的,其它并不传输有效数据,只是将地址写到总线上,等待应答即可//所以将发送缓存区长度置为0 .再根据读/写操作,调整msg[0]的标志位//这类传输只需要一次总线交互case I2C_SMBUS_QUICK:msg[0].len = 0;/* Special case: The read/write field is used as data */msg[0].flags = flags | (read_write==I2C_SMBUS_READ)?I2C_M_RD:0;num = 1;break;case I2C_SMBUS_BYTE://BYTE类型指一次写和读只有一个字节.这种情况下,读和写都只会交互一次//这种类型的读有例外,它读取出来的数据不是放在msg[1]中的,而是存放在msg[0]if (read_write == I2C_SMBUS_READ) {/* Special case: only a read! */msg[0].flags = I2C_M_RD | flags;num = 1;}break;case I2C_SMBUS_BYTE_DATA://Byte_Data是指命令+数据的传输形式.在这种情况下,写只需要一次交互,读却要两次//第一次将command写到总线上,第二次要转换方向.要将设备地址和read标志写入总线.//应回答之后再进行read操作//写操作占两字节,分别是command+data.读操作的有效数据只有一个字节//交互次数用初始化值就可以了if (read_write == I2C_SMBUS_READ)msg[1].len = 1;else {msg[0].len = 2;msgbuf0[1] = data->byte;}break;case I2C_SMBUS_WORD_DATA://Word_Data是指命令+双字节的形式.这种情况跟Byte_Data的情况类似//两者相比只是交互的数据大小不同if (read_write == I2C_SMBUS_READ)msg[1].len = 2;else {msg[0].len=3;msgbuf0[1] = data->word & 0xff;msgbuf0[2] = data->word >> 8;}break;case I2C_SMBUS_PROC_CALL://Proc_Call的方式与write 的Word_Data相似,只不过写完Word_Data之后,要等待它的应答//应该它需要交互两次,一次写一次读num = 2; /* Special case */read_write = I2C_SMBUS_READ;msg[0].len = 3;msg[1].len = 2;msgbuf0[1] = data->word & 0xff;msgbuf0[2] = data->word >> 8;break;case I2C_SMBUS_BLOCK_DATA://Block_Data:指command+N段数据的情况.//如果是读操作,它首先要写command到总线,然后再读N段数据.要写的command已经//放在msg[0]了.现在只需要将msg[1]的标志置I2C_M_RECV_LEN位,msg[1]有效长度为1字节.因为//adapter驱动会处理好的.现在现在还不知道要传多少段数据.//对于写的情况:msg[1]照例不需要.将要写的数据全部都放到msb[0]中.相应的也要更新//msg[0]中的缓存区长度if (read_write == I2C_SMBUS_READ) {msg[1].flags |= I2C_M_RECV_LEN;msg[1].len = 1; /* block length will be added bythe underlying bus driver */} else {//data->block[0]表示后面有多少段数据.总长度要加2是因为command+count+N段数据msg[0].len = data->block[0] + 2;if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 2) {dev_err(&adapter->dev, "smbus_access called with ""invalid block write size (%d)\n",data->block[0]);return -1;}for (i = 1; i < msg[0].len; i++)msgbuf0[i] = data->block[i-1];}break;case I2C_SMBUS_BLOCK_PROC_CALL://Proc_Call:表示写完Block_Data之后,要等它的应答消息它和Block_Data相比,只是多了一部份应答而已num = 2; /* Another special case */read_write = I2C_SMBUS_READ;if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {dev_err(&adapter->dev, "%s called with invalid ""block proc call size (%d)\n", __func__,data->block[0]);return -1;}msg[0].len = data->block[0] + 2;for (i = 1; i < msg[0].len; i++)msgbuf0[i] = data->block[i-1];msg[1].flags |= I2C_M_RECV_LEN;msg[1].len = 1; /* block length will be added bythe underlying bus driver */break;case I2C_SMBUS_I2C_BLOCK_DATA://I2c Block_Data与Block_Data相似,只不过read的时候,数据长度是预先定义好了的.另外//与Block_Data相比,中间不需要传输Count字段.(Count表示数据段数目)if (read_write == I2C_SMBUS_READ) {msg[1].len = data->block[0];} else {msg[0].len = data->block[0] + 1;if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 1) {dev_err(&adapter->dev, "i2c_smbus_xfer_emulated called with ""invalid block write size (%d)\n",data->block[0]);return -1;}for (i = 1; i <= data->block[0]; i++)msgbuf0[i] = data->block[i];}break;default:dev_err(&adapter->dev, "smbus_access called with invalid size (%d)\n", size);return -1;}//如果启用了PEC.Quick和I2c Block_Data是不支持PEC的i = ((flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK&& size != I2C_SMBUS_I2C_BLOCK_DATA);if (i) {/* Compute PEC if first message is a write *///如果第一个操作是写操作if (!(msg[0].flags & I2C_M_RD)) {//如果只是写操作if (num == 1) /* Write only *///如果只有写操作,写缓存区要扩充一个字节,用来存放计算出来的PECi2c_smbus_add_pec(&msg[0]);else /* Write followed by read *///如果后面还有读操作,先计算前面写部份的PEC(注意这种情况下不需要//扩充写缓存区,因为不需要发送PEC.只会接收到PEC)partial_pec = i2c_smbus_msg_pec(0, &msg[0]);}/* Ask for PEC if last message is a read *///如果最后一次是读消息.还要接收到来自slave的PEC.所以接收缓存区要扩充一个字节if (msg[num-1].flags & I2C_M_RD)msg[num-1].len++;}if (i2c_transfer(adapter, msg, num) < 0)return -1;。
at24c128程序范例
AT24C128是一种串行EEPROM(电可擦可编程只读存储器)芯片,通常用于存储小量的数据,比如配置参数或设备状态。
它可以通过I2C总线与微控制器进行通信。
以下是一个简单的示例程序,展示了如何使用AT24C128 EEPROM芯片。
这个例子使用的是C语言,适用于常见的微控制器,如基于AVR或PIC的微控制器。
请注意,你需要根据你的具体硬件和开发环境来调整代码。
```c#include <i2c.h> // 包含I2C库函数// 定义AT24C128的I2C地址#define EEPROM_SLAVE_ADDRESS 0x50// 定义要写入的数据uint8_t data_to_write[2] = {0x01, 0x02};// 定义EEPROM的页大小#define EEPROM_PAGE_SIZE 32// 定义EEPROM的页地址uint8_t page_address = 0;// 函数声明void write_byte(uint8_t address, uint8_t value);void write_data(uint8_t page, uint8_t *data, uint8_t length);int main() {// 初始化I2C总线i2c_init();// 写入数据到EEPROMwrite_data(page_address, data_to_write, sizeof(data_to_write));// 其他代码...// 主循环while(1) {// 读取EEPROM中的数据// 其他代码...}}// 函数定义void write_byte(uint8_t address, uint8_t value) {// 写入一个字节到EEPROM的指定地址// 实现细节取决于你的硬件和I2C库}void write_data(uint8_t page, uint8_t *data, uint8_t length) {// 将数据写入EEPROM的指定页面// 实现细节取决于你的硬件和I2C库// 确保写入的数据不超过EEPROM页的大小if (length > EEPROM_PAGE_SIZE) {length = EEPROM_PAGE_SIZE;}// 写入数据// 实现细节取决于你的硬件和I2C库}```请注意,这个示例程序是非常基础的,它没有包含错误检查和处理,也没有实现具体的I2C 通信细节。
PIC单片机CCS之C语言(#USE I2C)
PIC 单片机CCS 之C 语言(#USE I2C)#USE I2C 语法:#use i2c(options)options 被逗号隔开,可能是:MASTER //设置成主机方式SLAVE //设置成从机方式SCL=pin //指定SCL 引脚(pin 是一个位地址)SDA=pin //指定SDA 引脚ADDRESS=nn //指定从机方式地址FAST //使用fast I2C 规范SLOW //使用slow I2C 规范RESTART_WDT //在I2C_READ 等待的时候,重新启动WDTFORCE_HW //使用硬件I2C 函数NOFLOAT_HIGH //不允许信号漂浮至高,从低到高驱动信号SMBUS //总线不使用I2C,但很相似,即模拟I2C目的:I2C 的零件库包含了一个实现I2C 总线的函数, #USE I2C 使得I2C_START, I2C_STOP, I2C_READ, I2C_WRITE 和I2C_POLL 函数保持有效,直到下一个#USE I2C 的出现为止.除非指定了FORCE_HW,否则会产生模拟I2C的软件函数.SLAVE 方式只能同内置的SSP 一起被使用.例子:#use I2C(master, sda=PIN_B0, scl=PIN_B1)例子:#use i2c(master,sda=EEPROM_SDA, scl=EEPROM_SCL)// init_ext_eeprom(); Call before the other functions are used //// write_ext_eeprom(a, d); Write the byte d to the address a //// d = read_ext_eeprom(a); Read the byte d from the address a //。
利用PIC单片机与IIC总线通信
^ V'( ))*+, _` #$ Y& a bc789"def'(gh Y+ ,i jef'(kl , m nopq rs _t aghuvwxyz 6uvQI: {kl|]}~k gh ,i a6kl|]}~k gh}~" |yz" auvV 6v Z0v ¡ & ¢£ghuvU¤ *Y¥ ¦§ + ¢£¨©ª ¦§ ¢£«(¬© ª aghuv®¯°9±²" ,³+ ´ µ[ ¶·®¯°9¸¹º6 »¼½ ¾x¿±²ÀeÁ ,³*³ajÀeÁZ[no±²eÁ ,³+ 6ÃÄuvQI: _`"¬Y¨ÅÆÇVx("
ùú à ûü ýþÿ CROSS ASSEMBLER 2.00 d:\seeprom\appnotes\i2cbus.asm Apr 11 15:36:02 1990 PAGE 1 TWO WIRE/I2C BUS INTERFACE WITH PIC16C5x 0001 TITLE “TWO WIRE/I2C BUS INTERFACE WITH PIC16C5x” 0002 ; 0003 LIST P=16C54 0004 ; 0005 ;****************************************************** 0006 ;** Two wire/I2C Bus READ/WRITE Sample Routines ; of Microchip’s 24CXX/85CXX serial CMOS 0007 ;** EEPROM interfacing to a PIC16C54 8-bit CMOS 0008 ;** single chip microcomputer 0009 ;** 0010 ;** Part use = PIC16C54-XT/JW 0011 ;** Note: 1) All timings are based on a ; reference crystal frequency of 2 MHz which ; is equivalent to an instruction cycle 0012 ;** time of 2 usec. 0013 ;** 2) Address and literal values are read ; in octal unless otherwise specified. ; 3) The following sample program is ; intended to interface a two wire/I2C ; serial EEPROM with a PIC16C54 on a ; stand-alone application only. ; In the case where the two wire bus is ; multiplexing with other circuitry, it is ; recommended to check the 24CXX/85CXX in ; standby mode to avoid bus contention. 0014 ;** 0015 ;******************************************************* 0016 ; 0017 ;-----------------------------------------------------0018 ; Files Assignment 0019 ;-----------------------------------------------------0020 ; 0021 0002 PC EQU 2 ; Program counter 0022 0004 FSR EQU 4 ; File Select Register 0023 0005 RA EQU 5 ; Port A use to select ; device address 0024 0006 RB EQU 6 ; RB7 = SDA, RB6 = SCL 0025 ; 0026 0010 STATUS EQU 10 ; Status register 0027 0011 FLAG EQU 11 ; Common flag bits
PIC单片机之I2C(从模式)
PIC 单片机之I2C(从模式)
网上有许多讲解单片机实现I2C 主模式,但是从模式的很少。
我现在就来讲讲PIC 单片机使用MSSP 模块实现I2C 从模式。
有关I2C 协议的具体介绍可以看《PIC 单片机之I2C(主模式)》,我们这里直接讲解实例
实例讲解:我们模仿AT24C02 EEPROM 的协议。
让一个主模式的单片机,来读取从模式单片机的数据。
下面为AT24C02 的随机地址读取的协议。
第一个字节:输入7 位地址和一位的写状态位,
第二个字节:然后写入EEPROM 数据地址,
第三个字节:输入7 位地址和一位的读状态位,
第四~N 个字节:读出的EEPROM 的数据。
我们来讲解下程序的基本思路:我们使能了MSSP 中断,即是I2C 接收中。
SPI、I2C、UART
SPI、I2C、UART三种串行总线协议的区别和SPI接口介绍(2014-02-17 14:38:31)转载▼分类:单片机、嵌入系统SPI、I2C、UART三种串行总线协议的区别第一个区别当然是名字:SPI(Serial Peripheral Interface:串行外设接口);I2C(INTER IC BUS)UART(Universal Asynchronous Receiver Transmitter:通用异步收发器)第二,区别在电气信号线上:SPI总线由三条信号线组成:串行时钟(SCLK)、串行数据输出(SDO)、串行数据输入(SDI)。
SPI总线可以实现多个SPI设备互相连接。
提供SPI串行时钟的SPI设备为SPI主机或主设备(Master),其他设备为SPI从机或从设备(Slave)。
主从设备间可以实现全双工通信,当有多个从设备时,还可以增加一条从设备选择线。
如果用通用IO口模拟SPI总线,必须要有一个输出口(SDO),一个输入口(SDI),另一个口则视实现的设备类型而定,如果要实现主从设备,则需输入输出口,若只实现主设备,则需输出口即可,若只实现从设备,则只需输入口即可。
I2C总线是双向、两线(SCL、SDA)、串行、多主控(multi-master)接口标准,具有总线仲裁机制,非常适合在器件之间进行近距离、非经常性的数据通信。
在它的协议体系中,传输数据时都会带上目的设备的设备地址,因此可以实现设备组网。
如果用通用IO口模拟I2C总线,并实现双向传输,则需一个输入输出口(SDA),另外还需一个输出口(SCL)。
(注:I2C资料了解得比较少,这里的描述可能很不完备)UART总线是异步串口,因此一般比前两种同步串口的结构要复杂很多,一般由波特率产生器(产生的波特率等于传输波特率的16倍)、UART接收器、UART发送器组成,硬件上由两根线,一根用于发送,一根用于接收。
显然,如果用通用IO口模拟UART总线,则需一个输入口,一个输出口。
pic芯片
pic芯片PIC芯片(Peripheral Interface Controller)是由美国微芯科技公司(Microchip Technology)所设计与生产的一种微控制器。
PIC芯片是一种嵌入式控制器,主要用于各种消费电子产品以及工业控制系统中。
它具有低功耗、高性能、易用性等优点,被广泛应用于智能家居、智能手机、智能手表、智能电视、工业自动化等领域。
PIC芯片采用哈佛结构,具有8位、16位和32位不同的型号。
其中,8位的PIC芯片最为常见,被广泛应用于各种小型终端设备。
16位和32位的PIC芯片则更适用于需要更高计算能力和更大存储空间的系统。
PIC芯片内部集成了CPU、存储器、IO口、定时器、串口、PWM输出、模拟输入输出等功能模块。
它具有多个GPIO引脚,可以连接外部的传感器、执行器和其他外设。
此外,PIC芯片还支持多种通信协议,如I2C、SPI和UART,可以与其他设备进行数据交互。
PIC芯片的编程方式有汇编语言和C语言两种。
通过编程,可以实现对芯片内部各功能模块的控制和管理。
PIC芯片的开发工具非常完善,Microchip公司提供了一套包括C编译器、调试器、编程器等在内的开发套件。
开发人员可以使用这些工具进行软件开发、调试和下载。
PIC芯片具有低功耗的特点,一般工作电压为3.3V或5V。
它采用的最新工艺和设计技术,使得芯片在低功耗下能够提供较高的计算性能和IO处理能力。
同时,PIC芯片还支持多种低功耗模式,可以在不同的工作环境中灵活调整功耗和性能。
PIC芯片的应用非常广泛,可以满足各种不同应用场景的需求。
例如,在智能家居中,PIC芯片可以用于智能插座、智能开关、智能灯具等设备的控制和管理。
在智能手机中,PIC芯片可以用于触摸屏控制、传感器数据处理、电源管理等。
在工业控制系统中,PIC芯片可以用于智能化生产设备的控制和调度。
总之,PIC芯片是一种功能强大、易用性高、低功耗的微控制器。
它的广泛应用和不断创新的技术使得PIC芯片在嵌入式控制领域中拥有重要的地位和发展前景。
pic单片机调试IIC
pic单片机调试IIC
1、ADC采样
如果采用DMA,最多只能设置16个通道,多于16个通道时,用中断,在中断中切换通道
2、I2C
采用软件模拟I2C时,
IIC总线有个特点就是开漏输出,这是总线竞争的需要,如果要做到开漏输出,有些IO口本来有这样的功能,如果用这样的IO口去仿真就比较方便。
但如果IO口没有开漏输出功能,那就只能在输入和输出之间切换。
所以还是比较烦的。
当在需要输出低电平的时候将IO口设置成输出并输出0,输出高电平时将IO口设置成输入(高阻状态),让外部上拉电阻将电平拉高。
关键字:pic单片机调试IIC。
PIC单片机I2C从模式程序
//I2C从模式(适用用于PIC16F1705/1708/1709)//函数初始化与主体函数,经过反复检验,程序OK,程序贴出来帮助和我同样曾经迷惑的人们. //如有疑问可以联系我,QQ:370886719 ………给更多的人照亮道路void i2c_slave_init(void){ANSA4=0;ANSA2=0;WPUAbits.WPUA2 = 0;WPUAbits.WPUA4 = 0;RA4PPS=0X10; //CLKRA2PPS=0X11; //SDASSP1CON1bits.SSPM0 = 0;SSP1CON1bits.SSPM1 = 1;SSP1CON1bits.SSPM2 = 1;SSP1CON1bits.SSPM3 = 0; // I2C slave mode ,7bit addressSSP1CON1bits.CKP = 1; // enable clocSSP1STAT =0;SSP1MSK=0XFE; //允许数据地址匹配SSPCON2bits.SEN=1;SSPCON3bits.SDAHT=1;SSPCON3bits.SBCDE = 1; // Enable slave bus collision detect interrupts//SSPCON3bits.AHEN=1; ////////////////////////////////SSPCON3bits.DHEN=1; /////////////////////////////SSPCON3bits.BOEN=1;PIR1bits.SSP1IF = 0; // Clear the serial port interrupt flagPIR2bits.BCL1IF = 0; // Clear the bus collision interrupt flagPIE2bits.BCL1IE = 1; // Enable bus collision interruptsPIE1bits.SSP1IE = 1;//Enabe interrupt MSSPINTCONbits.PEIE = 1;INTCONbits.GIE = 1;}#include"proc.h"//#include <stdio.h>//#include <stdlib.h>//#define RX_BUF_LEN 16#define while_delay 10000u8i2c_address,word_address,array_receive_x[32]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,1 9,20,21,22,23,24,25,26,27,28,29,30,31};u8array_receive_y[32]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27, 28,29,30,31};u8 Register_flow[5]={10,1,2,3,4};u8 Register_temp[9]={36,1,2,3,4,5,6,7,8};u8 Register[17];u8 i2c_counter,RX_BUF_LEN,TX_BUF_LEN;bit read_temp_F,receive_data_end_F;/***********************************************************************/void i2c_slave_tx(void){u16 timercounter;PIR2bits.BCL1IF = 0; // Clear the bus collision interrupt flag//asm("nop");asm("nop");asm("nop");asm("nop");if(SSP1STATbits.R_nW ==0)//Read operation.{PIR1bits.SSP1IF = 0;i2c_address = SSP1BUF;//i2c_counter = word_address; //得到要发送的数据地址i2c_counter = 0;if(read_temp_F==0){TX_BUF_LEN=5; //发送5个温度相关数据给上位机while(i2c_counter < TX_BUF_LEN){SSP1BUF=Register_flow[i2c_counter]; //send datatimercounter=while_delay;while( PIR1bits.SSP1IF == 0){timercounter--;if(timercounter==0){return;}}//waiting for ~`ACKPIR1bits.SSP1IF = 0;asm("nop");asm("nop");asm("nop");asm("nop");if(SSP1CON2bits.ACKSTAT == 1) //主机没有应答{return ; //NOACK}else{i2c_counter++;//ACK}}SSP1IF = 0;}else //协议切换,发送流量传感器数据给上位机{TX_BUF_LEN=9; //发送5个温度相关数据给上位机while(i2c_counter < TX_BUF_LEN){while( PIR1bits.SSP1IF == 0){timercounter--;if(timercounter==0){return;}}//waiting for ~`ACKPIR1bits.SSP1IF = 0;asm("nop");asm("nop");asm("nop");asm("nop");if(SSP1CON2bits.ACKSTAT == 1) //主机没有应答{return ; //NOACK}else{i2c_counter++;//ACK}}SSP1IF = 0;read_temp_F=0;}}}/*****************************************************************/void i2c_salve_rx(void) //master writer{if(SSP1STATbits.R_nW ==0){PIR1bits.SSP1IF = 0;i2c_address = SSP1BUF;// timercounter=while_delay;/*while(PIR1bits.SSP1IF == 0){timercounter--;if(timercounter==0){timercounter=while_delay;return ;}} //waiting for send ~ACKPIR1bits.SSP1IF = 0;*///word_address = SSP1BUF;// i2c_address = SSP1BUF;asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");word_address=0;return ;}if(SSP1STATbits.R_nW ==0 && SSP1STATbits.D_nA==1){/****************************************************************/if(Register[0]==0xd0) //读温度模式{read_temp_F=1;RX_BUF_LEN=0;SSP1CON1bits.CKP=1;return;}/****************************************************************/if(Register[0]==0xc0) //{RX_BUF_LEN=3;read_temp_F=0;}/***************************************************************/switch (Register[0]){case 0xe0: read_temp_F=0;RX_BUF_LEN=17;if(word_address>0 && word_address<=16){*(array_receive_x+word_address-1)=*(Register+word_address);}break;case 0xe1: read_temp_F=0;RX_BUF_LEN=17;if(word_address>0 && word_address<=16){*(array_receive_x+word_address+16-1)=*(Register+word_address);}break;case 0xe2: read_temp_F=0;RX_BUF_LEN=17;if(word_address>0 && word_address<=16){*(array_receive_y+word_address-1)=*(Register+word_address);}break;case 0xe3: read_temp_F=0;RX_BUF_LEN=17;if(word_address>0 && word_address<=16){*(array_receive_y+word_address+16-1)=*(Register+word_address);}break;}word_address++;/**************************************************************/if(word_address>=RX_BUF_LEN){word_address=0;if(Register[0]==0xe3){receive_data_end_F=1; //所有完整的数据都已经收到}}asm("nop");asm("nop");asm("nop");asm("nop");return;}}。
pic单片机 i2c起始信号发送不了
pic单片机 i2c起始信号发送不了PIC单片机I2C起始信号发送不了可能有以下几个原因:1.硬件问题:首先要检查硬件连接是否正确,是否有松动,接地是否良好。
还要检查I2C设备的电源电压是否稳定,电源电压严重不足时也可能导致I2C起始信号发送不了。
2.软件问题:如果硬件连接没有问题,那么就要检查软件部分。
首先要确保程序中I2C模块的初始化设置正确,包括I2C的工作频率、从设备地址、I2C总线上是否存在其他主设备等方面。
3.信号干扰问题:如果硬件连接和软件程序都没有问题,那么就有可能是信号干扰导致I2C起始信号发送不了。
这种情况下可以通过在SCL 和SDA线上加上RC滤波电路或异步传输器等电路来解决。
解决方法:1.检查硬件连接:首先需检查I2C设备的硬件连接是否正确、接口是否松动以及接地是否良好,接着再检查设备电源电压是否稳定。
如果发现硬件连接或电源电压存在问题,则需要重新连接或调整电压。
2.检查软件部分:如果硬件连接没有问题,则需要检查I2C模块的初始化设置是否正确,包括I2C的工作频率、从设备地址、I2C总线上是否存在其他主设备等方面。
程序中建议使用I2C模块自带的起始信号发送函数,避免手工发送。
3.加入RC滤波电路或异步传输器等电路:如果软硬件设置都没有问题,那么I2C起始信号发送不了可能是因为信号干扰导致的。
这种情况下可以通过在SCL和SDA线上加上RC滤波电路、异步传输器等电路来解决。
RC滤波电路是利用滤波器去除信号上的噪声,而异步传输器则是将I2C主机和从机之间的信号解耦,防止信号互相干扰。
总结:在解决I2C起始信号发送不了的问题时,首先要检查硬件连接是否正确并检查设备电源电压是否稳定。
如果硬件连接没有问题,再检查软件部分的设置是否正确,包括I2C的工作频率、从设备地址、I2C总线上是否存在其他主设备等方面。
最后是对信号干扰进行排查并解决。
如果还是无法解决问题,可能需要更换硬件或找到其他原因。
rk linux下i2c总线驱动读写原理
RK Linux下I2C总线驱动读写原理RK Linux,一个开源的、强大的、可定制的操作系统,广泛应用于各种嵌入式系统。
I2C总线是一种常用的通信协议,常用于连接低速外围设备,如传感器、EEPROM等。
在RK Linux下,I2C总线驱动的读写原理是什么呢?首先,我们来了解下I2C总线的基本概念。
I2C总线是一种双线串行通信总线,由数据线SDA和时钟线SCL组成。
通过这两根线,多个设备可以在同一总线上进行通信。
每个设备都有一个唯一的地址,主机可以通过发送设备的地址来选择与之通信的设备。
在RK Linux下,I2C总线驱动的读写操作主要依赖于内核提供的API。
这些API 包括i2c_read()、i2c_write()等,它们提供了与I2C设备通信的接口。
那么,这些API是如何实现读写操作的呢?在内核中,I2C驱动程序负责管理I2C总线上所有的设备和它们的通信。
当需要从设备读取数据时,驱动程序首先会向设备发送读请求。
设备接收到请求后,会将数据写入SDA线。
驱动程序会持续监听SDA线,一旦接收到数据,就会将其保存并通知应用程序。
同样地,当需要向设备写入数据时,驱动程序会向设备发送写请求。
设备接收到请求后,会准备好接收数据。
驱动程序会将数据写入SDA线,设备接收到数据后,会将数据保存到内部寄存器中。
需要注意的是,I2C总线的读写操作都是通过驱动程序来完成的。
应用程序只需要调用内核提供的API即可与I2C设备进行通信。
这样设计的好处是应用程序可以专注于自己的业务逻辑,而不需要关心底层的通信细节。
同时,这也使得应用程序与具体的I2C设备无关,具有更好的可移植性和扩展性。
PIC单片机IIC程序
I2C_delay();
err_count++;
if(err_count>10)
{
temp=1;
return;
}
}
po_DEMOD_SCL=0;
// I2C_SET_SCL(LOW1);
// I2C_Delay(I2C_LOW);
po_DEMOD_SCL=1;
// I2C_Delay(30);
dir_DEMOD_SDA=1;
//I2C_delay();
while(pi_DEMOD_SDA)
}
/*
void I2C_Send(unsigned char ccdata)
{
unsigned char temp,err_count;
temp=8;err_count=0;
dir_DEMOD_SCL=0;
dir_DEMOD_SDA=0;
}
I2C_delay();
po_DEMOD_SCL=1;
I2C_delay();
po_DEMOD_SCL=0;
ccdata<<=1;
}
//I2C_TxData(ccdata) ;
// I2C_SET_SDA(HIGH1);
unsigned char I2C_Recive(void)
{
unsigned char temp=8,buf;
dir_DEMOD_SDA=1;
I2C_delay();
////// I2C总线在标准的方式下最高时钟为100KHZ, ///////
////时钟信号最小低电平时段为4.7us,高电平时段不得小于4us. ///////
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
#include <pic.h>
#define uchar unsigned char
#define Slave_Addr 0x54
#define Fosc 11059200
uchar Buf; //接收到的数据
bit Flag = 0; //收发标志位,1-->主机读从机,0-->主机写从机void uart_init(unsigned int baud)
{
SYNC = 0; //选择异步传输模式
BRGH = 1; //高速波特率模式
TXEN = 1; //使能发送模式
SPEN = 1; //使能引脚
//CREN = 1; //使能接收
BRG16 = 1; //16位波特率发生器
SPBRGH = (Fosc/baud/4 - 1) / 256;
SPBRG = (Fosc/baud/4 - 1) % 256;
//RCIE = 1; //接收中断使能
//SPBRGH = 287 / 256;
//SPBRG = 287 % 256; //9600bps
}
void Send_Byte(char s)
{
TXREG = s;
while(!TXIF);//等待数据发送完毕
}
void SendStr(char *str)
{
while(*str != '\0')
Send_Byte(*(str++));
}
//**************I2C从机初始化**************
void I2C_Init(void)
{
TRISC |= 0x11; //SCL,SDA为输入
SSPADD = Slave_Addr; //从机地址0x54
BF = 0;
SSPIE = 1; //允许MSSP中断
GIE = 1;
PEIE = 1;
SSPOV = 0;
SSPCON = 0x2E; //从动,7位地址,使能I2C//SSPEN = 1; }
//************应答发送一个字节**************
void Slave_Re(uchar Byte)
{
SSPIE = 0; //禁止MSSP中断
SSPBUF = Byte; //装载数据
CKP = 1; //使能SCL
while(!SSPIF);
SSPIF = 0;
SSPIE = 1; //允许MSSP中断
}
//**********MSSP中断接收程序****************
void interrupt Slave_Recive(void)
{
SendStr("tesT\r\n");
// SSPOV = 0;
// BF = 0;
if(SSPIF == 1)
{
SSPIF = 0;
if(BF)
{
Buf = SSPBUF;
if(SSPSTAT & 0x20) //收到的是数据
{
Send_Byte(Buf);
SendStr("\r\n");
}
else //收到的是地址
{
if(SSPSTAT & 0x04) //写操作
Flag = 1;
else //写操作
Flag = 0;
}
}
}
}
void main(void)
{
uart_init(9600);
I2C_Init();
SendStr("test MSSP!!\r\n");
while(1)
{
if(Flag == 1)
{
Flag = 0;
Slave_Re('a');
}
}
}。