STM32 中,硬件I2C每次动作都会伴随着事件的产生,本次笔记是对I2C作为主机时产生的几个事件进行讲解。
以I2C硬件发送为例:
void I2C_READ_BUFFER(u8 SlaveAddr,u8 readAddr,u8* pBuffer,u16 NumByteToRead){
//I2C发送字符串
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY));
//读取BUSY标志位,等待I2C空闲后进入下列程序
I2C_GenerateSTART(I2C1,ENABLE);//I2C发送起始信号
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT));
//I2C_CheckEvent:检查最近一次 I2C 事件是否是输入的事件 本次事件为EV5
//对照主发送器传送序列图,本次事件在开始发生在之后
//在发送起始信号后,函数就开始了EV5的检测,产生EV5的条件就是开始信号发送完成
//当开始信号未发送完成,ev5事件不会产生,会一直卡在while循环中
//本次事件可以确保起始信号正常发送,并且事件会在结束while循环后写地址操作时,对事件进行重置
I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Transmitter);
//(I2Cx,从机地址,传送数据方向): 此功能为发送从机7位地址
//并且当地址数据填入DR数据寄存器时,硬件自动清除EV5事件
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
//本次事件为EV6,事件发生在主机发送地址,从机接收地址并且答应后
//在我们发送地址数据后,就开始了事件检测,当检测到从设备产生答应信号时,事件产生并跳出循环
I2C_Cmd(I2C1,ENABLE);
//例程自带,删了也对程序无影响......不知其功能,233
I2C_SendData(I2C1,readAddr);
//发送从设备上的内存的地址,本质也就是发送一个字节的数据
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));
//事件EV8,当位移寄存器非空,数据寄存器空时产生,也就是在数据发送时候就产生了
//此事件的产生代表着I2C已经将数据从数据寄存器发送到位移寄存器也就是SDA线上了
//也就是数据发送完成,标志位置位了,主机正常发送,当然从机有没有正常接收就是未知了
//当重新开始需要发送新的数据时,EV8事件自动重置
//简单解释为数据发送成功,标志位值位,数据未发送完成,会卡在while循环,等待发送完成
I2C_GenerateSTART(I2C1,ENABLE);
//LM75A,EEPROM之类设备都有类似操作,将写操作转换为读操作
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT)); //ev5
I2C_Send7bitAddress(I2C1,SlaveAddr,I2C_Direction_Receiver);
//(I2C1,从机地址,方向为读)
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
//EV6,确保从机接收到数据并答应后跳出
while(NumByteToRead){ //NumByteToRead表示剩余所要读取的字节数量
if(NumByteToRead == 1){
//当最后一个字节时候,主设备要关闭答应
I2C_AcknowledgeConfig(I2C1,DISABLE);
I2C_GenerateSTOP(I2C1,ENABLE); //产生停止信号
}
if(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED)){
//事件7,表示数据都已经接收到了并保存在数据寄存器中了
//关于事件7,可以看下图,事件7产生在上个数据接收完成并答应之后
//产生数据7时候,数据寄存器非空,当对DR读数据时,ev7重置
//文章末尾还有对此事件的讲解
*pBuffer = I2C_ReceiveData(I2C1);
//保存数据到数组中
pBuffer++; //数组加1
NumByteToRead--; //还需读取字节数减1;
}
}
I2C_AcknowledgeConfig(I2C1,ENABLE);
//重启答应位,保证下次能够正常使用I2C.
}
刚开始我对于为什么在接收最后一个字节前就先直接关闭答应位,先关闭答应位和发出停止信号,不是会对之后接收最后要给字节的数据有影响吗?
后来,我参考图246,解开了一些困惑,当你对本次接收到的数据从DR中读取时,下一组数据已经在总线上进行发送了,但是看上图发现,EV7_1 事件,当你在DR读取上一组数据,下一组数据还在位移寄存器中,当新的数据还没完全移送到DR中是不会产生答应的…I2C的最大传输速率也就在 400K,而单片机频率达到了Mhz的级别,所以当你处理完接收到上组数据,新的数据还没完全接收成功,这中间足够有事件关闭答应位了.
至于停止位使能为什么也会放在读取最后一个函数前处理,就在刚刚我也有了自己的理解:停止位使能后,并不是马上将总线停止的,他需要等到总线上发出了非答应位后,才会产生停止信号.
说实话,感觉有点绕,但是最简单的办法起始就是直接参考时序图进行编程就是了.
添加要给刚刚对接收数据答应位的一个误解,我刚开始以为答应位是在你读取数据后主机才发送的答应位,结果突然发现好像不是这么理解的,正确的应该是当你接收到EV7之前,硬件I2C就已经对从机发出答应信号了,当你使能了答应位,每次完整接收到数据,主机都会自动答应,而程序在while函数中用if函数一直检测EV7的产生.
也就是说,当你主机自动答应后,新的数据是马上开始传送过来的,所以要实时检测ev7事件的发生,并对事件进程处理.
其实你会发现,如果去了事件的产生,那么他和软件I2C时序看起来没什么区别,但是软件I2C中通常我们会对时序的把控更加自如,而对于硬件I2C我们需要对其产生的事件进行检测处理,以确保能够数据的准确性.
就拿EV7事件来说,如果你检测到EV7事件后不马上对其进行处理,可以做个实验,如果从机一直发送数据过来,不马上处理DR中的数据,延迟个1s后再进行读取,会发现你读到的不是第一个接收到的数据,而是最后一个......你软件I2C对于时序的调控就会比硬件更加自如一点.
以上是对硬件I2C基础应用的个人的一些见解…