DMA方式实现的I2C通信库.docx
《DMA方式实现的I2C通信库.docx》由会员分享,可在线阅读,更多相关《DMA方式实现的I2C通信库.docx(10页珍藏版)》请在冰豆网上搜索。
![DMA方式实现的I2C通信库.docx](https://file1.bdocx.com/fileroot1/2023-2/3/ac1d2080-acc5-40a6-a0f9-367bb2f0fd8e/ac1d2080-acc5-40a6-a0f9-367bb2f0fd8e1.gif)
DMA方式实现的I2C通信库
1.函数库接口
a)I2C_Comm_MasterSend
i.返回参数:
:
void
ii.函数参数:
1.端口:
I2Cx
2.从设备地址:
slave_addr
3.从设备偏移量:
offset
4.发送数据的内容:
pBuffer
5.发送数据的长度:
length
iii.函数意义:
从某个端口向slave_addr这个从设备发送数据pBuffer,并写入它的偏移量offset开始的地方。
如果从设备没有偏移量,则offset取值0xffffffff。
b)I2C_Comm_MasterReceive
i.返回参数:
void
ii.函数参数:
1.端口:
I2Cx
2.从设备地址:
slave_addr
3.从设备偏移量:
offset
4.接收数据存放的地方:
pBuffer
5.接收数据的长度:
length
iii.函数意义:
从某个端口向slave_addr这个从设备的偏移量offset的地方读取数据,放到pBuffer指定的地方。
c)I2C_Comm_SlaveReceive
i.返回参数:
void
ii.函数参数:
1.端口:
I2Cx
2.接收到的偏移量:
pOffset
3.接收数据存放的地方:
pBuffer
4.数据长度:
length
iii.函数意义:
从某个端口接收到length个数据放到从pBuffer开始,偏移量为*pOffset的地方。
d)I2C_Comm_SlaveSend
i.返回参数:
void
ii.函数参数:
1.端口:
I2Cx
2.接收到的偏移量:
pOffset
3.发送数据存放的地方:
pBuffer
4.数据长度:
length
iii.函数意义:
从某个端口接发出放在pBuffer的偏移量pOffset开始的地方length个数据。
注意1:
对于Slave函数来说,pBuffer始终指向的是它存放所有数据的总地址,真正要发送时,是从pBuffer开始的*pOffset开始的地方取出的数据;接受数据时,也是放到从pBuffer开始的*pOffset开始的地方。
另外一个参数pOffset也是从master传过来的,不管Slave是要接收还是发送数据,这个偏移量都是master来指定的。
注意2:
当使用STM32作为主设备时,如果对方从设备是类似E2PROM的器件,对其读写操作时,在“从设备偏移量Offset”这个参数指明;如果对方是比如另外一个MCU的I2C端口,双方只是通过I2C传递数据,那么“从设备偏移量Offset”这个参数可以指明0xff。
但是这样的通信协议,需要对方MCU知道,以处理收到的无效偏移量。
注意3:
当使用STM32作为从设备时,对方主设备应该是MCU。
需要对方MCU知道,需要发送“偏移量”这个参数来满足通信协议。
通信协议如下:
2.通信过程<事件/寄存器描述>
3.关于I2C总线的控制:
a)正常情况下,初始化I2C对应GPIO后,总线SDA&SCK都处于高电平。
从主设备发出START位开始,总线进入通信状态(BUSY@SR2被硬件置位),知道主设备发出STOP位,总线又恢复空闲状态(BUSY@SR2被硬件清零)。
b)异常情况:
i.主设备在通信过程中(不管是MasterSender还是MasterReceiver),因为异常重启,也就是没有发送STOP。
则SCK保持低,会导致I2C时钟一旦使能,BUSY@SR2马上被硬件置位。
这样的话,再想进行I2C通信,则不能成功。
[Solution:
]
(1)重新发送STOP,可以使得SCK恢复高电平。
或者软件置位SWRST@CR1,等SCK恢复高电平了,再清零
(2)SWRST@CR1
ii.主设备接收通信过程中,如果没有正确发出NAK,则SDA将保持低电平。
[什么叫做没有正确发出NAK:
在while和中断方式下,没有在合时的地方清零ACK@CR1;在DMA方式下,没有在合时的地方置位LAST@CR2]
[Solution:
]
(1)板子断电
(2)关掉I2CAPB时钟,恢复GPIO功能,将SDA推高,再重新使能I2C。
iii.…
注释1:
如果I2C总线上还有其他主设备,可能会造成争抢对总线的控制,从而带来总线仲裁的问题。
因此,这里可以不发送Stop,而是直接发送ReStart,以免是否总线。
注释2:
为什么会有0x20010(BUSY+STOPF)的情况。
当I2C2接收DMA完成后,处理TC中断。
在这个过程中,I2C1这个主设备发出的STOP出现在总线上。
即在I2C2还没有退出TC中断时,STOPF已经置位,对应中断挂起位也被置位。
于是,在I2C2退出TC中断后,又因为STOPF进入中断,在这个过程中,因为I2C1发出STOP后,马上进入check阶段,又开始总线通信,所以在I2C2看来,BUSY也被置位了。
于是,I2C2会有个SR2<<8|SR1=0x20010(BUSY+STOPF)的情况。
注释3:
中线虚线-红色:
数据传输阶段;
中线虚线-绿色:
check阶段。
这是为了统一作为接收的对方是一般性的MCU的I2C接口,还是E2PROM之类的器件。
后者需要一定的写内部逻辑完成时间,所以需要这个check阶段。
注释4:
代码中,I2C1(主)I2C2(丛)。
在各自的ISR,有trace数组test1[50]&test2[50],调试用。
但是如果接收方是E2PROM的时候,注释掉。
因为E2PROM的check阶段会比较长,若干次start+addr+(nak)过程,溢出数组。
注释1:
在此设置LAST@CR2,表示下一个DMAEOT是最后一次数据传输,在收到最后一个数据后,由主设备在总线上产生一个NAK信号。
注释2:
对于MasterReceive()没有check这个阶段。
以上流程对应的主设备/从设备代码已经保存为bak_AppNote@各自的文件夹
现在对以上项目作进一步修改:
4.修改后的函数库接口
a)I2C_Comm_MasterSend
i.返回参数:
:
void
ii.函数参数:
1.端口:
I2Cx
2.从设备地址:
slave_addr
3.从设备偏移量:
offset
4.发送数据的内容:
pBuffer
5.发送数据的长度:
length
iii.函数意义:
从某个端口向slave_addr这个从设备发送数据pBuffer,并写入它的偏移量offset开始的地方。
如果从设备没有偏移量,则offset取值0xffffffff。
b)I2C_Comm_MasterReceive
i.返回参数:
void
ii.函数参数:
1.端口:
I2Cx
2.从设备地址:
slave_addr
3.从设备偏移量:
offset
4.接收数据存放的地方:
pBuffer
5.接收数据的长度:
length
iii.函数意义:
从某个端口向slave_addr这个从设备的偏移量offset的地方读取数据,放到pBuffer指定的地方。
c)I2C_Comm_SlaveReceive
i.返回参数:
void
ii.函数参数:
1.端口:
I2Cx
2.接收数据存放的地方:
pBuffer
3.数据长度:
length
iii.函数意义:
从某个端口接收到length个数据放到从pBuffer开始的某个地方,偏移量由master传过来。
d)I2C_Comm_SlaveSend
i.返回参数:
void
ii.函数参数:
1.端口:
I2Cx
2.发送数据存放的地方:
pBuffer
3.数据长度:
length
iii.函数意义:
从某个端口接发出放在pBuffer某个地方的length个数据,偏移量从master传过来。
1.修改Slave读写函数的参数,没有pOffset,因为它是由Master传过来的。
Slave根据此,条件性地修改DMA参数。
done
2.于是全局变量OwnOffset在每次SlaveSend/Receive()通信中,接收offset时被赋值,并且一直保持。
直到下次调用SlaveSend/Receive()是被清零。
done
修改后的函数接口为:
3.进一步修改从设备操作的参数当STM32模拟E2PROM时,往往并不知道对方读写的数据长度!
维持函数接口不变,只是函数内部处理:
如果length=0xffffffff,表示从设备预先不知道通信数据长度;否则,length为有效值,表示从设备预先知道数据长度,以此可以直接初始化DMA参数。
Length=0xffffffff,将DMAtransferlength设置为max=65536。
注意:
初始化DMA参数结构,DMA_InitStructure.DMA_BufferSize=65535;而不是65536!
!
!
!
因为CNDTR寄存器为16位,如果写65536(0x10000),会把低有效的16位全部清零。
这样的话,“Ifthisregisteriszero,notransactioncanbeservedwhetherthechannelisenabledornot.”
[调试时就犯了这个错误!
使得总线只能出现master发送的前两个数据。
那是因为前两个数据已把slaveI2C模块的DR和shiftReg填满了。
没有slave的接收DMA传输,DR和shiftReg都是满的,master发送方自己的DR和shiftReg也是满的,无法触发发送法自己的发送DMArequest。
]
如何检测何时结束DMA。
所以说:
Master-->slave:
stop位在ACK后,slave的该标志位可以置位;
[Solution]可以用Stop中断来结束DMA传输。
这样的话,代码流程会有变化:
由于DMAtransfersize初始化成了最大值,所以代码到不了DMATC中断了。
由于没有“接收完最后一个数据后的ISR,就不会有在那个ISR过程中stopf置位,退出中断,在进入时check阶段开始,busy又置位的情况”。
于是0x20010(busy+stopf)+0x20002(busy+ADDR)+0x10(stopf)的序列变成:
0x10(stopf)+0x20002(busy+ADDR)+0x10(stopf)。
所以0x10case要做相应修改。
流程图中,灰色框表示改变的部分。
Master<--slave:
stop位在NAK后,slave的该标志位不被置位。
[Solution]在NAK中断结束DMA传输,反正没有check阶段。
同样,所以代码到不了DMATC中断了。
在i2c2_err_isr()的NAKcase中,添加结束代码。