stm32硬件iic
一. 对于BTF(Byte Transfer Finished)何时置1,给出详解。
上述给出了手册中对BTF位的定义,以及在主发送模式下和主接收模式下,BTF置为时机的描述。
---------------------------1.主发送模式下BTF置位详解
、
---------------------------2**.主接收模式下BTF置位详解**
/
/
总结:
BTF 的核心作用是标志移位寄存器完成当前字节的发送或接收,并协调数据寄存器的状态。它为主机提供了关键的控制点,用于:
发送:判断是否继续写入数据或结束传输。
接收:及时读取数据以确保接收过程的连续性。
最终,BTF 的设计目的是确保 I2C 通信过程的高效性和可靠性。
/
/
/
二,使用dma配合硬件iic传输
1.dma的工作模式要选择单次模式
如果设置dma为循环模式,可能出现问题
例如
下面是我实际遇到的一个问题,配置dma为循环模式,使用按键的外部中断,在按键中断的回调函数中,启动HAL_I2C_Mem_Write_DMA(),对at24c02存储器写入指定数据,如果写入完成在iic存储器写入完成回调函数中,用串口输出 uart_printf(‘写入成功’),出现的问题是在按按键第一次的时候,能写入成功,可以看到串口发来的‘写入成功’,而后面接着按按键,就会出现没反应的现象。
在经过我debug后发现,这由于dma连续模式下,转运完成数据后,其state=HAL_DMA_STATE_BUSY,而并没有改变为HAL_DMA_STATE_READY,因此下次按键调用HAL_I2C_Mem_Write_DMA(),在其中调用HAL_DMA_Start_IT(),因为dma状态仍为HAL_DMA_STATE_BUSY,dma此刻认为此刻有数据传输而配置失败,因此导致只能通过按键写入一次数据传输。
归根结底是HAL引入状态机的,符合相应状态,执行相应操作!
处理方法:手动将连续模式下的dma在完成一次传输后,将状态更改HAL_DMA_STATE_READY
即可。
三,带seq的操作与不带seq的操作区别
HAL_I2C_Master_Transmit_IT
这是一个简单的发送函数,适用于不需要复杂通信序列的情况。发送数据后,通信结束,I2C总线会生成一个 STOP 信号。
用于一次性传输数据。
适合与只需要一段数据传输的从设备通信。
HAL_I2C_Master_Seq_Transmit_IT(uint32_t XferOptions)
这是一个更高级的函数,支持序列传输模式,即可以在一次通信中实现多阶段传输,例如带有重复启动条件的连续传输。适合需要更复杂的通信序列。
支持有Restart条件的通信。
常用于多个连续数据段传输(如读取寄存器后紧接着写入数据)。 HAL_I2C_Master_Seq_Transmit_IT 根据参数XferOptions 可以选择是否发送 STOP 信号,或者是否继续传输下一段数据。
不同的XferOption区别在于是否有start,stop信号,这很关键,乱选胡导致时序出现问题,影响通信。
四,HAL库硬件iic,在主接收模式下,只能接收一次数据的问题,bug
(鄙人是在stm32f103的HAL库中,发现的,其他系列的HAL库,鄙人没看,详细阅读其他系列源码即知,还有在下面图片中,为了能在一张图片中完整显示调用HAL_I2C_MasterRxCpltCallback的情况,其中HAL库的提供的函数有删减,而保留了关键部分,正如我上述所说,只是为了能在一张图片中更好的显示而别无作用)
//注意标题所说,仅在主接收模式有问题,因此其他的从接收模式,从存储器接收模式等均正常。
/
问题描述:主机stm32f1操作BH1750光敏模块时,在主机向BH1750发完命令后,调用HAL_I2C_Master_Seq_Receive_DMA()此函数,从BH1750读数据,发现只能读一次数据,而第二次会出现无法读数据的情况。
原因:HAL库中iic句柄有这两个成员:PreviousState(反应先前iic的状态)和State(当前状态)
HAL库中在函数中调用HAL_I2C_MemRxCpltCallback(),主接收完成回调函数共有3处(下面图片即为这几处),在调用主接收完成回调函数之前,对iic句柄状态变量的处理,可以看出,都将PreviousState = I2C_STATE_MASTER_BUSY_RX表面上一状态为主机接收模式,思索可以发现,三处均将PreviousState = I2C_STATE_MASTER_BUSY_RX,有问题,主机可以接收单帧也可以接收多帧,
**(1)**如果在接收单帧的情况下,接收完这一帧就产生结束条件终止本次接收,而上一状态就应该是无状态,不然就会影响下次接收,因为HAL库是状态机处理,上次的状态会影响此次的处理,因此需要一个函数,在调用主接收完成回调函数之前,将PreviousState =I2C_STATE_NONE。
**(2)**如果是在接收多帧的情况下,为了表明此次传输没有结束,有函数将PreviousState = I2C_STATE_MASTER_BUSY_RX是合理的,但是必然需要一个函数,在调用主接收完成回调函数之前,将PreviousState =I2C_STATE_NONE。来标志此次传输结束。
/---------------------------------------HAL库iic句柄中的PreviousState与State---------------------------------/
/--------------------调用HAL_I2C_MemRxCpltCallback()前的三个函数源码--------------------------/
/----------------------------------------------------------------------------------------------------------------------------/
/------在调用主接收完成回调函数之前, 都将PreviousState = I2C_STATE_MASTER_BUSY_RX的后果
当你再次调用函数HAL_I2C_Master_Seq_Receive_DMA接收数据时,正如上述所说,此时PreviousState =为I2C_STATE_MASTER_BUSY_RX,读下面给出的源码可知,此时清除了应答位也就会导致主机不会对从机发的数据表示应答,并且设置了其他的两个位,会导致主机不对从机发的数据应答,并且主机会产生结束信号,结束此次接收。
这也就解释了为什么,只能接收一次数据。
/-------------------------函数HAL_I2C_Master_Seq_Receive_DMA源码----------------------------------/
/
/--------下图为给出的一张 在 调用发送完成回调函数 之前的一个函数对于标志位的处理,在这个示例中有对PreviousState置busy的,也有对PreviousState置NONE。------/
/
解决方案1.:在主机接收完成回调函数处理完需求后 , 调用 HAL_I2C_Master_Seq_Transmit_DMA来发送0个数据,这是允许的,来将标志位处理到正常状态,其中函数要传入的参数XferOptions,自行具体选择。 (原因即为PreviousState为Busy_RX对下次写入没有影响,而对下次继续接收有影响,读源码即知)
/----------------------------------------------------------处理示例-------------------------------------------------------/
五,iic序列化操作,一次操作中 写入与读取 交叉进行。
例如,主机 发->收->收 正如上述bug点所说,最后主机最后一下接收会导致出bug,影响下次接收。 因此优化为 主机 发 -> 收 -> 收 -> 发(发0个数据,仅作为该状态标志位作用) 主机从机 收和发每次都传输3Byte数据
XferOptions依次选择为 I2C_FIRST_FRAMEI2C_NEXT_FRAME I2C_LAST_FRAME_NO_STOP ------------------------------------------------------------------------------------------------------------------------- I2C_LAST_FRAME 对于从机而言,首先接收3字节数据,然后发送6字节数据。
**注意点:**这里从机一次性发送6字节数据,而不是分两次,一次传3字节数据,因为反向操作例如收和发之间是 有restart信号和再次发地址信号的,而同向操作之间(例如收收之间)是没有这些信号的,对于本例子中,从机需要一次性发送6字节数据。
如果分两次发送数据,会导致SCK始终拉低,等待从机发数据,而一直得不到,就会导致以后的任何收发操作都无效,因为SCK被拉低了。
/---------------------下图为从机分两次发数据,一次发送3字节数据的时序图--------------------------/
下图给出正常情况,从机一次发送6字节数据的时序图,从图中你可以看出我们上面所说的反向操作间有restart信号,而同向信号间无restart信号。