学习STM32F1固件库利用IIC与EEPROM通信中CheckEvent()与GetFlagStatus()区别整理
我在学习中遇到的问题
在我学习STM32库函数的IIC库,利用库函数写完发送和接收函数后。在主函数中调用发现结果与我的预期不一致。
找到解决方案后发现在单纯IIC检测(只发送地址)时,无法使用CheckEvent()函数检测IIC通信状态。
初始函数
EEPROM工作时序
这是EEPROM数据手册提供的写时序:
图中可以看出写一字节与按页写入基本一致的。
值得注意的是按页写入时每次最多可以写入八字节数据,这与EEPROM存储方式有关。
读时序:
若MCU按照如上方式发送数据,EEPROM只会输出“当前”内存地址的数据。
若想要自己定义输出起始地址,则需要按照如下方式读取。
IIC收发函数
注:图中函数比较简单并没有进行超时检测,后期可以自己补充完整。
//发送一个字节数据
void EEPROM_Byte_Write(uint8_t addr,uint8_t data)
{
I2C_GenerateSTART(EEPRON_I2C,ENABLE);//产生起始信号
while(I2C_CheckEvent(EEPRON_I2C,I2C_EVENT_MASTER_MODE_SELECT)==ERROR); //检测到EV5事件
I2C_Send7bitAddress(EEPRON_I2C,EEPROM_ADDR,I2C_Direction_Transmitter);
while(I2C_CheckEvent(EEPRON_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
I2C_SendData(EEPRON_I2C,addr); //发送EEPROM存储地址
while(I2C_CheckEvent(EEPRON_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING)==ERROR);
I2C_SendData(EEPRON_I2C,data); //发送数据
while(I2C_CheckEvent(EEPRON_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED)==ERROR);
I2C_GenerateSTOP(EEPRON_I2C,ENABLE);
}
//读出一个字节数据
void EEPROM_Read(uint8_t addr,uint8_t *data,uint8_t num)
{
I2C_GenerateSTART(EEPRON_I2C,ENABLE);//产生起始信号
while(I2C_CheckEvent(EEPRON_I2C,I2C_EVENT_MASTER_MODE_SELECT)==ERROR); //检测到EV5事件
I2C_Send7bitAddress(EEPRON_I2C,EEPROM_ADDR,I2C_Direction_Transmitter);
while(I2C_CheckEvent(EEPRON_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);//检测到EV6事件
I2C_SendData(EEPRON_I2C,addr); //发送EEPROM存储地址
while(I2C_CheckEvent(EEPRON_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING)==ERROR);
I2C_GenerateSTART(EEPRON_I2C,ENABLE);//产生二次起始信号
while(I2C_CheckEvent(EEPRON_I2C,I2C_EVENT_MASTER_MODE_SELECT)==ERROR); //检测到EV5事件
I2C_Send7bitAddress(EEPRON_I2C,EEPROM_ADDR,I2C_Direction_Receiver); //设置接收
while(I2C_CheckEvent(EEPRON_I2C,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)==ERROR);//检测到EV6事件
while(num--)
{
while(I2C_CheckEvent(EEPRON_I2C,I2C_EVENT_MASTER_BYTE_RECEIVED)==ERROR);//检测到EV7事件
*(data++) = I2C_ReceiveData (EEPRON_I2C);
if(num == 1)
I2C_AcknowledgeConfig(EEPRON_I2C,DISABLE);
}
I2C_GenerateSTOP(EEPRON_I2C,ENABLE);
I2C_AcknowledgeConfig(EEPRON_I2C,ENABLE); //重新使能以便下次通讯
}
主函数
由图中可以看出我在对EEPROM写入数据之后没有进行EEPROM的检测,立即发出了读据的指令。要知道在EEPROM写入数据的时候是无法检测外部IIC总线的变化,导致在我发出起始信号后EEPROM无法检测,程序陷入死循环。
执行结果:并没有从EEPROM中读出数据
检测函数
经过调试和查阅数据手册后知道,MCU的工作频率要远比EEPROM工作频率高,所以在MCU执行写字节后,
EEPROM会将收到的数据进行写入(写入方式8字节对齐),并且对外输出高阻态,无法监听总线变化。
由此,我们应当写一个函数来检测EEPROM在执行写操作后,是否会对总线做出反应。
检测函数:
void ReadForWait()
{
do
{
I2C_GenerateSTART(EEPRON_I2C,ENABLE);//产生起始信号
while(I2C_CheckEvent(EEPRON_I2C,I2C_FLAG_SB)==RESET); //检测到EV5事件
I2C_Send7bitAddress(EEPRON_I2C,EEPROM_ADDR,I2C_Direction_Transmitter);
}while(I2C_CheckEvent(EEPRON_I2C,I2C_FLAG_ADDR)==RESET); //检测到EV6事件
I2C_GenerateSTOP(EEPRON_I2C,ENABLE);
}
由于我在检测函数中检测标志位变化用的是上面IIC收发函数的检测方法,所以我在接下来运行的过程中发现程序还是无法运行。
在调试过程中发现检测函数一直处于循环状态:没有发生EV6事件,经过浏览历程后知道若将I2C_CheckEvent()函数替换为I2C_GetFlagStatus();程序便可以运行。
主函数:
输出结果:
CheckEvent()与GetFlagStatus()区别
CheckEvent(I2C_TypeDef * I2Cx , uint32_t I2C_EVENT )函数参数定义
FlagStatus I2C_GetFlagStatus ( I2C_TypeDef * I2Cx , uint32_t I2C_FLAG)参数定义
但单单从参数中我们不能看出两者之间的区别;
让我们来观察源码:
ErrorStatus I2C_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
uint32_t lastevent = 0;
uint32_t flag1 = 0, flag2 = 0;
ErrorStatus status = ERROR;
/* Check the parameters */
assert_param(IS_I2C_ALL_PERIPH(I2Cx));
assert_param(IS_I2C_EVENT(I2C_EVENT));
/* Read the I2Cx status register */
flag1 = I2Cx->SR1;
flag2 = I2Cx->SR2;
flag2 = flag2 << 16;
/* Get the last event value from I2C status register */
lastevent = (flag1 | flag2) & FLAG_Mask; //FLAG_Mask = 0x00FFFFFF 屏蔽SR2的PEC检测
/* Check whether the last event contains the I2C_EVENT */
if ((lastevent & I2C_EVENT) == I2C_EVENT)
//将SRx寄存器中的标志位与需要的标志位进行对比
{
/* SUCCESS: last event is equal to I2C_EVENT */
status = SUCCESS;
}
else
{
/* ERROR: last event is different from I2C_EVENT */
status = ERROR;
}
/* Return status */
return status;
}
FlagStatus I2C_GetFlagStatus(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG)
{
FlagStatus bitstatus = RESET;
__IO uint32_t i2creg = 0, i2cxbase = 0;
/* Check the parameters */
assert_param(IS_I2C_ALL_PERIPH(I2Cx));
assert_param(IS_I2C_GET_FLAG(I2C_FLAG));
/* Get the I2Cx peripheral base address */
i2cxbase = (uint32_t)I2Cx;
/* Read flag register index */
i2creg = I2C_FLAG >> 28;
/* Get bit[23:0] of the flag */
I2C_FLAG &= FLAG_Mask;
if(i2creg != 0) //判断要检测的标志位在哪个寄存器中
{
/* Get the I2Cx SR1 register address */
i2cxbase += 0x14; //若为SR1寄存器基地址偏移量为0x14;
}
else
{
/* Flag in I2Cx SR2 Register */
I2C_FLAG = (uint32_t)(I2C_FLAG >> 16);
/* Get the I2Cx SR2 register address */
i2cxbase += 0x18;
//若为SR1寄存器基地址偏移量为0x18;
}
if(((*(__IO uint32_t *)i2cxbase) & I2C_FLAG) != (uint32_t)RESET)
{
/* I2C_FLAG is set */
bitstatus = SET;
}
else
{
/* I2C_FLAG is reset */
bitstatus = RESET;
}
/* Return the I2C_FLAG status */
return bitstatus;
}
看到这时我发现两者之间的思想大同小异,在我看完这两个函数时还是很不清楚两者之间为什么会导致结果差异。
直到我看完参数的宏定义发现(以EV6检测为例):
I2C_CheckEvent宏定义:
#define I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED ((uint32_t)0x00070082) /* BUSY, MSL, ADDR, TXE and TRA flags */
I2C_GetFlagStatus宏定义:
#define I2C_FLAG_ADDR ((uint32_t)0x10000002)
从中我们可以发现当调用I2C_CheckEvent时传入的EV6检测会查询BUSY, MSL, ADDR, TXE 和TRA 状态值。而调用I2C_GetFlagStatus其函数提供给我们的宏则只是其中一个状态位的查询。由此不难理解,当我们调用I2C_GetFlagStatus函数检测EV6时,会检测当前IIC状态:总线上是否有数据进行通信,是否工作在主模式,外设地址是否已发送,数据寄存器是否为空,数据是否已发送。而用I2C_GetFlagStatus时,我们只需要知道ADDR位是否为1便可。
单从字面意思理解两个函数应当同样返回真值,但STM官方文档中SR1寄存器位7明确指出:– 在发送数据时,数据寄存器为空时该位被置’1’,在发送地址阶段不设置该位。这就是为什么调用两个意义相近的函数而返回的值却不同的原因了。在STM32学习中有很多函数与IIC中的函数相近,例如USART的状态检测一样,我们在日常编程中应当熟读手册规范编程。