写在前面:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
目录
一、I2C协议
还是一样,在实现 I2C之前必须要了解 I2C协议的实现,请看之前的文章:UART/ USRAT、I2C、SPI通信方式扫盲,这里就不费劲再说一遍了
二、AT24Cxx芯片
为了加深对 I2C协议的理解以及操作,先来认识一下 AT24C02型号的 EEPROM芯片,因为它是使用 I2C协议通讯的,可以结合着来了解 I2C协议,下面挑一下信息了解一下这芯片,具体的看 Datasheet
1、功能框图:
2、AT24Cxx存储
3、不同容量芯片的设备地址
4、硬件地址引脚描述
三、硬件的 I2C
虽说,目前大多数人都说 STM32的硬件 I2C不好用、会经常出现死机现象,但这样并不影响我们对它的研究;当然,现在 ST官方也有给出一些解决方案,这些资料后面有链接给出来,好了,先来了解一下硬件结构部分吧
1、功能描述
I2C模块接收和发送数据,并将数据从串行转换成并行,或并行转换成串行。可以开启或禁止中断。接口通过数据引脚(SDA)和时钟引脚(SCL)连接到 I2C总线。允许连接到标准(高达 100kHz)或快速(高达 400kHz)的 I2C总线。
2、特点
● 并行总线/I2C总线协议转换器
● 多主机功能:该模块既可做主设备也可做从设备
● I2C主设备功能
─ 产生时钟
─ 产生起始和停止信号
● I2C从设备功能 ─ 可编程的I2C地址检测 ─ 可响应2个从地址的双地址能力 ─ 停止位检测
● 产生和检测7位/10位地址和广播呼叫
● 支持不同的通讯速度 ─ 标准速度(高达100 kHz) ─ 快速(高达400 kHz)
● 状态标志:
─ 发送器/接收器模式标志
─ 字节发送结束标志
─ I2C总线忙标志
● 错误标志
─ 主模式时的仲裁丢失
─ 地址/数据传输后的应答(ACK)错误
─ 检测到错位的起始或停止条件
─ 禁止拉长时钟功能时的上溢或下溢
● 2个中断向量
─ 1个中断用于地址/数据通讯成功
─ 1个中断用于错误
● 可选的拉长时钟功能
● 具单字节缓冲器的DMA
● 可配置的PEC(信息包错误检测)的产生或校验:
─ 发送模式中PEC值可以作为最后一个字节传输
─ 用于最后一个接收字节的PEC错误校验
● 兼容SMBus 2.0
─ 25 ms时钟低超时延时
─ 10 ms主设备累积时钟低扩展时间
─ 25 ms从设备累积时钟低扩展时间
─ 带ACK控制的硬件PEC产生/校验
─ 支持地址分辨协议(ARP)
● 兼容SMBus
注: 不是所有产品中都包含上述所有特性。请参考相关的数据手册,确认该产品支持的I2C功能。
3、主模式下的通讯序列图
4、从模式下的通讯序列图
5、硬件 I2C配置的相关代码
在使用硬件 I2C中,官方给出的一个解决方案是 DMA + 最高中断,其中,收发超过 1Byte数据才使用 DMA处理;在这里,我们试着把使用 DMA跟 不使用 DMA的代码整合一下(懒得分开了)
首先,为了区分是否使用 DMA,我们需要一个宏来确定:#define IIC_DMA_ENABLE
接着不用说了,首要的就是配置了;这里很简单,我们利用宏 IIC_DMA_ENABLE来决定是否增加 DMA的配置
#if (IIC_DMA_ENABLE) /************************************************ 函数名称 : IIC_NVIC_Config 功 能 : 配置嵌套向量中断控制器 NVIC 参 数 : 无 返 回 值 : 无 *************************************************/ static void IIC_NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; /* Configure and enable I2C DMA TX Channel interrupt */ NVIC_InitStructure.NVIC_IRQChannel = EE_I2C_DMA_TX_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = EE_I2C_DMA_PREPRIO; NVIC_InitStructure.NVIC_IRQChannelSubPriority = EE_I2C_DMA_SUBPRIO; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); /* Configure and enable I2C DMA RX Channel interrupt */ NVIC_InitStructure.NVIC_IRQChannel = EE_I2C_DMA_RX_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = EE_I2C_DMA_PREPRIO; NVIC_InitStructure.NVIC_IRQChannelSubPriority = EE_I2C_DMA_SUBPRIO; NVIC_Init(&NVIC_InitStructure); } /************************************************ 函数名称 : IIC_DMA_Init 功 能 : 配置嵌套向量中断控制器 NVIC 参 数 : 无 返 回 值 : 无 *************************************************/ static void IIC_DMA_Init(void) { /* Enable the DMA clock */ RCC_AHBPeriphClockCmd(EE_I2C_DMA_CLK, ENABLE); /* I2C DMA TX and RX channels configuration */ s_EEDMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)EE_I2C_DR_Address; s_EEDMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)0; /* This parameter will be configured durig communication */ s_EEDMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; /* This parameter will be configured durig communication */ s_EEDMA_InitStructure.DMA_BufferSize = 0xFFFF; /* This parameter will be configured durig communication */ s_EEDMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; s_EEDMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; s_EEDMA_InitStructure.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte; s_EEDMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; s_EEDMA_InitStructure.DMA_Mode = DMA_Mode_Normal; s_EEDMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; // 最高优先级 s_EEDMA_InitStructure.DMA_M2M = DMA_M2M_Disable; /* I2C TX DMA Channel configuration */ DMA_DeInit(EE_I2C_DMA_CHANNEL_TX); DMA_Init(EE_I2C_DMA_CHANNEL_TX, &s_EEDMA_InitStructure); /* I2C RX DMA Channel configuration */ DMA_DeInit(EE_I2C_DMA_CHANNEL_RX); DMA_Init(EE_I2C_DMA_CHANNEL_RX, &s_EEDMA_InitStructure); /* Enable the DMA Channels Interrupts */ DMA_ITConfig(EE_I2C_DMA_CHANNEL_TX, DMA_IT_TC, ENABLE); DMA_ITConfig(EE_I2C_DMA_CHANNEL_RX, DMA_IT_TC, ENABLE); } /************************************************ 函数名称 : IIC_DMA_Config 功 能 : 通讯期间的 DMA配置 参 数 : Address ---- 缓冲地址 BufferSize ---- 缓冲数据大小 Direction ---- EE_DIRECTION_TX or EE_DIRECTION_RX 返 回 值 : 无 *************************************************/ static void IIC_DMA_Config( uint32_t Address, uint32_t BufferSize, uint32_t Direction ) { /* Initialize the DMA with the new parameters */ if (Direction == EE_DIRECTION_TX) { /* Configure the DMA Tx Channel with the buffer address and the buffer size */ s_EEDMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Address; s_EEDMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; s_EEDMA_InitStructure.DMA_BufferSize = (uint32_t)BufferSize; DMA_Init(EE_I2C_DMA_CHANNEL_TX, &s_EEDMA_InitStructure); } else { /* Configure the DMA Rx Channel with the buffer address and the buffer size */ s_EEDMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Address; s_EEDMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; s_EEDMA_InitStructure.DMA_BufferSize = (uint32_t)BufferSize; DMA_Init(EE_I2C_DMA_CHANNEL_RX, &s_EEDMA_InitStructure); } } #endif /* IIC_DMA_ENABLE */ /************************************************ 函数名称 : AT24Cxx_Config 功 能 : AT24Cxx配置 参 数 : 无 返 回 值 : 无 *************************************************/ void AT24Cxx_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; I2C_InitTypeDef I2C_InitStructure; /* AT24C_I2Cx IO Periph clock enable */ AT24C_IO_APBxClock_FUN(AT24C_SCL_CLK | AT24C_SDA_CLK, ENABLE); /* AT24C_I2Cx Periph clock enable */ AT24C_I2C_APBxClock_FUN(AT24C_I2C_CLK, ENABLE); /* Configure AT24C_I2Cx pins: SCL, SDA */ /* Confugure SCL and SDA pins as Alternate Function Open Drain Output */ GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Pin = AT24C_SCL_PINS; GPIO_Init(AT24C_SCL_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = AT24C_SDA_PINS; GPIO_Init(AT24C_SDA_PORT, &GPIO_InitStructure); #if IIC_DMA_ENABLE /* DMA selective data processing */ IIC_NVIC_Config(); IIC_DMA_Init(); #endif /* IIC_DMA_ENABLE */ /* AT24C_I2Cx configuration */ I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStructure.I2C_OwnAddress1 = I2C_SLAVE_ADDRESS7; I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_InitStructure.I2C_ClockSpeed = I2C_SPEED; I2C_Init(AT24C_I2Cx, &I2C_InitStructure); /* Enable AT24C_I2Cx */ I2C_Cmd(AT24C_I2Cx, ENABLE); #if IIC_DMA_ENABLE /* Enable the AT24C_I2Cx peripheral DMA requests */ I2C_DMACmd(AT24C_I2Cx, ENABLE); #endif /* IIC_DMA_ENABLE */ }配置完成后,我们看单字节的处理(不使用 DMA)
在操作代码前,先回过头看下读写操作的时序先
单字节写时序:
读时序(读时序有很多种方式,为了方便,我们用随机读取的方式,这样可以随便定位地址):
/************************************************ 函数名称 : AT24Cxx_Write_Byte 功 能 : AT24Cxx写一个字节 参 数 : Byte ---- 数据 Address ---- 地址 返 回 值 : 0 / 1 *************************************************/ uint8_t AT24Cxx_Write_Byte( uint8_t Byte, uint16_t Address ) { /* While the bus is busy */ AT24C_TimeOut = MAX_LONGTIME_OUT; while(I2C_GetFlagStatus(AT24C_I2Cx, I2C_FLAG_BUSY)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(4); } /* Send START condition */ I2C_GenerateSTART(AT24C_I2Cx, ENABLE); /* Test on EV5 and clear it */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(5); } /* Send EEPROM address for write */ AT24C_TimeOut = MAX_TIME_OUT; I2C_Send7bitAddress(AT24C_I2Cx, s_AT24Cxx_Addr, I2C_Direction_Transmitter); /* Test on EV6 and clear it */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(6); } #ifndef AT24CXX_16BIT_ADDR /* Send the EEPROM's internal address to write to : only one byte Address */ I2C_SendData(AT24C_I2Cx, (uint8_t)(Address & 0x00FF)); #else /* Send the EEPROM's internal address to write to : MSB of the address first */ I2C_SendData(AT24C_I2Cx, (uint8_t)((Address & 0xFF00) >> 8)); /* Test on EV8 and clear it */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(6.5); } /*!< Send the EEPROM's internal address to write to : LSB of the address */ I2C_SendData(AT24C_I2Cx, (uint8_t)(Address & 0x00FF)); #endif /* AT24CXX_16BIT_ADDR */ /* Test on EV8 and clear it */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(7); } /* Send the current byte */ I2C_SendData(AT24C_I2Cx, Byte); /* Test on EV8 and clear it */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(8); } /* Send STOP condition */ I2C_GenerateSTOP(AT24C_I2Cx, ENABLE); /* If all operations OK, return 1 */ return 1; } /************************************************ 函数名称 : AT24Cxx_Read_Byte 功 能 : AT24Cxx读一个字节 参 数 : Address ---- 地址 返 回 值 : temp ---- 数据 *************************************************/ uint8_t AT24Cxx_Read_Byte( uint16_t Address ) { uint8_t temp; /*!< While the bus is busy */ AT24C_TimeOut = MAX_LONGTIME_OUT; while(I2C_GetFlagStatus(AT24C_I2Cx, I2C_FLAG_BUSY)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(9); } /*!< Send START condition */ I2C_GenerateSTART(AT24C_I2Cx, ENABLE); /*!< Test on EV5 and clear it (cleared by reading SR1 then writing to DR) */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(10); } /*!< Send EEPROM address for write */ I2C_Send7bitAddress(AT24C_I2Cx, s_AT24Cxx_Addr, I2C_Direction_Transmitter); /*!< Test on EV6 and clear it */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(11); } /* Clear EV6 by setting again the PE bit */ I2C_Cmd(AT24C_I2Cx, ENABLE); #ifndef AT24CXX_16BIT_ADDR /*!< Send the EEPROM's internal address to read from: Only one byte address */ I2C_SendData(AT24C_I2Cx, (uint8_t)(Address & 0x00FF)); #else /*!< Send the EEPROM's internal address to read from: MSB of the address first */ I2C_SendData(AT24C_I2Cx, (uint8_t)((Address & 0xFF00) >> 8)); /*!< Test on EV8 and clear it */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(11.5); } /*!< Send the EEPROM's internal address to read from: LSB of the address */ I2C_SendData(AT24C_I2Cx, (uint8_t)(ReadAddr & 0x00FF)); #endif /*!< AT24CXX_16BIT_ADDR */ /*!< Test on EV8 and clear it */ AT24C_TimeOut = MAX_TIME_OUT; while(I2C_GetFlagStatus(AT24C_I2Cx, I2C_FLAG_BTF) == RESET) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(12); } /*!< Send STRAT condition a second time */ I2C_GenerateSTART(AT24C_I2Cx, ENABLE); /*!< Test on EV5 and clear it (cleared by reading SR1 then writing to DR) */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(13); } /*!< Send EEPROM address for read */ I2C_Send7bitAddress(AT24C_I2Cx, s_AT24Cxx_Addr, I2C_Direction_Receiver); /* Test on EV6 and clear it */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(14); } /* Disable Acknowledgement */ I2C_AcknowledgeConfig(AT24C_I2Cx, DISABLE); /* Send STOP Condition */ I2C_GenerateSTOP(AT24C_I2Cx, ENABLE); /* Test on EV7 and clear it */ AT24C_TimeOut = MAX_LONGTIME_OUT; while(I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)==0) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(15); } /* Read a byte from the EEPROM */ temp = I2C_ReceiveData(AT24C_I2Cx); /* Enable Acknowledgement to be ready for another reception */ I2C_AcknowledgeConfig(AT24C_I2Cx, ENABLE); /* If all operations OK, return receive data */ return temp; }以上的代码,具体的每步操作以及判定,其实我们按照上面的芯片对应操作时序以及主模式下的序列图顺序走就可以
好了,单字节处理完,那么就来考虑多数据连续读取了,毕竟,真正用的多的是大片数据读取,下面的代码保留了不用 DMA时也能读取大片数据操作
页编程:
/************************************************ 函数名称 : AT24Cxx_Page_Program 功 能 : AT24Cxx页编程 参 数 : pBuffer ---- 数据 Address ---- 地址 Len ---- 长度 返 回 值 : 0 / 1 *************************************************/ uint8_t AT24Cxx_Page_Program( uint8_t *pBuffer, uint16_t Address, uint16_t Len ) { /* Set the pointer to the Number of data to be written. This pointer will be used by the DMA Transfer Completer interrupt Handler in order to reset the variable to 0. User should check on this variable in order to know if the DMA transfer has been complete or not. */ g_EEData_WritePointer = Len; /* While the bus is busy */ AT24C_TimeOut = MAX_LONGTIME_OUT; while(I2C_GetFlagStatus(AT24C_I2Cx, I2C_FLAG_BUSY)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(4); } /* Send START condition */ I2C_GenerateSTART(AT24C_I2Cx, ENABLE); /* Test on EV5 and clear it */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(5); } /* Send EEPROM address for write */ AT24C_TimeOut = MAX_TIME_OUT; I2C_Send7bitAddress(AT24C_I2Cx, s_AT24Cxx_Addr, I2C_Direction_Transmitter); /* Test on EV6 and clear it */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(6); } #ifndef AT24CXX_16BIT_ADDR /* Send the EEPROM's internal address to write to : only one byte Address */ I2C_SendData(AT24C_I2Cx, (uint8_t)(Address & 0x00FF)); #else /* Send the EEPROM's internal address to write to : MSB of the address first */ I2C_SendData(AT24C_I2Cx, (uint8_t)((Address & 0xFF00) >> 8)); /* Test on EV8 and clear it */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(6.5); } /*!< Send the EEPROM's internal address to write to : LSB of the address */ I2C_SendData(AT24C_I2Cx, (uint8_t)(Address & 0x00FF)); #endif /* AT24CXX_16BIT_ADDR */ /* Test on EV8 and clear it */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(7); } #if (0 == IIC_DMA_ENABLE) /* While there is data to be written */ while(Len--) { /* Send the current byte */ I2C_SendData(AT24C_I2Cx, *pBuffer); /* Point to the next byte to be written */ pBuffer++; /* Test on EV8 and clear it */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(8); } } /* Send STOP condition */ I2C_GenerateSTOP(AT24C_I2Cx, ENABLE); #else /* Configure the DMA Tx Channel with the buffer address and the buffer size */ IIC_DMA_Config((uint32_t)pBuffer, Len, EE_DIRECTION_TX); /* Enable the DMA Tx Channel */ DMA_Cmd(EE_I2C_DMA_CHANNEL_TX, ENABLE); #endif /* IIC_DMA_ENABLE */ /* If all operations OK, return 1 */ return 1; }随机读取大片数据:
/************************************************ 函数名称 : AT24Cxx_Read_EEPROM 功 能 : 从 AT24Cxx中读取数据块 参 数 : pBuffer ---- 数据 Address ---- 地址 Len ---- 长度 返 回 值 : 0 / 1 *************************************************/ uint8_t AT24Cxx_Read_EEPROM( uint8_t *pBuffer, uint16_t Address, uint16_t Len ) { /* Set the pointer to the Number of data to be read. This pointer will be used by the DMA Transfer Completer interrupt Handler in order to reset the variable to 0. User should check on this variable in order to know if the DMA transfer has been complete or not. */ g_EEData_ReadPointer = Len; /*!< While the bus is busy */ AT24C_TimeOut = MAX_LONGTIME_OUT; while(I2C_GetFlagStatus(AT24C_I2Cx, I2C_FLAG_BUSY)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(9); } /*!< Send START condition */ I2C_GenerateSTART(AT24C_I2Cx, ENABLE); /*!< Test on EV5 and clear it (cleared by reading SR1 then writing to DR) */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(10); } /*!< Send EEPROM address for write */ I2C_Send7bitAddress(AT24C_I2Cx, s_AT24Cxx_Addr, I2C_Direction_Transmitter); /*!< Test on EV6 and clear it */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(11); } /* Clear EV6 by setting again the PE bit */ I2C_Cmd(AT24C_I2Cx, ENABLE); #ifndef AT24CXX_16BIT_ADDR /*!< Send the EEPROM's internal address to read from: Only one byte address */ I2C_SendData(AT24C_I2Cx, (uint8_t)(Address & 0x00FF)); #else /*!< Send the EEPROM's internal address to read from: MSB of the address first */ I2C_SendData(AT24C_I2Cx, (uint8_t)((Address & 0xFF00) >> 8)); /*!< Test on EV8 and clear it */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(11.5); } /*!< Send the EEPROM's internal address to read from: LSB of the address */ I2C_SendData(AT24C_I2Cx, (uint8_t)(ReadAddr & 0x00FF)); #endif /*!< AT24CXX_16BIT_ADDR */ /*!< Test on EV8 and clear it */ AT24C_TimeOut = MAX_TIME_OUT; while(I2C_GetFlagStatus(AT24C_I2Cx, I2C_FLAG_BTF) == RESET) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(12); } /*!< Send STRAT condition a second time */ I2C_GenerateSTART(AT24C_I2Cx, ENABLE); /*!< Test on EV5 and clear it (cleared by reading SR1 then writing to DR) */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(13); } /*!< Send EEPROM address for read */ I2C_Send7bitAddress(AT24C_I2Cx, s_AT24Cxx_Addr, I2C_Direction_Receiver); /* Test on EV6 and clear it */ AT24C_TimeOut = MAX_TIME_OUT; while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(14); } /* While there is data to be read */ while(Len) { if(Len == 1) { /* Disable Acknowledgement */ I2C_AcknowledgeConfig(AT24C_I2Cx, DISABLE); /* Call User callback for critical section start (should typically disable interrupts) */ EE_EnterCriticalSection_UserCallback(); /* Send STOP Condition */ I2C_GenerateSTOP(AT24C_I2Cx, ENABLE); /* Call User callback for critical section end (should typically re-enable interrupts) */ EE_ExitCriticalSection_UserCallback(); #if (0 == IIC_DMA_ENABLE) } /* Test on EV7 and clear it */ AT24C_TimeOut = MAX_LONGTIME_OUT; while(I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)==0) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(15); } /* Read a byte from the EEPROM */ *pBuffer = I2C_ReceiveData(AT24C_I2Cx); /* Point to the next location where the byte read will be saved */ pBuffer++; /* Decrement the read bytes counter */ Len--; } /* Enable Acknowledgement to be ready for another reception */ I2C_AcknowledgeConfig(AT24C_I2Cx, ENABLE); #else /* Wait for the byte to be received */ AT24C_TimeOut = MAX_LONGTIME_OUT; while(I2C_GetFlagStatus(AT24C_I2Cx, I2C_FLAG_RXNE)==0) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(16); } /* Read a byte from the EEPROM */ *pBuffer = I2C_ReceiveData(AT24C_I2Cx); /* Decrement the read bytes counter */ Len--; /* Wait to make sure that STOP control bit has been cleared */ AT24C_TimeOut = MAX_TIME_OUT; while(AT24C_I2Cx->CR1 & I2C_CR1_STOP) { if(0 == (AT24C_TimeOut--)) return TimeOut_Callback(17); } /*!< Re-Enable Acknowledgement to be ready for another reception */ I2C_AcknowledgeConfig(AT24C_I2Cx, ENABLE); } else { /* Configure the DMA Rx Channel with the buffer address and the buffer size */ IIC_DMA_Config((uint32_t)pBuffer, Len, EE_DIRECTION_RX); /* Inform the DMA that the next End Of Transfer Signal will be the last one */ I2C_DMALastTransferCmd(AT24C_I2Cx, ENABLE); /* Enable the DMA Rx Channel */ DMA_Cmd(EE_I2C_DMA_CHANNEL_RX, ENABLE); break; } } #endif /* IIC_DMA_ENABLE */ /* If all operations OK, return 1 */ return 1; }既然用到了 DMA,那么肯定要处理一下 DMA的中断
#if (IIC_DMA_ENABLE) /************************************************************************/ /* STM32F10x I2C DMA Interrupt Handlers */ /************************************************************************/ /** * @brief This function handles the DMA Tx Channel interrupt Handler. * @param None * @retval None */ void EE_I2C_DMA_TX_IRQHandler(void) { /* Check if the DMA transfer is complete */ if(DMA_GetFlagStatus(EE_I2C_DMA_FLAG_TX_TC) != RESET) { /* Disable the DMA Tx Channel and Clear all its Flags */ DMA_Cmd(EE_I2C_DMA_CHANNEL_TX, DISABLE); DMA_ClearFlag(EE_I2C_DMA_FLAG_TX_GL); /*!< Wait till all data have been physically transferred on the bus */ AT24C_TimeOut = MAX_LONGTIME_OUT; while(!I2C_GetFlagStatus(AT24C_I2Cx, I2C_FLAG_BTF)) { if((AT24C_TimeOut--) == 0) TimeOut_Callback(3); } /*!< Send STOP condition */ I2C_GenerateSTOP(AT24C_I2Cx, ENABLE); /* Perform a read on SR1 and SR2 register to clear eventualaly pending flags */ (void)AT24C_I2Cx->SR1; (void)AT24C_I2Cx->SR2; /* Reset the variable holding the number of data to be written */ g_EEData_WritePointer = 0; } } /** * @brief This function handles the DMA Rx Channel interrupt Handler. * @param None * @retval None */ void EE_I2C_DMA_RX_IRQHandler(void) { /* Check if the DMA transfer is complete */ if(DMA_GetFlagStatus(EE_I2C_DMA_FLAG_RX_TC) != RESET) { /*!< Send STOP Condition */ I2C_GenerateSTOP(AT24C_I2Cx, ENABLE); /* Disable the DMA Rx Channel and Clear all its Flags */ DMA_Cmd(EE_I2C_DMA_CHANNEL_RX, DISABLE); DMA_ClearFlag(EE_I2C_DMA_FLAG_RX_GL); /* Reset the variable holding the number of data to be read */ g_EEData_ReadPointer = 0; } } #endif /* IIC_DMA_ENABLE */值得注意的是,无论是使用了 DMA还是没用,只要是使用了硬件 I2C,那么每次操作都必须进行忙等待检测,而使用了 DMA,更要等待 DMA传输完成,代码中的这两个变量 __IO uint16_t g_EEData_ReadPointer;__IO uint16_t g_EEData_WritePointer;便是对 DMA传输完成的检查
四、模拟的 I2C
模拟 I2C,我们要做的,就只是先把它的每个状态封装起来,然后再按照时序拼凑起来
先来引脚配置,两个引脚都配置成开漏输出
/************************************************ 函数名称 : Simulate_IIC_Config 功 能 : 模拟 IIC IO配置 参 数 : 无 返 回 值 : 无 *************************************************/ void Simulate_IIC_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; SL_IIC_SCL_APBxClock_FUN(SL_IIC_SCL_CLK, ENABLE); SL_IIC_SDA_APBxClock_FUN(SL_IIC_SDA_CLK, ENABLE); GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; /* SCL */ GPIO_InitStructure.GPIO_Pin = SL_IIC_SCL_PINS; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_Init(SL_IIC_SCL_PORT, &GPIO_InitStructure); /* SDA */ GPIO_InitStructure.GPIO_Pin = SL_IIC_SDA_PINS; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_Init(SL_IIC_SDA_PORT, &GPIO_InitStructure); }接着是各种状态的实现:
/************************************************ 函数名称 : IIC_Start 功 能 : IIC写启动 参 数 : 无 返 回 值 : 0 / 1 *************************************************/ uint8_t IIC_Start(void) { IIC_SCL(HIGH); IIC_SDA(HIGH); IIC_Delay_us(WAIT_TIME); if(!IIC_SDA_READ) // 回检一次电平状态 { return 0; } IIC_SDA(LOW); IIC_Delay_us(WAIT_TIME); if(IIC_SDA_READ) // 回检一次电平状态 { return 0; } IIC_SCL(LOW); return 1; } /************************************************ 函数名称 : IIC_Stop 功 能 : IIC写停止 参 数 : 无 返 回 值 : 无 *************************************************/ void IIC_Stop(void) { IIC_SCL(LOW); IIC_SDA(LOW); IIC_Delay_us(WAIT_TIME); IIC_SCL(HIGH); IIC_Delay_us(WAIT_TIME); IIC_SDA(HIGH); } /************************************************ 函数名称 : IIC_Ack 功 能 : Ack应答 参 数 : 无 返 回 值 : 无 *************************************************/ void IIC_Ack(void) { IIC_SDA(LOW); IIC_Delay_us(WAIT_TIME); IIC_SCL(HIGH); IIC_Delay_us(WAIT_TIME); IIC_SCL(LOW); IIC_Delay_us(WAIT_TIME); IIC_SDA(HIGH); } /************************************************ 函数名称 : IIC_UnAck 功 能 : Ack不应答 参 数 : 无 返 回 值 : 无 *************************************************/ void IIC_UnAck(void) { IIC_SCL(LOW); IIC_SDA(HIGH); IIC_Delay_us(WAIT_TIME); IIC_SCL(HIGH); IIC_Delay_us(WAIT_TIME); IIC_SCL(LOW); } /************************************************ 函数名称 : IIC_Wait_Ack 功 能 : ACK等待 参 数 : 无 返 回 值 : 0 / 1 *************************************************/ uint8_t IIC_Wait_Ack(void) { uint8_t time = 80; IIC_SCL(LOW); IIC_Delay_us(WAIT_TIME); IIC_SDA(HIGH); IIC_Delay_us(WAIT_TIME); IIC_SCL(HIGH); IIC_Delay_us(WAIT_TIME); while(IIC_SDA_READ) { time--; if(!time) { IIC_Stop(); return 0; } } IIC_SCL(LOW); return 1; } /************************************************ 函数名称 : Write_IIC_Byte 功 能 : IIC写一个字节 参 数 : Byte ---- 数据 返 回 值 : 无 *************************************************/ void Write_IIC_Byte( uint8_t Byte ) { uint8_t i; IIC_SCL(LOW); IIC_Delay_us(WAIT_TIME); for(i = 0;i < 8;i++) { IIC_SDA((BitAction)((Byte & 0x80) >> 7)); Byte <<= 1; IIC_Delay_us(WAIT_TIME); IIC_SCL(HIGH); IIC_Delay_us(WAIT_TIME); IIC_SCL(LOW); IIC_Delay_us(WAIT_TIME); } } /************************************************ 函数名称 : Read_IIC_Byte 功 能 : IIC读一个字节 参 数 : 无 返 回 值 : temp ---- 数据 *************************************************/ uint8_t Read_IIC_Byte(void) { uint8_t i; uint8_t temp = 0; IIC_SDA(HIGH); IIC_Delay_us(WAIT_TIME); for(i = 0;i < 8;i++) { temp <<= 1; IIC_SCL(LOW); IIC_Delay_us(WAIT_TIME); IIC_SCL(HIGH); IIC_Delay_us(WAIT_TIME); #if 0 if(IIC_SDA_READ) { temp++; } #else temp |= IIC_SDA_READ; #endif } IIC_SCL(LOW); IIC_Delay_us(WAIT_TIME); return temp; }最后,只需要把上面的状态,对应着芯片给出的操作时序图,封装起来就好了
例如读写一个字节:
/************************************************ 函数名称 : EE_Write_Byte 功 能 : EEPROM模拟写一个字节 参 数 : DeveiceAddr ---- 设备地址 Data ---- 数据 WordAddr ---- 字地址 返 回 值 : 0 / 1 *************************************************/ uint8_t EE_Write_Byte( uint8_t DeveiceAddr, uint8_t Data, uint16_t WordAddr ) { if(IIC_Start()) { Write_IIC_Byte(DeveiceAddr | AT24C_WRITE); if(0 == IIC_Wait_Ack()) goto WriteFail; Write_IIC_Byte((uint8_t)(WordAddr & 0x00FF)); if(0 == IIC_Wait_Ack()) goto WriteFail; Write_IIC_Byte(Data); if(0 == IIC_Wait_Ack()) goto WriteFail; IIC_Stop(); return 1; } WriteFail: IIC_Stop(); return 0; } /************************************************ 函数名称 : EE_Read_Byte 功 能 : EEPROM模拟读一个字节 参 数 : DeveiceAddr ---- 设备地址 WordAddr ---- 字地址 返 回 值 : data ---- 数据 *************************************************/ uint8_t EE_Read_Byte( uint8_t DeveiceAddr, uint16_t WordAddr ) { uint8_t data = 0; if(IIC_Start()) { Write_IIC_Byte(DeveiceAddr | AT24C_WRITE); if(0 == IIC_Wait_Ack()) goto ReadFail; Write_IIC_Byte((uint8_t)(WordAddr & 0x00FF)); if(0 == IIC_Wait_Ack()) goto ReadFail; if(IIC_Start()) { Write_IIC_Byte(DeveiceAddr | AT24C_READ); if(0 == IIC_Wait_Ack()) goto ReadFail; data = Read_IIC_Byte(); IIC_UnAck(); IIC_Stop(); return data; } } ReadFail: IIC_Stop(); return 0; }五、AT24C02测试操作 /************************************************ 函数名称 : EE_Test 功 能 : EEPROM测试函数 参 数 : 无 返 回 值 : 无 *************************************************/ static uint8_t EE_Test(void) { #if _EE_TEST uint8_t I2c_Buf_Write[256] = {0}; uint8_t I2c_Buf_Read[256] = {0}; uint16_t i; /* 单字节读写测试 */ AT24C_DUBUG_PRINTF("单字节读写测试\n"); if(AT24Cxx_Write_Byte(0xBB, USE_TEST_ADDR + 0x200)) { AT24Cxx_Busy_Wait(); // 硬件 IIC下,调用该函数防止后面出错 #if USE_SIMULATE_IIC Delay_us(0x6FFFF); // 模拟读写需要等待一段时间,否则会出错 #endif /* USE_SIMULATE_IIC */ AT24C_DUBUG_PRINTF("data:0x%02X\n",AT24Cxx_Read_Byte(USE_TEST_ADDR + 0x200)); AT24Cxx_Busy_Wait(); GPIO_ResetBits(GPIOB, GPIO_Pin_0); } #if 1 /* 单字节读写测试 */ AT24C_DUBUG_PRINTF("页读写测试\n"); for (i = 0;i < 256;i++) // 填充缓冲 { I2c_Buf_Write[i] = i; } I2c_Buf_Write[0] = 0xAA; // 将 I2c_Buf_Write中顺序递增的数据写入 EERPOM中 AT24Cxx_Write_EEPROM(I2c_Buf_Write, USE_TEST_ADDR, 256); // 写入函数已经包含了busy检查了 // EE_DMA_RxWait(); // AT24Cxx_Busy_Wait(); #if USE_SIMULATE_IIC Delay_us(0x6FFFF); // 模拟读写需要等待一段时间,否则会出错 #endif /* USE_SIMULATE_IIC */ // 将 EEPROM读出数据顺序保持到 I2c_Buf_Read中 AT24Cxx_Read_EEPROM(I2c_Buf_Read, USE_TEST_ADDR, 256); EE_DMA_RxWait(); // DMA模式下必须等待 DMA传输完成,否则数据不对 AT24Cxx_Busy_Wait(); // 将 I2c_Buf_Read中的数据通过串口打印 for (i = 0;i < 256;i++) { if(I2c_Buf_Read[i] != I2c_Buf_Write[i]) { AT24C_DUBUG_PRINTF("0x%02X , i = %d\n", I2c_Buf_Read[i], i); AT24C_DUBUG_PRINTF("错误:I2C EEPROM写入与读出的数据不一致\n\r"); return 0; } printf("0x%02X ", I2c_Buf_Read[i]); if(i%11 == 10 || i == 255) printf("\n\r"); } AT24C_DUBUG_PRINTF("I2C(AT24C02)读写测试成功\n\r"); #endif #endif /* _EE_TEST */ return 1; }
代码:https://github.com/Arachnid-97/BasicDriver/tree/master/AT24Cxx