作为使用目前嵌入式设备使用最多的协议之一,I2C和SPI都是要研究透的。在我使用的开发板上,SPI协议集成了,只需要使用3个GPIO引脚即可。但I2C协议没有集成,还得自己写I2C的时序驱动,I2C的时序图大家可以百度到,专门的协议文档也有。关于时序图我会贴到博客里。
1. I2C串行总线的组成与工作原理
a. 组成:2根双向信息线,一根数据线SDA,一根时钟线SCL
b. I2C总线上多个器件的挂接(注意,每个器件都有唯一的地址)
c. 数据的通讯方式
主从方式。主机负责主动联系从机,从机被动回应主机(上图里I2C设备都是从机)
2. 工作方式:I2C总线通过上拉电阻接正电源,当总线空闲时,2根线均为高电平,各期件的SDA及SCL都是“线与”关系[单一器件输出低电平,整条总线的信号都拉低,I2C总线上的器件都可以读取到,主从机通讯就是这种机制]
3. 数据位的有效性规定(规定就是没有理由)
SCL处于高电平期间,这时对数据进行操作,数据线上的数据必须保持稳定。只有SCL处于低电平期间,SDA状态才能变化
4. I2C字节的传送与应答
每一位字节必须保持8位长度。数据传送时,先传送最高位MSB, 每一个被传送的字节最后必须跟随一个应答位(及一帧9位,应答位要用于说明一个字节已经传完,I2C总线重新置空闲态)
5. 时序协议的驱动的理解要逻辑性良好(这点是对各个应答信号时序的理解与编程)
4个基本时序信号:起始信号, 终止信号, 应答“0”, 应答“1”。(注意,一定要严格按照时序图来编写程序,延时也有严格的要求)
void I2C_Start() /*起始信号*/
{
I2C_SDA_OUT(); /*先SDA输出模式,主机MCU负责主动联系从机,所以MCU是输出*/
I2C_SDA_H;
I2C_SCL_H;
delay_us(5);
I2C_SDA_L;
delay_us(6);
I2C_SCL_L;
}
void I2C_Stop() /*终止信号*/
{
I2C_SDA_OUT(); /*先SDA输出模式,同理主机也负责切断与从机的通讯*/
I2C_SCL_L;
I2C_SDA_L;
I2C_SCL_H;
delay_us(6);
I2C_SDA_H;
delay_us(6);
}
void I2C_Ack() /*主机应答*/
{
I2C_SCL_L;
I2C_SDA_OUT(); /*SDA输出,主机应答函数,从机器件自然内置相应的应答函数*/
I2C_SDA_L;
delay_us(2);
I2C_SCL_H;
delay_us(5);
I2C_SCL_L; /*SDA在“0”时,SCL保持高电平则作为读状态*/
}
void I2C_NAck() /*主机非应答*/
{
I2C_SCL_L;
I2C_SDA_OUT(); /*SDA输出*/
I2C_SDA_H;
delay_us(2);
I2C_SCL_H;
delay_us(5);
I2C_SCL_L; /*同理*/
}
这是这上面的几个基础时序程序。要使用I2C协议收,发数据还要研究
6. I2C写数据,读数据驱动
由图可知,发送的数据(地址和有效数据)不是只使用高低电平就可以的,2个字节的间隔区分,主从机是否接受到,数据的正确与否。。。都要求要有起始信号,终止信号,应答,还有检测应答函数。 所以主机也要写检测应答函数(从机集成了不用关心,即使你想了解也和主机的检测应答函数一样,只是它转成从机了)
u8 I2C_Wait_ACK() /*主机应答函数,检测从机应答的*/
{
u8 tempTime=0;
I2C_SDA_IN();
I2C_SDA_H;
delay_us(1);
I2C_SCL_H;
delay_us(1);
while(GPIO_ReadInputDataBit(GPIO_I2C,I2C_SDA)) /*GPIO_ReadInputBit读取GPIO_I2C:I2C_SDA的状态*/
{
tempTime++;
if(tempTime>250)
{
I2C_Stop();
return 1;
}
}
I2C_SCL_L;
return 0;
} //前期主机释放SDA, SCL总线
void I2C_Send_Byte(u8 txd) /*发送一个字节的数据: txd*/·
{
u8 i=0;
I2C_SDA_OUT(); //SDA
I2C_SCL_L;
for(i=0;i<8;i++)
{
if((txd&0x80)>0) /*这里的发送与0,1电平的发送没什么区别。SCL高电平保持一段时间,SDA则传到从机哪里了*/
{
I2C_SDA_H;
}
else
{
I2C_SDA_L;
}
txd<<=1;
I2C_SCL_H;
delay_us(2);
I2C_SCL_L;
delay_us(2);
}
}
u8 I2C_Read_Byte(u8 ack) /*读取一个字节的数据*/
{
u8 i=0,receive=0;
I2C_SDA_IN(); //SDA
for(i=0;i<8;i++)
{
I2C_SCL_L;
delay_us(2);
I2C_SCL_H;
receive<<=1;
if(GPIO_ReadInputDataBit(GPIO_I2C,I2C_SDA)) //¶ÁÈ¡I/O״̬µÄº¯Êý
{
receive++;
}
delay_us(1);
}
if(ack==0) //ackΪ0´ú±í·ÇÓ¦´ð
{
I2C_NAck();
}
else
{
I2C_Ack();
}
return receive;
}
这里使用并没有用到主机向从机读写数据的具体用法。下面贴代码AT24C02的使用,这个就是典型的I2C芯片
I2C.c文件
#include "I2C.h"
void I2C_init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
/* GPIOµÄÅäÖà */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = I2C_SCL|I2C_SDA;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB,&GPIO_InitStructure);
I2C_SCL_H;
I2C_SDA_H;
}
void I2C_SDA_OUT()
{
GPIO_InitTypeDef GPIO_InitStructure;
/* GPIOµÄÅäÖà */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = I2C_SDA;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
void I2C_SDA_IN()
{
GPIO_InitTypeDef GPIO_InitStructure;
//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = I2C_SDA; //Ö»ÅäÖÃSDA¹Ü½Å£¬SCLÊÇͬ²½Ïß
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //ÅäÖÃģʽ(ÊäÈëÉÏÀ)
GPIO_Init(GPIOB,&GPIO_InitStructure); //µ÷ÓÃGPIO³õʼ»¯ÅäÖú¯Êý
}
/** GPIOÓëI/O²»Í¬¾ÍÊÇÒªÅäÖÃģʽ£¬ËùÒÔI/O²»ÊÇËæ±ãÓà **/
/******************** ¸÷ÖÖÓ¦´ðº¯Êý **********************************/
void I2C_Start()
{
I2C_SDA_OUT();
I2C_SDA_H;
I2C_SCL_H;
delay_us(5);
I2C_SDA_L;
delay_us(6);
I2C_SCL_L;
}
void I2C_Stop()
{
I2C_SDA_OUT();
I2C_SCL_L;
I2C_SDA_L;
I2C_SCL_H;
delay_us(6);
I2C_SDA_H;
delay_us(6);
}
void I2C_Ack()
{
I2C_SCL_L;
I2C_SDA_OUT();
I2C_SDA_L;
delay_us(2);
I2C_SCL_H;
delay_us(5);
I2C_SCL_L;
}
void I2C_NAck()
{
I2C_SCL_L;
I2C_SDA_OUT();
I2C_SDA_H;
delay_us(2);
I2C_SCL_H;
delay_us(5);
I2C_SCL_L;
}
u8 I2C_Wait_ACK()
{
u8 tempTime=0;
I2C_SDA_IN();
I2C_SDA_H;
delay_us(1);
I2C_SCL_H;
delay_us(1);
while(GPIO_ReadInputDataBit(GPIO_I2C,I2C_SDA))
{
tempTime++;
if(tempTime>250)
{
I2C_Stop();
return 1;
}
}
I2C_SCL_L;
return 0;
}
void I2C_Send_Byte(u8 txd)
{
u8 i=0;
I2C_SDA_OUT();
I2C_SCL_L;
for(i=0;i<8;i++)
{
if((txd&0x80)>0)
{
I2C_SDA_H;
}
else
{
I2C_SDA_L;
}
txd<<=1;
I2C_SCL_H;
delay_us(2);
I2C_SCL_L;
delay_us(2);
}
}
u8 I2C_Read_Byte(u8 ack) {
u8 i=0,receive=0;
I2C_SDA_IN();
for(i=0;i<8;i++)
{
I2C_SCL_L;
delay_us(2);
I2C_SCL_H;
receive<<=1; //7´Î
if(GPIO_ReadInputDataBit(GPIO_I2C,I2C_SDA))
{
receive++;
}
delay_us(1);
}
if(ack==0) //ackΪ0´ú±í·ÇÓ¦´ð
{
I2C_NAck();
}
else
{
I2C_Ack();
}
return receive;
}
AT24Cxx.c文件
#include "AT24Cxx.h"
//¼Çס£¬AT24CxxҪʹÓõ½I2C¡£ËùÒÔÒªÔÚÆäAT24Cxx.hÖÐÒýÈëI2C.h
u8 AT24Cxx_ReadOneByte(u16 addr) //¶ÁÒ»¸ö×Ö½Ú¡£ ´ÓÄĸöµØÖ·¿ªÊ¼¶ÁÊý¾Ý
{
u8 temp;
I2C_Start();
if(EE_TYPE>AT24C16)
{
I2C_Send_Byte(0xa0);
I2C_Wait_ACK();
I2C_Send_Byte(addr>>8); //·¢ËÍÊý¾ÝµØÖ·¸ßλ
}
else
{
I2C_Send_Byte(0xa0+((addr/256)<<1)); //Æ÷¼þµØÖ·+Êý¾ÝµØÖ·
}
I2C_Wait_ACK();
I2C_Send_Byte(addr%256); //Ë«×Ö½ÚÊǶÁÈ¡µØÖ·µÍλ
I2C_Wait_ACK();
I2C_Start(); //ÆðʼÐźÅ
I2C_Send_Byte(0xa1); //´Ó»úµØÖ·+1
I2C_Wait_ACK();
temp=I2C_Read_Byte(0); //0´ú±í·ÇÓ¦´ð£¬1´ú±íÓ¦´ð
I2C_NAck();
I2C_Stop();
return temp;
}
void AT24Cxx_WriteOneByte(u16 addr,u8 dt) //дһ¸ö×Ö½Ú
{
I2C_Start();
if(EE_TYPE>AT24C16)
{
I2C_Send_Byte(0xa0);
I2C_Wait_ACK();
I2C_Send_Byte(addr>>8); //Æ÷¼þµØÖ·£¬AT24C16ÒÔÉÏÇ°8λ»áÌîÂú
}
else
{
I2C_Send_Byte(0xa0+((addr/256)<<1)); //Æ÷¼þµØÖ·+Êý¾ÝµØÖ·
}
I2C_Wait_ACK();
I2C_Send_Byte(addr%256); //¶ÁÈ¡µØÖ·
I2C_Wait_ACK();
I2C_Send_Byte(dt);
I2C_Wait_ACK();
I2C_Stop();
delay_ms(10);
}
u16 AT24Cxx_ReadTwoByte(u16 addr) //¶Á2¸ö×Ö½Ú¡£ ´ÓÄĸöµØÖ·¿ªÊ¼¶ÁÊý¾Ý
{
u16 temp=0;
I2C_Start();
if(EE_TYPE>AT24C16)
{
I2C_Send_Byte(0xa0);
I2C_Wait_ACK();
I2C_Send_Byte(addr>>8); //Æ÷¼þµØÖ·£¬AT24C16ÒÔÉÏÇ°8λ»áÌîÂú
}
else
{
I2C_Send_Byte(0xa0+((addr/256)<<1)); //Æ÷¼þµØÖ·+Êý¾ÝµØÖ·
}
I2C_Wait_ACK();
I2C_Send_Byte(addr%256); //¶ÁÈ¡µØÖ·
I2C_Wait_ACK();
I2C_Start(); //ÆðʼÐźÅ
I2C_Send_Byte(0xa1); //´Ó»úµØÖ·+1
I2C_Wait_ACK();
temp=I2C_Read_Byte(1); //0´ú±í·ÇÓ¦´ð£¬1´ú±íÓ¦´ð
temp<<=8;
temp|=I2C_Read_Byte(0); //0´ú±í·ÇÓ¦´ð£¬1´ú±íÓ¦´ð.¶ÁÁË2¸ö×Ö½Ú£¬²»ÓÃÓ¦´ðÁË
I2C_Stop();
return temp;
}
void AT24Cxx_WriteTwoByte(u16 addr,u16 dt) //д2¸ö×Ö½Ú
{
I2C_Start();
if(EE_TYPE>AT24C16)
{
I2C_Send_Byte(0xa0);
I2C_Wait_ACK();
I2C_Send_Byte(addr>>8); //Æ÷¼þµØÖ·£¬AT24C16ÒÔÉÏÇ°8λ»áÌîÂú
}
else
{
I2C_Send_Byte(0xa0+((addr/256)<<1)); //Æ÷¼þµØÖ·+Êý¾ÝµØÖ·
}
I2C_Wait_ACK();
I2C_Send_Byte(addr%256); //¶ÁÈ¡µØÖ·
I2C_Wait_ACK();
I2C_Send_Byte(dt>>8); //ÏÈд¸ß×Ö½Ú
I2C_Wait_ACK();
I2C_Send_Byte(dt&0xff); //ÏÈдµÍ×Ö½Ú
I2C_Wait_ACK();
I2C_Stop();
delay_ms(10);
}
main.c文件
#include "public.h"
#include "Systick.h"
#include "AT24Cxx.h"
#include "printf.h"
int main()
{
u16 wdata=16;
u16 value=0;
printf_init(); //³õʼ»¯
I2C_init();
delay_ms(1); //I2CÒª½øÐÐÉϵ磬עÒâ¿´ÊֲᣬÓÐÎÊÌâÒ²¿ÉÒÔ¿´¿´
AT24Cxx_WriteOneByte(0,wdata);
printf("%d ",wdata);
value=(AT24Cxx_ReadOneByte(0)+1); //µ«Êµ¼Ê³öÏÖµÄÎÊÌâÊÇ255+1Õâ¸öÖµ¡£½©Ó²
printf("%d ",value);
while(1);
}