实验内容:分别采用软件/硬件IIC读写EEPROM(AT24C02)。
一、原理图
—— —— —— —— —— —— —— —— 软件IIC—— —— —— —— —— —— —— ——
二、 CubeMX配置
Step1.打开 STM32CubeMX,点击“New Project”,选择芯片型号,STM32F103VETx。
Step2.选择时钟源,并配置时钟树。选择Crystal/Ceramic Resonator,并配置系统时钟为72M。
Step3.配置SYS,我们这里选择的是Serial Wire。(正常情况配置不配置不影响,debug可以使用。但是你不可以把这两个引脚用于其他复用功能,如果用于其他复用功能,debug就不起作用了。)
Step4.串口配置(主要为了在串口调试助手显示读写数据),因为没有用到中断和DMA所以我们就不过多讲解。
Step5.配置软件IIC所需要的的GPIO口,需要注意的是这边要设置成开漏输出模式,上拉模式。这里选用PB6,PB7作为SCL,SDA,因为是软件IIC,所以这里的选择接口是不受限制的。
到这里关于软件IIC参数配置基本已经完成,只需要根据之前文章《STM32Cube HAL:GPIO输入/输出(一)》Step4-Step8,设置相关工程参数和生成代码。
三、添加功能代码
1、我们等会会向串口调试助手发送数据,进行实验结果的验证。 发送数据我们采用printf函数,所有需要重定向c库函数printf到串口。注意使用时需要在keil设置中勾选微库(use mircolib),同时需要添加头文件#include <stdio.h>。重定向代码如下(usart.c)
//重定向c库函数printf到串口DEBUG_USART,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
/* 发送一个字节数据到串口DEBUG_USART */
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
return (ch);
}
//重定向c库函数scanf到串口DEBUG_USART,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
int ch;
HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 1000);
return (ch);
}
2、为了把IIC和EEPROM的相关接口区分开,方便以后移植到其他IIC设备。在cubemx自动生成的gpio.c/gpio.h IIC的相关接口,同时新建了i2c_ee.c/i2c_ee.c中写读写EEPROM的接口。
(1)IIC相关接口的书写。
首先是在gpio.h中,对SCL/SDA相关IO口的写0/1和读取进行宏定义,提高代码可读性。以及等会涉及到读写位进行宏定义。最后就是函数的声明。
#define I2C_SCL_1() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);
#define I2C_SCL_0() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);
#define I2C_SDA_1() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
#define I2C_SDA_0() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
#define I2C_SDA_READ() HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7)
#define I2C_WR 0 //写控制bit
#define I2C_RD 1 //读控制bit
void MX_GPIO_Init(void);
void i2c_Start(void);
void i2c_Stop(void);
void i2c_SendByte(uint8_t HS_Byte);
uint8_t i2c_ReadByte(void);
uint8_t i2c_WaitAck(void);
void i2c_Ack(void);
void i2c_NAck(void);
uint8_t i2c_CheckDevice(uint8_t _Address);
然后就是在gpio.c中,把我们刚才声明的函数进行完善。
/*延时函数*/
static void i2c_Delay(void)
{
uint8_t i;
/*
下面的时间是通过逻辑分析仪测试得到的。
工作条件:CPU主频72MHz ,MDK编译环境,1级优化
循环次数为10时,SCL频率 = 205KHz
循环次数为7时,SCL频率 = 347KHz, SCL高电平时间1.5us,SCL低电平时间2.87us
循环次数为5时,SCL频率 = 421KHz, SCL高电平时间1.25us,SCL低电平时间2.375us
*/
for (i = 0; i < 10; i++);
}
/* CPU发起I2C总线启动信号:当 SCL 线是高电平时 SDA 线从高电平向低电平切换*/
void i2c_Start(void)
{
I2C_SCL_1();
I2C_SDA_1();
i2c_Delay();
I2C_SDA_0();
i2c_Delay();
I2C_SCL_0();
}
/* CPU发起I2C总线停止信号:当 SCL 是高电平时 SDA 线由低电平向高电平切换*/
void i2c_Stop(void)
{
I2C_SCL_1();
I2C_SDA_0();
i2c_Delay();
I2C_SDA_1();
i2c_Delay();
I2C_SCL_0();
}
/*CPU向I2C总线设备发送8bit数据*/
void i2c_SendByte(uint8_t HS_Byte)
{
uint8_t i;
for(i=0;i<8;i++)
{
if(HS_Byte&0X80)//先发送高位
{
I2C_SDA_1();
}
else
{
I2C_SDA_0();
}
i2c_Delay();
I2C_SCL_1();
i2c_Delay();
I2C_SCL_0();
if(i==7)//发送完成一个字节,释放数据总线,接收应答/非应答信号
{
I2C_SDA_1();
}
HS_Byte<<=1;//左移一位
i2c_Delay();
}
}
/*CPU从I2C总线设备读取8bit数据*/
uint8_t i2c_ReadByte(void)
{
uint8_t SH_Byte, i;
SH_Byte=0;
for(i=0;i<8;i++)//先读取高位
{
SH_Byte <<= 1;
I2C_SCL_1();
i2c_Delay();
if(I2C_SDA_READ())
{
SH_Byte++;
}
I2C_SCL_0();
i2c_Delay();
}
return SH_Byte;
}
/*CPU产生一个时钟,并读取器件的ACK应答信号*/
uint8_t i2c_WaitAck(void)
{
uint8_t re;
I2C_SDA_1(); // CPU释放SDA总线
i2c_Delay();
I2C_SCL_1(); //CPU驱动SCL = 1, 此时器件会返回ACK应答
i2c_Delay();
if (I2C_SDA_READ()) // CPU读取SDA口线状态
{
re = 1;
}
else
{
re = 0;
}
I2C_SCL_0();
i2c_Delay();
return re;
}
/*CPU产生1个ACK信号*/
void i2c_Ack(void)
{
I2C_SDA_0(); // CPU驱动SDA = 0 ,应答信号
i2c_Delay();
I2C_SCL_1(); // CPU产生1个时钟
i2c_Delay();
I2C_SCL_0();
i2c_Delay();
I2C_SDA_1(); // CPU释放SDA总线
}
/*CPU产生1个NACK信号*/
void i2c_NAck(void)
{
I2C_SDA_1(); // CPU驱动SDA = 1 ,非应答信号
i2c_Delay();
I2C_SCL_1(); // CPU产生1个时钟
i2c_Delay();
I2C_SCL_0();
i2c_Delay();
}
/*检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在*/
uint8_t i2c_CheckDevice(uint8_t Addr)
{
uint8_t ucAck;
i2c_Start(); //发送启动信号
/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
i2c_SendByte(Addr|I2C_WR);
ucAck = i2c_WaitAck(); // 检测设备的ACK应答
i2c_Stop(); // 发送停止信号
return ucAck;
}
(2)使用刚才写好的IIC的接口编写对EEPROM的读写、擦除等程序。
还是一样在i2c_ee.h中,进行相关代码宏定义,提高代码可读性。还有就是函数的声明。
* AT24C02 2kb = 2048bit = 2048/8 B = 256 B
* 32 pages of 8 bytes each
*
* Device Address
* 1 0 1 0 A2 A1 A0 R/W
* 1 0 1 0 0 0 0 0 = 0XA0
* 1 0 1 0 0 0 0 1 = 0XA1
*/
/* AT24C01/02每页有8个字节
* AT24C04/08A/16A每页有16个字节
*/
#define EEPROM_DEV_ADDR 0xA0 // 24xx02的设备地址
#define EEPROM_PAGE_SIZE 8 // 24xx02的页面大小
#define EEPROM_SIZE 256 // 24xx02总容量
uint8_t ee_CheckOk(void);
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize);
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize);
void ee_Erase(void);
uint8_t ee_Test(void);
在i2c_ee.c完善相关函数
/* 判断串行EERPOM是否正常 */
uint8_t ee_CheckOk(void)
{
if(i2c_CheckDevice(EEPROM_DEV_ADDR))
{
i2c_Stop();//失败后,切记发送I2C总线停止信号
return 0;//设备异常
}
else
{
return 1;//设备正常
}
}
/*从串行EEPROM指定地址处开始读取若干数据*/
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize)
{
uint16_t i;
i2c_Start();//第1步:启动I2C总线
i2c_SendByte(EEPROM_DEV_ADDR|I2C_WR);//第2步:发送地址+写指令
if(i2c_WaitAck()!=0)//第3步:判断设备是否响应
{
goto res_error;
}
i2c_SendByte(_usAddress);//第4步:发送字节地址
if(i2c_WaitAck()!=0)//第5步:判断设备是否响应
{
goto res_error;
}
i2c_Start();//第6步:重启I2C总线,开始读数据
i2c_SendByte(EEPROM_DEV_ADDR|I2C_RD);//第7步:发送地址+读指令
if(i2c_WaitAck()!=0)//第8步:判断设备是否响应
{
goto res_error;
}
for(i=0;i<_usSize;i++)
{
_pReadBuf[i]=i2c_ReadByte();//第9步:开始读取数据
if(i!=_usSize-1)//第10步:在接收_usSize-1个字节前都发送应答信号,最后一个字节发送非应答信号
{
i2c_Ack();
}
else
{
i2c_NAck();
}
}
i2c_Stop();//第11步:停止I2C总线
return 1;//读取成功
res_error:
printf("\r\n响应超时\r\n");
return 0;//读取失败
}
/*向串行EEPROM指定地址写入若干数据,采用页写操作提高写入效率*/
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)
{
uint16_t i,m,Addr;
Addr=_usAddress;
for(i=0;i<_usSize;i++)
{
if((i==0)||(Addr&(EEPROM_PAGE_SIZE - 1))==0)//当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址
{
i2c_Stop();//发停止信号,启动内部写操作
for(m=0;m<1000;m++)//通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms,CLK频率为200KHz时,查询次数为30次左右
{
i2c_Start();//第1步:发起I2C总线启动信号
i2c_SendByte(EEPROM_DEV_ADDR|I2C_WR);//第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读
if(i2c_WaitAck()==0)//第3步:判断设备是否响应
{
break;
}
}
if(m==1000)//响应超时
{
goto res_error;
}
i2c_SendByte(Addr);// 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址
if(i2c_WaitAck()!=0)//第5步:判断设备是否响应
{
goto res_error;
}
}
i2c_SendByte(_pWriteBuf[i]);//第6步:开始写入数据
if(i2c_WaitAck()!=0)//第7步:判断设备是否响应
{
goto res_error;
}
Addr++;
}
i2c_Stop();//第8步:停止I2C总线
return 1;//写入成功
res_error:
printf("\r\n响应超时\r\n");
return 0;//写入失败
}
/* 擦除EEPROM */
void ee_Erase(void)
{
uint16_t i;
uint8_t buf[EEPROM_SIZE];
for (i = 0; i < EEPROM_SIZE; i++)// 填充缓冲区
{
buf[i] = 0xFF;
}
if (ee_WriteBytes(buf, 0, EEPROM_SIZE) == 0)//写EEPROM, 起始地址 = 0,数据长度为 256
{
printf("擦除eeprom出错!\r\n");
}
else
{
printf("擦除eeprom成功!\r\n");
}
}
/* 测试程序 */
uint8_t ee_Test(void)
{
uint16_t i;
uint8_t write_buf[EEPROM_SIZE];
uint8_t read_buf[EEPROM_SIZE];
if (ee_CheckOk() == 0)// 没有检测到EEPROM
{
printf("没有检测到串行EEPROM!\r\n");
return 0;
}
for (i = 0; i < EEPROM_SIZE; i++)//填充测试缓冲区
{
write_buf[i] = i;
}
if (ee_WriteBytes(write_buf, 0, EEPROM_SIZE) == 0)//向EEPROM写入数据
{
printf("写eeprom出错!\r\n");
return 0;
}
else
{
printf("写eeprom成功!\r\n");
}
HAL_Delay(1);//写完之后需要适当的延时再去读,不然会出错
if (ee_ReadBytes(read_buf, 0, EEPROM_SIZE) == 0)//从EEPROM读取数据
{
printf("读eeprom出错!\r\n");
return 0;
}
else
{
printf("读eeprom成功,数据如下:\r\n");
}
for (i = 0; i < EEPROM_SIZE; i++)//比较读取和写入的数据是否一致
{
if(read_buf[i] != write_buf[i])
{
printf("0x%02X ", read_buf[i]);
printf("错误:EEPROM读出与写入的数据不一致");
return 0;
}
printf(" %02X", read_buf[i]);
if ((i & 15) == 15)
{
printf("\r\n");
}
}
printf("eeprom读写测试成功\r\n");
return 1;
}
3、在主函数中调用i2c_ee.c中的测试函数即可。
ee_Test();
关于软件IIC有几点需要注意的:
1、我们传输速率的设置:硬件IIC,标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。这里使用软件IIC的设置SCL频率200kbit/s左右,大家可以根据需要设置gpio.c中i2c_Delay(void)的延时时间来修改SCL频率。
2、在i2c_ee.c 写ee_ReadBytes、ee_WriteBytes,可以发现步骤有一点区别,ee_WriteBytes在多了一个for循环来重复判断设备内的写操作是否完成,而不是直接采用判断一次。虽然后者的写法在实验结果上也没有出错,但是为了代码严谨性,避免不必要的错误,选用前者。
3、在写入数据到EEPROM的时候,根据数据的大小,如果需要读写的话,需要进行一定的延时,否则会错。
—— —— —— —— —— —— —— ——硬件IIC—— —— —— —— —— —— —— ——
二、 CubeMX配置
step1-step4与上面软件IIC模式一致
step5.IIC外设配置,这里选用的I2C1,因为使用的主机模式,所以从机参数默认即可。
到这里关于硬件IIC参数配置基本已经完成,只需要根据之前文章《STM32Cube HAL:GPIO输入/输出(一)》Step4-Step8,设置相关工程参数和生成代码。
三、添加功能代码
1、我们等会会向串口调试助手发送数据,进行实验结果的验证。 发送数据我们采用printf函数,所有需要重定向c库函数printf到串口。注意使用时需要在keil设置中勾选微库(use mircolib),同时需要添加头文件#include <stdio.h>。(代码同上: 软件IIC)
2、在i2c.h文件中添加相关的宏定义以及函数声明。
#define EEPROM_ADDRESS 0xA0 // 24xx02的设备地址
#define EEPROM_PAGESIZE 8 // 24xx02的页面大小
#define DATA_Size 7//256
#define EEP_Firstpage 0x02 // 0x00
void MX_I2C1_Init(void);
uint8_t I2C_Test(void);
void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite);
uint32_t I2C_EE_ByteWrite(uint8_t* pBuffer, uint8_t WriteAddr);
uint32_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint8_t NumByteToWrite);
uint32_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr, uint16_t NumByteToRead);
3、在i2c.c实现EEPROM的读写函数,以及用于测试的测试函数。
/*将缓冲区中的数据写到I2C EEPROM中*/
void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite)
{
uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
Addr = WriteAddr % EEPROM_PAGESIZE;//判断写入的首地址是否与EEPROM页的首地址对齐,0为对齐
count = EEPROM_PAGESIZE - Addr;//计算从写入的首地址需要写多少数据才能填满当前页
NumOfPage = NumByteToWrite / EEPROM_PAGESIZE;//计算写入数据需要写几个完整页(地址对齐的情况)
NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;//计算写完完整页剩下的数据个数(地址对齐的情况)
if(Addr == 0) //判断写入的首地址是否与页地址对齐
{
if(NumOfPage == 0) //如果页对齐,判断数据是否不满一页
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//如果不满一页,直接写入数据
}
else //在数据满一页的情况下,通过地址自增方式,循环写入数据(页写入的形式)
{
while(NumOfPage--)//循环写入数据:先写入完整页
{
I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE);
WriteAddr += EEPROM_PAGESIZE;
pBuffer += EEPROM_PAGESIZE;
}
if(NumOfSingle!=0)//循环写入数据:再写入不满一页的数据
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
}
else
{
if(NumOfPage== 0) //如果页不对齐,判断数据是否不满一页
{
if(NumOfSingle<=count)//如果不满一页,判断数据是否跨页
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//如果不跨页,直接写入数据
}
else
{
I2C_EE_PageWrite(pBuffer, WriteAddr, count);//如果跨页,先写首页数据,再写次页数据
I2C_EE_PageWrite(pBuffer+count, WriteAddr+count, NumOfSingle-count);
}
}
else
{
/*如果数据满一页,对数据进行分离*/
NumByteToWrite -= count;//扣除第一页数据个数
NumOfPage = NumByteToWrite / EEPROM_PAGESIZE;//计算写入数据需要写几个完整页
NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE; //计算写完完整页剩下的数据个数
if(count != 0)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, count);//写入首页数据
WriteAddr += count;//写地址自增
pBuffer += count;//缓冲区指针自增
}
while(NumOfPage--)//依次写入完整页的数据
{
I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE);
WriteAddr += EEPROM_PAGESIZE;//写地址自增
pBuffer += EEPROM_PAGESIZE; //缓冲区指针自增
}
if(NumOfSingle != 0)//判断最后一页的数据是否是填满完整一页的
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle); //写入最后一页的数据
}
}
}
}
/*写一个字节到I2C EEPROM中*/
uint32_t I2C_EE_ByteWrite(uint8_t* pBuffer, uint8_t WriteAddr)
{
HAL_StatusTypeDef status = HAL_OK;
status = HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDRESS, (uint16_t)WriteAddr, I2C_MEMADD_SIZE_8BIT, pBuffer, 1, 100);
/* 检测IIC外设是否传输完成,并准备进行下一次通信 */
if(status != HAL_OK)
{
/* Execute user timeout callback */
}
while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
{
}
/* 检测目标设备是否准备好进行通信(即目标设备是否在进行读写操作)*/
while (HAL_I2C_IsDeviceReady(&hi2c1, EEPROM_ADDRESS, 300, 300) == HAL_TIMEOUT);
// while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
// {
//
// }
return status;
}
/*在EEPROM的一个写循环中可以写多个字节,但一次写入
的字节数不能超过EEPROM页的大小,AT24C02每页有8个字节*/
uint32_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint8_t NumByteToWrite)
{
HAL_StatusTypeDef status = HAL_OK;
status=HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDRESS,WriteAddr, I2C_MEMADD_SIZE_8BIT, (uint8_t*)(pBuffer),NumByteToWrite, 100);
/* 检测IIC外设是否传输完成,并准备进行下一次通信 */
while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
{
}
/* 检测目标设备是否准备好进行通信(即目标设备是否在进行读写操作)*/
while (HAL_I2C_IsDeviceReady(&hi2c1, EEPROM_ADDRESS, 300, 300) == HAL_TIMEOUT);
// while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
// {
//
// }
return status;
}
/*从EEPROM里面读取一块数据 */
uint32_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr, uint16_t NumByteToRead)
{
HAL_StatusTypeDef status = HAL_OK;
status=HAL_I2C_Mem_Read(&hi2c1,EEPROM_ADDRESS,ReadAddr, I2C_MEMADD_SIZE_8BIT, (uint8_t *)pBuffer, NumByteToRead,1000);
return status;
}
uint8_t I2C_Test(void)
{
uint16_t i;
printf("写入的数据");
for ( i=0; i<DATA_Size; i++ ) //填充缓冲
{
I2c_Buf_Write[i] =i;
printf("0x%02X ", I2c_Buf_Write[i]);
if(i%16 == 15)
printf("\n\r");
}
//将I2c_Buf_Write中顺序递增的数据写入EERPOM中
I2C_EE_BufferWrite( I2c_Buf_Write, EEP_Firstpage, DATA_Size);
printf("读出的数据");
//将EEPROM读出数据顺序保持到I2c_Buf_Read中
I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, DATA_Size);
//将I2c_Buf_Read中的数据通过串口打印
for (i=0; i<DATA_Size; i++)
{
if(I2c_Buf_Read[i] != I2c_Buf_Write[i])
{
printf("0x%02X ", I2c_Buf_Read[i]);
printf("错误:I2C EEPROM写入与读出的数据不一致");
return 0;
}
printf("0x%02X ", I2c_Buf_Read[i]);
if(i%16 == 15)
printf("\n\r");
}
printf("I2C(AT24C02)读写测试成功");
return 1;
}
4、在主函数中添加测试代码验证功能是否正常。
I2C_Test();
以上程序主要参考野火的硬件IIC程序,但是在页写入程序中添加了一个判断,用于判断在地址不对齐,且数据不满一页的情况下,数据是否跨页。数据不跨页直接写入数据,跨页分两次写入数据。代码如下:
if(NumOfPage== 0) //如果页不对齐,判断数据是否不满一页
{
if(NumOfSingle<=count)//如果不满一页,判断数据是否跨页
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//如果不跨页,直接写入数据
}
else
{
I2C_EE_PageWrite(pBuffer, WriteAddr, count);//如果跨页,先写首页数据,再写次页数据
I2C_EE_PageWrite(pBuffer+count, WriteAddr+count, NumOfSingle-count);
}
}
同时需要注意的EEPROM的几个关键特性:(AT24C02)
在写数据的过程中,每成功写入一个字节,E2PROM存储空间的地址就会自动加1,当加到0xFF后,再写一个字节,地址就会溢出又变成0x00。
还有需要有一个页的概念,页写入的时候,写入的数据有跨页现象,需要进行数据分离处理,否则数据会重复覆盖当前页(发送的内存地址所在页),而不会到下一页,这也是我们代码中进行页写入的时候提前处理数据的原因。
写数据的时候需要注意,E2PROM是先写到缓冲区,然后再“搬运到”到掉电非易失区。所以这个过程需要一定的时间,AT24C02这个过程是不超过5ms。
这里总结软件IIC和硬件IIC的优缺点:
1、软件IIC可灵活选用引脚;硬件IIC引脚固定。
2、软件IIC需要通过代码模拟时序以及读写;硬件IIC只需调用库函数。
3、硬件IIC效率更高,可以使用DMA ,中断。
4、软件IIC代码相对较多,但是流程更清晰;硬件IIC由于使用很多封装函数,处理的过程不够直观,不易理解。