提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
最近一直在做STM32的项目,需要调通多个外设,其中就需要调通的经典外设就是EEPROM,型号为2K大小的24C02,STM32的型号是STM32F103RET6,这块板卡有着两个硬件I2C口,分别是I2C1口和I2C2口,EEPROM这块外设硬件设计上就挂载在I2C2口上,现在记录一下调试的整个过程。
提示:以下是本篇文章正文内容,下面案例可供参考
一、硬件I2C的初始化配置
首先使用库函数对I2C2口进行硬件上GPIO和I2C的配置,GPIO配置I2C2口的IO引脚并使能,速度选择50MHz, 引脚模式设置为复用开漏输出。然后进行I2C2口的初始化配置,包括复位、使能、I2C模式、占空比、设备地址(随意区一个地址不冲突即可),应答,应答地址大小,I2C速率等。
具体代码如下:
//初始化IIC接口
void I2C2_GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = PIN_I2C2_SCL | PIN_I2C2_SDA;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //50
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_Init(PORT_I2C_SCL, &GPIO_InitStructure);
}
void I2C2_Configuration(void)
{
I2C_InitTypeDef I2C_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
I2C_DeInit(I2C2); //复位I2C
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; //I2C模式
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; //占空比(快速模式时)
I2C_InitStructure.I2C_OwnAddress1 = 0x00; //设备地址 0X77
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //应答
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = I2C_SPEED; //速度
I2C_Cmd(I2C2, ENABLE); //使能I2C
I2C_Init(I2C2, &I2C_InitStructure);
}
二、EEPROM读写接口的编写
1.EEPROM写接口
EEPROM的写接口过程有几个事件的检测过程,I2C控制器产生开始信号(S),然后通过检测EV5事件,判断是否启动成功。 在满足EV5事件后,主机发送器件地址+W,然后通过检测EV6事件、判断是否发送器件地址成功。 在满足EV6事件后,主机发送数据,然后通过检测EV8事件,判断是否发送数据成功。在发送完最后一个数据后,主机发送结束信号§结束通信过程。
代码如下(示例):
uint8_t EEPROM_WriteByte(I2C_TypeDef* I2Cx,uint16_t Addr, uint8_t Data)
{
I2CTimeout1 = I2CT_FLAG_TIMEOUT;
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY))
{
if((I2CTimeout1--) == 0) return I2C_TIMEOUT_UserCallback(I2Cx,20);
}
/* 1.开始 */
I2C_GenerateSTART(I2Cx, ENABLE);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if((I2CTimeout1--) == 0) return I2C_TIMEOUT_UserCallback(I2Cx,21);
}
/* 2.设备地址/写 */
I2C_Send7bitAddress(I2Cx, EEPROM_DEV_ADDR, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if((I2CTimeout1--) == 0) return I2C_TIMEOUT_UserCallback(I2Cx,22);
}
/* 3.数据地址 */
I2C_SendData(I2Cx, (Addr&0x00FF)); //数据地址(8位)
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if((I2CTimeout1--) == 0) return I2C_TIMEOUT_UserCallback(I2Cx,23);
}
/* 4.写一字节数据 */
I2C_SendData(I2Cx, Data);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if((I2CTimeout1--) == 0) return I2C_TIMEOUT_UserCallback(I2Cx,24);
}
/* 5.停止 */
I2C_GenerateSTOP(I2Cx, ENABLE);
return RET_SUCC;
}
2.EEPROM读接口
EEPROM读接口也有几个事件的检验过程:I2C控制器产生开始信号,然后通过检测EV5事件,判断是否启动成功。 在满足EV5事件后,主机发送器件地址,然后通过检测EV6事件,判断是否发送器件地址成功。 在满足EV6事件后,主机准备接收从机发送过来的数据,然后通过检测EV7事件,判断是否接收数据成功。如果接收的不是最后一个数据的话,则主机发送ACK信号给从机。 如果接收的是最后一个数据的话,则主机发送一个NACK信号,并发送结束信号,结束通信。
代码如下(示例):
uint8_t EEPROM_ReadByte(I2C_TypeDef* I2Cx, uint16_t Addr, uint8_t *Data)
{
I2CTimeout1 = I2CT_FLAG_TIMEOUT;
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY))
{
if((I2CTimeout1--) == 0)
return I2C_TIMEOUT_UserCallback(I2Cx,25);
}
/* 1.开始 */
I2C_GenerateSTART(I2Cx, ENABLE);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if((I2CTimeout1--) == 0)
return I2C_TIMEOUT_UserCallback(I2Cx,26);
}
/* 2.设备地址/写 */
I2C_Send7bitAddress(I2Cx, EEPROM_DEV_ADDR, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if((I2CTimeout1--) == 0)
return I2C_TIMEOUT_UserCallback(I2Cx,27);
}
/* 3.数据地址 */
I2C_SendData(I2Cx, (Addr&0x00FF)); //数据地址(8位)
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if((I2CTimeout1--) == 0) return I2C_TIMEOUT_UserCallback(I2Cx,28);
}
/* 4.重新开始 */
I2C_GenerateSTART(I2Cx, ENABLE);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if((I2CTimeout1--) == 0) return I2C_TIMEOUT_UserCallback(I2Cx,29);
}
/* 5.设备地址/读 */
I2C_Send7bitAddress(I2Cx, EEPROM_DEV_ADDR, I2C_Direction_Receiver);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
{
if((I2CTimeout1--) == 0) return I2C_TIMEOUT_UserCallback(I2Cx,30);
}
/* 6.读一字节数据 */
I2C_AcknowledgeConfig(I2Cx, DISABLE); //产生非应答
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_RXNE) == RESET)
{
if((I2CTimeout1--) == 0) return I2C_TIMEOUT_UserCallback(I2Cx,31);
}
*Data = I2C_ReceiveData(I2Cx); //读取数据
/* 7.停止 */
I2C_GenerateSTOP(I2Cx, ENABLE);
return RET_SUCC;
}
以上是写一字节和读一字节的接口,如果需要写多个字节和读多个字节需要另外的接口。
3.EEPROM写多个字节接口
EEPROM写多个字节接口需要使用要写页的方式,因为一页只有8字节,超过的需要从下一页开始,这个过程需要对要写的内容做很多判断。
代码如下(示例):
uint8_t EEPROM_WriteNByte(I2C_TypeDef* I2Cx, uint16_t Addr, uint8_t *pData, uint16_t Length)
{
uint16_t addr_offset; //偏移地址
uint8_t num_page = 0; //页数(Length字节共多少页)
uint8_t num_single = 0; //"单"字节数(除了整页外的字节数)
uint8_t count = 0; //页剩余字节数量(偏移地址 -> 页末)
addr_offset = Addr % EEPROM_PAGE_SIZE; //"起始地址"偏移该页地址多少
count = EEPROM_PAGE_SIZE - addr_offset; //页剩余字节数量
num_page = Length / EEPROM_PAGE_SIZE; //页数
num_single = Length % EEPROM_PAGE_SIZE;
/* 1.起始地址未偏移(位于页首地址) */
if(0 == addr_offset)
{
/* 数据量小于1页 */
if(0 == num_page)
{
EEPROM_WritePage(I2Cx, Addr, pData, Length); //页首地址,写入小于1页的数据
}
/* 数据量大于等于1页 */
else
{
while(num_page--)
{ //写num_page页数据
EEPROM_WritePage( I2Cx, Addr, pData, EEPROM_PAGE_SIZE);
Addr += EEPROM_PAGE_SIZE;
pData += EEPROM_PAGE_SIZE;
}
if(0 != num_single)
{ //写整页外剩下的字节数
EEPROM_WritePage(I2Cx, Addr, pData, num_single);
}
}
}
/* 2.起始地址已偏移(不在页首地址) */
else
{
/* 数据量小于1页 */
if(0 == num_page)
{
/* 不超过该页 */
if(Length < count)
{
EEPROM_WritePage(I2Cx,Addr, pData, Length); //页偏移地址,写入小于该页的数据
}
/* 超过该页 */
else
{
EEPROM_WritePage(I2Cx, Addr, pData, count); //页偏移地址,写满该页的数据
Addr += count;
pData += count;
//下页首地址,写完剩下的数据
EEPROM_WritePage(I2Cx,Addr, pData, Length - count);
}
}
/* 数据量大于等于1页 */
else
{
Length -= count;
num_page = Length / EEPROM_PAGE_SIZE; //剩下的页数(减去前面写的数)
num_single = Length % EEPROM_PAGE_SIZE; //最后一页需要写的字节数
EEPROM_WritePage(I2Cx,Addr, pData, count); //页偏移地址,写满该页的数据
Addr += count;
pData += count;
while(num_page--)
{ //写num_page页数据
delay_ms(5); //写周期延时5ms
EEPROM_WritePage(I2Cx, Addr, pData, EEPROM_PAGE_SIZE);
Addr += EEPROM_PAGE_SIZE;
pData += EEPROM_PAGE_SIZE;
}
if(0 != num_single)
{ //写整页外剩下的字节数
delay_ms(5);
EEPROM_WritePage(I2Cx, Addr, pData, num_single);
}
}
}
return RET_SUCC;
}
4.EEPROM读多个字节接口
EEPROM读多个字节接口跟读一个字节相比就多了一个要读取指定参数数量的参数,然后重复读取一个字节的过程,数据存入到对应的数组中。
代码如下(示例):
uint8_t EEPROM_Buffer_Read(I2C_TypeDef* I2Cx, uint16_t Addr, uint8_t *pData, uint16_t Length)
{
I2CTimeout1 = I2CT_FLAG_TIMEOUT;
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY))
{
if((I2CTimeout1--) == 0) return I2C_TIMEOUT_UserCallback(I2Cx,37);
}
/* 1.开始 */
I2C_GenerateSTART(I2Cx, ENABLE);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if((I2CTimeout1--) == 0) return I2C_TIMEOUT_UserCallback(I2Cx,38);
}
/* 2.设备地址/写 */
I2C_Send7bitAddress(I2Cx, EEPROM_DEV_ADDR, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if((I2CTimeout1--) == 0) return I2C_TIMEOUT_UserCallback(I2Cx,39);
}
/* 3.数据地址 */
I2C_SendData(I2Cx, (Addr&0x00FF)); //数据地址(8位)
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if((I2CTimeout1--) == 0) return I2C_TIMEOUT_UserCallback(I2Cx,40);
}
I2C_GenerateSTART(I2Cx, ENABLE);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if((I2CTimeout1--) == 0) return I2C_TIMEOUT_UserCallback(I2Cx,41);
}
/* 5.设备地址/读 */
I2C_Send7bitAddress(I2Cx, EEPROM_READ_ADDR , I2C_Direction_Receiver);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
{
if((I2CTimeout1--) == 0) return I2C_TIMEOUT_UserCallback(I2Cx,42);
}
while(Length --)
{
if(Length == 0)
{
//作出非应答信号
I2C_AcknowledgeConfig(I2Cx, DISABLE);
}
else
{
//作出应答信号
I2C_AcknowledgeConfig(I2Cx, ENABLE);
}
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED))
{
if((I2CTimeout1--) == 0) return I2C_TIMEOUT_UserCallback(I2Cx,43);
}
//接收数据
*pData = I2C_ReceiveData(I2Cx);
pData++;
}
/* 7.停止 */
I2C_GenerateSTOP(I2Cx, ENABLE);
return RET_SUCC;
}
总结
以上就是提供了硬件IIC读写EEPROM单个字节和多个字节的接口,硬件IIC主要是要多调试,一次调不通很正常,通过加打印看卡在读写接口的那个事件上,当然如果初始化配置过程不正确,也可能调试不通,会有多个方面的原因,欢迎交流。