EEPROM
bsp_i2c_1.h
#ifndef __BSP_I2C_1_H #define __BSP_I2C_1_H #include "stm32f4xx.h" #define I2C_1_MODE 0 //0:硬件I2C 1:软件I2C #if I2C_1_MODE == 0 //STM32 I2C 数据传输模式 #define I2C_1_STANDARD 100000 //I2C的标准传输模式,不得超过100kbps #define I2C_1_SPEEDINESS 400000 //I2C的快速传输模式,不得超过400kbps #define I2C_1_ADDRESS_7 0x0A //STM32自身I2C的地址 #define I2C_1 I2C1 //I2C #define I2C_1_CLK RCC_APB1Periph_I2C1 //I2C的时钟 #define I2C_1_CLOCKCMD RCC_APB1PeriphClockCmd //I2C外设的时钟启动函数 #define I2C_1_SCL_GPIO_PORT GPIOB // I2C端口 #define I2C_1_SCL_GPIO_PIN GPIO_Pin_6 //连接到SCL时钟线的GPIO #define I2C_1_SCL_GPIO_PinSource GPIO_PinSource6 //GPIO引脚源 #define I2C_1_SCL_GPIO_AF GPIO_AF_I2C1 //I2C1外设 #define I2C_1_SDA_GPIO_PORT GPIOB //I2C端口 #define I2C_1_SDA_GPIO_PIN GPIO_Pin_7 //连接到SDA数据线的GPIO #define I2C_1_SDA_GPIO_PinSource GPIO_PinSource7 //GPIO引脚源 #define I2C_1_SDA_GPIO_AF GPIO_AF_I2C1 //I2C1外设 #define I2C_1_GPIO_CLK RCC_AHB1Periph_GPIOB //GPIO端口时钟 #define I2C_1_GPIO_CLOCKCMD RCC_AHB1PeriphClockCmd //打开端口时钟的函数 void I2C_1_ConfigInit(void); #elif I2C_1_MODE == 1 //定义I2C总线连接的GPIO端口, 用户只需要修改下面4行代码即可任意改变SCL和SDA的引脚 #define I2C_1_SCL_GPIO_PORT GPIOB //I2C端口 #define I2C_1_SCL_GPIO_PIN GPIO_Pin_6 //连接到SCL时钟线的GPIO #define I2C_1_SDA_GPIO_PORT GPIOB //I2C端口 #define I2C_1_SDA_GPIO_PIN GPIO_Pin_7 //连接到SDA数据线的GPIO #define I2C_1_GPIO_CLK RCC_AHB1Periph_GPIOB //GPIO端口时钟 #define I2C_1_GPIO_CLOCKCMD RCC_AHB1PeriphClockCmd //打开端口时钟的函数 #define I2C_1_WR 0 //写控制bit #define I2C_1_RD 1 //读控制bit //定义读写SCL和SDA的宏,已增加代码的可移植性和可阅读性 #if 1 //条件编译: 1 选择GPIO的库函数实现IO读写 #define I2C_1_SCL_L() GPIO_ResetBits(I2C_1_SCL_GPIO_PORT, I2C_1_SCL_GPIO_PIN) #define I2C_1_SDA_L() GPIO_ResetBits(I2C_1_SDA_GPIO_PORT, I2C_1_SDA_GPIO_PIN) #define I2C_1_SCL_H() GPIO_SetBits(I2C_1_SCL_GPIO_PORT, I2C_1_SCL_GPIO_PIN) #define I2C_1_SDA_H() GPIO_SetBits(I2C_1_SDA_GPIO_PORT, I2C_1_SDA_GPIO_PIN) #define I2C_1_SDA_READ() GPIO_ReadInputDataBit(I2C_1_SDA_GPIO_PORT, I2C_1_SDA_GPIO_PIN) //读SDA口线状态 #else //这个分支选择直接寄存器操作实现IO读写 //注意:如下写法,在IAR最高级别优化时,会被编译器错误优化 #define I2C_1_SCL_L() I2C_1_SCL_GPIO_PORT->BRR = I2C_1_SCL_GPIO_PIN //SCL = 0 #define I2C_1_SDA_L() I2C_1_SDA_GPIO_PORT->BRR = I2C_1_SDA_GPIO_PIN //SDA = 0 #define I2C_1_SCL_H() I2C_1_SCL_GPIO_PORT->BSRR = I2C_1_SCL_GPIO_PIN //SCL = 1 #define I2C_1_SDA_H() I2C_1_SDA_GPIO_PORT->BSRR = I2C_1_SDA_GPIO_PIN //SDA = 1 #define I2C_1_SDA_READ() ((I2C_1_SDA_GPIO_PORT->IDR & I2C_1_SDA_GPIO_PIN) != 0) //读SDA口线状态 #endif void I2C_1_ConfigInit(void); void I2C_1_Start(void); void I2C_1_Stop(void); uint8_t I2C_1_WaitAck(void); void I2C_1_Ack(void); void I2C_1_NAck(void); void I2C_1_SendData(uint8_t Data); uint8_t I2C_1_ReceiveData(void); uint8_t I2C_1_CheckDevice(uint8_t Address); #endif #endif
bsp_i2c_1.c
#include "bsp_i2c_1.h" #if I2C_1_MODE == 0 /** * @brief 配置I2C总线的GPIO * @param 无 * @retval 无 */ static void I2C_1_GPIO_Config(void) { I2C_1_GPIO_CLOCKCMD(I2C_1_GPIO_CLK, ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_Pin = I2C_1_SCL_GPIO_PIN ; GPIO_Init(I2C_1_SCL_GPIO_PORT,&GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = I2C_1_SDA_GPIO_PIN; GPIO_Init(I2C_1_SDA_GPIO_PORT,&GPIO_InitStruct); } /** * @brief I2C 工作模式配置 * @param 无 * @retval 无 */ static void I2C_1_MODE_Config(void) { GPIO_PinAFConfig(I2C_1_SCL_GPIO_PORT, I2C_1_SCL_GPIO_PinSource, I2C_1_SCL_GPIO_AF); //连接PXx引脚到xxx外设 GPIO_PinAFConfig(I2C_1_SDA_GPIO_PORT, I2C_1_SDA_GPIO_PinSource, I2C_1_SDA_GPIO_AF); //连接PXx引脚到xxx外设 I2C_1_CLOCKCMD(I2C_1_CLK,ENABLE ); //使能与 I2C 有关的时钟 I2C_InitTypeDef I2C_InitStruct; I2C_InitStruct.I2C_OwnAddress1 =I2C_1_ADDRESS_7; //设置SM32自身的I2C地址 I2C_InitStruct.I2C_ClockSpeed = I2C_1_SPEEDINESS; //设置I2C的传输速率 I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; //I2C的使用方式,I2C在此处不需要区分主从模式,直接设置I2C_Mode_I2C I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; //I2C的SCL线时钟的占空比,这里要求一般不是太严格,随便选 I2C_InitStruct.I2C_Ack = I2C_Ack_Enable ; //此处一般使能,即在应答时发送响应信号 I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //本成员设置I2C的寻址模式是7位地址还是10位地址 I2C_Init(I2C_1,&I2C_InitStruct); //I2C模式初始化 I2C_Cmd(I2C_1,ENABLE); //使能 I2C I2C_AcknowledgeConfig(I2C_1,ENABLE); //使能I2C的应答功能 } /** * @brief I2C初始化 * @param 无 * @retval 无 */ void I2C_1_ConfigInit(void) { I2C_1_GPIO_Config(); I2C_1_MODE_Config(); } #elif I2C_1_MODE == 1 /** * @brief 配置I2C总线的GPIO * @param 无 * @retval 无 */ static void I2C_1_GPIO_Config(void) { I2C_1_GPIO_CLOCKCMD(I2C_1_GPIO_CLK, ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStruct.GPIO_Pin = I2C_1_SCL_GPIO_PIN ; GPIO_Init(I2C_1_SCL_GPIO_PORT, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = I2C_1_SDA_GPIO_PIN; GPIO_Init(I2C_1_SDA_GPIO_PORT, &GPIO_InitStruct); } /** * @brief I2C初始化 * @param 无 * @retval 无 */ void I2C_1_ConfigInit(void) { I2C_1_GPIO_Config(); I2C_1_Stop(); //给一个停止信号, 复位I2C总线上的所有设备到待机模式 } /** * @brief 配置I2C的SDA线为输入 * @param 无 * @retval 无 */ static void I2C_1_SDA_INTPUT(void) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN; //输入 GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; //浮空输入 GPIO_InitStruct.GPIO_Pin = I2C_1_SDA_GPIO_PIN; GPIO_Init(I2C_1_SDA_GPIO_PORT, &GPIO_InitStruct); } /** * @brief 配置I2C的SDA线为输出 * @param 无 * @retval 无 */ static void I2C_1_SDA_OUTPUT(void) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //输出 GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; //开漏输出 GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStruct.GPIO_Pin = I2C_1_SDA_GPIO_PIN; GPIO_Init(I2C_1_SDA_GPIO_PORT, &GPIO_InitStruct); } /** * @brief I2C总线位延迟,最快400KHz * @param 无 * @retval 无 */ static void I2C_1_Delay(void) { for (uint32_t i=0;i<30;i++); //上面的时间是通过逻辑分析仪测试得到的 //工作条件:F4的CPU主频为180MHz ,MDK编译环境,1级优化 //循环次数为20~250时都能通讯正常 } /** * @brief CPU发起I2C总线启动信号 * @param 无 * @retval 无 */ void I2C_1_Start(void) { //当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 I2C_1_SDA_OUTPUT(); I2C_1_SDA_H(); I2C_1_SCL_H(); I2C_1_Delay(); I2C_1_SDA_L(); I2C_1_Delay(); I2C_1_SCL_L(); I2C_1_Delay(); } /** * @brief CPU发起I2C总线停止信号 * @param 无 * @retval 无 */ void I2C_1_Stop(void) { //当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 I2C_1_SDA_OUTPUT(); I2C_1_SCL_L(); I2C_1_SDA_L(); I2C_1_Delay(); I2C_1_SCL_H(); I2C_1_Delay(); I2C_1_SDA_H(); I2C_1_Delay(); } /** * @brief CPU产生一个时钟,并读取器件的ACK应答信号 * @param 无 * @retval 返回0表示正确应答,1表示无器件响应 */ uint8_t I2C_1_WaitAck(void) { uint8_t re; //应答信号 0:应答 1:非应答 I2C_1_SCL_L(); I2C_1_SDA_H(); //CPU释放SDA总线 I2C_1_Delay(); I2C_1_SCL_H(); //CPU驱动SCL = 1, 此时器件会返回ACK应答 I2C_1_Delay(); I2C_1_SDA_INTPUT(); while (I2C_1_SDA_READ()) { re++; if (re > 250) { I2C_1_Stop(); return 1; } } I2C_1_SCL_L(); I2C_1_Delay(); return 0; } /** * @brief CPU产生一个ACK应答信号,SDA低电平 * @param 无 * @retval 无 */ void I2C_1_Ack(void) { I2C_1_SDA_OUTPUT(); I2C_1_SCL_L(); I2C_1_SDA_L(); //CPU驱动SDA = 0 I2C_1_Delay(); I2C_1_SCL_H(); //CPU产生1个时钟 I2C_1_Delay(); I2C_1_SCL_L(); I2C_1_Delay(); I2C_1_SDA_H(); //CPU释放SDA总线 I2C_1_Delay(); } /** * @brief CPU产生1个NACK非应答信号,SDA高电平 * @param 无 * @retval 无 */ void I2C_1_NAck(void) { I2C_1_SDA_OUTPUT(); I2C_1_SCL_L(); I2C_1_SDA_H(); //CPU驱动SDA = 1 I2C_1_Delay(); I2C_1_SCL_H(); //CPU产生1个时钟 I2C_1_Delay(); I2C_1_SCL_L(); I2C_1_Delay(); I2C_1_SDA_H(); //CPU释放SDA总线 I2C_1_Delay(); } /** * @brief CPU向I2C总线设备写8bit数据 * @param Data :要发送的数据 * @retval 无 */ void I2C_1_SendData(uint8_t Data) { uint8_t temp = 0; I2C_1_SDA_OUTPUT(); I2C_1_SCL_L(); for (uint8_t i=0;i<8;i++) //先发送字节的高位bit7 { temp = (Data & 0x80) >> 7; if (temp == 0x01) //判断当前位是否为高电平 { I2C_1_SDA_H(); } else { I2C_1_SDA_L(); } I2C_1_Delay(); I2C_1_SCL_H(); //SCL处于高电平时SDA数据有效 I2C_1_Delay(); I2C_1_SCL_L(); //SCL处于低电平时SDA进行高低电平切换 Data <<= 1; if (i == 7) //最后一位数据发送完 { I2C_1_SDA_H(); //CPU释放SDA总线 } } } /** * @brief CPU从I2C总线设备读取8bit数据 * @param 无 * @retval 读到的数据 */ uint8_t I2C_1_ReceiveData(void) { uint8_t data=0; I2C_1_SDA_INTPUT(); I2C_1_SCL_L(); for (uint8_t i=0;i<8;i++) //读到第1个bit为数据的bit7 { data = data << 1; //移位放在读SDA数据前面,放后面的话读完数据后会向左移一位,数据出错 I2C_1_SCL_H(); //SCL处于高电平时读取SDA数据 I2C_1_Delay(); if (I2C_1_SDA_READ()) //读SDA口的状态,读数据 { data++; } I2C_1_Delay(); I2C_1_SCL_L(); I2C_1_Delay(); } return data; } /** * @brief 检测I2C总线设备,CPU向I2C发送设备地址,然后读取设备应答来判断该设备是否存在 * @param Address:设备的I2C总线地址 * @retval 返回值 0 表示有应答, 返回1表示未探测到 */ uint8_t I2C_1_CheckDevice(uint8_t Address) { uint8_t ack; I2C_1_Start(); //发送启动信号 I2C_1_SendData(Address | I2C_1_WR); //发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 ack = I2C_1_WaitAck(); //检测设备的ACK应答 ,0表示应答,1表示非应答 I2C_1_Stop(); //发送停止信号 return ack; } #endif
eeprom_drive_function.h
#ifndef __EEPROM_DRIVE_FUNCTION_H #define __EEPROM_DRIVE_FUNCTION_H #include "main.h" /*********************************EEPROM驱动******************************************/ //AT24C01/02 每页有8个字节 //AT24C04/08A/16A 每页有16个字节 //EEPROM AT24C02芯片的设备地址(I2C地址)一共有7位,其中高4位固定为1010b,低3位则由A0,A1,A2信号线的电平决定,还有一位R/W是 //用来决定EEPROM的读写方向的 // 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 读取的地址 //AT24C02 2kb = 2048bit = 2048/8 Byte = 256 Byte 32页,每个8字节 #define EEPROM_I2C I2C_1 //EEPROM对应的I2C外设 #define EEPROM_ADDRESS 0xA0 //EEPROM 24xx02设备的地址和STM32的读写方向(写),8位中的最后一位位读写方向 #define EEPROM_PAGE_SIZE 8 //EEPROM 24xx02的页面大小,按页写入,每页8字节 #define EEPROM_PAGE_NUM 32 //EEPROM 24xx02的页数 #define EEPROM_SIZE 256 //EEPROM 24xx02的总容量 /*EEPROM通信等待超时时间*/ #define EEPROM_SHORT_WAIT_TIMEOUT ((uint32_t)0x1000) #define EEPROM_LONG_WAIT_TIMEOUT ((uint32_t)(10 * EEPROM_SHORT_WAIT_TIMEOUT)) #if I2C_1_MODE == 0 uint8_t EEPROM_WriteByte(uint8_t Write_Addr, uint8_t Write_Data); uint8_t EEPROM_WriteBytes(uint8_t Write_Addr, uint32_t Write_Num, uint8_t* p_Write_Buffer); uint8_t EEPROM_WritePage(uint8_t Write_Addr, uint32_t Write_Num, uint8_t* p_Write_Buffer); void EEPROM_WriteBuffer(uint8_t Write_Addr, uint32_t Write_Num, uint8_t* p_Write_Buffer); uint8_t EEPROM_ReadBuffer(uint8_t Read_Addr, uint32_t Read_Num, uint8_t* p_Read_Buffer); #elif I2C_1_MODE == 1 #define EEPROM_I2C_Start I2C_1_Start #define EEPROM_I2C_Stop I2C_1_Stop #define EEPROM_I2C_SendData I2C_1_SendData #define EEPROM_I2C_ReceiveData I2C_1_ReceiveData #define EEPROM_I2C_WaitAck I2C_1_WaitAck #define EEPROM_I2C_Ack I2C_1_Ack #define EEPROM_I2C_NAck I2C_1_NAck #define EEPROM_I2C_CheckDevice I2C_1_CheckDevice uint8_t EEPROM_WriteBuffer(uint8_t Write_Addr, uint32_t Write_Num, uint8_t* p_Write_Buffer); uint8_t EEPROM_ReadBuffer(uint8_t Read_Addr, uint32_t Read_Num, uint8_t* p_Read_Buffer); uint8_t EEPROM_CheckOk(void); #endif /******************************************************************************************* 函数声明 *******************************************************************************************/ void EEPROM_Erasure(void); void EEPROM_Read_Data(void); uint8_t EEPROM_Char_Test(void); uint8_t EEPROM_Int_Double_Test(void); #endif
eeprom_drive_function.c
#include "eeprom_drive_function.h" /****************************************************************************************************************** EEPROM通信规则: EEPROM数据写入和擦除的最小单位是“字节”不是“页”,数据写入前不需要擦除整页。 EEPROM 的单字节时序规定,向它写入数据的时候,第一个字节为内存地址,第二个字节是要写入的数据内容。所以我们需 要理解:命令、地址的本质都是数据,对数据的解释不同,它就有了不同的功能。 单字节写入通讯结束后,EEPROM 芯片会根据这个通讯结果擦写该内存地址的内容,这需要一段时间,所以我们在单字节写 入多个数据时,要先等待 EEPROM 内部擦写完毕。 在以上的数据通讯中,每写入一个数据都需要向 EEPROM 发送写入的地址,我们希望向连续地址写入多个数据的时候,只要 告诉 EEPROM 第一个内存地址 address1,后面的数据按次序写入到 address2、 address3… 这样可以节省通讯的内容,加快速度。 为应对这种需求, EEPROM 定义了一种页写入时序,根据页写入时序,第一个数据被解释为要写入的内存地址 address1,后续可 连续发送n个数据data,这些数据会依次写入到内存中。其中AT24C02型号的芯片页写入时序最多可以一次发送8个数据(即n = 8 ), 该值也称为页大小,某些型号的芯片每个页写入时序最多可传输16 个数据。 从EEPROM读取数据是一个复合的I2C时序,它实际上包含一个写过程和一个读过程。读时序的第一个通讯过程中,使用 I2C 发 送设备地址寻址(写方向),接着发送要读取的“内存地址”;第二个通讯过程中,再次使用 I2C 发送设备地址寻址,但这个时候的数 据方向是读方向;在这个过程之后, EEPROM 会向主机返回从“内存地址”开始的数据,一个字节一个字节地传输,只要主机的响应 为“应答信号”,它就会一直传输下去,主机想结束传输时,就发送“非应答信号”,并以“停止信号”结束通讯,作为从机的EEPROM 也 会停止传输。 **********************************************************************************************************************/ /******************************************************************************************* EEPROM驱动函数封装 *******************************************************************************************/ #if I2C_1_MODE == 0 static __IO uint32_t eeprom_i2c_timeout = EEPROM_LONG_WAIT_TIMEOUT; static uint8_t eeprom_error_code=0; //错误代号 /** * @brief 打印EEPROM通信出错的地方 * @param errorCode:错误代码,可以用来定位是哪个环节出错. * @retval 返回1,表示IIC读取失败. */ static uint8_t EEPROM_ErrorCode(uint8_t Error_Code) { printf("I2C 等待超时!eeprom_error_code = %d\n",Error_Code); return 1; } /** * @brief 等待EEPROM处于准备状态(准备状态即可以读取或写入数据),该函数在连续写入多个数据时调用 * @param 无 * @retval 无 */ static void EEPROM_WaitEepromStandbyState(void) { vu16 sr1_tmp = 0; do { I2C_GenerateSTART(EEPROM_I2C, ENABLE); sr1_tmp = I2C_ReadRegister(EEPROM_I2C, I2C_Register_SR1); I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Transmitter); } while (!(I2C_ReadRegister(EEPROM_I2C, I2C_Register_SR1) & 0x0002)); I2C_ClearFlag(EEPROM_I2C, I2C_FLAG_AF); I2C_GenerateSTOP(EEPROM_I2C, ENABLE); } /** * @brief 写一个字节到EEPROM中,一次只能写一个数据到EEPROM中,每次写数据时都需要先发送要写入的地址 * @param Write_Addr:数据要写入的地址 * Write_Data:要写入的数据 * @retval 0:正常 其它:失败 */ uint8_t EEPROM_WriteByte(uint8_t Write_Addr, uint8_t Write_Data) { I2C_GenerateSTART(EEPROM_I2C, ENABLE); //产生I2C起始信号 eeprom_i2c_timeout = EEPROM_SHORT_WAIT_TIMEOUT; //设置超时等待时间 while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT)) //检测EV5事件并清除标志 { if ((eeprom_i2c_timeout--) == 0) { DBG_PRINTF("I2C和EEPROM通信超时!"); eeprom_error_code = 1; return EEPROM_ErrorCode(eeprom_error_code); } } I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Transmitter); //发送EEPROM设备地址 eeprom_i2c_timeout = EEPROM_SHORT_WAIT_TIMEOUT; while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) //检测EV6事件并清除标志 { if ((eeprom_i2c_timeout--) == 0) { DBG_PRINTF("I2C和EEPROM通信超时!"); eeprom_error_code = 2; return EEPROM_ErrorCode(eeprom_error_code); } } I2C_SendData(EEPROM_I2C, Write_Addr); //发送要写入的寄存器地址,该地址也属于数据 eeprom_i2c_timeout = EEPROM_SHORT_WAIT_TIMEOUT; while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) //检测EV8_2事件并清除标志 { if ((eeprom_i2c_timeout--) == 0) { DBG_PRINTF("I2C和EEPROM通信超时!"); eeprom_error_code = 3; return EEPROM_ErrorCode(eeprom_error_code); } } I2C_SendData(EEPROM_I2C, Write_Data); //发送要写入EEPROM的数据 eeprom_i2c_timeout = EEPROM_SHORT_WAIT_TIMEOUT; while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) //检测EV8_2事件并清除标志 { if ((eeprom_i2c_timeout--) == 0) { DBG_PRINTF("I2C和EEPROM通信超时!"); eeprom_error_code = 4; return EEPROM_ErrorCode(eeprom_error_code); } } I2C_GenerateSTOP(EEPROM_I2C, ENABLE); //产生结束信号 return 0; } /** * @brief 该函数可以在一个写循环中可以写多个字节,这是采用单字节的方式,速度比页写入慢 ,每次写入数据前都需要 * 调用EEPROM_WaitEepromStandbyState()函数来等待EEPROM内部擦写完毕,即上一次的数据写入完毕 * @param * @arg Write_Addr:要写入的地址 * @arg Write_Num:写的字节数 * @arg p_Write_Buffer:缓冲区指针 * @retval 0:正常 其它:失败 */ uint8_t EEPROM_WriteBytes(uint8_t Write_Addr, uint32_t Write_Num, uint8_t* p_Write_Buffer) { for (uint32_t i=0;i<Write_Num;i++) { EEPROM_WaitEepromStandbyState(); //等待EEPROM处于空闲状态 EEPROM_WriteByte(Write_Addr,*p_Write_Buffer); p_Write_Buffer++; } return 0; } /** * @brief 该函数可以在EEPROM的一个写循环中可以写多个字节,但一次写入的字节数不能超过EEPROM页的大小,AT24C02每页只有8个字节 * @param * @arg Write_Addr:要写入的地址 * @arg Write_Num:写的字节数(使用该函数时,每次写入的字节数要小于等于EEPROM时序规定的页大小,才能正常传输) * @arg p_Write_Buffer:缓冲区指针 * @retval 0:正常 1:失败 */ uint8_t EEPROM_WritePage(uint8_t Write_Addr, uint32_t Write_Num, uint8_t* p_Write_Buffer) { eeprom_i2c_timeout = EEPROM_LONG_WAIT_TIMEOUT; //设置超时等待时间 while (I2C_GetFlagStatus(EEPROM_I2C, I2C_FLAG_BUSY)) //检查I2C是否设置了指定的标志 { if ((eeprom_i2c_timeout--) == 0) { DBG_PRINTF("I2C和EEPROM通信超时!"); eeprom_error_code = 5; return EEPROM_ErrorCode(eeprom_error_code); } } I2C_GenerateSTART(EEPROM_I2C, ENABLE); //产生I2C起始信号 eeprom_i2c_timeout = EEPROM_SHORT_WAIT_TIMEOUT; while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT)) //检测EV5事件并清除标志 { if ((eeprom_i2c_timeout--) == 0) { DBG_PRINTF("I2C和EEPROM通信超时!"); eeprom_error_code = 6; return EEPROM_ErrorCode(eeprom_error_code); } } I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Transmitter); //发送从机地址 eeprom_i2c_timeout = EEPROM_SHORT_WAIT_TIMEOUT; while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) //检测EV6事件并清除标志 { if ((eeprom_i2c_timeout--) == 0) { DBG_PRINTF("I2C和EEPROM通信超时!"); eeprom_error_code = 7; return EEPROM_ErrorCode(eeprom_error_code); } } I2C_SendData(EEPROM_I2C, Write_Addr); //发送要写入的EEPROM的内部地址 eeprom_i2c_timeout = EEPROM_SHORT_WAIT_TIMEOUT; while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) //检测EV8_2事件并清除标志 { if ((eeprom_i2c_timeout--) == 0) { DBG_PRINTF("I2C和EEPROM通信超时!"); eeprom_error_code = 8; return EEPROM_ErrorCode(eeprom_error_code); } } while (Write_Num--) { I2C_SendData(EEPROM_I2C, *p_Write_Buffer); //发送要写入EEPROM的数据 eeprom_i2c_timeout = EEPROM_SHORT_WAIT_TIMEOUT; while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) //检测EV8_2事件并清除标志 { if ((eeprom_i2c_timeout--) == 0) { DBG_PRINTF("I2C和EEPROM通信超时!"); eeprom_error_code = 9; return EEPROM_ErrorCode(eeprom_error_code); } } p_Write_Buffer++; } I2C_GenerateSTOP(EEPROM_I2C, ENABLE); //数据发送完才产生结束信号 return 0; } /** * @brief 该函数可以将缓冲区中的多个数据连续写到EEPROM中 * @param * @arg Write_Addr:要写入的地址 * @arg Write_Num:要写的字节数 * @arg p_Write_Buffer:要写入的数据 * @retval 无 */ void EEPROM_WriteBuffer(uint8_t Write_Addr, uint32_t Write_Num, uint8_t* p_Write_Buffer) { uint8_t WR_Addr = 0, Less_Num = 0, Whole_Page_Num = 0, Residue_Num = 0; WR_Addr = Write_Addr % EEPROM_PAGE_SIZE; //要写入的首地址是否为页的起始地址,WR_Addr=当前要写入的地址%页的大小 Less_Num = EEPROM_PAGE_SIZE - WR_Addr; //差Less_Num个字节,刚好可以对齐到页地址 Whole_Page_Num = Write_Num / EEPROM_PAGE_SIZE; //计算要写多少整数页 Residue_Num = Write_Num % EEPROM_PAGE_SIZE; //不满一页剩余的字节数 if (WR_Addr == 0) //要写入的首地址为页的起始地址 { if (Whole_Page_Num == 0) //不满一页或刚满一页 { EEPROM_WritePage(Write_Addr, Residue_Num, p_Write_Buffer); EEPROM_WaitEepromStandbyState(); } else //多页 { while (Whole_Page_Num--) { EEPROM_WritePage(Write_Addr, EEPROM_PAGE_SIZE, p_Write_Buffer); EEPROM_WaitEepromStandbyState(); Write_Addr += EEPROM_PAGE_SIZE; p_Write_Buffer += EEPROM_PAGE_SIZE; }//整页写完 if (Residue_Num!=0) //不满一页剩余的字节数 { EEPROM_WritePage(Write_Addr, Residue_Num, p_Write_Buffer); EEPROM_WaitEepromStandbyState(); } } } else //要写入的首地址不是页的起始地址 { if (Whole_Page_Num== 0) //不满一页 { EEPROM_WritePage(Write_Addr, Residue_Num, p_Write_Buffer); EEPROM_WaitEepromStandbyState(); } else//满一页 { Write_Num -= Less_Num; Whole_Page_Num = Write_Num / EEPROM_PAGE_SIZE; Residue_Num = Write_Num % EEPROM_PAGE_SIZE; if (Less_Num != 0)// { EEPROM_WritePage(Write_Addr, Less_Num, p_Write_Buffer); EEPROM_WaitEepromStandbyState(); Write_Addr += Less_Num; p_Write_Buffer += Less_Num; } while (Whole_Page_Num--)//整页数 { EEPROM_WritePage(Write_Addr, EEPROM_PAGE_SIZE, p_Write_Buffer); EEPROM_WaitEepromStandbyState(); Write_Addr += EEPROM_PAGE_SIZE; p_Write_Buffer += EEPROM_PAGE_SIZE; } if (Residue_Num != 0) { EEPROM_WritePage(Write_Addr, Residue_Num, p_Write_Buffer); EEPROM_WaitEepromStandbyState(); } } } } /********************************************************************************* 读数据 **********************************************************************************/ /** * @brief 从EEPROM里面读取一块数据 * @param * @arg Read_Addr:要读取的EEPROM的内部地址 * @arg Read_Num:要从EEPROM读取的字节数 * @arg p_Read_Buffer:存放从EEPROM读取的数据的缓冲区指针 * @retval 0:正常 1:失败 */ uint8_t EEPROM_ReadBuffer(uint8_t Read_Addr, uint32_t Read_Num, uint8_t* p_Read_Buffer) { eeprom_i2c_timeout = EEPROM_LONG_WAIT_TIMEOUT; //设置超时等待时间 while (I2C_GetFlagStatus(EEPROM_I2C, I2C_FLAG_BUSY)) //检查I2Cx是否设置了指定的标志 { if ((eeprom_i2c_timeout--) == 0) { DBG_PRINTF("I2C和EEPROM通信超时!"); eeprom_error_code = 10; return EEPROM_ErrorCode(eeprom_error_code); } } I2C_GenerateSTART(EEPROM_I2C, ENABLE); //产生I2C起始信号 eeprom_i2c_timeout = EEPROM_SHORT_WAIT_TIMEOUT; while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT)) { if ((eeprom_i2c_timeout--) == 0) { DBG_PRINTF("I2C和EEPROM通信超时!"); eeprom_error_code = 11; return EEPROM_ErrorCode(eeprom_error_code); } } I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Transmitter); //发送要读取的EEPROM设备地址 eeprom_i2c_timeout = EEPROM_SHORT_WAIT_TIMEOUT; while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { if ((eeprom_i2c_timeout--) == 0) { DBG_PRINTF("I2C和EEPROM通信超时!"); eeprom_error_code=12; return EEPROM_ErrorCode(eeprom_error_code); } } I2C_Cmd(EEPROM_I2C, ENABLE); //通过重新设置PE位清除EV6事件 I2C_SendData(EEPROM_I2C, Read_Addr); //发送要读取的EEPROM的内部地址 eeprom_i2c_timeout = EEPROM_SHORT_WAIT_TIMEOUT; while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { if ((eeprom_i2c_timeout--) == 0) { eeprom_error_code = 13; return EEPROM_ErrorCode(eeprom_error_code); } } I2C_GenerateSTART(EEPROM_I2C, ENABLE); //产生第二次I2C起始信号 eeprom_i2c_timeout = EEPROM_SHORT_WAIT_TIMEOUT; while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT)) { if ((eeprom_i2c_timeout--) == 0) { eeprom_error_code = 14; return EEPROM_ErrorCode(eeprom_error_code); } } I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Receiver); //发送要读取的EEPROM设备地址和读命令 eeprom_i2c_timeout = EEPROM_SHORT_WAIT_TIMEOUT; while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) { if ((eeprom_i2c_timeout--) == 0) { eeprom_error_code = 15; return EEPROM_ErrorCode(eeprom_error_code); } } while (Read_Num) { if (Read_Num == 1) //Read_Num == 1表示已经接收到最后一个数据了,发送非应答信号,结束传输 { I2C_AcknowledgeConfig(EEPROM_I2C, DISABLE); //发送非应答信号 I2C_GenerateSTOP(EEPROM_I2C, ENABLE); //发送停止信号 } eeprom_i2c_timeout = EEPROM_LONG_WAIT_TIMEOUT; while (I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_RECEIVED)==0) { if ((eeprom_i2c_timeout--) == 0) { eeprom_error_code = 16; return EEPROM_ErrorCode(eeprom_error_code); } } *p_Read_Buffer = I2C_ReceiveData(EEPROM_I2C); //从设备中读取一个字节的数据 p_Read_Buffer++; Read_Num--; } I2C_AcknowledgeConfig(EEPROM_I2C, ENABLE); //使能应答,方便下一次传输 return 0; } #elif I2C_1_MODE == 1 //软件I2C /** * @brief 向串行EEPROM指定地址写入若干数据,采用页写操作提高写入效率 * @param Write_Addr : 要写入EEPROM的起始地址 * Write_Num : 要写入的数据长度,单位为字节 * p_Write_Buffer : 要写入的数据 * @retval 0:表示成功,1表示失败 */ uint8_t EEPROM_WriteBuffer(uint8_t Write_Addr, uint32_t Write_Num, uint8_t* p_Write_Buffer) { //写串行EEPROM不像读操作可以连续读取很多字节,每次写操作只能在同一个page。 //对于24xx02,page size = 8 //简单的处理方法为:按字节写操作模式,每写1个字节,都发送地址 //为了提高连续写的效率: 本函数采用page wirte操作。 int m=0; uint16_t WR_Addr = Write_Addr; for (uint32_t i=0;i<Write_Num;i++) { if ((i == 0)||(WR_Addr & (EEPROM_PAGE_SIZE - 1)) == 0) //当发送第1个字节或者本次写入的是页面首地址时,需要重新发起启动信号和地址 { EEPROM_I2C_Stop(); //第0步:发停止信号,启动内部写操作 for (m=0;m<1000;m++)//通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms ,CLK频率为200KHz时,查询次数为30次左右 { EEPROM_I2C_Start(); //第1步:发起I2C总线启动信号 EEPROM_I2C_SendData(I2C_1_WR | EEPROM_ADDRESS);//第2步:发送设备地址和读写方向 if (EEPROM_I2C_WaitAck() == 0) //第3步:发送一个时钟,判断器件是否正确应答,正确应答退出循环 { break; } } if (m == 1000) { goto cmd_fail; //EEPROM器件写超时 } EEPROM_I2C_SendData((uint8_t)WR_Addr); //第4步:发送寄存器地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 if (EEPROM_I2C_WaitAck() != 0) //第5步:发送一个时钟,判断器件是否正确应答 { goto cmd_fail; //EEPROM器件无应答 } } EEPROM_I2C_SendData(p_Write_Buffer[i]); //第6步:开始写入数据 if (EEPROM_I2C_WaitAck() != 0) //第7步:发送一个时钟,判断器件是否正确应答 { goto cmd_fail; //EEPROM器件无应答 } WR_Addr++; //地址增1 } EEPROM_I2C_Stop(); //命令执行成功,发送I2C总线停止信号 return 0; cmd_fail: //命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 EEPROM_I2C_Stop(); //发送I2C总线停止信号 return 1; } /** * @brief 从指定的EEPROM地址处开始读取若干数据 * @param Read_Addr :要读取的数据的起始地址 * Read_Num : 要读取的数据长度,单位为字节 * p_Read_Buffer : 存放读到数据的缓冲区指针_ * @retval 0:表示成功,1:表示失败 */ uint8_t EEPROM_ReadBuffer(uint8_t Read_Addr, uint32_t Read_Num, uint8_t* p_Read_Buffer) { //采用串行EEPROM随即读取指令序列,连续读取若干字节 EEPROM_I2C_Start(); //第1步:发起I2C总线启动信号 EEPROM_I2C_SendData(I2C_1_WR | EEPROM_ADDRESS); //第2步:发送设备地址和读写方向 if (EEPROM_I2C_WaitAck() != 0) // 第3步:发送一个时钟,判断器件是否正确应答 { goto cmd_fail; //EEPROM器件无应答 } EEPROM_I2C_SendData((uint8_t)Read_Addr); //第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */ if (EEPROM_I2C_WaitAck() != 0) //第5步:发送一个时钟,判断器件是否正确应答 { goto cmd_fail; //EEPROM器件无应答 } EEPROM_I2C_Start(); //第6步:重新启动I2C总线。前面的代码的目的向EEPROM传送地址,下面开始读取数据 EEPROM_I2C_SendData(I2C_1_RD | EEPROM_ADDRESS); //第7步:发送设备地址和读写方向 if (EEPROM_I2C_WaitAck() != 0) //第8步:发送一个时钟,判断器件是否正确应答 { goto cmd_fail; //EEPROM器件无应答 } for (uint32_t i=0;i<Read_Num;i++) //第9步:循环读取数据 { p_Read_Buffer[i] = EEPROM_I2C_ReceiveData(); // 读1个字节 if (i != Read_Num - 1) //每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nack { EEPROM_I2C_Ack(); //中间字节读完后,CPU产生ACK信号(驱动SDA = 0) } else { EEPROM_I2C_NAck(); //最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) } } EEPROM_I2C_Stop(); //发送I2C总线停止信号 return 0; //执行成功 cmd_fail: //命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 EEPROM_I2C_Stop(); //发送I2C总线停止信号 return 1; //执行失败 } /** * @brief 检测EERPOM是否正常 * @param 无 * @retval 0:表示正常 1: 表示不正常 */ uint8_t EEPROM_CheckOk(void) { if (EEPROM_I2C_CheckDevice(EEPROM_ADDRESS) == 0) //有应答表示正常 { printf("EEPROM正常\n"); return 0; } else { printf("EEPROM异常\n"); EEPROM_I2C_Stop(); return 1; } } #endif /************************************************************************** 测试代码 **************************************************************************/ /** * @brief 简单延时函数 * @param 无 * @retval 无 */ static void EEPROM_Delay(__IO uint32_t Time) //简单的延时函数 { for (; Time != 0; Time--); } /** * @brief EEPROM全部擦除(写入0xFF) * @param 无 * @retval 无 */ void EEPROM_Erasure(void) { uint8_t data_buffer[EEPROM_SIZE]= {0}; for (uint32_t i=0;i<EEPROM_SIZE;i++) //填充缓冲区 { data_buffer[i] = 0xFF; } EEPROM_WriteBuffer(0,EEPROM_SIZE,data_buffer); //起始地址 = 0,数据长度为 256 } /** * @brief 开机时先读取EEPROM中的数据 * @param 无 * @retval 无 */ void EEPROM_ReadData(void) { uint8_t data_buffer[256]; if (EEPROM_ReadBuffer(0, EEPROM_SIZE, data_buffer) == 0) //读取数据 { for (uint32_t i=0;i<256;i++) { printf("data_buffer[%d] = %d,",i,data_buffer[i]); } } return ; } /** * @brief 单字节读写测试 * @param 无 * @retval 0::正常 1:异常 */ uint8_t EEPROM_CharTest(void) { uint8_t write_buf[EEPROM_SIZE]; uint8_t read_buf[EEPROM_SIZE]; //填充测试缓冲区 for (uint32_t i=0;i<EEPROM_SIZE;i++) { write_buf[i] = i; } EEPROM_WriteBuffer(0x00, EEPROM_SIZE, write_buf); //往EEPROM中写入数据 EEPROM_Delay(0x0FFFFF); //写完之后需要适当的延时再去读,不然会出错 if (EEPROM_ReadBuffer(0x00, EEPROM_SIZE, read_buf) == 0) //从EEPROM中读数据 { printf("读eeprom成功,数据如下:\r\n"); } else { printf("读eeprom出错!\r\n"); return 1; } for (uint32_t i=0;i<EEPROM_SIZE;i++) { if (read_buf[i] != write_buf[i]) { printf("0x%02X ", read_buf[i]); printf("错误:EEPROM读出与写入的数据不一致"); return 1; } printf(" %02X", read_buf[i]); if ((i & 15) == 15) { printf("\r\n"); } } printf("eeprom读写测试成功\r\n"); return 0; } /** * @brief 整数和小数读写测试 * @param 无 * @retval 0:正常 1:异常 */ uint8_t EEPROM_IntDoubleTest(void) { int write_buf_int[10]; int read_buf_int[10]; long double write_buf_double[10]; long double read_buf_double[10]; uint8_t int_addr=0x00; uint8_t double_addr=0x60; for (uint32_t i=0;i<10;i++) { write_buf_int[i]=500*i+3*i; write_buf_double[i] = i+i*0.2; } EEPROM_WriteBuffer(int_addr, sizeof(write_buf_int),(void *)write_buf_int); //往EEPROM中写入10个整形数据 EEPROM_WriteBuffer(double_addr, sizeof(write_buf_double),(void *)write_buf_double); //往EEPROM中写入10个小数数据 EEPROM_Delay(0x0FFFFF); //写完之后需要适当的延时再去读,不然会出错 if (EEPROM_ReadBuffer(int_addr, sizeof(read_buf_int),(void *)read_buf_int) == 0) //从EEPROM中读10个整形数据 { printf("读eeprom成功,数据如下:\r\n"); } else { printf("读eeprom出错!\r\n"); return 1; } for (uint32_t i=0;i<10;i++) { if (read_buf_int[i] != write_buf_int[i]) { printf("0x%d ", read_buf_int[i]); printf("错误:EEPROM读出与写入的数据不一致"); return 1; } printf("%d ", read_buf_int[i]); } printf("\r\n"); if (EEPROM_ReadBuffer(double_addr, sizeof(read_buf_double),(void *)read_buf_double) == 0) //从EEPROM中读10个小数数据 { printf("读eeprom成功,数据如下:\r\n"); } else { printf("读eeprom出错!\r\n"); return 1; } for (uint32_t i=0;i<10;i++) { if (read_buf_double[i] != write_buf_double[i]) { printf("0x%LF ", read_buf_double[i]); printf("错误:EEPROM读出与写入的数据不一致"); return 1; } printf("%LF ", read_buf_double[i]); } printf("\r\n"); printf("eeprom读写测试成功\r\n"); return 0; }
 
                    
                     
                    
                 
                    
                
 
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号