1、IIC总线介绍
IIC(Inter-Integrated Circuit)总线是一种由PHILIPS(飞利浦)公司在80年代开发的两线式串行总线,用于连接微控制器及其外围设备,IIC通信是半双工通信。
IIC通信的主要特点有:
(1)并行总线IIC总线协议转换器
(2)多主机功能:该模块既可做主设备也可做 从设备
(3)IIC主设备功能:①产生时钟;②产生起始和停止信号
(4)IIC从设备功能:①可编程的IIC地址检测;②可响应2个从地址的双地址能力;③停止位检测
(5)产生和检测7位/10位地址和广播呼叫
(6)支持不同的通讯速度:①标准速度(高达100khz);②快速(高达400khz)
(7)状态标志:①发送器/接收器模式标志;②字节发送结束标志;③IIC总线忙标志
(8)错误标志:①主模式时的仲裁丢失;②地址/数据传输后的应答(ACK)错误;③检测到错位的起始或停止条件;④禁止拉长时钟功能时的上溢或下溢
(9)2个中断向量:①1个中断用于地址/数据通讯成功;②1个中断用于错误
(10)可选的拉长时钟功能
(11)具单字节缓冲器的DMA
(12)可配置的PEC(信息包错误检测)的产生或校验:①发送模式中PEC值可以作为最后一个字节传输;②用于最后一个接收字节的PEC错误校验
(13)兼容SMBus 2.0
(14)兼容SMBus
2、IIC通信的优点
(1)最主要的优点就是简单性和有效性,因为IIC接口直接连接在组件之上,因此IIC总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本,总线的长度可高达25英尺,并且能够以10kbps的最大传输速率支持40个组件。
(2)其次IIC还有一个优点就是:支持多主控,在任何能够进行发送和接受的设备都可以成为主控,一个主控能够控制信号的传输和时钟频率,当然在任何一个时间点,有且只能有一个主控。
3、IIC的物理接口
图3.1
IIC串行总线一般需要两根信号线,一根用于数据传输的双向数据线SDA,一根用于提供通信时钟的时钟线SCL,时钟线上的时钟信号是由主控制器件产生,所有接到IIC总线设备的串行数据都会接到IIC总线上的SDA线上,各设备的时钟线SCL接到IIC总线上的时钟线SCL,见图3.1,对于并联在一条总线上的每个IIC设备都有自己唯一的地址用于识别设备。
一般情况之下,数据线SDA和时钟线SCL都是处于上拉电阻状态,因为在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平,见图3.2所示。
图3.2 总线物理拓扑图
4、IIC协议
IIC总线在传输数据的过程中一共有三种类型信号,分别为:开始信号、结束信号和应答信号,这些信号中,起始信号是必须的,结束信号和应答信号都可以不要,同时在数据通信中还需要注意通信时的空闲状态、数据的有效性、数据传输。
IIC总线协议时序图如图3.3所示:
图3.3 IIC总线协议时序图
5、空闲状态
当IIC总线的数据线SDA和时钟线SCL两条信号线同时处于高电平时,规定为总线的空闲状态,此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
6、起始信号与停止信号
(1)起始信号:当时钟线SCL为高期间,数据线SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平信号;
(2)停止信号:当时钟线SCL为高期间,数据线SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。
起始信号与停止信号时序图见图3.4.
图3.4 起始信号与停止信号时序图
7、应答信号
发送器每发送一个字节(8bit),就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号,见图3.5。
(1)应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
(2)应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
图3.5
对于反馈有效应答位ACK的要求是:接收器在第9个时钟脉冲之前的低电平期间将数据线SDA拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放数据线SDA,以便主控接收器发送一个停止信号P。
8、数据有效性
IIC总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定;只有在时钟线上的信号为低电平的时候,数据线上的高电平或低电平状态才允许变化。
即:数据在时钟线SCL的上升沿到来之前就需要准备好,并在下降沿到来之前 必须稳定。
9、数据传达
在总线IIC上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位串行传输每一位数据,且每一位数据的传输都是边沿触发。
10、延时时间
11、IIC总线上的数据传输
IIC总线上的每个设备都可以作为主设备或从设备,且每个设备都会对应一个唯一的地址,主从设备之间就通过这个地址来确定与哪个器件进行通信(地址通过物理接地或拉高),主从设备之间就通过这个地址来确定与哪个器件进行通信。
主设备在传输有效数据之前要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位的,然后协议规定再给地址添加一个最低位用来表示接下来数据传输的方向,0表示主设备向从设备写数据,1表示主设备向从设备读数据。
(1)主设备向从设备写数据的格式如下:
淡蓝色表示数据由主机向从机发送,粉红色表示数据由从机向主机发送。
(2)主设备向从设备中读数据,数据传输格式如下
在从机产生响应时,主机从发送变成接收,从机从接收变成发送,之后,数据由从机发送,主机接收,每个应答由主机产生,时钟信号仍由主机产生。若主机要终止本次传输,则发送一个非应答信号,接着主机产生停止条件。
主设备往从设备写数据,然后重启起始条件,紧接着从从设备中读取数据;或主设备向从设备读数据,然后重启起始条件,紧接着主设备往从设备中写数据,数据传输格式如下所示:
在多主设备的通信系统中,总线上有多个节点,它们都有自己的寻址地址,可以作为从节点被别的节点访问,同时它们都可以作为主节点向其它的节点发送控制字节和传送数据。但是如果有两个或两个以上的节点都向总线上发送启动信号并开始传送数据,就会形成冲突,这就需要进行仲裁判决。
void CT_IIC_Init(void)
{
RCC->APB2ENR |=(0x01<<3)|(0x01<<7);//开启B、F端口时钟
GPIOB->CRL &=~(0x0f<<4);
GPIOB->CRL |=(0x03<<4);//PB1通用推挽输出,速度50Mhz
GPIOF->CRH &=~(0x0f<<4);
GPIOF->CRH |=(0x03<<4);//PF9通用推挽输出,速度50Mhz
}
//产生IIC起始信号
void CT_IIC_Start(void)
{
CT_SDA_OUT(); //sda线输出
CT_IIC_SDA=1;
CT_IIC_SCL=1;
delay_us(30);
CT_IIC_SDA=0;//START:when CLK is high,DATA change form high to low
CT_Delay();
CT_IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}
void CT_Delay(void)
{
delay_us(2);
}
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void CT_IIC_Send_Byte(u8 txd)
{
u8 t;
CT_SDA_OUT();
CT_IIC_SCL=0;//拉低时钟开始数据传输
CT_Delay();
for(t=0;t<8;t++)
{
CT_IIC_SDA=(txd & 0x80)>>7;
txd <<= 1;
CT_IIC_SCL=1;
CT_Delay();
CT_IIC_SCL=0;
CT_Delay();
}
}
//产生ACK应答
void CT_IIC_Ack(void)
{
CT_IIC_SCL=0;
CT_SDA_OUT();
CT_Delay();
CT_IIC_SDA=0;
CT_Delay();
CT_IIC_SCL=1;
CT_Delay();
CT_IIC_SCL=0;
}
//不产生ACK应答
void CT_IIC_NAck(void)
{
CT_IIC_SCL=0;
CT_SDA_OUT();
CT_Delay();
CT_IIC_SDA=1;
CT_Delay();
CT_IIC_SCL=1;
CT_Delay();
CT_IIC_SCL=0;
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 CT_IIC_Read_Byte(unsigned char ack)
{
u8 i,receive=0;
CT_SDA_IN();//SDA设置为输入
delay_us(30);
for(i=0;i<8;i++ )
{
CT_IIC_SCL=0;
CT_Delay();
CT_IIC_SCL=1;
receive<<=1;
if(CT_READ_SDA)receive++;
}
if (!ack)CT_IIC_NAck();//发送nACK
else CT_IIC_Ack(); //发送ACK
return receive;
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 CT_IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
CT_SDA_IN(); //SDA设置为输入
CT_IIC_SDA=1;
CT_IIC_SCL=1;
CT_Delay();
while(CT_READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
CT_IIC_Stop();
return 1;
}
CT_Delay();
}
CT_IIC_SCL=0;//时钟输出0
return 0;
}
//产生IIC停止信号
void CT_IIC_Stop(void)
{
CT_SDA_OUT();//sda线输出
CT_IIC_SCL=1;
delay_us(30);
CT_IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
CT_Delay();
CT_IIC_SDA=1;//发送I2C总线结束信号
}
void CT_IIC_Start(void);
void CT_Delay(void);
void CT_IIC_Send_Byte(u8 txd);
u8 CT_IIC_Wait_Ack(void);
void CT_IIC_Stop(void);
u8 CT_IIC_Read_Byte(unsigned char ack);
#define CT_SDA_IN() {GPIOF->CRH&=0XFFFFFF0F;GPIOF->CRH|=8<<4;}
#define CT_SDA_OUT() {GPIOF->CRH&=0XFFFFFF0F;GPIOF->CRH|=3<<4;}
#define CT_IIC_SCL PBout(1) //SCL
#define CT_IIC_SDA PFout(9) //SDA
#define CT_READ_SDA PFin(9) //输入SDA
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入
#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入