一、stm32读写at24c02存储芯片,并通过串口打印。
1、首先简单介绍下AT24Cx系列存储芯片。详细内容需参考相应芯片手册。
AT24C01/02/04/08/16 是低工作电压的 1K/2K/4K/8K/16K 位串行电可擦除只读存储器,内部组织为
128/256/512/1024/2048 个字节,每个字节 8 位。二线串口接口(SCL,SDA)。
2、I2C总线协议介绍。
总线上扩展的外围器件及外设接口通过总线寻址,是具备总线仲裁和高低速设备同步等功能的高性能多主机总线。标准速率为100kbps
3、I2C总线工作原理。
由串行数据线SDA和串行时钟线SCL构成的。数据线SDA可以发送和接收数据。
挂接在总线上所有器件要依靠SDA发送的地址信号寻址,所以不需要片选线。
此地址(7位)由器件地址和引脚地址2部分组成。
I2C总线上数据是伴随着时钟脉冲,一位一位的传送,数据位又高到低传送。
在数据传送时,SDA上数据的改变在时钟线为低电平时完成,而SCL为高电平时,SDA必须保持稳定,否则SDA上的变化会被当作起始或终止信号而致使数据传输停止。
4、
起始信号:SCL保存高电平状态下,SDA由高变低;(变化时的高低电平要保持5us以上)
终止信号:SCL保存高电平状态下,SDA由低到高;
应答信号:接收数据的器件在接收到8位数据后,向发送数据的器件发出特定的低电平脉冲,表示已收到数据。用于表明I2C总线数据传输的结束。
非应答信号:发出特定的高电平脉冲。
总线空闲:SCL和SDA都保持高电平。
5、I2C驱动代码如下:(已在24c02/24C08芯片上验证,能够正常读写。)
//芯片1次最多可连续写入字节个数就是每页字节个数,如24c08最多1次写8个字节。
读字节个数则没有限制。
//STM32相应头文件需要根据自己的工程添加!! #define SCL_H GPIO_SetBits(GPIOB, GPIO_Pin_10) #define SCL_L GPIO_ResetBits(GPIOB, GPIO_Pin_10) #define SDA_H GPIO_SetBits(GPIOB, GPIO_Pin_11) #define SDA_L GPIO_ResetBits(GPIOB, GPIO_Pin_11) #define SCL_read GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_10) #define SDA_read GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) #define I2C_PageSize 16 //24C02每页8字节 24C08每页16字节 void Delay(u16 speed) //1us { u16 i; while(speed!=0) { speed--; for(i=0;i<20;i++); } } void tus(u16 speed) //N us { Delay(speed); } void tms(u16 speed) //1ms { while(speed!=0) { speed--; Delay(1000); } } void I2C_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); /* Configure I2C1 pins: SCL and SDA */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_Init(GPIOB, &GPIO_InitStructure); } void I2C_delay(void) { u8 i=100; //这里可以优化速度 ,经测试最低到5还能写入 while(i) { i--; } } bool I2C_Start(void) { SDA_H; SCL_H; I2C_delay(); if(!SDA_read)return FALSE; //SDA线为低电平则总线忙,退出 SDA_L; I2C_delay(); if(SDA_read) return FALSE; //SDA线为高电平则总线出错,退出 SDA_L; I2C_delay(); return TRUE; } void I2C_Stop(void) { SCL_L; I2C_delay(); SDA_L; I2C_delay(); SCL_H; I2C_delay(); SDA_H; I2C_delay(); } void I2C_Ack(void) { SCL_L; I2C_delay(); SDA_L; I2C_delay(); SCL_H; I2C_delay(); SCL_L; I2C_delay(); } void I2C_NoAck(void) { SCL_L; I2C_delay(); SDA_H; I2C_delay(); SCL_H; I2C_delay(); SCL_L; I2C_delay(); } bool I2C_WaitAck(void) //返回为:=1有ACK,=0无ACK { SCL_L; I2C_delay(); SDA_H; I2C_delay(); SCL_H; I2C_delay(); if(SDA_read) { SCL_L; return FALSE; } SCL_L; return TRUE; } void I2C_SendByte(u8 SendByte) //数据从高位到低位 { u8 i=8; while(i--) { SCL_L; I2C_delay(); if(SendByte&0x80) SDA_H; else SDA_L; SendByte<<=1; I2C_delay(); SCL_H; I2C_delay(); } SCL_L; } u8 I2C_ReceiveByte(void) //数据从高位到低位// { u8 i=8; u8 ReceiveByte=0; SDA_H; while(i--) { ReceiveByte<<=1; SCL_L; I2C_delay(); SCL_H; I2C_delay(); if(SDA_read) { ReceiveByte|=0x01; } } SCL_L; return ReceiveByte; } //写入1字节数据 待写入数据 待写入地址 器件类型(24c16或SD2403) bool I2C_WriteByte(u8 SendByte, u16 WriteAddress, u8 DeviceAddress) { if(!I2C_Start())return FALSE; I2C_SendByte(((WriteAddress & 0x0700) >>7) | DeviceAddress & 0xFFFE); //设置高起始地址+器件地址 if(!I2C_WaitAck()){I2C_Stop(); return FALSE;} I2C_SendByte((u8)(WriteAddress & 0x00FF)); //设置低起始地址 I2C_WaitAck(); I2C_SendByte(SendByte); I2C_WaitAck(); I2C_Stop(); //注意:因为这里要等待EEPROM写完,可以采用查询或延时方式(10ms) //Systick_Delay_1ms(10); tms(10); return TRUE; } //注意不能跨页写 //写入1串数据 待写入数组地址 待写入长度 待写入地址 器件类型(24c16或SD2403) bool I2C_BufferWrite(u8* pBuffer, u8 length, u16 WriteAddress, u8 DeviceAddress) { if(!I2C_Start())return FALSE; I2C_SendByte(((WriteAddress & 0x0700) >>7) | DeviceAddress & 0xFFFE); //设置高起始地址+器件地址 if(!I2C_WaitAck()){I2C_Stop(); return FALSE;} I2C_SendByte((u8)(WriteAddress & 0x00FF)); //设置低起始地址 I2C_WaitAck(); while(length--) { I2C_SendByte(* pBuffer); I2C_WaitAck(); pBuffer++; } I2C_Stop(); //注意:因为这里要等待EEPROM写完,可以采用查询或延时方式(10ms) //Systick_Delay_1ms(10); tms(10); return TRUE; } //跨页写入1串数据 待写入数组地址 待写入长度 待写入地址 器件类型(24c16或SD2403) void I2C_PageWrite( u8* pBuffer, u8 length, u16 WriteAddress, u8 DeviceAddress) { u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0; Addr = WriteAddress % I2C_PageSize; //写入地址是开始页的第几位 count = I2C_PageSize - Addr; //在开始页要写入的个数 NumOfPage = length / I2C_PageSize; //要写入的页数 NumOfSingle = length % I2C_PageSize; //不足一页的个数 if(Addr == 0) //写入地址是页的开始 { if(NumOfPage == 0) //数据小于一页 { I2C_BufferWrite(pBuffer,NumOfSingle,WriteAddress,DeviceAddress); //写少于一页的数据 } else //数据大于等于一页 { while(NumOfPage)//要写入的页数 { I2C_BufferWrite(pBuffer,I2C_PageSize,WriteAddress,DeviceAddress);//写一页的数据 WriteAddress += I2C_PageSize; pBuffer += I2C_PageSize; NumOfPage--; tms(10); } if(NumOfSingle!=0)//剩余数据小于一页 { I2C_BufferWrite(pBuffer,NumOfSingle,WriteAddress,DeviceAddress); //写少于一页的数据 tms(10); } } } else //写入地址不是页的开始 { if(NumOfPage== 0) //数据小于一页 { I2C_BufferWrite(pBuffer,NumOfSingle,WriteAddress,DeviceAddress); //写少于一页的数据 } else //数据大于等于一页 { length -= count; NumOfPage = length / I2C_PageSize; //重新计算要写入的页数 NumOfSingle = length % I2C_PageSize; //重新计算不足一页的个数 if(count != 0) { I2C_BufferWrite(pBuffer,count,WriteAddress,DeviceAddress); //将开始的空间写满一页 WriteAddress += count; pBuffer += count; } while(NumOfPage--) //要写入的页数 { I2C_BufferWrite(pBuffer,I2C_PageSize,WriteAddress,DeviceAddress);//写一页的数据 WriteAddress += I2C_PageSize; pBuffer += I2C_PageSize; } if(NumOfSingle != 0)//剩余数据小于一页 { I2C_BufferWrite(pBuffer,NumOfSingle,WriteAddress,DeviceAddress); //写少于一页的数据 } } } } //读出1串数据 存放读出数据 待读出长度 待读出地址 器件类型(24c16或SD2403) bool I2C_ReadByte(u8* pBuffer, u8 length, u16 ReadAddress, u8 DeviceAddress) { if(!I2C_Start())return FALSE; I2C_SendByte(((ReadAddress & 0x0700) >>7) | DeviceAddress & 0xFFFE);//设置高起始地址+器件地址 if(!I2C_WaitAck()){I2C_Stop(); return FALSE;} I2C_SendByte((u8)(ReadAddress & 0x00FF)); //设置低起始地址 I2C_WaitAck(); I2C_Start(); I2C_SendByte(((ReadAddress & 0x0700) >>7) | DeviceAddress | 0x0001); I2C_WaitAck(); while(length) { *pBuffer = I2C_ReceiveByte(); if(length == 1)I2C_NoAck(); else I2C_Ack(); pBuffer++; length--; } I2C_Stop(); return TRUE; }