昀晟电子技术网's Archiver

admin 发表于 2008-7-25 08:47

基于I2C总线的处理器联网设计

<H1><FONT color=blue size=4>基于I<SUP>2</SUP>C总线的处理器联网设计</FONT></H1>
<P>&nbsp;</P>
<P><FONT size=3></FONT>&nbsp;</P>
<P><FONT size=3></FONT>&nbsp;</P>
<P><FONT size=3>随着微控制器的价格越来越低,功能越来越强大,电气设计人员发现在单板和多板系统中都使用多个小型控制器是一种更加经济高效的方法。这种辅助<A href="http://www.ed-china.com/SEARCH/ART/%B4%A6%C0%ED%C6%F7.HTM"><FONT color=#0000ff>处理器</FONT></A>能够减轻主处理器在耗时任务上面的处理开销,例如扫描键盘、显示控制器和电机控制。这些控制器也可以配置为各种各样的专用外设。 </P>
<P>
<P>最近,我接受了一项任务:开发一种能够方便地适用于多种应用的接口(软/硬件),且要符合嵌入式处理器中常用的行业标准。在分析了一些典型应用之后,我们列出了一些针对该硬件接口的设计需求:常用于32位和8位处理器;能够得到常用外设器件的支持;外设接口代码量低于0.5kB;引脚数量少;数据带宽可达10kBps;RAM用量少;一条总线上支持多种外设;方便使用API;不需要外部接口驱动硬件。
<P>
<P>由于要求引脚数量少,所以必须采用串行接口。目前处理器中常见的串行接口包括SPI、I<SUP>2</SUP>C、USB和RS-232。通过从不同方面权衡比较这些接口,我最终选择了I<SUP>2</SUP>C,因为它接口简单,灵活性好,得到了大多数低成本控制器的支持。在不需要很高传输速度的情况下,较少的引脚数和流量控制功能还使得I<SUP>2</SUP>C接口相比SPI接口具有更大的优势。
<P>
<P><B>I<SUP>2</SUP>C的工作原理</B>
<P>
<P>I<SUP>2</SUP>C是一种双线双向接口,包括一个时钟信号和一个数据信号(SCL和SDA)。在不增加任何其他信号的情况下,一条I<SUP>2</SUP>C总线就可以支持多达12个设备。I<SUP>2</SUP>C接口规范包括三种工作速度:100kbps、400kbps和3.4Mbps。大多数常见的控制器只支持100-和400kbps两种模式。I<SUP>2</SUP>C总线支持一个主设备多个从设备,或者多个主设备的配置结构。
<P>
<P>I<SUP>2</SUP>C一个非常重要的特性就是它支持流量控制。如果某个从设备无法保持连续的字节传输,它可以将总线挂起,直到能够跟上主设备的传输速度。这对于包含最小规模的I<SUP>2</SUP>C硬件并且必须在固件上支持部分传输协议的从设备来说是非常有用的。I<SUP>2</SUP>C总线规范支持7b和10b两种寻址协议。我发现7b寻址模式在大部分应用中的效率更高。
<P>
<P>在开始编写代码之前,我们需要很好地了解I<SUP>2</SUP>C总线的工作原理。任何情况下I<SUP>2</SUP>C总线至少要包含一个主设备,至少要挂有一个或多个从设备。主设备总是由主到从发起数据传输操作。无论有多少个外设挂接在总线上,I<SUP>2</SUP>C接口只有两个信号。
<P>
<P>两个信号都是集电极开路的,通过大小为2.7k左右的上拉电阻接Vcc电源。SDA信号是双向的,可以由主设备或从设备驱动。SCL信号是由主设备驱动的,但是在一个数据字节的末尾从设备必须保持SCL信号为低,以延迟总线直到从设备开始处理数据。主设备在数据字节的最后一位传输完之后释放SCL信号,然后检查SCL信号是否变高。如果SCL没有变高,那么主设备认为从设备正在请求主设备延迟,直到其开始处理数据。
<P>
<P>当通过I<SUP>2</SUP>C总线发送数据时,只有当SCL为低电平时才能进行数据变换。当SCL信号为高时,任何方向的数据都应该是稳定的(如图1所示)。
<P>
<P>
<TABLE align=center>
<TBODY>
<TR>
<TD><IMG alt="基于I<SUP>2</SUP>C总线的处理器联网设计" src="http://www.ed-china.com/ARTICLE_IMAGES/200807/20080724_NC_CTRLD_TS_01F1.JPG" border=0></TD></TR></TBODY></TABLE>
<P>
<P>当总线空闲时,主设备和从设备都不下拉SDA和SCL信号。在发起一次数据传输时,主设备驱动SDA信号从高电平变成低电平,同时SCL信号为高。一般地,当SCL信号为高电平时,SDA信号的状态保持不变,但启动或停止条件下除外。当SCL信号为高并且SDA信号从低变高时,是传输停止的情况(如图2所示)。
<P>
<P>
<TABLE align=center>
<TBODY>
<TR>
<TD><IMG alt="基于I<SUP>2</SUP>C总线的处理器联网设计" src="http://www.ed-china.com/ARTICLE_IMAGES/200807/20080724_NC_CTRLD_TS_01F2.JPG" border=0></TD></TR></TBODY></TABLE>
<P>
<P>I<SUP>2</SUP>C总线以8b为单位传输数据。每传输一个字节时,必须得到数据接收方的确认。所有的数据都是从MSB(最高有效位)开始传输的。</P>
<P>&nbsp;</P>
<P>在每次传输开始的时候,由一个START命令启动本次传输,然后传输一个7位的从地址,接着传输R/W标记位。I<SUP>2</SUP>C标准也支持10位地址模式,但是本文的应用只需要7位地址。如果某个从设备识别出该地址,它就在ACK状态期间下拉SDA信号,然后释放。
<P>
<P>R/W标记位决定了主设备和从设备之间数据传输的方向。如果R/W位为低,那么数据从主设备向从设备传输。如果该位为高,那么表示由主设备读取从设备的数据。
<P>
<P>一个数据包内所有字节的传输方向都是相同的。每传输完一个字节,根据数据传输的方向,都要由主设备或从设备进行ACK确认。图3给出了一个多字节读写的实例。
<P>
<P>
<TABLE align=center>
<TBODY>
<TR>
<TD><IMG alt="基于I<SUP>2</SUP>C总线的处理器联网设计" src="http://www.ed-china.com/ARTICLE_IMAGES/200807/20080724_NC_CTRLD_TS_01F3.JPG" border=0></TD></TR></TBODY></TABLE>
<P>
<P>I<SUP>2</SUP>C接口可以看成是一种简单的栈结构。栈的最底层是物理层,由电信号组成。之上一层是传输协议。它定义了主设备和从设备之间寻址和数据传输的方式。自底向上第三层是“数据格式”层,通常由外设决定。它指出数据在外设中存储和寻址的方式。最顶层是“可选的命令协议”,它不是I<SUP>2</SUP>C规范的组成部分。这一层是由用户定义的。本文稍后将介绍一个可能的实现范例。
<P>
<P>由于数据格式层取决于外设,每种外设决定了应该存储什么格式的数据。大部分外设都具有一个或多个可以读或写的数据字节。某些外设甚至具有128个以上的可以由主设备访问的字节。
<P>
<P>为了优化数据传输,我们需要设计一种内部偏移机制,使得当主设备需要读或写第100个字节时,它不需要读或写之前的99个字节。因此,写序列中的第一个字节总是存储在外设中的数据阵列的偏移量。如果需要写多个字节,那么将按照由第一个字节决定的偏移量来写入第二个字节。
<P>
<P>偏移量是相对不变的,这意味着,如果在一个写序列之后进行一次读,那么被读取的数据将从之前写操作的偏移量开始。如果某个写序列中只发送了一个字节,那么只有偏移量指针发生变化。真正的数据将不会写入到外设(如图4所示)。
<P>
<P>在图4中,第一个序列表示从偏移量AA开始向某个外设写入三个字节。例如,如果外设有10个可以写入数据的字节位置,AA等于4,那么数据将被写入该序列的第5、6和7个字节,而偏移量为零时将会写入该序列的第一个字节。图4中的第二个序列只写偏移量。第三个序列从偏移量“BB”开始读取四个字节。如果再将第三个序列执行一遍,那么它将读出相同的四个字节。在指针改变之前,读操作总是从相同的偏移量开始。
<P>
<P>
<TABLE align=center>
<TBODY>
<TR>
<TD><IMG alt="基于I<SUP>2</SUP>C总线的处理器联网设计" src="http://www.ed-china.com/ARTICLE_IMAGES/200807/20080724_NC_CTRLD_TS_01F4.JPG" border=0></TD></TR></TBODY></TABLE>
<P>
<P>
<TABLE align=center>
<TBODY>
<TR>
<TD><IMG alt="基于I<SUP>2</SUP>C总线的处理器联网设计" src="http://www.ed-china.com/ARTICLE_IMAGES/200807/20080724_NC_CTRLD_TS_01F4A.JPG" border=0></TD></TR></TBODY></TABLE>
<P>
<P><B>外设API</B>
<P>
<P>在定义好与外部处理器的接口之后,我们需要定义从设备的API。一般而言,通信接口必须与外设的应用密切结合,但是如果除了几个配置API命令之外应用层甚至不必知道I<SUP>2</SUP>C接口的存在又该怎么样?这样您就可以很方便地增加I<SUP>2</SUP>C接口,而不必大幅度修改应用层。例如,您可以创建一个通过I<SUP>2</SUP>C主设备即可访问外部CPU存储器的接口,而该主设备只访问从设备允许的RAM区域。
<P>
<P>第一步是使I<SUP>2</SUP>C接口以ISR(Interrupt Service Routine,中断服务例程)的方式后台运行。这可以使主设备读写存储器的操作对外设应用是透明的,不需要轮询寄存器,不用重定向或拷贝数据,不需要在应用中交叉存储I<SUP>2</SUP>C接口的代码。</P>
<P>&nbsp;</P>
<P>配置API时必须告诉I<SUP>2</SUP>C ISR将数据放在哪里,以及它可以读写的数据边界或长度是什么。但是,您不希望I<SUP>2</SUP>C主设备访问它不应该访问的数据。例如,您不希望主设备偶然改写主应用栈。API应该将数据的位置和长度告诉接口。最好设置读/写区域和只读区域。
<P>
<P>图5给出了在外部CPU和I<SUP>2</SUP>C主设备之间映射存储器的方法。API命令“I<SUP>2</SUP>C_ SetRamBuffer (BufferSize, R/W_Length, DataPointer )”用于设置长度(BufferSize)、读/写长度(R/W_Length)和数据指针(DataPointer)。数据可以存储在外部CPU RAM空间的任何位置上。
<P>
<P>
<TABLE align=center>
<TBODY>
<TR>
<TD><IMG alt="基于I<SUP>2</SUP>C总线的处理器联网设计" src="http://www.ed-china.com/ARTICLE_IMAGES/200807/20080724_NC_CTRLD_TS_01F5.JPG" border=0></TD></TR></TBODY></TABLE>
<P>
<P>另一方面,I<SUP>2</SUP>C主设备只能看到API调用暴露的存储器。在这个例子中只能看到10个字节,并且只有前4个字节可写。不论存储缓冲放在哪里,主设备看到的都是从地址0x00开始直到地址0x09的一个数据序列。
<P>
<P>在该例子中,10个字节的数据定义为一个结构。应用在使用这些变量时就好像是在访问其他地址或者全局变量。如果在编译时将该结构定义为全局的,那么大多数编译器将会把它展平,使得在每次访问一个元素时不必计算偏移量。换句话说,使用这种结构不会产生额外的代码开销。
<P>
<P><B>实现</B>
<P>
<P>在设计好主从设备之间的接口之后,接下来需要编写一些代码。由于很多高性能的微处理器中都支持I<SUP>2</SUP>C接口,因此很多厂商都提供了支持I<SUP>2</SUP>C的开发工具和库。我们仍然需要编写一些自己的代码,这将加快应用研发的速度。例如,赛普拉斯PSoC微控制器中包含一些底层的I<SUP>2</SUP>C硬件,这些硬件可以通过PSoC Designer和专用的EzI<SUP>2</SUP>C用户模块进行定制。
<P>
<P>除了基本的硬件配置命令,例如启用和禁用接口的I<SUP>2</SUP>C_Start( )和I<SUP>2</SUP>C_Stop( ),大量的代码将通过ISR(Interrupt Service Routine,中断服务例程)来实现。底层的I<SUP>2</SUP>C硬件能够识别I<SUP>2</SUP>C总线的Start和Stop条件,在接收到从地址和R/W位的时候设置状态标记位。它不对地址匹配情况进行检查,而是通过固件执行这一任务。
<P>
<P>图6给出了基本的固件流程图。注意,该流程图不包含与特定制造商硬件相关的一些硬件细节。
<P>
<P>
<TABLE align=center>
<TBODY>
<TR>
<TD><IMG alt="基于I<SUP>2</SUP>C总线的处理器联网设计" src="http://www.ed-china.com/ARTICLE_IMAGES/200807/20080724_NC_CTRLD_TS_01F6.JPG" border=0></TD></TR></TBODY></TABLE>
<P>
<P>对于各个字节之间彼此相互独立的很多应用而言,这种接口能够满足需求。</P>
<P>&nbsp;</P>
<P>这个例子包含一个8针的PSoC CY8C27143-24PXI微控制器、两个LED、两个LED限流电阻、一个按钮和一个用于模拟可变电压的电位器。在内部,下列组件都有各自的实例元件:ADC、PGA、两个LED驱动器和EzI<SUP>2</SUP>C用户模块。本文所列出的代码是用户针对这一应用必须编写的固件。I<SUP>2</SUP>C接口代码由ISR进行处理。在本应用中,I<SUP>2</SUP>C主设备可以监测ADC的值,检查开关的状态,设置LED的状态。无须再次更改接口,这种接口就可以在很多研发项目上进行重用。
<P>
<P>图7给出了下至真正存储单元的存储器表达式。该项目使用了1076B的Flash和19B的RAM。I<SUP>2</SUP>C代码的大小约为275B,正好低于为这一接口分配的512B空间。
<P>
<P>
<TABLE align=center>
<TBODY>
<TR>
<TD><IMG alt="基于I<SUP>2</SUP>C总线的处理器联网设计" src="http://www.ed-china.com/ARTICLE_IMAGES/200807/20080724_NC_CTRLD_TS_01F7.JPG" border=0></TD></TR></TBODY></TABLE>
<P>
<P>某些应用需要在主从设备之间进行握手,而不仅仅是进行匿名的数据读写操作。对上述接口进行扩展以执行握手操作只需要对主和从设备/应用代码进行少量的增加。增加这一功能有很多种方式。
<P>
<P>例如,如果某次ADC变换的结果超过了8b,那么主机有可能读取一次ADC变换的MSB和下一次变换的LSB。如果读数非常稳定,那么没有问题。但是如果结果位于两个值之间,例如0x0200和0x01FF,您可能偶尔会读到0x02FF。
<P>
<P>为避免这一问题,我们可以增加一个命令字节或者信号灯。列表2给出了前一个例子修改后的结构。其中已经增加了一个新的元素“bCMD”,ADC结果变量从8位的“bADC”变成了16位的“iADC”。
<P>
<P>这样一来,外部固件不必盲目地更新ADC的结果,而是等待主设备的命令或者信号灯。该命令可以是任意非零的bCMD值,或者说bCMD值可以是从设备/外设能够执行的一个很宽的命令范围。为简化起见,LED和开关将继续不断更新。另一方面,iADC值仅仅当bCMD设置为非零值的时候进行更新。
<P>
<P>现在,应用监测bCMD,当它为非零值时,它将把最新的ADC结果放在iADC中,然后设bCMD为零。主设备然后监测bCMD,仅当bCMD恢复为零时才更新iADC。按照这种方式,主设备永远得不到不同步的ADC结果。命令/信号灯的规则是主设备可以设置它的值,从设备只能清除它的值。这就是之前所说的顶层“可选命令协议”的实现方式。我们不必设置比这种方式更复杂的机制。
<P>&nbsp;
<P>
<P><I>作者:Mark Hastings,电气工程师,赛普拉斯半导体公司,Email address: <A href="mailto:meh@cypress.com">meh@cypress.com</A></I></P>
<P><EM></EM>&nbsp;</P>
<P>&nbsp;</P></FONT>

页: [1]

Powered by Discuz! Archiver 7.0.0  © 2001-2009 Comsenz Inc.