IIC
原理:I2C是两线型串行总线,由时钟线SCL和数据线SDA构成的串行总线可发送和接受数据。
I2C在传输过程中有三种类型的信号:开始信号,结束信号,应答信号
- 空闲状态:SDA和SCL两条线同时处于高电平的时候,规定为总线是空闲信号
- 开始信号: 当SCL为高电平的时候,SDA由高到低的跳变;是一种电平跳变时序信号
- 停止信号:SCL为高电平期间,SDA由低到高的跳变,
- 应答信号:发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号
应答信号为低电平时,规定为有效应答位,表示成功接收该字节;应答为高电平表示接收器接收失败
要求在第九个时钟脉冲之前要把SDA拉低,并且确保在这个时钟高电平期间为稳定的低电平
- 数据的有效信:在数据传输时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线的高电平或者低电平状态才允许变化
数据在SCL的上升沿到来之前就需要准备好。并在在下降沿到来的之前是稳定的
- 数据传输:在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据,边沿触发
下面来点代码:
1.用GPIO软件模拟I2C通讯过程
1.1初始话GOPIO和IIC,最开始SDA,SCL线是拉高的
1 void IIC_Init(void) 2 { 3 GPIO_InitTypeDef GPIO_Initure; 4 5 __HAL_RCC_GPIOH_CLK_ENABLE(); 8 GPIO_Initure.Pin=GPIO_PIN_4|GPIO_PIN_5; 9 GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; 10 GPIO_Initure.Pull=GPIO_PULLUP; 11 GPIO_Initure.Speed=GPIO_SPEED_FAST; 12 HAL_GPIO_Init(GPIOH,&GPIO_Initure); 13 14 IIC_SDA(1); 15 IIC_SCL(1); 16 }
//起始信号;当SCL为高电平的时候,SDA由高到低的跳变;
1 2 void IIC_Start(void) 3 { 4 SDA_OUT(); //SDA线输出 5 IIC_SDA(1); //先都拉高 6 IIC_SCL(1); 7 delay_us(4); 8 IIC_SDA(0);//START:when CLK is high,DATA change form high to low 9 delay_us(4); 10 IIC_SCL(0); 11 }
SCL为高电平期间,SDA由低到高的跳变
void IIC_Stop(void)
{
SDA_OUT();//sdaÏßÊä³ö
IIC_SCL(0);
IIC_SDA(0);//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL(1);
delay_us(4);
IIC_SDA(1);
}
1.2接下来是读写和应答处理
//应答等待 u8 IIC_Wait_Ack(void) { u8 ucErrTime=0; SDA_IN(); // 释放SDA,SDA为输入 IIC_SDA(1);delay_us(1); IIC_SCL(1);delay_us(1); while(READ_SDA) { ucErrTime++; if(ucErrTime>250) { IIC_Stop(); return 1; } } IIC_SCL(0);//时钟输出0 return 0; } //产生应答 void IIC_Ack(void) { IIC_SCL(0); SDA_OUT(); IIC_SDA(0); delay_us(2); IIC_SCL(1); delay_us(2); IIC_SCL(0); } //不产生 void IIC_NAck(void) { IIC_SCL(0); SDA_OUT(); IIC_SDA(1); delay_us(2); IIC_SCL(1); delay_us(2); IIC_SCL(0); } // void IIC_Send_Byte(u8 txd) { u8 t; SDA_OUT(); IIC_SCL(0); for(t=0;t<8;t++)//从高到底发送八个比特 { IIC_SDA((txd&0x80)>>7);//取最高位 txd<<=1; //左移 delay_us(2); IIC_SCL(1); delay_us(2); IIC_SCL(0); delay_us(2); } } u8 IIC_Read_Byte(unsigned char ack) { unsigned char i,receive=0; SDA_IN();//SDA设置为输入 for(i=0;i<8;i++ ) { IIC_SCL(0); delay_us(2); IIC_SCL(1); receive<<=1; if(READ_SDA)receive++; delay_us(1); } if (!ack) IIC_NAck(); else IIC_Ack(); return receive; }
//IO方向
#define SDA_IN() {GPIOH->MODER&=~(3<<(5*2));GPIOH->MODER|=0<<5*2;}
#define SDA_OUT() {GPIOH->MODER&=~(3<<(5*2));GPIOH->MODER|=1<<5*2;}
//IO
#define IIC_SCL(n) (n?HAL_GPIO_WritePin(GPIOH,GPIO_PIN_4,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOH,GPIO_PIN_4,GPIO_PIN_RESET)) //SCL
#define IIC_SDA(n) (n?HAL_GPIO_WritePin(GPIOH,GPIO_PIN_5,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOH,GPIO_PIN_5,GPIO_PIN_RESET)) //SDA
#define READ_SDA HAL_GPIO_ReadPin(GPIOH,GPIO_PIN_5)
//初始化IIC接口 void AT24CXX_Init(void) { IIC_Init();//IIC初始化 } //在AT24CXX指定地址读出一个数据 //ReadAddr:开始读数的地址 //返回值 :读到的数据 u8 AT24CXX_ReadOneByte(u16 ReadAddr) { u8 temp=0; IIC_Start(); if(EE_TYPE>AT24C16) { IIC_Send_Byte(0XA0); //发送写命令 IIC_Wait_Ack(); IIC_Send_Byte(ReadAddr>>8);//发送高地址 }else IIC_Send_Byte(0XA0+((ReadAddr/256)<<1)); //发送器件地址0XA0,写数据 IIC_Wait_Ack(); IIC_Send_Byte(ReadAddr%256); //发送低地址 IIC_Wait_Ack(); IIC_Start(); IIC_Send_Byte(0XA1); //进入接收模式 IIC_Wait_Ack(); temp=IIC_Read_Byte(0); IIC_Stop();//产生一个停止条件 return temp; } //在AT24CXX指定地址写入一个数据 //WriteAddr :写入数据的目的地址 //DataToWrite:要写入的数据 void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite) { IIC_Start(); if(EE_TYPE>AT24C16) { IIC_Send_Byte(0XA0); //发送写命令 IIC_Wait_Ack(); IIC_Send_Byte(WriteAddr>>8);//发送高地址 }else IIC_Send_Byte(0XA0+((WriteAddr/256)<<1)); //发送器件地址0XA0,写数据 IIC_Wait_Ack(); IIC_Send_Byte(WriteAddr%256); //发送低地址 IIC_Wait_Ack(); IIC_Send_Byte(DataToWrite); //发送字节 IIC_Wait_Ack(); IIC_Stop();//产生一个停止条件 delay_ms(10); } //在AT24CXX里面的指定地址开始写入长度为Len的数据 //该函数用于写入16bit或者32bit的数据. //WriteAddr :开始写入的地址 //DataToWrite:数据数组首地址 //Len :要写入数据的长度2,4 void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len) { u8 t; for(t=0;t<Len;t++) { AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff); } } //在AT24CXX里面的指定地址开始读出长度为Len的数据 //该函数用于读出16bit或者32bit的数据. //ReadAddr :开始读出的地址 //返回值 :数据 //Len :要读出数据的长度2,4 u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len) { u8 t; u32 temp=0; for(t=0;t<Len;t++) { temp<<=8; temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1); } return temp; } //检查AT24CXX是否正常 //这里用了24XX的最后一个地址(255)来存储标志字. //如果用其他24C系列,这个地址要修改 //返回1:检测失败 //返回0:检测成功 u8 AT24CXX_Check(void) { u8 temp; temp=AT24CXX_ReadOneByte(255);//避免每次开机都写AT24CXX if(temp==0X55)return 0; else//排除第一次初始化的情况 { AT24CXX_WriteOneByte(255,0X55); temp=AT24CXX_ReadOneByte(255); if(temp==0X55)return 0; } return 1; } //在AT24CXX里面的指定地址开始读出指定个数的数据 //ReadAddr :开始读出的地址 对24c02为0~255 //pBuffer :数据数组首地址 //NumToRead:要读出数据的个数 void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead) { while(NumToRead) { *pBuffer++=AT24CXX_ReadOneByte(ReadAddr++); NumToRead--; } } //在AT24CXX里面的指定地址开始写入指定个数的数据 //WriteAddr :开始写入的地址 对24c02为0~255 //pBuffer :数据数组首地址 //NumToWrite:要写入数据的个数 void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite) { while(NumToWrite--) { AT24CXX_WriteOneByte(WriteAddr,*pBuffer); WriteAddr++; pBuffer++; } }
上面是用软件I2C读取的AT24C02
A0A1A2是设置器件的地址地址
WD是写保护,接地取消写保护
如果A1A2A3=0,读地址ADDress=0xA1,写就是ADDress=0xA0;
写时序: 写模式(确定是读还是写),写地址(确定芯片内部的存储单元),写数据
读操作,先确定读的地址,写模式(设置为写模式)-》 写地址(写要读的地址)-》读模式-》读数据
下面是用51进行读写24c04,实现原理都差不多的。
#include "24c04.h" #include "intrins.h" void vDelay10us(void) { unsigned char i; for(i=0;i<1;i++) ; } //启动信号开始的时候,clk = 1 data = 1 结束的时候 clk = 0 data = 0 void vIICStart(void) { PDAT =1 ;vDelay10us(); PCLK = 1 ; vDelay10us(); PDAT =0 ; vDelay10us(); PCLK = 0 ; vDelay10us(); } //stop的开始条件 clk = 0 dat = 0 void vIICStop(void) { PDAT = 0 ; vDelay10us(); PCLK = 1 ;vDelay10us(); PDAT = 1 ; vDelay10us(); } //发送数据的时候,clk从0开始,到0结束。data 最后是0 。 void vNoACK(void) { PCLK = 0 ;vDelay10us(); PDAT = 1 ;vDelay10us(); PCLK = 1 ;vDelay10us(); PCLK = 0 ;vDelay10us(); PDAT = 0 ;vDelay10us(); } //发送数据的时候,clk从0开始,到0结束。data 最后是0 。 void vSend1Bit(void) { PCLK = 0 ;vDelay10us(); PDAT = 1 ;vDelay10us(); PCLK = 1 ;vDelay10us(); PCLK = 0 ;vDelay10us(); PDAT = 0 ;vDelay10us(); } void vSend0Bit(void) { PCLK = 0 ;vDelay10us(); PDAT = 0 ; vDelay10us(); PCLK = 1 ; vDelay10us(); PCLK = 0 ; vDelay10us(); } void vSend1Byte(unsigned char u8Data) { unsigned char i; for(i = 0 ;i <8 ;i++) { if( u8Data & 0x80 ) { vSend1Bit(); } else { vSend0Bit(); } u8Data <<= 1 ; } } //读取24c的ack 如果有正确的ACK 则得到1 否则得到0 unsigned char u8ReadACK(void) { PDAT = 1 ; vDelay10us(); PCLK = 1 ; vDelay10us(); PCLK = 0 ; vDelay10us(); if( 1 == PDAT) return 0; else return 1 ; } void v24CWrite1Byte(unsigned char u8Address , unsigned char u8Data) { vIICStart(); vSend1Byte(0xA0); u8ReadACK(); vSend1Byte(u8Address); u8ReadACK(); vSend1Byte(u8Data); u8ReadACK(); vIICStop(); } unsigned char u824CRead1Byte(unsigned char u8Address) { unsigned char i,u8Temp; vIICStart(); vSend1Byte(0xA0); u8ReadACK(); vSend1Byte(u8Address); u8ReadACK(); vIICStart(); vSend1Byte(0xA1); u8ReadACK(); //读取8bit PDAT = 1 ; vDelay10us(); for(i = 0 ;i<8;i++) { u8Temp <<= 1 ; PCLK = 0 ; vDelay10us(); PCLK = 1 ; vDelay10us(); if(1 == PDAT) { u8Temp |= 0x01; } } vNoACK(); vIICStop(); return u8Temp; }