打印

小白菜的IIC学习之路

[复制链接]
3132|14
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
3htech|  楼主 | 2012-8-24 16:37 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 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    I1批发送的数据的首地址。这部分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还真是有,改改更健康~~

IIC.rar

5.86 KB

小白菜的IIC学习之路.pdf

93.61 KB

相关帖子

沙发
3htech|  楼主 | 2012-8-24 16:38 | 只看该作者
使用说明
4.1 移植修改
移植修改都在H文件中的移植修改部分。下面进行具体说明。


//----------------------------------------------------------------------------//
// 编号1
// 名称
// 功能:单片机寄存器头文件,例如reg51.h
//----------------------------------------------------------------------------//
#include "ATT703x.H"


4.1.1
这部分是请您把使用的单片机的头文件包含进来。大虾们用过MCU多了,知道不同的MCU,其寄存器定义是不一样滴,不是所有的51单片机都用Reg51.HReg52.H头文件的。




//----------------------------------------------------------------------------//
// 编号2
// 名称SDA SCL
// 功能:模拟I2C数据传送位
//----------------------------------------------------------------------------//
#if defined(IIC_IO_ENABLE)
   sbit SDA = P0^0;    // 模拟I2C数据传送位。
   sbit SCL = P2^6;    // 模拟I2C时钟控制位。
#endif


4.1.2
SDASCL口线定义。这里就是您用的口线,如果您告诉我您不知道怎么改,好吧,你赢了……




//----------------------------------------------------------------------------//
// 编号3
// 名称IIC_Delay_1US()
// 功能:精确的1微秒延时函数。请根据您所用的单片机来正确设置。
//     :如果您的系统中有精确的微妙级延时函数,那么您可以直接使用。
//     :例如,您的延时函数是Delay_1us(),那么您可以使用下句
//     #defineIIC_Delay_1us()  Delay_1us()
//     :来实现延时。
//----------------------------------------------------------------------------//
#defineIIC_Delay_500ns()   _nop_();_nop_();_nop_();_nop_();_nop_();_nop_();
#define IIC_Delay_1US()   IIC_Delay_500ns();IIC_Delay_500ns();


4.1.3软件延时函数,这里是标准IIC,不是快速IIC1us的延时怎么做呢?当然是nop函数了。如果您不知道一个nop的执行时间,那么说明您需要好好看看手册了。
4.1.4
好多单片机都需要设置时钟,设置GPIO状态,所以在使用IIC之前,请一定确保MCU先初始化完毕。


4.2 函数说明


4.2.1 MCUIIC器件发送多字节数据函数
//----------------------------------------------------------------------------//
//                    MCUIIC器件发送多字节数据函数(对外提供服务)

//函数名称:IIC_MCU_Send_Str
//函数功能:MCUIIC从器件发送多字节数据。本函数是写IIC从器件的抽象函数。
//入口参数:
//   *PAddr   IIC地址以及子地址。PAddr[0]中存放IIC地址,后面的存放子地址。
//  AddrNum IIC以及子地址的字节数。不可为0.
//
//  *PDataAddr 2批发送的数据的首地址。这部分是发送的数据。
//   DataNum
2批要发送的字节数(最大为65536个字节)。为0时不发送这一部分。
//出口参数:0 = 操作成功,1 = 操作出错。
//重要说明:这是一个从启动IIC总线到发送数据再到最后结束总线为止的完整的发送过程。
//         数据发送的顺序是先发送PAddr[0],最后发送PAddr[AddrNum - 1],然后发送
//         PDataAddr[0],最后发送PDataAddr[DataNum - 1]
//         一般地,PAddr用于发送器件IIC地址和子地址,PDataAddr用于发送数据。
//         本函数对有无子地址的IIC器件都适用。
//----------------------------------------------------------------------------//
extern uint8 IIC_MCU_Send_Str(uint8 *PAddr,uint8 AddrNum, uint8 *PDataAddr, uint16 DataNum)


应用示例:
0x00字节地址开始写AT24C02,写入10个字节数(这里不考虑页写等待,因为和IIC写无关),这10B数据存放在unsigned char Buf[10]中,写入时要求Buf[0]写入0x00字节地址,Buf[1]写入0x01字节地址……。
A2A1A0全接地。(有人说我没说明WP的接法……我只有一个问题,你是来砸场子的么!!!)


首先组织IIC地址,设置一数组unsigned char Addr[2],其中Addr[0] = 0xA0Addr[1] = 0x00;
Addr [0]中存放的是(二进制表示)  1  0   1   0     A2  A1  A0  0LSB
Addr [1]中存放的是(二进制表示)  a7 a6  a5  a4    a3   a2  a1  a0LSB


调用时 IIC_MCU_Send_Str(Addr,2,  Buf, 10);
您还应当查看一下函数的返回值,是0表示操作成功,否则操作失败。


4.2.1 MCU从有子地址的IIC器件中接收多字节函数


//----------------------------------------------------------------------------//
//              MCU从有子地址的IIC器件中接收多字节函数(对外接口)

//函数名称:IICMCURcvStr
//函数功能:本函数用于有子地址的IIC器件的读操作。
//入口参数:
//     *PAddr    IIC地址以及子地址。PAddr[0]中存放IIC地址,后面的存放子地址。
//     AddrNum   IIC以及子地址的字节数。为0时出错.
//     *PDataAddr:存放所接收数据的首地址
//     DataNum
要接收的字节数。合法值1-65535。为0时出错。
//
//出口参数:0 = 操作成功,1 = 操作出错。
//重要说明:
// 读取的第一个数据存放在PDataAddr[0]中,第一个存放在PDataAddr[1]中……
// 有子地址的IIC器件的读操作是:
// MCU先启动总线,然后发送器件的IIC地址和需要操作的子地址
//(这一部分就是*PAddr),之后重新启动总线,再次发送器件的IIC地址且最低位置1以表明是读
// 操作,等待应答后便开始接收数据(这一部分是*PDataAddr),最后关闭总线。
//----------------------------------------------------------------------------//


extern uint8 IIC_MCU_Rcv_Str(uint8 *PAddr,uint8 AddrNum,  uint8 *PDataAddr, uint16DataNum)


读取的第一个数据存放在PDataAddr[0]中,第一个存放在PDataAddr[1]中……


应用示例:
0x00字节地址开始读AT24C02,读10个字节数并且存放在unsignedchar Buf[10]中,
A2A1A0全接地。(有人说我没说明WP的接法……还提这个问题!!)


首先组织IIC地址,设置一数组unsigned char Addr[2],其中Addr[0] = 0xA0Addr[1] = 0x00;
Addr [0]中存放的是(二进制表示)  1  0   1   0     A2  A1  A0  0LSB
Addr [1]中存放的是(二进制表示)  a7 a6  a5  a4    a3   a2  a1  a0LSB


调用时 IIC_MCU_Rcv_Str(Addr, 2,  Buf,  10);
这样,Buf[0]是读取到的0x00字节地址的数据,Buf[1]0x01字节地址的数据……


您还应当查看一下函数的返回值,是0表示操作成功,否则操作失败。




最后的废话


好吧,有人会说这个实现的有点罗嗦,不如直接以IIC地址,寄存器地址做参数来的方便;虽然有时候我也这么觉得。
但是、但是、但是什么呢?下期见!

使用特权

评论回复
板凳
3htech|  楼主 | 2012-8-24 16:46 | 只看该作者
欢迎各位童鞋拍砖,但谢绝人身攻击

使用特权

评论回复
地板
jany.wei| | 2012-8-24 17:45 | 只看该作者
小白菜

使用特权

评论回复
5
www5911839| | 2012-8-24 18:33 | 只看该作者
记录是个好习惯

使用特权

评论回复
6
3htech|  楼主 | 2012-8-24 22:04 | 只看该作者
5# www5911839
恩恩,谢谢前辈教诲

使用特权

评论回复
7
stuppid| | 2012-8-24 23:16 | 只看该作者
学习了,谢谢分享

使用特权

评论回复
8
hotpower| | 2012-8-25 04:20 | 只看该作者
估计一下

使用特权

评论回复
9
hotpower| | 2012-8-25 04:21 | 只看该作者
鼓励一下

使用特权

评论回复
10
ljp98| | 2012-8-25 09:11 | 只看该作者
学习了,顶!

使用特权

评论回复
11
完完全全| | 2012-8-25 11:44 | 只看该作者
很好的模块!连我这样的菜鸟都看懂了个大慨!

使用特权

评论回复
12
ghost_like_li| | 2013-8-19 15:27 | 只看该作者
楼主好人,好人一生平安

使用特权

评论回复
13
whxcxc| | 2014-5-4 16:41 | 只看该作者
我要下载!谢谢楼主分享

使用特权

评论回复
14
乁year| | 2014-5-4 19:49 | 只看该作者
好习惯,好成长!

使用特权

评论回复
15
YingziSeek| | 2014-12-18 15:07 | 只看该作者
很有用

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:我是一颗小白菜~!

20

主题

416

帖子

3

粉丝