IIC总线

一、IIC总线的特征

•  只要求两条总线线路:一条串行数据线SDA ,一条串行时钟线SCL;

•  每个连接到总线的器件都可以通过唯一的地址和一直存在的简单的主机/从机关系软件设定地
址,主机可以作为主机发送器或主机接收器;

•  它是一个真正的多主机总线,如果两个或更多主机同时初始化数据传输,可以通过冲突检测和仲裁
防止数据被破坏;

•  串行的8位双向数据传输位速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速
模式下可达3.4Mbit/s;

•  片上的滤波器可以滤去总线数据线上的毛刺波,保证数据完整;

•  连接到相同总线的IC数量只受到总线的最大电容400pF限制。

二、IIC总线的概念

IIC总线支持任何IC生产过程(NMOS CMOS 双极性)。两线――串行数据SDA 和串行时钟SCL 线在连接到总线的器件间传递信息。每个器件都有一个唯一的地址识别(无论是微控制器、LCD驱动器、存储器或键盘接口),而且都可以作为一个发送器或接收器(由器件的功能决定)。很明显,LCD驱动器只是一个接收器,而存储器则既可以接收又可以发送数据。除了发送器和接收器外,器件在执行数据传输时也可以被看作是主机或从机(见表1)。主机是初始化总线的数据传输并产生允许传输的时钟信号的器件。此时任何被寻址的器件都被认为是从机。

表1 IIC总线术语的定义

术语

描述

发送器 

发送数据到总线的器件

接收器 

从总线接收数据的器件

主机

初始化发送产生时钟信号和终止发送的器件

从机

被主机寻址的器件

多主机

同时有多于一个主机尝试控制总线但不破坏报文

仲裁

是一个在有多个主机同时尝试控制总线但只允许其中一个控制总线并使报文不被破坏的过程

同步

两个或多个器件同步时钟信号的过程

I2C 总线是一个多主机的总线,这就是说可以连接多于一个能控制总线的器件到总线。由于主机通常是微控制器,让我们考虑以下数据在两个连接到 I2C 总线的微控制器之间传输的情况。(见图 2 )

这突出了 I2C 总线的主机、从机和接收器、发送器的关系。应当注意的是这些关系不是持久的,只由当时数据传输的方向决定。传输数据的过程如下:

1.假设微控制器 A 要发送信息到微控制器 B

• 微控制器A(主机)寻址微控制器B(从机)

• 微控制器A(主机、发送器)发送数据到微控制器B (从机、接收器)

• 微控制器A 终止传输

2.如果微控制器A 想从微控制器B 接收信息

• 微控制器A (主机) 寻址微控制器B (从机)

• 微控制器A(主机、接收器) 从微控制器B(从机、发送器)接收数据

• 微控制器A 终止传输

甚至在这种情况下,主机(微控制器 A )也产生定时而且终止传输。

连接多于一个微控制器到 I2C 总线的可能性意味着超过一个主机可以同时尝试初始化传输数据。为了避免由此产生混乱,发展出一个仲裁过程,它依靠线与连接所有 I2C 总线接口到 I2C 总线。

如果两个或多个主机尝试发送信息到总线,在其他主机都产生 0 的情况下,首先产生一个 1 的主机将丢失仲裁,仲裁时的时钟信号是用线与连接到 SCL 线的主机产生的时钟的同步结合(关于仲裁的更详细信息参考第8章)。

image

图 2 使用两个微控制器的 I2C 总线配置举例

在 I2C 总线上产生时钟信号通常是主机器件的责任。当在总线上传输数据时,每个主机产生自己的时钟信号。主机发出的总线时钟信号只有在以下的情况才能被改变:慢速的从机器件控制时钟线并延长时钟信号,或者在发生仲裁时被另一个主机改变。

三、具体介绍(以下为摘取部分,故编号从5开始,延续原文编号)

5 总体特征

SDA 和 SCL 都是双向线路,都通过一个电流源或上拉电阻连接到正的电源电压(见图 3)。 当总线空闲时,这两条线路都是高电平,连接到总线的器件输出级必须是漏极开路或集电极开路才能执行线与的功能。I2C 总线上数据的传输速率在标准模式下可达 100kbit/s, 在快速模式下可达 400kbit/s, 在高速模式下可达 3.4Mbit/s 。连接到总线的接口数量只由总线电容是 400pF 的限制决定。关于高速模式主机器件的信息请参考第 13 章。

6 位传输

由于连接到 I2C 总线的器件有不同种类的工艺(CMOS NMOS 双极性)。逻辑 0 (低) 和 1(高) 的电平不是固定的,它由 VDD 的相关电平决定(见第 15 章的电气规范)。每传输一个数据位就产生一个时钟脉冲。

6.1 数据的有效性

SDA 线上的数据必须在时钟的高电平周期保持稳定。数据线的高或低电平状态只有在 SCL 线的时钟信号是低电平时才能改变(见图 4)。

image

clip_image001

图 4 I2C 总线的位传输

6.1 起始和停止条件

在 I2C 总线中,唯一出现的是被定义为起始(S) 和停止(P)条件(见图 5) 的情况。其中一种情况是在 SCL 线是高电平时,线从高电平向低电平切换,这个情况表示起始条件。当 SCL 是高电平时,SDA 线由低电平向高电平切换表示停止条件。

起始和停止条件一般由主机产生。总线在起始条件后被认为处于忙的状态。在停止条件的某段时间后,总线被认为再次处于空闲状态。总线的空闲状态将在第 15 章详细说明。

如果产生重复起始(Sr)条件而不产生停止条件 ,总线会一直处于忙的状态。此时的起始条件和重复起始 Sr 条件在功能上是一样的(见图 10)。因此在本文档的剩余部分,符号 S 将作为一个通用的术语既表示起始条件又表示重复起始条件,除非有特别声明的(Sr)。

如果连接到总线的器件合并了必要的接口硬件,那么用它们检测起始和停止条件十分简便。但是,没有这种接口的微控制器在每个时钟周期至少要采样SDA 线两次来判别有没有发生电平切换clip_image005[7]

image

7传输数据

7.1 字节格式

发送到 SDA 线上的每个字节必须为 8 位。每次传输可以发送的字节数量不受限制 。每个字节后必须跟一个响应位。首先传输的是数据的高位(MSB)(见图 6)。 如果从机要完成一些其他功能后(例如一个内部中断服务程序)才能接收或发送下一个完整的数据字节,可以使时钟线 SCL 保持低电平迫使主机进入等待状态。当从机准备好接收下一个数据字节并释放时钟线 SCL 后,数据传输继续。

在一些情况下可以用与 I2C 总线格式不一样的格式(例如兼容 CBUS 的器件)。甚至在传输一个字节时 用这样的地址起始的报文可以通过产生停止条件来终止。此时不会产生响应(见 10.1.3 节)。

7.2 响应

数据传输必须带响应。相关的响应时钟脉冲由主机产生。在响应的时钟脉冲期间,发送器释放SDA 线(高)。

在响应的时钟脉冲期间,接收器必须将 SDA 线拉低,使它在这个时钟脉冲的高电平期间保持稳定的低电平(见图 7 )。当然必须考虑建立和保持时间(在第 15 章详细说明)。通常,被寻址的接收器在接收到的每个字节后,除了用 CBUS 地址开头的报文 必须产生一个响应(见10.1.3 节)。

当从机不能响应从机地址时(例如它正在执行一些实时函数不能接收或发送),从机必须使数据线保持高电平。主机然后产生一个停止条件终止传输或者产生重复起始条件开始新的传输。

如果从机-接收器响应了从机地址但是在传输了一段时间后不能接收更多数据字节,主机必须再一次终止传输。这个情况用从机在第一个字节后没有产生响应来表示。从机使数据线保持高电平,主机产生一个停止或重复起始条件。

如果传输中有主机接收器,它必须通过在从机不产生时钟的后一个字节不产生一个响应,向从机发送器通知数据结束。从机-发送器必须释放数据线允许主机产生一个停止或重复起始条件。

image

图 6 I2C 总线的数据传输

image

8 仲裁和时钟发生

8.1 同步

所有主机在 SCL 线上产生它们自己的时钟来传输 I2C 总线上的报文。数据只在时钟的高电平周期有效。因此,需要一个确定的时钟进行逐位仲裁。

时钟同步通过线与连接 I2C 接口到 SCL 线来执行。这就是说:SCL 线的高到低切换会使器件开始数它们的低电平周期,而且一旦器件的时钟变低电平,它会使 SCL 线保持这种状态直到到达时钟的高电平(见图8)。但是,如果另一个时钟仍处于低电平周期,这个时钟的低到高切换不会改变 SCL 线的状态。因此SCL 线被有最长低电平周期的器件保持低电平。此时,低电平周期短的器件会进入高电平的等待状态。

image

图 8 仲裁过程中的时钟同步

当所有有关的器件数完了它们的低电平周期后,时钟线被释放并变成高电平。之后, 器件时钟和 SCL 线的状态没有差别。而且所有器件会开始数它们的高电平周期 。首先完成高电平周期的器件会再次将 SCL 线拉低。

这样,产生的同步 SCL 时钟的低电平周期由低电平时钟周期长的器件决定,而高电平周期由高电平时钟周期短的器件决定。

8.2 仲裁

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

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

仲裁可以持续多位。它的第一个阶段是比较地址位(有关的寻址信息请参考第 10 章和第 14 章)。如果每个主机都尝试寻址相同的器件,仲裁会继续比较数据位(如果是主机-发送器),或者比较响应位(如果是主机-接收器)。因为 I2C 总线的地址和数据信息由赢得仲裁的主机决定,在仲裁过程中不会丢失信息。

丢失仲裁的主机可以产生时钟脉冲直到丢失仲裁的该字节末尾clip_image007[16]

由于 Hs 模式的主机有一个唯一的 8 位主机码,因此一般在第一个字节就可以结束仲裁(见第13 章)。

如果主机也结合了从机功能,而且在寻址阶段丢失仲裁,它很可能就是赢得仲裁的主机在寻址的器件。因此,丢失仲裁的主机必须立即切换到它的从机模式。

图 9 显示了两个主机的仲裁过程。当然,可能包含更多的内容(由连接到总线的主机数量决定)。此时产生 DATA1 的主机的内部数据电平与 SDA 线的实际电平有一些差别,如果关断数据输出,这就意味着总线连接了一个高输出电平。这不会影响由赢得仲裁的主机初始化的数据传输。

image

由于 I2C 总线的控制只由地址或主机码以及竞争主机发送的数据决定,没有中央主机,总线也没有任何定制的优先权。

必须特别注意的是,在串行传输时,当重复起始条件或停止条件发送到 I2C 总线的时侯,仲裁过程仍在进行。如果可能产生这样的情况,有关的主机必须在帧格式相同位置发送这个重复起始条件或停止条件。也就是说,仲裁在不能下面情况之间进行:

• 重复起始条件和数据位

• 停止条件和数据位

• 重复起始条件和停止条件

从机不被卷入仲裁过程。

 

8.3 用时钟同步机制作为握手

时钟同步机制除了在仲裁过程中使用外,还可以用于使能接收器处理字节级或位级的快速数据传输。

在字节级的快速传输中,器件可以快速接收数据字节,但需要更多时间保存接收到的字节或准备另一个要发送的字节。然后,从机以一种握手过程 (见图 6) 在接收和响应一个字节后使 SCL 线保持低电平,迫使主机进入等待状态,直到从机准备好下一个要传输的字节。

在位级的快速传输中,器件(例如对 I2C 总线有或没有限制的微控制器)可以通过延长每个时钟的低电平周期减慢总线时钟。从而。任何主机的速度都可以适配这个器件的内部操作速率

在 Hs 模式中 握手的功能只能在字节级使用(见第 13 章)。

9 7 位的地址格式

数据的传输遵循图 10 所示的格式。在起始条件(S)后,发送了一个从机地址。这个地址共有 7 位,紧接着的第 8 位是数据方向位(R/ W),0 表示发送(写),1 表示请求数据(读)。数据传输一般由主机产生的停止位(P) 终止。但是,如果主机仍希望在总线上通讯,它可以产生重复起始条件(Sr )和寻址另一个从机,而不是首先产生一个停止条件。在这种传输中,可能有不同的读/写格式结合。image

图 10 完整的数据传输

可能的数据传输格式有:

• 主机-发送器发送到从机-接收器,传输的方向不会改变(见图 11)。

• 在第一个字节后,主机立即读从机(见图 12)。在第一次响应时,主机-发送器变成主机-接收器,从机-接收器变成从机-发送器。第一次响应仍由从机产生。之前发送了一个不响应信号的主机产生停止条件。

• 复合格式(见图 13)。传输改变方向的时侯,起始条件和从机地址都会被重复。但 R/ W 位取反。如果主机接收器发送一个重复起始条件,它之前应该发送了一个不响应信号。

注意:

1 复合格式可以用于例如控制一个串行存储器。在第一个数据字节期间,要写内部存储器的位置。在重复起始条件和从机地址后,数据可被传输。

2 自动增加或减少之前访问的存储器位置等所有决定都由器件的设计者决定。

3 每个字节都跟着一个响应位,在序列中用 A 或 A非 模块表示。

4 兼容 I2C 总线的器件在接收到起始或重复起始条件时必须复位它们的总线逻辑,甚至在这些起始条件没有根据正确的格式放置,它们也都期望发送从机地址。

5  起始条件后面立即跟着一个停止条件(报文为空)是一个不合法的格式。

image

图 11 主机 发送器用 7 位地址寻址从机接收器传输方向不变

image

图 12 在第一个字节后主机立即读从机

image

图 13 复合格式

10 7 位寻址

I2C 总线的寻址过程是通常在起始条件后的第一个字节决定了主机选择哪一个从机。例外的情况是可以寻址所有器件的广播呼叫地址。使用这个地址时,理论上所有器件都会发出一个响应。但是,也可以使器件忽略这个地址。广播呼叫地址的第二个字节定义了要采取的行动。这个过程将在 10.1.1 节详细介绍。有关 10 位寻址的信息请参考第 14 章。

10.1 第一个字节的位定义

第一个字节的头 7 位组成了从机地址,见图 14。最低位 LSB 是第 8 位,它决定了报文的方向。第一个字节的低位是 0 表示主机会写信息到被选中的从机。 1 表示主机会向从机读信息。

当发送了一个地址后,系统中的每个器件都在起始条件后将头 7 位与它自己的地址比较。如果一样,器件会认为它被主机寻址。至于是从机-接收器还是从机-发送器都由 R/ W 位决定。

image

从机地址由一个固定和一个可编程的部分构成。由于很可能在一个系统中有几个同样的器件,从机地址的可编程部分使最大数量的这些器件可以连接到 I2C 总线上。器件可编程地址位的数量由它可使用的管脚决定。例如如果器件有 4 个固定的和 3 个可编程的地址位,那么相同的总线上共可以连接 8 个相同的器件。

I2C 总线委员会协调 I2C 地址的分配。进一步的信息可以从后列出的 Philips 代理商处获得。保留的两组 8 位地址 0000XXX 和 1111XXXclip_image006[20]的用途见表 2。 从机地址的 11110XX 位组合保留给 10 位寻址,见第 14 章。

表 2 第一个字节中位的定义

从机地址

R/ W 位

描述

0000 000

0

广播呼叫地址

0000 000

1

起始字节 1

0000 001

X

CBUS 地址 2

0000 010

X

保留给不同的总线格式 3

0000 011

X

保留到将来使用

0000 1XX

X

Hs 模式主机码

1111 1XX

X

保留到将来使用

1111 0XX

X

10 位从机寻址

注:

1 没有器件允许在接收到起始字节后响应

2 CBUS 地址已被保留使可以在相同的系统内部混合兼容 CBUS 和兼容 I2C 总线的器件。接收到这个地址时,兼容 I2C 总线的器件不能响应。

3 保留给不同总线格式的地址包括使能 I2C 和其他协议混合。只有可以在这种格式和协议下工作兼容 I2C 总线的器件才能响应这个地址。

10.1.1 广播呼叫地址

广播呼叫地址是用来寻址连接到 I2C 总线上的每个器件。但是,如果器件在广播呼叫结构中不需要任何数据,它可以通过不发出响应来忽略这个地址。如果器件要求从广播呼叫地址得到数据,它会响应这个地址并作为从机-接收器运转。第二个和接下来的字节会被能处理这些数据的每个从机clip_image006[22]接收器响应。广播呼叫地址的含意通常在第二个字节说明,见图 15。

这里要考虑两种情况
•  当最低位B是0
•  当最低位B是1

image

当位B是0 时,第二个字节的定义如下:
•  00000110 ,H 06 。通过硬件写入和复位从机地址的可编程部分接收到这个两字节序列时,所有打算响应这个广播呼叫地址的器件将复位并接受它们地址的可编程部分。要采取预防措施确保器件不会在加上电源电压后将SDA或SCL线拉低,因为这些低电平会阻塞总线。
•  00000100, H 04。 通过硬件写从机地址的可编程部分。所有通过硬件定义地址可编程部分(和响应广播呼叫地址)的器件会在接收这两个字节序列时锁存可编程的部分。器件不会复位。
•  00000000, H 00 。这个代码不允许在第二个字节使用。

编程过程的顺序请参考相应器件的数据表。

剩下的代码没有被确定,器件必须忽略它们。

当位B是1 时,这个两字节序列是一个硬件广播呼叫。即序列由一个硬件主机器件发送。例如键盘扫描器,它们不能编程来发送一个期望的从机地址。由于硬件主机预先不知道报文要传输给哪个器件,它只能产生这个硬件广播呼叫和它自己的地址,让系统识别它,见图16。

第二个字节中剩下的7位是硬件主机的地址,这个地址被一个连接到总线的智能器件识别(例如微控制器),并指引硬件主机的信息。如果硬件主机也可以作为从机,它的从机地址和主机地址一样。

image

在一些系统中,可以选择在系统复位后在从机-接收器模式中设置硬件主机发送器。这样,系统的配置主机可以告诉硬件主机发送器(现在处于从机接收器模式)数据必须发送到哪个地址,见图 17。这个编程过程后,硬件主机仍处于主机-发送器模式。

10.1.2  起始字节

微控制器可以用两种方法连接到I2C总线。有片上硬件I2C总线接口的微控制器可被编程为只由总线的请求中断。当器件没有这种接口时,它必须经常通过软件监控总线。很显然,微控制器监控或查询总线的次数越多,用于执行自己功能的时间越少。

因此,快速硬件器件和相关的依靠查询的慢速微控制器有速度差别。

此时数据传输前应有一个比正常时间长的起始过程,见图18 。起始过程包括:

•  起始条件S

•  起始字节00000001

•  响应时钟脉冲ACK

•  重复起始条件Sr

image

image

在要求总线访问的主机发送起始条件S后,发送起始字节00000001。 因此另一个微控制器可以采样以低采样速率采样SDA线,直到在启动字节检测到7个0 。在SDA线检测到这个低电平后,微控制器切换到一个更高的采样速率寻找用于同步的重复起始条件Sr。

接收到重复起始条件Sr后硬件接收器会复位,从而忽略了起始字节。

在起始字节后产生一个相关的响应时钟脉冲。这只在遵守总线的字节处理格式时出现。没有器件允许响应起始字节。

http://pan.baidu.com/s/1mgyW0hM

/**********************************************************************************
 * IIC的宏和全局变量,函数原型等
 * 2014/4/23
 **********************************************************************************/

#ifndef PAL_TWI_H
#define    PAL_TWI_H

/*************************************INCLUDES*************************************/

#include <pal.h>
#include <pal_config.h>    //用到该头文件里面定义的F_CPU
#include <app_config.h>    //用到该头文件里面定义的MODULE_GLOBAL

/*************************************TYPES*************************************/
typedef struct twi_communication_buffer_tag
{
    /* Transmit buffer */
    uint8_t tx_buf[TWI_MAX_TX_BUF_LENGTH];

    /* Receive buffer */
    uint8_t rx_buf[TWI_MAX_RX_BUF_LENGTH];
    uint8_t rx_buf_protocol_head;
    /*消息头标志*/
    uint8_t rx_buf_protocol_renewed;
    /*消息刷新标志*/


    /* Transmit buffer head */
    uint8_t tx_buf_head;

    /* Transmit buffer tail */
    uint8_t tx_buf_tail;

    /* Receive buffer head */
    uint8_t rx_buf_head;

    /* Receive buffer tail */
    uint8_t rx_buf_tail;

    /* Number of bytes in transmit buffer */
    uint8_t tx_count;

    /* Number of bytes in receive buffer */
    uint8_t rx_count;

} twi_communication_buffer_t;

/*************************************MACROS*************************************/
//控制twi的SCL频率
#define BAUD 9600
#define MYUBRR ((F_CPU/8/BAUD)-1)

//TWI状态寄存器的值
#define START                 0x08
#define REPEATED_START         0x10

#define MT_SLA_ACK             0x18
#define MT_SLA_NO_ACK         0x20
#define MT_DATA_ACK         0x28
#define MT_DATA_NO_ACK         0x30    
#define MT_ARB_LOST         0x38    

#define SR_SLA_ACK             0x60
#define SR_ARB_LOST            0x68    
#define SR_DATA_ACK         0x80    
#define SR_DATA_NO_ACK         0x88
#define SR_STOP                0xA0

#define NO_INFORMATION        0xF8
#define BUS_ERROR            0x00

/* Enables the interrupt of TWI */
#define ENABLE_TWI_INT()   (TWCR |= (1<<TWIE))

/* Disables the interrupt of TWI */
#define DISABLE_TWI_INT()  (TWCR &= ~(1<<TWIE))


//地址寄存器的值:全局网模块地址0x1c,攻击网模块B地址0x2c
#ifdef     MODULE_GLOBAl
#define MASTER_ADDR     0x1c
#define SLAVE_ADDR         0x2c  /*注意地址冲突*/
#else
#define MASTER_ADDR     0x2c
#define SLAVE_ADDR         0x1c
#endif
 
#define SLA_W (SLAVE_ADDR<<1|0x00)
#define SLA_R (SLAVE_ADDR<<1|0x01)

/***********************************PROTOTYPES***********************************/
#ifdef __cplusplus
extern "C" {
#endif

uint8_t  sio_twi_init(void);
uint8_t  sio_twi_start(void);
uint8_t  sio_twi_stop(void);
uint8_t  sio_twi_tx(uint8_t *data,uint8_t length);
uint8_t  sio_twi_rx(uint8_t *data);
uint8_t  sio_twi_receive(void);
uint8_t  sio_twi_transmit(void);

#ifdef __cplusplus
} /* extern "C" */
#endif

/*************************************GLOBALS*************************************/

#endif    //"PAL_TWI_H"

/*************************************End of File*************************************/
pal_twi.h
/********************************************************************************
 * twi总线的实现文件
 * 2014/4/25
 * zhangleilei
 ********************************************************************************/
#include <stdint.h>
#include "pal_twi.h"

/*****************************GLOBALS********************************************/

volatile twi_communication_buffer_t twi_buffer;

/********************************************************************************
 *硬件端口和端口方向的初始化
 ********************************************************************************/
uint8_t sio_twi_init_port(void)
{
    DDRB    |= (1<<5)|(1<<6)|(1<<7);
    PORTB    |= (1<<5);
    PORTB    &= ~((1<<6)|(1<<7));
    return 1;
}

/********************************************************************************
 * twi的初始化。设置状态寄存器(时钟),控制寄存器和地址寄存器
 ********************************************************************************/
uint8_t sio_twi_init(void)
{
    sio_twi_init_port();
    TWSR = 0x00;

//全局网模块和攻击网模块使用多主机模式。不同的主机使用不同的SCL频率
#ifdef MODULE_GLOBAl
    TWBR = 0x10;                            //SCL Frequency=CPU Clock/((16+2*TWBR)*(4^TWPS))
#else
    TWBR = 0x20;
#endif
    
    TWCR = (1<<TWEA)|(1<<TWEN)|(1<<TWIE);    //Enable ACK;TWI enable bit; TWI interrupt Enable
    TWAR = MASTER_ADDR<<1;                    //地址寄存器前7位存放地址。

    return 1;
}

/********************************************************************************
 * 发送start指令
 ********************************************************************************/

uint8_t sio_twi_start(void)
{
    TWCR |= (1<<TWINT)|(1<<TWSTA);
    return 1;    
}

/********************************************************************************
 * 发送stop指令
 ********************************************************************************/

uint8_t sio_twi_stop(void)
{
    TWCR |= (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);    //Transmit STOP condition
    TWCR &= ~(1<<TWSTA);
    return 1;    
}

/**************************************************************************************
 * 函数功能:将需要通过twi发送的数据存储到twi的发送缓冲区,如果没有发送正在进行,发送开始
 *             指令。数据的发送在twi中断中进行处理。
 * 入口参数:*data:待发送数据的头指针;length:待发送数据的长度。
 * 出口参数:成功发送数据的长度。
 **************************************************************************************/
uint8_t sio_twi_tx(uint8_t *data,uint8_t length)
{
    uint8_t bytes_to_be_written;
    uint8_t head;
    uint8_t tail;
    uint8_t size;
    uint8_t back;

    DISABLE_TWI_INT();        //关闭twi的中断
    
    /*
     * Calculate available buffer space
     */
    head = twi_buffer.tx_buf_head;
    tail = twi_buffer.tx_buf_tail;

    if (tail >= head)
    {
        size = (TWI_MAX_TX_BUF_LENGTH - 1) - (tail - head);
    }
    else
    {
        size = (head - 1) - tail;
    }

    if (size < length)
    {
        /* Not enough buffer space available. Use the remaining size. */
        bytes_to_be_written = size;
    }
    else
    {
        bytes_to_be_written = length;
    }

    /* Remember the number of bytes transmitted. */
    back = bytes_to_be_written;

    /* The data is copied to the transmit buffer. */
    while (bytes_to_be_written > 0)
    {
        twi_buffer.tx_buf[twi_buffer.tx_buf_tail] = *data;

        if ((TWI_MAX_TX_BUF_LENGTH - 1) == twi_buffer.tx_buf_tail)
        {
            /* Reached the end of buffer, revert back to beginning of buffer. */
            twi_buffer.tx_buf_tail = 0;
        }
        else
        {
            /*
             * Increment the index to point the next character to be
             * inserted.
             */
            twi_buffer.tx_buf_tail++;
        }

        bytes_to_be_written--;
        data++;
    }

    ENABLE_TWI_INT();

    /*
     * Check whether there is a transmission ongoing. Otherwise issue a
     * START condition. Transmission of subsequent bytes / data
     * will be taken care in the ISR.
     */
    if (twi_buffer.tx_count == 0)
    {
        sio_twi_start();
    }
    return back;    
}

/*************************************************************************************
 * 函数功能:取出存储在twi接收缓冲区的数据
 * 入口参数:*data:数据存放区的首地址
 * 出口参数:实际从接收缓冲区取到的数据长度
 *************************************************************************************/
uint8_t sio_twi_rx(uint8_t *data)
{
    uint8_t length = twi_buffer.rx_count;

    if (twi_buffer.rx_count == 0)
    {
        return 0;
    }

    DISABLE_TWI_INT();

    while (length > 0)
    {
        *data++ = twi_buffer.rx_buf[twi_buffer.rx_buf_head];
        
        if ((TWI_MAX_RX_BUF_LENGTH-1) == twi_buffer.rx_buf_head)
        {
            twi_buffer.rx_buf_head = 0;
        }
        else
            twi_buffer.rx_buf_head++;
        
        length--;
    }

    ENABLE_TWI_INT();

    return twi_buffer.rx_count;
}

/********************************************************************************************************
 * 函数功能:将twi发送缓冲区的数据通过twi总线发送出去。
 * 注意事项:sio_twi_transmit()和sio_twi_tx()函数的区别:
 *             sio_twi_tx():用户需要通过twi发送数据时调用。
 *             sio_twi_transmit():供中断服务程序调用,用户不要使用。
 ********************************************************************************************************/
uint8_t  sio_twi_transmit(void)
{
    if(twi_buffer.tx_count == 0)    //如果还没有发送数据的话,发送数据的第一个字节。
    {
        TWDR = twi_buffer.tx_buf[twi_buffer.tx_buf_head];    //Load DATA into TWDR Register.
        
        if ((TWI_MAX_TX_BUF_LENGTH - 1) == twi_buffer.tx_buf_head)
            twi_buffer.tx_buf_head = 0;            /* Reached the end of buffer, revert back to beginning of buffer. */
        else
            twi_buffer.tx_buf_head++;            /*Increment the index to point the next character to be transmitted.*/

        twi_buffer.tx_count = 1;
        TWCR |= (1<<TWINT)|(1<<TWEN);    //Clear TWINT bit in TWCR to start transmission of data
        TWCR &= ~((1<<TWSTA)|(1<<TWSTO));    
    }
    else
    {
        if (twi_buffer.tx_buf_head != twi_buffer.tx_buf_tail)    //如果队列中还有数据,继续发送;否则发送停止指令。
        {
            TWDR = twi_buffer.tx_buf[twi_buffer.tx_buf_head];
    
            if ((TWI_MAX_TX_BUF_LENGTH - 1) == twi_buffer.tx_buf_head)
                twi_buffer.tx_buf_head = 0;
            else
                twi_buffer.tx_buf_head++;

            TWCR &= ~((1<<TWSTA)|(1<<TWSTO));
            TWCR |= (1<<TWINT)|(1<<TWEN);
        }
        else
        {
            /* No more data for transmission */
            twi_buffer.tx_count = 0;
            sio_twi_stop();
        }    
    }
    return 1;
}

/********************************************************************************************
 * 函数功能:将从twi总线接收的数据存储到twi的接收缓冲区。同时对消息头和消息尾不正确的消息进行过滤。
 * 注意事项: sio_twi_rx()和sio_twi_receive()函数的区别:
 *             sio_twi_receive():将通过twi接收的数据存储到twi的接收缓冲区。
 *             sio_twi_rx():用户从twi接收缓冲区中取数据。             
 ********************************************************************************************/
uint8_t  sio_twi_receive(void)
{
    uint8_t tempData = TWDR;
  
    if(3 != twi_buffer.rx_buf_protocol_head)        //如果消息头未完整接受,判断数据
    {
        switch (tempData)
        {
        case 0x7e:
            if(0 == twi_buffer.rx_buf_protocol_head)//见到第一个消息头
                twi_buffer.rx_buf_protocol_head++;
            else 
                twi_buffer.rx_buf_protocol_head = 0;
            break;

        case 0x44:
            if(1 == twi_buffer.rx_buf_protocol_head)//见到第二个消息头
                twi_buffer.rx_buf_protocol_head++;
            else 
                twi_buffer.rx_buf_protocol_head = 0;
            break;

        case 0x00:
            if(2 == twi_buffer.rx_buf_protocol_head)//见到第三个消息头
            {
                twi_buffer.rx_buf_protocol_head++;

                twi_buffer.rx_buf_head = twi_buffer.rx_buf_tail;    //缓冲区消息头指针指向消息尾,准备开始接受数据
                
                /**************************将消息头填入***********************************/
                twi_buffer.rx_count = 3;

                twi_buffer.rx_buf[twi_buffer.rx_buf_tail] = 0x7e;

                if ((TWI_MAX_RX_BUF_LENGTH - 1) == twi_buffer.rx_buf_tail)
                    twi_buffer.rx_buf_tail = 0;
                else  
                    twi_buffer.rx_buf_tail++;


                twi_buffer.rx_buf[twi_buffer.rx_buf_tail] = 0x44;

                if ((TWI_MAX_RX_BUF_LENGTH - 1) == twi_buffer.rx_buf_tail)
                    twi_buffer.rx_buf_tail = 0;
                else  
                    twi_buffer.rx_buf_tail++;


                twi_buffer.rx_buf[twi_buffer.rx_buf_tail] = 0x00;

                if ((TWI_MAX_RX_BUF_LENGTH - 1) == twi_buffer.rx_buf_tail)
                    twi_buffer.rx_buf_tail = 0;
                else  
                    twi_buffer.rx_buf_tail++;

                /***************************将消息头填入**********************************/
            }
            else 
                twi_buffer.rx_buf_protocol_head = 0;
            break;
        default :
            twi_buffer.rx_buf_protocol_head = 0;
            break;        
        }    //switch (tempData)
    }
    

    else  //if(3 != twi_buffer.rx_buf_protocol_head) end .
    {
        if(3 == twi_buffer.rx_buf_protocol_head)    //消息头已完整接受,判断消息头后的数据
        {
            switch (tempData)
            {
            case 0x7e:        //收到消息体结尾
            {
                twi_buffer.rx_count++;
                twi_buffer.rx_buf[twi_buffer.rx_buf_tail] = 0x7e;

                if ((TWI_MAX_RX_BUF_LENGTH - 1) == twi_buffer.rx_buf_tail)
                    twi_buffer.rx_buf_tail = 0;
                else  
                    twi_buffer.rx_buf_tail++;
                
                twi_buffer.rx_buf_protocol_head = 0;    //清零消息头个数
                twi_buffer.rx_buf_protocol_renewed = 1;    //消息体完整接收刷新标志
                break;
            }

            default:
            {
                twi_buffer.rx_count++;
                twi_buffer.rx_buf[twi_buffer.rx_buf_tail] = tempData;

                if ((TWI_MAX_RX_BUF_LENGTH - 1) == twi_buffer.rx_buf_tail)
                    twi_buffer.rx_buf_tail = 0;
                else 
                    twi_buffer.rx_buf_tail++;
            }
            break; 
            }     
        }
    }
    return 1;
}

/********************************************************************************
 * 函数功能:twi中断服务程序
 ********************************************************************************/
ISR(TWI_vect) 
{
    unsigned int state;
    
    state = TWSR & 0xF8;    //twi总线当前的状态

    switch(state)
    {
    case START:
        TWDR = SLA_W;                        //Load SLA_W into TWDR Register
        TWCR |= (1<<TWINT) | (1<<TWEN);     //Clear TWINT bit in TWCR to start transmission of address
        TWCR &= ~( (1<<TWSTA) | (1<<TWSTO) );
        break;

    case REPEATED_START:
        TWDR = SLA_W;                        //Load SLA_W into TWDR Register
        TWCR |= (1<<TWINT) | (1<<TWEN);     //Clear TWINT bit in TWCR to start transmission of address
        TWCR &= ~( (1<<TWSTA) | (1<<TWSTO) );
        break;

    /*******************************Master Transmit 模式下的状态*******************************/
    case MT_SLA_ACK:
        sio_twi_transmit();                            //发送待发送数据的第一个字节。
        break;
        
    case MT_SLA_NO_ACK:
        TWCR |= (1<<TWINT)|(1<<TWEN)|(1<<TWSTA);    //Repeated START will be transmitted
        TWCR &= ~(1<<TWSTO);
        break;

    case MT_DATA_ACK:        
        sio_twi_transmit();                            //发送待发送数据的剩余字节
        break;

    case MT_DATA_NO_ACK:
        TWCR |= (1<<TWINT)|(1<<TWEN)|(1<<TWSTA);    //Repeated START will be transmitted
        TWCR &= ~(1<<TWSTO);
        break;

    case MT_ARB_LOST:
        TWCR |= (1<<TWINT)|(1<<TWEN)|(1<<TWSTA);    //A START condition will be transmitted when the bus becomes free.
        TWCR &= ~(1<<TWSTO);
        break;

    /*******************************Slave Receive 模式下的状态*******************************/
    case SR_SLA_ACK:
        TWCR |= (1<<TWINT)|(1<<TWEA);
        TWCR &= ~(1<<TWSTO); 
        break;

    case SR_ARB_LOST:
        TWCR |= (1<<TWINT)|(1<<TWEA);
        TWCR &= ~(1<<TWSTO); 
        break;

    case SR_DATA_ACK:
        sio_twi_receive();                            //接收从主机发送的数据
        TWCR |= (1<<TWINT)|(1<<TWEA);
        TWCR &= ~(1<<TWSTO);
        break;

    case SR_DATA_NO_ACK:
        sio_twi_receive();                            //接收从主机发送的数据
        TWCR |= (1<<TWINT)|(1<<TWEA);
        TWCR &= ~(1<<TWSTO);
        break;

    case SR_STOP:
        TWCR |= (1<<TWINT)|(1<<TWEA);
        TWCR &= ~( (1<<TWSTA) | (1<<TWSTO) );
        break;

    case NO_INFORMATION:
        //TWINT = 0 , but no relevant information available.So do nothing.
        break;

    case BUS_ERROR:
        TWCR |= (1<<TWINT)|(1<<TWSTO);
        TWCR &= ~(1<<TWSTA);
        break;
        
    default:
        TWCR |= (1<<TWINT);
        break; 
    }
}
/***********************************End of File*****************************************/
pal_twi.c
posted @ 2014-06-09 22:11  Andy Cheung  阅读(3047)  评论(0编辑  收藏  举报