本帖最后由 3htech 于 2012-8-24 16:45 编辑
小白菜的IIC学习之路
标准模式IIC模拟总线编写
一,为了那“可恶”的目的
2011年初,小白菜的工作比较清闲了,于是小白菜就开始IIC学习之路。
之前在用IIC总线(口线模拟)时,总感觉IIC移植时不够简介易懂,使用时,函数不能够适应所有IIC操作,于是小白菜想改写一下IIC总线操作,使之成为真正的万能IIC总线驱动。于是小白菜找到目标了:
一是编写一个移植性极其好的IIC总线操作,只需要简单的改动就能完成移植。
二是编写两个函数,一个是向器件发送数据函数,一个是从器件中读取数据函数。并且这两个函数真正适应所有不同的IIC器伯的操作。
二,过程抽象
为了达到以上的目的,小白菜需要先对IIC进行一次抽象。
1 单片机向器件发送数据时的抽象过程
(这里的抽象指的是抽象出不依赖具体器件的IIC操作)
发送时的流程:
(1) MCU启动总线
(2) 发送器件IIC地址 à
接收ACK信号
(3) 发送寄存器地址1 à
接收ACK信号 à [发送寄存器地址2 à
接收ACK信号]
有些器件可能没有寄存器地址(小白菜还没有遇到过),所以该步可能不需要。
(4) 发送数据1 à
接收ACK信号
à
发送数据2 à
接收ACK信号
à
发送数据3 à
接收ACK信号
……
(5) MCU关闭总线,发送完成。
我们来具体的分析一下,
第(1)步 启动总线,
第(2)步 发送1B数据(IIC地址),接收一个ACK信号
第(3)步 发送1B数据(寄存器地址1),接收一个ACK信号,发送1B数据(寄存器地址2),接收一个ACK信号
第(4)步 发送1B数据(数据1),接收一个ACK信号,发送1B数据(数据2),接收一个ACK信号……
第(5)步 关闭总线
写到这里,有些人可能看出来了,其实发送时不论数据或地址,都是一个相同过程,启动总线后,MCU发送一个字节,等一个ACK信号,再发送一个字节,等一个ACK信号,发送……,哎、发送完了,得了,关闭总线。
继续进行抽象,我们可以把第(2)、(3)、(4)步进行合并,得到IIC写操作抽象:
第(1)步 启动总线,
第(2) (3) (4)步 发送1B数据,接收一个ACK信号,直到发送完成
第(5)步 关闭总线
到这里之后,似乎是大功告成了,但是小白菜一想,这么多数据,参数肯定是用指针+数据字节数还进行传递,可是像读写EEPROM这样的器件,地址就丙三字节,但一次写入的数据可能有很多个字节,于是小白菜把(2) (3) 合在一起,把数据发送部分(4)单独拿出来。
小白菜得到了最终的IIC写操作的抽象
第(1)步 启动总线,
第(2) (3)步 发送1B数据,接收一个ACK信号,直到发送完成(发送地址)
第(4)步 发送1B数据,接收一个ACK信号,直到发送完成
(发送数据)
第(5)步 关闭总线
小白菜据此写出了函数名及形参和流程图(就是上面的抽象,所以嘛,就不写了)。
extern uint8 IIC_MCU_Send_Str(uint8 *PAddr,uint8 AddrNum, uint8 *PDataAddr, uint16 DataNum)
*PAddr :I第1批发送的数据的首地址。这部分IC地址以及子地址。PAddr[0]中存放IIC地址,后面的存放子地址。
AddrNum :IIC以及子地址的字节数。不可为0.
*PDataAddr :第2批发送的数据的首地址。这部分是发送的数据。
DataNum :第2批要发送的字节数(最大为65536个字节)。为0时不发送这一部分。
2单片机从有寄存器的IIC器件读取数据时的抽象过程:
发送时的流程:
(1) MCU启动总线
(2) 发送器件IIC地址 à
接收ACK信号
(3) 发送寄存器地址1 à
接收ACK信号 à [发送寄存器地址2 à
接收ACK信号]
有些器件可能没有寄存器地址(小白菜还没有遇到过),所以该步可能不需要。
(4) MCU重新启动总线
(5) 发送器件IIC地址(最低位置1以表明是读操作) à
接收ACK信号
(6)
接收数据1 à
发送ACK信号
接收数据2 à
发送ACK信号
接收数据3 à
发送ACK信号
……
(7) 接收最后一字节数据
à
发送非ACK信号
(8) MCU关闭总线,接收完成。
根据该流程,我们可以清楚地得到读取时的抽象:
第(1)步 MCU启动总线
第(2) (3) 步
发送1B数据,接收一个ACK信号,直到发送完成(发送地址)
第(4) 步 MCU重新启动总线
第(5) 步
发送器件IIC地址(最低位置1以表明是读操作) à
接收ACK信号
第(6) 步
接收前面的字节,发送ACK信号
第(7) 步
接收最后一字节数据,发送非ACK信号
第(8) 步 MCU关闭总线,接收完成。
虽然这里步骤多了,但是,函数参数也用不了几个,首先要知道地址吧,还要知道数据读出来后存放在哪里吧,小白菜想了想,这个函数的参数和写操作函数的参数一样就行了,
于是小白菜据写出了函数名及形参和流程图(就是上面的抽象,所以嘛,你懂得)
extern uint8 IIC_MCU_Rcv_Str(uint8 *PAddr,uint8 AddrNum, uint8 *PDataAddr, uint16DataNum)
*PAddr :发送的数据的首地址。这部分IC地址以及子地址。PAddr[0]中存放IIC地址,后面的存放子地址。
AddrNum :IIC以及子地址的字节数。不可为0.
*PDataAddr :存放所接收数据的首地址
DataNum :要接收的字节数。
三 奋笔疾书 + 代码移植
小白菜开始了写代码了。因为是小白菜嘛,所以一开始也不知道哪些地方在移植时需要修改,于是就开始先写代码,可能在写的时候就能知道了。写啊写,写啊写,终于让小白菜写完了。
写着写着还真让小白菜找出了哪里需要移植了。口线需要更改吧,不同的单片机,头文件不一样吧,还得有延时函数需要改吧……
于是小白菜把在移植时需要更改的地方做了一个“表”,放在H文件中的“移植修改”部分。这样,在修改时就可以只修改H文件中的一个地方,就能快速的完成移植。
现在想想,小白菜有的时候也不是那么菜嘛(偷笑ing)。
到现在为止,小白菜的目的已经达到了。突然,后背一凉,心里一个想法冒出来了,刚写完的代码还没测试就飘飘然了!哎,被胜利冲昏了头脑。于是小白菜自已测试了一番,Bug还真是有,改改更健康~~ |