第4节 I2C 总线
推荐给好友
打印
加入收藏
更新于2008-08-24 09:33:23

I2C(Inter-Integrated Circuit)是由PHILIPS公司开发的一种总线,它支持主从模式数据的发送和接受。I2C是一种两线式串行总线,它由串行数据线和串行时钟线组成,连接到总线上的设备可以通过这两个线来交换数据。I2C总线连接微控制器及其外围设备,每个外围设备根据分配的不同的地址既可以作为发送器又可以作为接收器。每一个连接到总线上的设备都必须开漏极或接有上拉电阻的开集电极输出。它支持多主控,并有冲突检测以及通信控制协议防止数据丢失,通信控制协议保证即使在有多个主控试图同时控制总线时,在任一时间点上也只能有一个主控来控制总线,同时,它还能启动等待模式下的MCU。在标准模式下的I2C总线的速度可达到100kbit/s,快速模式下400kbit/s,高速模式下可达3.4Mbit/s,如下图8-4所示:

8.4.1 I2C 的结构及功能

(1)I2C的功能模块如下图8-5所示:

点击查看图片

I2C由如下几个模块组成:

  • 时钟选择器,时钟分频器和移位时钟发生器:这个模块通过机器时钟来产生移位时钟。
  • 开始/停止状态发生电路:当总线空闲(SCL0和SDL0都为高电平)时,如果检测到开始状态,即在SCL0=“H” 时,SDL0由高电平变为低电平,则产生开始条件,主设备开始通信。在SCL0=“H”时,SDL0由低电平变为高电平,则产生停止条件,主设备停止通
    信。
  • 开始/停止状态检测电路:检测数据传输的开始/停止状态。
  • 仲裁丢失检测电路:这个接口电路支持多主设备模式,如果两个或多个主设备试图同时发送数据,就会产生仲裁条件(如果在SDA0变为低电平时,发送一个逻辑电平“1” )。
  • 从设备地址比较电路:这个模块在开始条件后接受从设备地址,并与所分配的地址进行比较,地址由七位数据和一位数据传输方向(R/W)组成。如果接受到的地址与自己的地址相同,比较电路就发送一个响应信号。
  • 总线状态寄存器( IBSR0 ):指示I2C总线的状态。
  • 总线控制寄存器( IBCR00/10 ): 选择工作模式,使能中断,确认信号以及从备用模式中唤醒MCU。
  • 时钟控制寄存器( ICCR0 ):使能I2C接口工作并选择移位时钟频率。
  • 地址寄存器( IAAR0 ):设置从设备地址。
  • 数据寄存器( IDDR0 ):保存发送或接受的移位数据或地址。

 
(2)I2C总线接口引脚
与 I2C总线相关的引脚有两条,分别是SDA0和SCL0,其配置如下: 

  • SDA0:SDA0是一个复用引脚,它可以作为通用I/O端口、外部中断输入(磁滞输入)、8bit串行I/O的串行数据输出引脚(N沟道开漏极)以及I2C数据I/O引脚。当启用I2C时(ICCR0:EN=1),SDA0自动作为I2C总线的I/O引脚。将其作为输入引脚时,使能I2C总线(ICCR0:EN=1),并在对应的数据方向寄存器(DDR)中写入0 。
  • SCL0:SCL0引脚可以作为N沟道开漏极I/O引脚,外部中断输入(磁滞输入),8bit 串行I/O的串行数据输出引脚 (磁滞输入)以及I2C串行时钟I/O引脚。当启用I2C时(ICCR0:EN=1),SCL0自动作为I2C总线的移位时钟I/O引脚。将其作为输入引脚时,使能I2C总线(ICCR0:EN=1),并在对应的数据方向寄存器(DDR)中写入0。



8.4.2 I2C 的工作模式

I2C系统利用串行数据线(SDA0)和串行时钟线(SCL0)来进行数据传输,每个连接到总线上的设备都有一个独立的地址,在信息的传输过程中,I2C总线上并接的每一模块电路既是主控器(或被控器),又是发送器(或接手器),这取决于它所要完成的功能。

(1)I2C协议

在SCL0为高电平时,如果SDA0产生一个下降沿,则为开始条件,紧接着开始条件后发送一个字节进行寻址,表示要进行通信的从设备的地址,这里的地址实际上时7个bit,占用了地址字节的高七位,可以对127个器件进行寻址,还字节的第0位用于表示数据的传世方向:当该位为高电平时,表示由从设备向主设备发送数据,即主设备对从设备进行读操作;当该位为低电平时,表示由主设备向从设备发送数据,即主设备对从设备进行写操作。
发送完地址后就可以发送数据了,数据都是8bit,每发送完一个字节后,必须跟一个响应位,在发送字节时,传输的数据字节按照由高到低的顺序发送。主设备在SCL0上产生8个脉冲,在第9个脉冲(为响应时钟脉冲,也由主设备产生)低电平期间,发送器释放SDA0总线,即置高SDA0,接受器则把SDA0拉成低电平,以给出一个确认位,确认数据发送成功,保证数据的完整性。在第9个脉冲高电平期间,发送器接受到这个确认位,即开始下一个字节的传输,下一个字节的第一个脉冲低电平期间,接受器释放SDA0。因此,每个字节传送需要9个脉冲,每次传送的字节数是不受限制的,在响应时钟脉冲周期期间,如果从设备不能及时响应(比如正在执行其它任务),则从设备始终保持SDA0为高电平。
对于同一个地址,可以连续发送8bit的数据和一个响应位。在SCL0为高电平时,如果SDA0产生一个上升沿,则为停止条件,但是,如果是多个重复的开始条件,则可以用来表示不同从设备的地址,此时无需产生停止条件。

(2)从备用模式唤醒MCU
当MCU处于停止/监测模式,如果IBCR00:WUE=1,并且在总线上检测到起始条件时,就会产生一个唤醒中断请求,从而将MCU转为正常工作模式。

8.4.3 I2C 的应用设计范例


(1)寄存器配置
在应用I2C总线时,应对相关的寄存器进行如下的设置:

  • 启用/禁用I2C操作:启用时将ICCR0.EN置1,禁用时置0 。 
  • 选择I2C的主从模式:主模式时将IBCR0.MSS置1,从模式时置0 。 
  • 选择移位时钟:通过时钟选择位(ICCR0.CS4/CS3/CS2/CS1/CS0) 
  • 当产生移位时钟时除以m:将ICCR0 .DMBP置1 。 
  • 控制I2C地址响应:启用地址响应输出时,将IBCR00.AACKK置1,否则置0。 
  • 控制I2C数据响应输出:启用数据响应输出时将IBCR10.DACKE置1,否则置0。
  • 重启I2C通信:将IBCR10,.SCC置1。 
  • 选择I2C数据接受传输完成标志位:在第八个时钟周期产生传输中断,将IBCR00.INTS置1 ;在第九个时钟周期产生传输中断则置0。
  • 与中断相关的寄存器:设置中断级别时,通过中断级别设置寄存器(ILR2)来设定,其地址为0007BH,中断向量为#10,地址为0FFE6H。 启用,禁用和清除中断:
    a.传输中断:
    启用数据传输完成中断时,将IBCR10.INTE置1 ;否则置0 ,清除中断请求时,将中断请求标志位IBCR10.INT置0 ;
    启用总线错误发生中断时,将中断请求标志位IBCR10.BEIE置1,否则置0 , 清除中断时,将中断请求标志位IBCR10.BER置0。
    b.停止中断:
    启用停止条件检测中断时,将中断请求使能位IBCR00.SPE置1 ,否则置0 ;清除中断请求时,将IBCR00.SPF置 0    。
    启用仲裁丢失检测中断时,将中断请求使能位IBCR00.ALE置1 ,否则置0 ;清除中断请求时,将IBCR00.ALF置0 。
    启用开始条件检测中断时,将中断请求使能位IBCR00.WUE置1 ,否则置0 ;清除中断请求时,将IBCR00.WUF置0 。


(2)实例分析:
下面的例子为MCU将通过UART(9600Baud)接受到的数据发送到I2C总线,数据发送到从地址为0x21。在发送数据的过程中,必须注意将SCL0和SDA0的引脚与一个上拉电阻连接(比如10k),否则就不可能进行发送或接受数据。其源程序如下所示:
#include "mb95100.h"
//共阳极LED的显示
#define SEG_a 0xFE
#define SEG_b 0xFD
#define SEG_c 0xFB
#define SEG_d 0xF7
#define SEG_e 0xEF

#define SEG_f 0xDF
#define SEG_g 0xBF
#define SEG_dp 0x7F
#define SEG_0 SEG_a & SEG_b & SEG_c & SEG_d & SEG_e & SEG_f
#define SEG_1 SEG_b & SEG_c
#define SEG_2 SEG_a & SEG_b & SEG_d & SEG_e & SEG_g
#define SEG_3 SEG_a & SEG_b & SEG_c & SEG_d & SEG_g
#define SEG_4 SEG_b & SEG_c & SEG_f & SEG_g
#define SEG_5 SEG_a & SEG_c & SEG_d & SEG_f & SEG_g
#define SEG_6 SEG_a & SEG_c & SEG_d & SEG_e & SEG_f & SEG_g
#define SEG_7 SEG_a & SEG_b & SEG_c
#define SEG_8 SEG_a & SEG_b & SEG_c & SEG_d & SEG_e & SEG_f & SEG_g
#define SEG_9 SEG_a & SEG_b & SEG_c & SEG_d & SEG_f & SEG_g
#define SEG_A SEG_a & SEG_b & SEG_c & SEG_e & SEG_f & SEG_g
#define SEG_B SEG_c & SEG_d & SEG_e & SEG_f & SEG_g
#define SEG_C SEG_a & SEG_d & SEG_e & SEG_f
#define SEG_D SEG_b & SEG_c & SEG_d & SEG_e & SEG_g
#define SEG_E SEG_a & SEG_d & SEG_e & SEG_f & SEG_g
#define SEG_F SEG_a & SEG_e & SEG_f & SEG_g
const unsigned char seg_display[16] = { SEG_0, SEG_1, SEG_2, SEG_3, SEG_4, SEG_5, SEG_6, SEG_7, SEG_8, SEG_9, SEG_A, SEG_B, SEG_C, SEG_D, SEG_E, SEG_F };
static const char ASCII[] = "0123456789ABCDEF";
#define SLAVE_ADDRESS 0x21 //定义从设备地址为0x21
#define I2C_READ 0x01 //定义I2C总线读为0x01
#define I2C_WRITE 0x00 //定义I2C总线写为0x00

/**************************************************************** UART ****************************************************************/
void UART_init (void)
{
PSSR0 = 0x05; BRSR0 = 130; // 波特率为 9600 Baud
SMC10 = 0x0C; /*00001100,从最低位开始传(0),无校验位(0), 偶校验(0) ,停止位1bit(0) ,字符长度8bit(11) ,专用波特率发生器(0),UART(0)*/
SMC20 = 0x58; /*01011000, 串行时钟不输出(0),UO pin 为输出(1), 清除SSR 的所有标志位(0)PER,OVE,FER, 允许接受数据(1),允许发送数据(1),没有中断(000)*/
SSR0 = 0x00; // 清除标志位
}

void UART_sendbyte (char ch) //发送字节
{
while (!SSR0_TDRE);
TDR0 = ch;
}
void UART_sendstring (const char *string) //发送字符串
{
unsigned int i;
for (i=0; i<strlen(string); i++)
{
if (string[i] == 10) UART_sendbyte(13);
UART_sendbyte(string[i]);
}
}
char UART_readbyte_wait (void)
{
while(!SSR0_RDRF);
return (RDR0); // 返回接受到的字节 }
void puthex( unsigned long n, int digits )
{
int i;
char ch,div=0;
UART_sendstring("0x"); // 表示十六进制数
div=(digits-1) << 2;
for (i=0; i<digits; i++)
{
ch = (n >> div) & 0xF; // 计算十六进制数
UART_sendbyte(ASCII[ch]); // 显示 ASCII码
div-=4;
}
}
/***************************************************************/
I2C
/***************************************************************/
void I2C_Init(void)
{
ICCR0_EN = 0; // 禁用I2C总线
ICCR0_CS4 = 0; // 设置时钟除法器 'm' => 6
ICCR0_CS3 = 1;
ICCR0_CS2 = 0; // 设置时钟除法器 'n' => 4
ICCR0_CS1 = 0;
ICCR0_CS0 = 0; /*Fsck =MCLK /(m *n+2) =>10MHz/(6*4+2) = 10MHz/26= 385kHz */
ICCR0_EN = 1; // 启用I2C总线
IDDR0 = 0x00; // 清除数据寄存器
IBCR00 = 0x04; /*启用地址响应位,在第九个时钟周期后启用传输完成中断,启用停止检测中断 */
IBCR10 = 0x00; /* 选择从模式,禁用数据响应位,禁用总线错误中断请求,禁用数据传输完成响应位 */
}
void I2C_Acknowlegde() { while(IBSR0_LRB == 1); /* 在第九个移位时钟周期没有检测到slave发送过来的响应位 */
}
void I2C_Start(unsigned char slave_address)
{
do
{
IBCR10_BER = 0; // 清除总线错误请求标志位
IDDR0 = slave_address; /* 将slave_address与开始条件一起发送出去 */
IBCR10_MSS = 1; // 设置为主模式并发送开始条件
IBCR10_INT = 0; // 清除发送完成中断标志位

while(IBCR10_INT == 0); /*检查是否在进行数据传输,如果IBCR10_INT== 0,则表示正在进行数据传输*/
}
while (IBCR10_BER == 1 | IBCR00_ALF == 1); /*如果检测到总线错误或仲裁丢失则重新发送 */
while(IBSR0_LRB == 1) /*在第九个时钟周期没有检测到响应位表示还void I2C_Stop(void)没有准备好接受 */
{
IBCR10_SCC = 1; // 产生重复的开始条件
while (IBCR10_INT == 0); // 等待数据传输完成
}
}

{ while (IBCR10_INT == 0); // 等待传输完成
IBCR10_MSS = 0; // 转换为slave模式并释放停止条//件
IBCR10_INT = 0; // 清除传输完成中断响应标志位
while (IBSR0_BB); // 等到总线空闲
}
void I2C_Write(unsigned char value)
{ IDDR0 = value; // 将数据或地址传输到数据寄存器
IBCR10_INT = 0; // 清除传输完成中断标志位
while (IBCR10_INT == 0); // 检查是否正在进行传输
I2C_Acknowlegde(); // 等待响应
}
/***************************************************************/
/* Main Routine */
/***************************************************************/
void main(void) { char ch;
InitIrqLevels(); // 初始化中断级别寄存器和IRQ向量表
__EI(); // 启用中断
__set_il(3); // 设置全局中断为最低级别
DDR0 = 0xFF; // LED 口
PDR0 = 0xFF;

UART_init();
UART_sendstring("\n*********************************\n");
UART_sendstring( " I2C-Master-Demo ");
UART_sendstring("\n*********************************\n");

I2C_Init();
UART_sendstring("\n\nSends all received characters on UART to I2C Slave address 0x21!\n");
while(1)
{
ch = 0x5A;
ch = UART_readbyte_wait();
UART_sendstring("\nSend to I2C Slave: ");
puthex(ch, 2);

I2C_Start((SLAVE_ADDRESS << 1) | I2C_WRITE);
I2C_Write(ch);
I2C_Stop();

}
}

 

上一节                    下一节

相关链接


 
关于我们 | 诚邀加盟 | 客户服务 | 相关法律 | 网站地图 | 友情链接 | 服务信箱:service@eefocus.com
© 2006 与非门科技信息咨询(北京)有限公司 All Rights Reserved.