不同的芯片的时序图可能不一样,但起始信号、等待应答信号、发送应答信号、读写一个字节、停止信号都是一样的,然后根据不同时序将这些组合起来就可以了。
写一页数据的时序图为(其他的时序图就不放了):
读一个字节的时序图为:
直接在HAL库里面配置成以下:
PB6作为时钟线SCL,PB7作为数据线SDA,PC8为读使能
代码如下:
相关宏定义(因为给SDA配置的是开漏模式,所以不需要变化模式,直接读即可):
#define SCL_Pin GPIO_PIN_6
#define SCL_GPIO_Port GPIOB
#define SDA_Pin GPIO_PIN_7
#define SDA_GPIO_Port GPIOB
#define I2C_READ 0xA1
#define I2C_WRITE 0xA0
#define I2C_WRITE_ENABLE HAL_GPIO_WritePin(I2C1_WP_GPIO_Port, I2C1_WP_Pin, GPIO_PIN_RESET)
#define I2C_WRITE_DISABLE HAL_GPIO_WritePin(I2C1_WP_GPIO_Port, I2C1_WP_Pin, GPIO_PIN_SET)
#define SCL_OUT(X) if(X){HAL_GPIO_WritePin(SCL_GPIO_Port,SCL_Pin,GPIO_PIN_SET);}else{HAL_GPIO_WritePin(SCL_GPIO_Port,SCL_Pin,GPIO_PIN_RESET);}
#define SDA_OUT(X) if(X){HAL_GPIO_WritePin(SDA_GPIO_Port,SDA_Pin,GPIO_PIN_SET);}else{HAL_GPIO_WritePin(SDA_GPIO_Port,SDA_Pin,GPIO_PIN_RESET);}
#define SDA_IN HAL_GPIO_ReadPin(SDA_GPIO_Port,SDA_Pin) //推挽模式下只有配置输入模式才能读取电平状态(开漏模式下可以随便读)
起始信号:(我的IO口的速度较快不需要延时的,其他的大多数都是需要有延时穿插的)
void IIC_Start(void)
{
SDA_OUT(1);
SCL_OUT(1);
SDA_OUT(0);
SCL_OUT(0);
}
停止信号:
void IIC_Stop(void)
{
SDA_OUT(0);
SCL_OUT(1);
SDA_OUT(1);
}
等待应答:
uint8_t IIC_Wait_Ack(void)
{
SDA_OUT(1);
SCL_OUT(1);//SCL拉高以后,主机便可以去读取从机给的信号
if(SDA_IN)
{
printf("iic wait Ack fail\n");
}
SCL_OUT(0);
return 0;
}
主机发送的应答信号ACK:低电平0表示应答,1表示非应答
void IIC_Ack(void) //主机发送应答信号Ack
{
SDA_OUT(0);
SCL_OUT(1);
SCL_OUT(0);
}
void IIC_NAck(void) //主机发送非应答信号NAck
{
SDA_OUT(1);
SCL_OUT(1);
SCL_OUT(0);
}
主机发送8位数据给从机MSB 高位先发:
void IIC_Send_Byte(uint8_t byte)
{
for(uint8_t i=0;i<8;i++)
{
SDA_OUT(byte & (0x80>>i));
SCL_OUT(1); //拉高时钟线,让从机读取数据
SCL_OUT(0); //拉低时钟线,告诉从机别读,主机准备发送下一位数据
}
}
从机发送8位数据给主机:
uint8_t IIC_Read_Byte(void)
{
uint8_t byte = 0;
SDA_OUT(1); //主机释放SDA,让从机操作SDA线
for(uint8_t i=0;i<8;i++)
{
SCL_OUT(1);//拉高时钟线,主机开始接收数据
if(SDA_IN == GPIO_PIN_SET)
{
byte |= (0x80>>i); //数据为 1
}
SCL_OUT (0);//让从机再次发送数据
}
return byte;
}
I2C写一个字节:
void IIC_Mem_Write_Byte(uint8_t dev_addr, uint16_t mem_addr,uint8_t pData)
{
IIC_Start();
IIC_Send_Byte(dev_addr);//设备地址
IIC_Wait_Ack();
IIC_Send_Byte(mem_addr>>8);//内存地址高8位
IIC_Wait_Ack();
IIC_Send_Byte(mem_addr&0xff);//内存地址低8位
IIC_Wait_Ack();
IIC_Send_Byte(pData);
IIC_Wait_Ack();
IIC_Stop();
}
I2C读一个字节:
uint8_t IIC_Mem_Read_Byte(uint8_t dev_addr, uint16_t mem_addr)
{
uint8_t data;
IIC_Start();
IIC_Send_Byte(dev_addr);//设备地址
IIC_Wait_Ack();
IIC_Send_Byte(mem_addr>>8);//内存地址高8位
IIC_Wait_Ack();
IIC_Send_Byte(mem_addr&0xff);//内存地址低8位
IIC_Wait_Ack();
IIC_Start(); //再次发送一次起始信号
IIC_Send_Byte(dev_addr | 0x01);//再发送一次设备地址(最后一位置1,表示将从机进行数据读操作)
IIC_Wait_Ack();
data = IIC_Read_Byte();
IIC_NAck();
IIC_Stop();
return data;
}
I2C写一页数据(可以写32个字节以下任何数量):
void IIC_Mem_Write_Page_Data(uint8_t dev_addr, uint16_t mem_addr,uint8_t *pData,uint16_t size)
{
uint8_t i=0;
IIC_Start();
IIC_Send_Byte(I2C_WRITE);//设备地址
IIC_Wait_Ack();
IIC_Send_Byte(mem_addr>>8);//内存地址高8位
IIC_Wait_Ack();
IIC_Send_Byte(mem_addr&0xff);//内存地址低8位
IIC_Wait_Ack();
while(size>0)
{
IIC_Send_Byte(*pData++);
IIC_Wait_Ack();
size--;
i++;
if(i == 32) //写满一页就退出
{
break;
}
}
IIC_Stop();
}
I2C读一页数据(可以读32个字节以下任何数量):
void IIC_Mem_Read_Page_Data(uint8_t dev_addr, uint16_t mem_addr,uint8_t *pData,uint16_t size)
{
uint8_t i=0;
IIC_Start();
IIC_Send_Byte(I2C_WRITE);//设备地址
IIC_Wait_Ack();
IIC_Send_Byte(mem_addr>>8);//内存地址高8位
IIC_Wait_Ack();
IIC_Send_Byte(mem_addr&0xff);//内存地址低8位
IIC_Wait_Ack();
IIC_Start(); //再次发送一次起始信号
IIC_Send_Byte(I2C_READ);//再发送一次设备地址(最后一位置1,表示对从机进行数据读操作)
IIC_Wait_Ack();
while(size>1)
{
*pData++ = IIC_Read_Byte();
IIC_Ack();
size--;
i++;
if(i == 32) //读完一页就退出
{
break;
}
}
*pData++ = IIC_Read_Byte();
IIC_NAck();
IIC_Stop();
}
部分测试代码(一个字节):
uint8_t dev_Addr = 0xA0;//设备地址,不同设备可能不一样
uint16_t memAddr = 0x0000;
uint8_t sendData = 0x55;
uint8_t Data;
I2C_WRITE_ENABLE; //打开I2C写使能
IIC_Mem_Write_Byte(dev_Addr , memAddr, sendData);
Delay_ms(10); //这里一定得有延时,具体多少不清楚
I2C_WRITE_DISABLE;
Data= IIC_Mem_Read_Byte(dev_Addr , memAddr);
printf("%d\n",Data);
结果:
部分测试代码(读取一页数据和写一页数据):
uint8_t con_value[5]={1,2,3,4,5};
I2C_WRITE_ENABLE;
Delay_ms(10);
IIC_Mem_Write_Page_Data(I2C_WRITE,(18<<5),(uint8_t *)con_value, 5);
Delay_ms(10);
I2C_WRITE_DISABLE;
con_value[0]=5;
con_value[1]=4;
con_value[2]=3;
con_value[3]=2;
con_value[4]=1;
IIC_Mem_Read_Page_Data(I2C_WRITE,(18<<5),(uint8_t *)con_value, 5);
printf("temp1=%d\n",con_value[0]);
printf("temp2=%d\n",con_value[1]);
printf("temp3=%d\n",con_value[2]);
printf("temp4=%d\n",con_value[3]);
printf("temp5=%d\n",con_value[4]);
结果