015 - STM32学习笔记 - I2C访问存储器(二)
1、完善I2C读写EEPROM
在上节内容中,学习了EEPROM的读写,我用的F429中EEPROM型号为:AT24C02,其容量为256*8=2Kb,这节学习一下如何对EEPROM进行更深层次的读写。
在上节的程序中,向EEPROM写入数据是一个byte写入,在读出一个byte,如果要写入大量数据,就得反复去调用EPROM_Byte_Write
和EEPROM_Byte_Read(uint8_t addr)
函数,这样就需要不停的等待响应,耗时又耗力。因此本节实现多字节读写和页写入。
页写入,在AT24C02中,按照8byte分页,可以分为256页,因此一页可以写入8个byte,实现如下:
/** @brief 向EEPROM里面写入一块数据* @param* @arg pBuffer:存放向EEPROM写入的数据的缓冲区指针* @arg ReadAddr:写入EEPROM的起始地址* @arg NumByteToRead:要写入的字节数* @retval 返回结果*/
uint8_t EEPROM_Page_Write(u8* pBuffer, u8 WriteAddr,u8 NumByteToWrite)
{/*设置超时等待时间*/I2CTimeout = I2CT_FLAG_TIMEOUT;//检测I2C总线状态,获取while(I2C_GetFlagStatus(EEPROM_I2C, I2C_FLAG_BUSY)) {if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(13);}/*设置超时等待时间*/I2CTimeout = I2CT_FLAG_TIMEOUT;/*-----------------------1 产生开始信号 ----------------------------*/I2C_GenerateSTART(EEPROM_I2C,ENABLE); /*-----------------------2 等待EV5事件 ----------------------------*/while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS){if((I2CTimeout--) == 0)return I2C_TIMEOUT_UserCallback(14);}/*设置超时等待时间*/I2CTimeout = I2CT_FLAG_TIMEOUT;/*-----------------------3 发送要访问的设备地址 ----------------------------*/I2C_Send7bitAddress(EEPROM_I2C,EEPROM_ADDR,I2C_Direction_Transmitter);/*-----------------------4 等待EV6事件 ----------------------------*/while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS){if((I2CTimeout--) == 0)return I2C_TIMEOUT_UserCallback(15);}/*设置超时等待时间*/I2CTimeout = I2CT_FLAG_TIMEOUT;/*------5 发送要写入的EEPROM内部地址,即EEPROM内部存储器地址 -----*/I2C_SendData(EEPROM_I2C,WriteAddr);/*-----------------------6 等待EV8事件 ----------------------------*/while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS){if((I2CTimeout--) == 0)return I2C_TIMEOUT_UserCallback(16);}while(NumByteToWrite--){/*设置超时等待时间*/I2CTimeout = I2CT_FLAG_TIMEOUT;/*-----------------------7 发送数据 ----------------------------*/I2C_SendData(EEPROM_I2C,*pBuffer);/*-----------------------8 等待EV8事件 ----------------------------*/while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS){if((I2CTimeout--) == 0)return I2C_TIMEOUT_UserCallback(17);}pBuffer++;}/*-----------------------9 发送停止信号 ----------------------------*/I2C_GenerateSTOP(EEPROM_I2C,ENABLE);return 1;
}
按页写入的实现,和之前EPROM_Byte_Write
基本类似,区别在于:
1、函数参数需要传入写入的首地址、写入数据的首地址以及写入数据的数量;
2、在发送数据的时候,通过while
循环来判断数据是否发完;
3、每次发完数据后,指针递增,数量递减。
利用页写入的功能,可以对多字节写入的函数进行改进,加快传输速度,情况比较多,程序里面有详细注释,各位看官可以看一下,不懂的话可以留言一起讨论,实现如下:
/** @brief 向EEPROM里面写入一组数据* @param* @arg pBuffer:存放向EEPROM写入的数据的缓冲区指针* @arg WriteAddr:写入EEPROM的首地址* @arg NumByteToWrite:要向EEPROM写入的字节数* @retval 无返回结果*/
void EEPROM_Buffer_Write(u8* pBuffer, u8 WriteAddr,u16 NumByteToWrite)
{u8 NumOfPage=0,NumOfSingle=0,Addr =0,count=0,temp =0;/* I2C_PageSize为宏定义,AT24C02每页有8个字节*/Addr = WriteAddr % I2C_PageSize; /* 若writeAddr是I2C_PageSize整数倍,运算结果Addr值为0,表示写入地址与页首地址对齐 */NumOfPage = NumByteToWrite / I2C_PageSize; /* 计算需要写入多少页 */NumOfSingle = NumByteToWrite % I2C_PageSize; /* 计算写入整页后,剩余多少字节,若写入数据量为页容量的倍数,且首地址与页首地址对齐,则NumOfSingle为0 */count = I2C_PageSize - Addr; /* 差count个数据值,刚好可以对齐到页地址 *//* 多字节写入分以下几种情况:1、写入首地址与EEPROM页首地址对齐:a、写入数据≤8个byte;c、写入数据 = (n * 8) + m(n为页数,m为不满1页数据量,m < 8)2、写入首地址与EEPROM页首地址不对齐:a、写入数据 ≤ 8个byte(一页内可写完);b、写入数据 = x+(n * 8) + y byte(x为前端不满一页数据量,x=0时,字节对齐,n为页数,y为后端不满一页数据量)*/if(Addr == 0) /* 如果写入地址与页首地址对齐 */{if (NumOfPage == 0) /* 若写入数据刚好一页或不满一页 */{EEPROM_Page_Write(pBuffer, WriteAddr, NumOfSingle);WaitEEpromStandbyState();}else {while (NumOfPage--) /* 若写入数据大于一页,则按页写入 */{EEPROM_Page_Write(pBuffer, WriteAddr,I2C_PageSize);WaitEEpromStandbyState();WriteAddr += I2C_PageSize;pBuffer += I2C_PageSize;}if (NumOfSingle!=0) /* 若有多余,但不满一页的数据 */{EEPROM_Page_Write(pBuffer, WriteAddr, NumOfSingle);WaitEEpromStandbyState();}}}else /* 若写入地址与页首地址不对齐 */{if (NumOfPage== 0) /* 若写入数据不满一页 */{if (NumOfSingle > count) /* 数据不满一页,但横跨两页 */{ temp = NumOfSingle - count; /* 先将不满一页,但首地址与页首地址不对齐的数据写入 */EEPROM_Page_Write(pBuffer, WriteAddr, count);WaitEEpromStandbyState(); /* 等待写入完成 */WriteAddr += count; /* 这里计算出来下一页的起始地址 */pBuffer += count; /* pBuffer地址后移count */EEPROM_Page_Write(pBuffer, WriteAddr, temp); /* 写入下一页数据(剩余数据) */WaitEEpromStandbyState(); /* 等待写入完成 */}else /* 如果本页可写完,直接写入即可 */{EEPROM_Page_Write(pBuffer, WriteAddr, NumByteToWrite);WaitEEpromStandbyState();}}else /* 如果要写入的数据超过页大小 */{NumByteToWrite -= count; /* 这里计算的是地址不对齐的情况下,前面多出的数据数量 */NumOfPage = NumByteToWrite / I2C_PageSize; /* 计算除了前部多出的数据外,需要的页数 */NumOfSingle = NumByteToWrite % I2C_PageSize; /* 计算除了前部多出的数据外,后端不足一页的数据量,若后端数据量为整页,则此结果为0 */if (count != 0){EEPROM_Page_Write(pBuffer, WriteAddr, count); /* 写入前端多余的数据 */WaitEEpromStandbyState();WriteAddr += count; /* 写入地址加上前端多余的数据量,就是下一页的首地址 */pBuffer += count;}while (NumOfPage--) /* 这里就是写中间满页的数据 */{EEPROM_Page_Write(pBuffer, WriteAddr, I2C_PageSize);WaitEEpromStandbyState();WriteAddr += I2C_PageSize; /* 地址按页增加 */pBuffer += I2C_PageSize;}if (NumOfSingle != 0) /* 写入后端不足一页的数据 */{EEPROM_Page_Write(pBuffer, WriteAddr, NumOfSingle);WaitEEpromStandbyState();}}}
}
在快速写入的过程中使用到了WaitEEpromStandbyState
函数,其作用是为了等待每次写入数据后,EEPROM回到准备状态,否则可能连续写入的时候,前面的数据刚写入,还没等EEPROM处理完,后面的数据就来了,这样容易写入失败。
static void WaitEEpromStandbyState(void)
{__IO uint16_t SR1_Tmp = 0;do{I2C_GenerateSTART(EEPROM_I2C, ENABLE); /* 发送起始信号 */SR1_Tmp = I2C_ReadRegister(EEPROM_I2C, I2C_Register_SR1); /* 读 I2C1 SR1 寄存器 */I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDR,I2C_Direction_Transmitter); /* 发送 EEPROM 地址 + 写方向 */}while (!(I2C_ReadRegister(EEPROM_I2C, I2C_Register_SR1) & 0x0002)); /* 等待地址发送成功 */I2C_ClearFlag(EEPROM_I2C, I2C_FLAG_AF); /* 清除 AF 位 */I2C_GenerateSTOP(EEPROM_I2C, ENABLE); /* 发送停止信号 */
}
再看一下多字节读取,和EEPROM_Page_Write
类似,同样是通过EEPROM_Buffer_Read
更改后完成的,实现如下:
/** @brief 从EEPROM里面读取一块数据* @param* @arg pBuffer:存放EEPROM读取的数据的缓冲区指针* @arg ReadAddr:读取EEPROM的起始地址* @arg NumByteToRead:要从EEPROM读取的字节数* @retval 返回结果*/
uint32_t EEPROM_Buffer_Read(u8* pBuffer,u8 ReadAddr,u16 NumByteToRead)
{I2CTimeout = I2CT_FLAG_TIMEOUT; /* 设置超时等待时间 */while(I2C_GetFlagStatus(EEPROM_I2C, I2C_FLAG_BUSY)) /* 检测I2C总线状态 */{if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(18);}I2CTimeout = I2CT_FLAG_TIMEOUT; /* 设置超时等待时间 *//*-----------------------1 产生开始信号 ----------------------------*/I2C_GenerateSTART(EEPROM_I2C,ENABLE); /*-----------------------2 等待EV5事件 ----------------------------*/while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS){if((I2CTimeout--) == 0)return I2C_TIMEOUT_UserCallback(19);}I2CTimeout = I2CT_FLAG_TIMEOUT; /* 设置超时等待时间 *//*-----------------------3 发送要访问的设备地址 ----------------------------*/I2C_Send7bitAddress(EEPROM_I2C,EEPROM_ADDR,I2C_Direction_Transmitter);/*-----------------------4 等待EV6事件 ----------------------------*/while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS){if((I2CTimeout--) == 0)return I2C_TIMEOUT_UserCallback(20);}I2CTimeout = I2CT_FLAG_TIMEOUT; /* 设置超时等待时间 *//*------5 发送要写入的EEPROM内部地址,即EEPROM内部存储器地址 -----*/I2C_SendData(EEPROM_I2C,ReadAddr);/*-----------------------6 等待EV8事件 ----------------------------*/while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS){if((I2CTimeout--) == 0)return I2C_TIMEOUT_UserCallback(21);}/****************************************************************************************/I2CTimeout = I2CT_FLAG_TIMEOUT; /* 设置超时等待时间 *//*-----------------------7 产生第二次开始信号 ----------------------------*/I2C_GenerateSTART(EEPROM_I2C,ENABLE); /*-----------------------8 等待EV5事件 ----------------------------*/while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS){if((I2CTimeout--) == 0)return I2C_TIMEOUT_UserCallback(22);}/*----------------------- 发送要访问的设备地址:发送read方向 ----------------------------*/I2C_Send7bitAddress(EEPROM_I2C,EEPROM_ADDR,I2C_Direction_Receiver);/*-----------------------9 等待EV6事件 ----------------------------*/while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS){if((I2CTimeout--) == 0)return I2C_TIMEOUT_UserCallback(23);}while(NumByteToRead){if(NumByteToRead == 1){I2C_AcknowledgeConfig(EEPROM_I2C,DISABLE); /* 关闭响应 */I2C_GenerateSTOP(EEPROM_I2C,ENABLE);}I2CTimeout = I2CT_FLAG_TIMEOUT; /* 设置超时等待时间 *//*-----------------------11 等待EV7事件 ----------------------------*/while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS){if((I2CTimeout--) == 0)return I2C_TIMEOUT_UserCallback(24);}/*-----------------------10 读取数据 ----------------------------*/*pBuffer = I2C_ReceiveData(EEPROM_I2C);pBuffer ++;NumByteToRead --;}/*-----------------------12 发送停止信号 ----------------------------*/I2C_GenerateSTOP(EEPROM_I2C,ENABLE);I2C_AcknowledgeConfig(EEPROM_I2C,ENABLE); /* 使能响应,方便下一次操作 */
}
大致的内容就是这些了,下面呈上测试程序:
#include "stm32f4xx.h"
#include "bsp_led.h"
#include "bsp_systick.h"
#include "bsp_usart_dma.h"
#include "bsp_i2c_ee.h"
#include <stdio.h>int main(void)
{uint16_t i = 0;u8 tmp = 0;u8 writeBuffer[256];u8 readBuffer[256];LED_Config();DEBUG_USART1_Config();SysTick_Init();printf("这是一个EEPROM测试实验\n");printf("\r\n初始化EEPROM\r\n");EEPROM_GPIO_Config();printf("\r\n初始化I2C模式配置\r\n");EEPROM_I2C_ModeConfig();EEPROM_INFO("待写入数据:\r\n");for(i = 0;i <= 255;i++) /* 向writeBuffer写入数据 */{writeBuffer[i] = i;printf("0x%02X ",writeBuffer[i]);if(i%16 == 15) /* 逢15换行 */printf("\n");}EEPROM_Buffer_Write(writeBuffer,0x00,256);EEPROM_INFO("I2C写入EEPROM成功!\r\n");EEPROM_Buffer_Read(readBuffer,0x00,256);EEPROM_INFO("读出数据:\r\n");for(i = 0;i <= 255;i++){printf("0x%02X ",readBuffer[i]);if(i%16 == 15)printf("\n");if(readBuffer[i] != writeBuffer[i]){tmp = 1;}}if(tmp == 0)EEPROM_INFO("I2C读取EEPROM成功\r\n");while(1){if(tmp){LED_R_TOGGLE;Delay_ms(1000);}else{LED_G_TOGGLE;Delay_ms(1000);}}
}
以上所有用到的bsp程序都是在之前的学习过程中慢慢写出来的,后面学习过程中也会不停的去积累,学习过的内容就不在此处重复占篇幅了,不懂得往前翻一翻看一下。
本文链接:https://my.lmcjl.com/post/13722.html
4 评论