最新日志

发表于:2008-7-18 21:52:01
标签:无标签

0

大型设计中FPGA的多时钟设计策略

利用FPGA实现大型设计时,可能需要FPGA具有以多个时钟运行的多重数据通路,这种多时钟FPGA设计必须特别小心,需要注意最大时钟速率、抖动、最 大时钟数、异步时钟设计和时钟/数据关系。设计过程中最重要的一步是确定要用多少个不同的时钟,以及如何进行布线,本文将对这些设计策略深入阐述。

FPGA设计的第一步是决定需要什么样的时钟速率,设计中最快的时钟将确定FPGA必须能处理的时钟速率。最快时钟速率由设计中两个触 发器之间一个信号的传输时间P来决定,如果P大于时钟周期T,则当信号在一个触发器上改变后,在下一个逻辑级上将不会改变,直到两个时钟周期以后才改变, 如图1所示。

传输时间为信号在第一个触发器输出处所需的保持时间加上两级之间的任何组合逻辑的延迟,再加两级之间的布线延迟以及信号进入第二级触发器的设置时 间。无论时钟速率为多少,每一个FPGA设计所用的时钟必须具有低抖动特性。抖动S是触发器的一个时钟输入到另一个触发器的时钟输入之间的最大延迟。为使 电路正常工作,抖动必须小于两个触发器之间的传输时间。

图2显示了如果抖动大于传输时间(S>P)将出现的情况,该电路用时钟的两个上升沿来延迟信号1。然而,信号1上的一个改变会在相同的时钟周期上传输到的信号3上,从而引起信号2的改变。因为S>P,电路将不能不正常。

须注意的是,时钟速率与传输延时并没有什么关系,甚至普通的100bps时钟也会出现抖动问题。这意味着虽然FPGA供应商宣称他们的芯片具有较短的传输时间和很高的时钟速率,但抖动问题可能会严重,甚至那些没有运行在最高速率上的设计也是如此。

好在FPGA供应商已经认识到时钟抖动的影响,并在他们的芯片中提供低抖动的布线资源。这些特殊的布线能够在芯片中一个给定范围内的任何两个触发器 之间提供一个确定的最大抖动。部分产品的低抖动资源覆盖了整个芯片,而其它的则可能只覆盖了FPGA逻辑块中的一个特定的行或列。对于一个需要很多不同时 钟源的设计,这些低抖动FPGA是比较理想的选择。

多时钟设计的最严重问题之一是用异步时钟将两级逻辑结合在一起。由于异步时钟会产生亚稳态,从而严重降低设计性能,或完全破坏设计所能实现的功能。 在触发器的时序要求产生冲突时(设置时间和保持时间)将产生亚稳态,触发器的最终输出是未知的,并使整个设计处于不确定状态。如果有一级逻辑要将数据异步 地发送到另一级,图3所示的情形将不能满足触发器的设置和保持时间要求。确切地说,如果设计中含有异步逻辑将有可能会产生亚稳态。在处置异步资源时必需非 常小心,因为这可能产生一些很严重的问题。

多时钟设计

本文以电信应用中的E3多路复用/解复用设计为例。如图4所示,多路复用器接收来自一组独立线路接口芯片的16个独立E1信道,每一个信道都工作于 2.048MHz;经复用后,这些E1流组合成4个E2流,分别工作在8.0448MHz;4个E2流最后组合成一个E3流,以34.368Mbps的速 率串行发送出去。在接收端执行相反的操作:解复用器从E3流提取4个E2数据流,然后从E2流提取16个E1流,最终将E1流发送到接收端的线路接口芯 片。

这些E1线路接口在发送和接收时都独立工作,因此2.048MHz的时钟速率可以有+/- 20ppm的偏差。同样,因为大多数系统同时发送和接收数据,分立的多路复用器和多路解复用器将提供2个独立的E3流(发送和接收)。因此,两个 34.368MHz的时钟可以存在细微的差异。

由于E2流是在芯片上产生的,这些E2多路复用器可以共享同一个8.448MHz时钟。然而,由于接收的数据速率与我们所设计的板无关(且不能假定所有E2多路复用器使用相同时钟),所以E2解复用器时钟必须能工作在略为不同的速率下。

此外,假定设计中需要一个由工作频率为1MHz的处理器控制的独立SPI(串行外围接口)总线接口,该接口用于状态和控制。这样一来,设计中总共用 了32个2.048MHz时钟,5个8.448MHz时钟,2个34.368MHz时钟和一个1MHz时钟,总共多达40个时钟。

本设计中最快时钟是34.368MHz E3时钟。FPGA的最大时钟速率的确定很重要,因为设计的差异将影响到该最大值。然而,在芯片商的资料手册中常常可以看到“全局时钟设置及保持时间”和 “至CLB输出的时钟”两个参数,将这两个参数的最大值相加,再增加25%就能可以得到最小时钟周期的初略值,在最大时钟速率条件下允许10%的余量,以 保证过热条件下能正常工作。因此,我们设置的最小速率为40MHz,很多较新的FPGA都能够很容易地支持该频率。事实上,FPGA供应商已经推出了超过 300MHz的器件。

在确定了能满足最大频率要求的FPGA后,就需要保证有足够的空间来实现你的设计。如果所选的FPGA没有足够的余量,就不能提供足够的布线资源来 满足设计的时序约束。通常芯片供应商宣称的速率是最佳条件下的速率,FPGA供应商一般建议FPGA逻辑在布线功能开始明显变差以前可以用到80%。在选 择FPGA器件时,建议在新的设计时最好使FPGA逻辑用到50%左右,这样就允许计算起始设计大小出现超差,以及为在设计起动后产生不可避免的设计变更 留出空间。如果最终的设计只占用低于50%的资源,则可以使用同一系列中较小的FPGA以降低成本。

通过时序约束来规定慢时钟速率,从而可以改进设计中最快时钟的布线。在多路复用器例子中,如果设置FPGA布线工具SPI总线时钟为1MHz,而 E3时钟为40MHz,布线工具将尽量使E3时钟的逻辑电路模块相邻布局。如果由于空间的限制而不能将全部电路布局在一起,则首先应将SPI逻辑另外布 局,因为SPI逻辑可以处理更长传输延迟。所有FPGA供应商的布线工具都能规定这些较慢时钟速率。

减少时钟数量

根据市场调查,目前还没有哪个FPGA器件能够支持这种多路复用器/解复用器设计所需的40个时钟。所以,我们必须减少所需要的时钟数。

首先了解E2和E3多路复用器的时钟。前面已经分析了4个E2多路复用器工作在相同时钟下的可接受度,E3多路复用器运行于比E2时钟高得多的速 率,必需使用一个不同的时钟。但是,如果我们从E3时钟中引出E2时钟是否可行呢?因为E3多路复用器要从每个E2支路得到数据,我们可以在需要E2多路 复用器给我们数据时,简单地将脉冲送给每个多路复用器。我们没有去掉任何时钟,但E2时钟现在是基于E3时钟。

如果在所有的多路复用器中也使用同样的时钟,并且只使用一个使能信号来告诉E2多路复用器什么时候工作,这时会产生什么问题呢?如果E3多路复用器 用34.368MHz时钟产生使能信号,在这些使能信号上的抖动不会比用在FPGA中任何其它同步逻辑更大。所以,使能信号可以使用正常(高抖动)布线资 源,这样就不需要单独的8.448MHz多路复用器时钟,读取E1数据缓冲器的数据时也是一样。换言之,如果E2多路复用器需要数据,它可以激活到特定缓 冲器的使能信号。到缓冲器的时钟本身能够保持E3多路复用器所用的34.368MHz时钟,如图5所示。

最后,我们检查16个从线路接口芯片输入到FPGA的E1时钟。这些时钟有会产生下面几个问题:首先,16个时钟将占用太多可用芯片时钟布线资源; 其次,在同一个FPGA中使用16个异步时钟来驱动相互邻近的触发器,由于地弹、串扰和其它效应将产生噪声问题。例如,由于噪声的原因,一个正边沿触发器 会在下降边沿时改变输出状态,此类问题将难以处理。

作为一种可能的解决方案,我们推荐使用一个最快的时钟来对16个E1时钟采样。16个输入时钟都接近2.048MHz,并且还有一个 34.368MHz的系统时钟。这样,我们可以用34.368MHz时钟来对16个E1时钟分别进行采样,并将结果存储在一个16位存储器中(每个E1时 钟一个位)。然后,我们可以使用一个算法来检测在E1时钟上由低至高的转换,为每一个E1数据信号产生一个使能信号,并在下一个周期 (34.368MHz)中存储数据。

要成功实现这种方案,还必需了解时钟-数据关系以避免在数据变化时对数据采样,参见图6。请注意在时钟采样电路的第一级中使用了两个触发器以确保在亚稳态下正常工作。另外需要注意的是,数据和时钟必须具有相同时钟周期数的延迟。

我们已经成功地将多路复用器的时钟减少到一个时钟,同样的方法可否用在解复用器呢?E3解复用器必须采用一个外部输入时钟,这是因为驱动E3输入数 据的同一个片外器件利用到该时钟。由于E3解复用器知道在什么时候发送数据到E2解复用器,并能对每个E2解复用器产生使能信号,而四个E2解复用器能工 作在与E3多路复用器相同的主时钟下。同样,E2多路复用器能够为每个E1流产生使能信号。

如果我们假设线路接口芯片能够接受有间隙的时钟(gapped clock),一旦确定发出E1使能信号,我们只需要发送一个时钟脉冲至线路接口。然而,只需要简单地发送使能信号本身至接口芯片而不必产生一个新的时 钟。因为送至接口的数据将在使能信号的下降沿产生改变(参见图5),我们需要确认接口在时钟的上升沿进行采样。因为使能信号仅在线路接口芯片上而不是在 FPGA内用作一个时钟,就没有必要在一个低抖动源中进行布线。注意这是在知道将再也不会用主时钟的连续脉冲送数据到相同的从属器件中时才这样做。

1MHz SPI时钟并不能简单去掉,但我们现在通过使用使能信号和时钟过采样技术,将原先40个时钟减少到3个,这样我们就有了更大的器件选择范围。

异步时钟

在用异步时钟产生任何逻辑前应该尽量先考虑采用其它替代方法,用异步时钟的组合逻辑是产生亚稳态问题的主要原因。同样,当违反触发器的设置和保持时间约束时,在一个短时间内输出将具有不确定性,并且将最终设定在“1”或“0”上,确切的状态不可预知。

幸运的是对于亚稳态性问题已经有一些解决方案。图6说明了这一方案,这是一种双寄存器方法:进入第一级触发器的数据与时钟异步,所以第一级触发器几 乎肯定是亚稳态;然而,只要亚稳态的长度小于时钟的周期,第二级触发器就不会进入亚稳态。但是,FPGA供应商很少提供亚稳态时间,尽管该时间一般小于触 发器的设置和保持时间之和。

如果时钟不是太快而且能满足时序约束的话,像图6所示的电路将可能不会产生亚稳态。只要所有输出到触发器的通路由相同时钟驱动,即使第一级触发器的 输出可用,通常还是需要用像图6中电路来将亚稳态隔离到一条短线。采用这种方法后,将不太可能出现由于电路的改变而无意地在无时钟驱动的逻辑中用到该亚稳 太线。

如果读数据的是一个计数器,像从一个异步FIFO读或写地址,你应该考虑下列情况:一个传统的3位计数器在状态之间有一个、两个或三个位的变化,例 如读数发生在计数器从“011”到“100”变化的瞬间,则所有三个位的值将不确定,读的值会是八种可能状态中的任一种。如果计数器是使用格雷码,如表所 示,则每次仅有一位发生状态改变,如果读数发生在计数器变化的瞬间,则只有一个位会有问题,所以在读操作中只有两种可能结果,而且这两种可能结果是计数器 正好在读以前的值和正好在读以后的值时。因为读正好发生在计数器产生变化的瞬间,你不可能确切地说哪个值是正确的,即两者都应该认为是有效的。

另一个避免异步时钟问题的方法是忽略较慢的时钟,并用较快的时钟来采样。这需要数据有特殊的成帧特性(例如,具有一个前导码)来定义数据边界。这是 一个常用的方法,在差不多每一个具有UART形式的嵌入式系统都有应用。该方法是:采用一个非常快的时钟,比如数据符号率的16倍,在连续发现15个起始 字符后开始采样,则下一个16(左右)位相当于送的第一个位,再下一个16(左右)位对应下一个位,并以次类推。

点击此处查看原文 >>

系统分类: 自由话题   |    用户分类: 无分类    |    来源: 无分类

评论(0) | 阅读(13)
发表于:2008-7-18 21:50:15
标签:无标签

0

四种常用FPGA/CPLD设计思想与技巧

本文讨论的四种常用FPGA/CPLD设计思想与技巧:乒乓操作、串并转换、流水线操作、数据接口同步化,都是FPGA/CPLD逻辑设计的内在规律的体现,合理地采用这些设计思想能在FPGA/CPLD设计工作种取得事半功倍的效果。

 

FPGA/CPLD的设计思想与技巧是一个非常大的话题,由于篇幅所限,本文仅介绍一些常用的设计思想与技巧,包括乒乓球操作、串并转换、流水线操 作和数据接口的同步方法。希望本文能引起工程师们的注意,如果能有意识地利用这些原则指导日后的设计工作,将取得事半功倍的效果!

 

乒乓操作

 

“乒乓操作”是一个常常应用于数据流控制的处理技巧,典型的乒乓操作方法如图1所示。

 

乒乓操作的处理流程为:输入数据流通过“输入数据选择单元”将数据流等时分配到两个数据缓冲区,数据缓冲模块可以为任何存储模块,比较常用的存储单 元为双口RAM(DPRAM)、单口RAM(SPRAM)、FIFO等。在第一个缓冲周期,将输入的数据流缓存到“数据缓冲模块1”;在第2个缓冲周期, 通过“输入数据选择单元”的切换,将输入的数据流缓存到“数据缓冲模块2”,同时将“数据缓冲模块1”缓存的第1个周期数据通过“输入数据选择单元”的选 择,送到“数据流运算处理模块”进行运算处理;在第3个缓冲周期通过“输入数据选择单元”的再次切换,将输入的数据流缓存到“数据缓冲模块1”,同时将“ 数据缓冲模块2”缓存的第2个周期的数据通过“输入数据选择单元”切换,送到“数据流运算处理模块”进行运算处理。如此循环。

图1

 

乒乓操作的最大特点是通过“输入数据选择单元”和“输出数据选择单元”按节拍、相互配合的切换,将经过缓冲的数据流没有停顿地送到“数据流运算处理 模块”进行运算与处理。把乒乓操作模块当做一个整体,站在这个模块的两端看数据,输入数据流和输出数据流都是连续不断的,没有任何停顿,因此非常适合对数 据流进行流水线式处理。所以乒乓操作常常应用于流水线式算法,完成数据的无缝缓冲与处理。

 

乒乓操作的第二个优点是可以节约缓冲区空间。比如在WCDMA基带应用中,1个帧是由15个时隙组成的,有时需要将1整帧的数据延时一个时隙后处 理,比较直接的办法是将这帧数据缓存起来,然后延时1个时隙进行处理。这时缓冲区的长度是1整帧数据长,假设数据速率是3.84Mbps,1帧长 10ms,则此时需要缓冲区长度是38400位。如果采用乒乓操作,只需定义两个能缓冲1个时隙数据的RAM(单口RAM即可)。


 
 
1 ? 2 ? 3 ? 4 下一页 末页

当向一块RAM写数据的时候,从另一块RAM读数据,然后送到处理单元处理,此时每块RAM的容量仅需2560位即可,2块RAM加起来也只有5120位的容量。图2

 

另外,巧妙运用乒乓操作还可以达到用低速模块处理高速数据流的效果。如图2所示,数据缓冲模块采用了双口RAM,并在DPRAM后引入了一级数据预 处理模块,这个数据预处理可以根据需要的各种数据运算,比如在WCDMA设计中,对输入数据流的解扩、解扰、去旋转等。假设端口A的输入数据流的速率为 100Mbps,乒乓操作的缓冲周期是10ms。以下分析各个节点端口的数据速率。

 

A端口处输入数据流速率为100Mbps,在第1个缓冲周期10ms内,通过“输入数据选择单元”,从B1到达DPRAM1。B1的数据速率也是 100Mbps,DPRAM1要在10ms内写入1Mb数据。同理,在第2个10ms,数据流被切换到DPRAM2,端口B2的数据速率也是 100Mbps,DPRAM2在第2个10ms被写入1Mb数据。在第3个10ms,数据流又切换到DPRAM1,DPRAM1被写入1Mb数据。

 

仔细分析就会发现到第3个缓冲周期时,留给DPRAM1读取数据并送到“数据预处理模块1”的时间一共是20ms。有的工程师困惑于DPRAM1的 读数时间为什么是20ms,这个时间是这样得来的:首先,在在第2个缓冲周期向DPRAM2写数据的10ms内,DPRAM1可以进行读操作;另外,在第 1个缓冲周期的第5ms起(绝对时间为5ms时刻),DPRAM1就可以一边向500K以后的地址写数据,一边从地址0读数,到达10ms 时,DPRAM1刚好写完了1Mb数据,并且读了500K数据,这个缓冲时间内DPRAM1读了5ms;在第3个缓冲周期的第5ms起(绝对时间为 35ms时刻),同理可以一边向500K以后的地址写数据一边从地址0读数,又读取了5个ms,所以截止DPRAM1第一个周期存入的数据被完全覆盖以 前,DPRAM1最多可以读取20ms时间,而所需读取的数据为1Mb,所以端口C1的数据速率为:1Mb/20ms=50Mbps。因此,“数据预处理 模块1”的最低数据吞吐能力也仅仅要求为50Mbps。同理,“数据预处理模块2”的最低数据吞吐能力也仅仅要求为50Mbps。换言之,通过乒乓操 作,“数据预处理模块”的时序压力减轻了,所要求的数据处理速率仅仅为输入数据速率的1/2。

 

通过乒乓操作实现低速模块处理高速数据的实质是:通过DPRAM这种缓存单元实现了数据流的串并转换,并行用“数据预处理模块1”和“数据预处理模块2”处理分流的数据,是面积与速度互换原则的体现!

 

串并转换设计技巧

 

串并转换是FPGA设计的一个重要技巧,它是数据流处理的常用手段,也是面积与速度互换思想的直接体现。

串并转换的实现方法多种多样,根据数据的排序和数量的要求,可以选用寄存器、RAM等实现。前面在乒乓操作的图例中,就是通过DPRAM实现了数据 流的串并转换,而且由于使用了DPRAM,数据的缓冲区可以开得很大,对于数量比较小的设计可以采用寄存器完成串并转换。如无特殊需求,应该用同步时序设 计完成串并之间的转换。比如数据从串行到并行,数据排列顺序是高位在前,可以用下面的编码实现:图3


prl_temp<={prl_temp,srl_in};

 

其中,prl_temp是并行输出缓存寄存器,srl_in是串行数据输入。

对于排列顺序有规定的串并转换,可以用case语句判断实现。对于复杂的串并转换,还可以用状态机实现。串并转换的方法比较简单,在此不必赘述。

 

流水线操作设计思想

 

首先需要声明的是,这里所讲述的流水线是指一种处理流程和顺序操作的设计思想,并非FPGA、ASIC设计中优化时序所用的“Pipelining”。

 

流水线处理是高速设计中的一个常用设计手段。如果某个设计的处理流程分为若干步骤,而且整个数据处理是“单流向”的,即没有反馈或者迭代运算,前一个步骤的输出是下一个步骤的输入,则可以考虑采用流水线设计方法来提高系统的工作频率。

 

流水线设计的结构示意图如图3所示。其基本结构为:将适当划分的n个操作步骤单流向串联起来。流水线操作的最大特点和要求是,数据流在各个步骤的处 理从时间上看是连续的,如果将每个操作步骤简化假设为通过一个D触发器(就是用寄存器打一个节拍),那么流水线操作就类似一个移位寄存器组,数据流依次流 经D触发器,完成每个步骤的操作。流水线设计时序如图4所示。

 

流水线设计的一个关键在于整个设计时序的合理安排,要求每个操作步骤的划分合理。如果前级操作时间恰好等于后级的操作时间,设计最为简单,前级的输 出直接汇入后级的输入即可;如果前级操作时间大于后级的操作时间,则需要对前级的输出数据适当缓存才能汇入到后级输入端;如果前级操作时间恰好小于后级的 操作时间,则必须通过复制逻辑,将数据流分流,或者在前级对数据采用存储、后处理方式,否则会造成后级数据溢出。

 

在WCDMA设计中经常使用到流水线处理的方法,如RAKE接收机、搜索器、前导捕获等。流水线处理方式之所以频率较高,是因为复制了处理模块,它是面积换取速度思想的又一种具体体现。

 

数据接口的同步方法

 

数据接口的同步是FPGA/CPLD设计的一个常见问题,也是一个重点和难点,很多设计不稳定都是源于数据接口的同步有问题。

 

在电路图设计阶段,一些工程师手工加入BUFT或者非门调整数据延迟,从而保证本级模块的时钟对上级模块数据的建立、保持时间要求。还有一些工程师 为了有稳定的采样,生成了很多相差90度的时钟信号,时而用正沿打一下数据,时而用负沿打一下数据,用以调整数据的采样位置。这两种做法都十分不可取,因 为一旦芯片更新换代或者移植到其它芯片组的芯片上,采样实现必须从新设计。而且,这两种做法造成电路实现的余量不够,一旦外界条件变换(比如温度升高), 采样时序就有可能完全紊乱,造成电路瘫痪。

 

下面简单介绍几种不同情况下数据接口的同步方法:

1. 输入、输出的延时(芯片间、PCB布线、一些驱动接口元件的延时等)不可测,或者有可能变动的条件下,如何完成数据同步?

 

对于数据的延迟不可测或变动,就需要建立同步机制,可以用一个同步使能或同步指示信号。另外,使数据通过RAM或者FIFO的存取,也可以达到数据同步目的。图4

 

把数据存放在RAM或FIFO的方法如下:将上级芯片提供的数据随路时钟作为写信号,将数据写入RAM或者FIFO,然后使用本级的采样时钟(一般 是数据处理的主时钟)将数据读出来即可。这种做法的关键是数据写入RAM或者FIFO要可靠,如果使用同步RAM或者FIFO,就要求应该有一个与数据相 对延迟关系固定的随路指示信号,这个信号可以是数据的有效指示,也可以是上级模块将数据打出来的时钟。对于慢速数据,也可以采样异步RAM或者FIFO, 但是不推荐这种做法。

 

数据是有固定格式安排的,很多重要信息在数据的起始位置,这种情况在通信系统中非常普遍。通讯系统中,很多数据是按照“帧”组织的。而由于整个系统 对时钟要求很高,常常专门设计一块时钟板完成高精度时钟的产生与驱动。而数据又是有起始位置的,如何完成数据的同步,并发现数据的“头”呢?

 

数据的同步方法完全可以采用上面的方法,采用同步指示信号,或者使用RAM、FIFO缓存一下。

找到数据头的方法有两种,第一种很简单,随路传输一个数据起始位置的指示信号即可,对于有些系统,特别是异步系统,则常常在数据中插入一段同步码(比如训练序列),接收端通过状态机检测到同步码后就能发现数据的“头”了,这种做法叫做“盲检测”。

 

上级数据和本级时钟是异步的,也就是说上级芯片或模块和本级芯片或模块的时钟是异步时钟域的。

 

前面在输入数据同步化中已经简单介绍了一个原则:如果输入数据的节拍和本级芯片的处理时钟同频,可以直接用本级芯片的主时钟对输入数据寄存器采样, 完成输入数据的同步化;如果输入数据和本级芯片的处理时钟是异步的,特别是频率不匹配的时候,则只有用处理时钟对输入数据做两次寄存器采样,才能完成输入 数据的同步化。需要说明的是,用寄存器对异步时钟域的数据进行两次采样,其作用是有效防止亚稳态(数据状态不稳定)的传播,使后级电路处理的数据都是有效 电平。但是这种做法并不能保证两级寄存器采样后的数据是正确的电平,这种方式处理一般都会产生一定数量的错误电平数据。所以仅仅适用于对少量错误不敏感的 功能单元。

 

为了避免异步时钟域产生错误的采样电平,一般使用RAM、FIFO缓存的方法完成异步时钟域的数据转换。最常用的缓存单元是DPRAM,在输入端口使用上级时钟写数据,在输出端口使用本级时钟读数据,这样就非常方便的完成了异步时钟域之间的数据交换。

 

2. 设计数据接口同步是否需要添加约束?

 

建议最好添加适当的约束,特别是对于高速设计,一定要对周期、建立、保持时间等添加相应的约束。

 

这里附加约束的作用有两点:

 

a. 提高设计的工作频率,满足接口数据同步要求。通过附加周期、建立时间、保持时间等约束可以控制逻辑的综合、映射、布局和布线,以减小逻辑和布线延时,从而提高工作频率,满足接口数据同步要求。

 

b. 获得正确的时序分析报告。几乎所有的FPGA设计平台都包含静态时序分析工具,利用这类工具可以获得映射或布局布线后的时序分析报告,从而对设计的性能做 出评估。静态时序分析工具以约束作为判断时序是否满足设计要求的标准,因此要求设计者正确输入约束,以便静态时序分析工具输出正确的时序分析报告。

 

Xilinx和数据接口相关的常用约束有Period、OFFSET_IN_BEFORE、OFFSET_IN_AFTER、 OFFSET_OUT_BEFORE和OFFSET_OUT_AFTER等;Altera与数据接口相关的常用约束有Period、tsu、tH、tco 等。

点击此处查看原文 >>

系统分类: 自由话题   |    用户分类: 无分类    |    来源: 无分类

评论(0) | 阅读(12)
发表于:2008-7-18 21:45:50
标签:无标签

0

基于NIOS II处理器的面阵CCD采集系统设计

SOPC (片上可编程系统)是Altera公司提出的一种灵活、高效的SOC解决方案,能将处理器、存储器、I/O 口等系统设计需要的功能模块集成到一个PLD器件上,构建一个可编程的片上系统。本文介绍的面阵CCD采集系统,利用Altera的SOPC Builder定制的NIOS II软核处理器及与采集相关功能的“软” 硬件模块来实现信号的采集。它与目前具有同样功能的CCD采集系统相比,具有灵活、稳定、易升级、易维护等优点。本系统的实验在现有的数码相机平台上进 行。

1 系统设计
    系统原理框图如图1所示。目标物经光学镜头成像于在RJ21P3AH0PT的光敏面上,产生电荷包强度分布的图像,RJ21P3AH0PT为隔列转移面阵 CCD,在四相垂直驱动脉冲和两相水平驱动脉冲来驱动下,通过CCD内部将光信号转化为电信号。当LR38642的时序发生器对其施加特定时序脉冲时,每 个像素的电荷信号将被依次移出CCD,并经放大变成电压幅度不等的模拟视频信号OS。OS视频信号送到LR38642的模拟前端电路并转换为数字信号,在 NIOS II处理器的控制下通过自定制的CCD采集控制接口以及NIOS II处理器的Avalon总线,将图像数据高速地存储到外部存储器SDRAM中。
 

2 系统硬件及功能模块
2.1 CCD芯片及专用控制芯片LR38642
    本系统面阵CCD采用夏普公司的RJ21P3AH0PT 图像传感器,如图2所示,它为1/1.8英寸,330万象素(2096*1560),有效象素为321万(2080*1544)。它具有高感广度,暗电流 非常小,要求的水平驱动脉冲频率为18MHz,需要4相垂直驱动脉冲和两相水平驱动脉冲来驱动。对其驱动控制选用专用集成控制芯片LR38642,它集成 了时序发生器(LR38617)、垂直驱动器电路(LR36685)以及模拟前端模块(IR3Y48A1)等功能,如图3所示。
 
LR38642功能框图 
2.2 NIOS II处理器
    本系统中FPGA选用Altera Cyclone EP1C6Q240,在实现对NIOS II处理器硬件开发时,使用嵌入在QuartuslI中的SOPC Builder来创建相应“软” 硬件,通过NIOS II Development Kit来实现相应模块之间的开发,为实现面阵CCD的采集所选用的相应外设自己定制,其中CCD采集控制IP 自定制。具体外设架构及内核结构框图如图4所示。
 
2.3 外同步行、场信号控制模块
    CCD芯片驱动控制及ADC采集的关键在于对时序的控制,特别是外同步HD、VD的控制要求很高,我们的系统中,采用计数分频方式实现HD、VD 信号的产生,考虑到EP1C6Q240本身资源有限,我们在NIOS处理器外用CPLDEPM7128AETC100-10来定制同步信号IP核,同时此 芯片中还包含有自动(手动)变焦控制,CCD 加(掉) 电控制等模块。该模块的关键在于行、场消隐以及行、场信号之间的对应关系,如果HD、VD信号不精确,系统将无法控制得到CCD的正常输出信号。该模块的 时钟HVCLK信号为36MHz晶振信号CKI经LR38642内部的2分频后而得到,控制采集时由NIOS II发送一启动控制信号START来启动外同步行、场信号控制电路。
2.4 SCCB控制模块
    SCCB总线控制是I2C总线的一种,仅靠两根连线就能实现全双工同步数据传送:一根串行数据线(SDA),一根串行时钟线(SCL),在数据传输过程中 严格依I2C总线协议,在我们的系统中,考虑到SOPc Builder中通用桥模块中没有Avalon Wishbone桥,我们利用PIO口模拟I2C总线方式来传送控制字。对于该面阵CCD采集控制时SCCB控制用两次,
第一次:通过 ED0~ED2来设置LR38642中相应的寄存器组来产生CCD所需要的驱动脉冲以及对控制电子快门等。这一过程在上电复位时进行,ED0为启动控制信 号,当ED0启动有效后,由ED1传送时钟脉冲,由ED2传送数据,当25位控制字符写到LR38642中相应的寄存器后,时序发生器电路产生不同垂直驱 动脉冲组合及相关功能。
第二次:在采集控制时,通过控制CSN、SCK 以及SDATA实现PGA 控制,双相关采样控制,黑电平补偿以及ADC的转换等功能。每次写入之后,为验证写入正确与否,把相应寄存器的数据读出并与写入的数据比较,如果相同,说 明写入正确,否则写入错误。图5给出了上电复位时实际配置波形。
 

2.5 ADC采集控制及存储模块(以帧累积模式为例进行说明)
    在帧累积方式下,采集控制时,先从CCD光电转换器读出模拟数据,通过VD 给CCD 一个长度为T1 (T1=9×55.7ns×2640,其中2640为一个HD的时钟CLK数,每个CLK为1/18M=55.7ns,9为保持9个HD) 的低电平,在VD)变为高电平时,CCD 在CLK 的同步控制下,HD先维持260个行消隐时钟的低电平,随后HD变为高电平,经464个时钟的积累时间,再经过24个时钟哑元象素,2个时钟黑电平象素输 出后,在CLK 的同步控制下输出2096个有用象素,输出完有用象素后,再次进入行消隐阶段,(进入行消隐的前54个时钟输出的为黑电平象素) 为下次行输出做准备。在帧累积模式下采用奇偶两场分别输出,每场848个HD,其中场消隐9个HD时间内VD为低电平,随后VD变为高电平,经58个HD 场累积时间,以及2个HD的黑电平象素后,在HD的同步控制下,输出780行有用象素,后再次进入场消隐阶段进入行消隐的前5个时钟输出的为黑电平象 素),依据先奇行后偶行的顺序串行输出视频OS信号。当传输完2096*1560个像素时表明当前一幅图像传输完毕,对于判别是否开始传输以及是否传输完 毕,在我们的系统中,通过自定义的CCD采集IP核来对数据作出判断。
    自定义的采集IP核依HD、VD信号确定什么时间开始为有用信号、什么时间为结束有用信号,什么时间表示一场(一行)数据开始(结束)等功能,知道了开始 传输、结束传输、行(场)开始与结束等信息后,在NIOS II处理器的控制下,可以依IORD宏指令很容易将数据写到SDRAM中。在我们的系统中SDRAM 选用采用HYNIX公司的HY57V561620CT-H型SDRAM存储器。它的容量是256Mbit(16M *16bit),速度可以达到133M赫兹。为便于开发系统中SDRAM 控制器选用SOPCBuilder中的IP核。
    下面给出基于自定义采集IP核以及GPIO读写宏指令实现数据采集的部分程序代码。
……
while (IORD_ALTERA_AVALON_PIO_DATA (CCD_VD_ADDR)== 0x00);//场消隐(场逆程)
while (IORD_ALTERA_AVALON_PIO_DATA (CCD_VD_ADDR)== 0x01);//场正程,开始新的场CCD_VD= IORD_ALTERA_AVALON_PIO_DATA (CCD_VD_ADDR);
while(CCD_VD ==0x00)//等待场消隐开始写入SDRAM ,如为场正程则不写入SDRAM
{
  while ((uchar)(IORD_ALTERA_AVALON_PIO_DATA (CCD_CLK_ADDR))== 0x00);
  *SDRAM_Point=IORD_ALTERA_AVALON_PIO_DATA (CCD_VD_ADDR);
  SDRAM_Point++;
}
2.6 CCD智能电源控制模块
    由于面阵CCD正常工作有严格的加(掉)电顺序,所以必须严格按技术文档要求按规定加(掉)电,对于电源模块,我们选用了MAXIM 公司的Max685,Max687分别提供+15V,-9V 和+3.3V。为确保加(掉) 电顺序,EPM7128AETC100-10内设计的电源控制模块(EPM7128供电+3.3V 与CCD供电模块分开),在系统上电后,当按下POWER键时控制Max685的SEQ 为高电平时,随后Max685在SHDN及SYNC的控制下先输出+15V,后输出-9V,而后控制Max687的SHDN 为高电平控制输出+3.3V。关闭(掉电)时,按下POWER键先控制Max687的SHDN 为低电平,关断+3.3V,后设Max685的SEQ 为低电平时,在SHDN及SYNC的控制下控制先关断-9V,再关断+15V。

3 系统软件
    本采集系统模块的软件主要有两部分:一是基于NIOS II的PIO 口实现模拟I2C总线功能,一是图像数据写入到SDRAM。其程序流程图分别如图7、图8所示。
 
 

4 结束语
    该面阵CCD采集系统利用NIOS II作为FPGA 嵌入式处理器的解决方案,能较容易地实现面阵CCD图像的采集,由于采用“软”硬件实现面阵CCD的开发,调试过程中能不断更改“软件”就可达到改进硬件 功能目的。经实验测试,在帧累积模式下可实现1.6s采集一幅静止图像。同时由于面阵CCD采集系统本身是一个复杂系统,不确定因素较多,如变焦控制、电 磁兼容性等,对该系统还有待作进一步的研究。

点击此处查看原文 >>

系统分类: 自由话题   |    用户分类: 无分类    |    来源: 无分类

评论(0) | 阅读(6)
发表于:2008-4-17 17:36:35
标签:c语言  嵌入式  

0

转载一个好东西——c语言里我们弄不清楚的东西真的还有很多

在一个网友的blog上看到的,好东西呀!以前一直认为自己c一点问题也没有了,看完这个,发现自己确实差的还远啊!好好学学!收藏!

C语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。这些年,我既参加也组织了许多这种测试,在这过程中我意识到这些测试能为带面试者和被面试者 提供许多有用信息,此外,撇开面试的压力不谈,这种测试也是相当有趣的。
从 被面试者的角度来讲,你能了解许多关于出题者或监考者的情况。这个测试只是出题者为显示其对ANSI标准细节的知识而不是技术技巧而设计吗?这个愚蠢的问 题吗?如要你答出某个字符的ASCII值。这些问题着重考察你的系统调用和内存分配策略方面的能力吗?这标志着出题者也许花时间在微机上而不上在嵌入式系 统上。如果上述任何问题的答案是“是”的话,那么我知道我得认真考虑我是否应该去做这份工作。
从面试者的角度来讲,一个测试也许能从多方面揭示应 试者的素质:最基本的,你能了解应试者C语言的水平。不管怎么样,看一下这人如何回答他不会的问题也是满有趣。应试者是以好的直觉做出明智的选择,还是只 是瞎蒙呢?当应试者在某个问题上卡住时是找借口呢,还是表现出对问题的真正的好奇心,把这看成学习的机会呢?我发现这些信息与他们的测试成绩一样有用。
有了这些想法,我决定出一些真正针对嵌入式系统的考题,希望这些令人头痛的考题能给正在找工作的人一点帮住。这些问题都是我这些年实际碰到的。其中有些题很难,但它们应该都能给你一点启迪。
这个测试适于不同水平的应试者,大多数初级水平的应试者的成绩会很差,经验丰富的程序员应该有很好的成绩。为了让你能自己决定某些问题的偏好,每个问题没有分配分数,如果选择这些考题为你所用,请自行按你的意思分配分数。
预处理器(Preprocessor)

1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)


#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
•; #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)


•; 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
•; 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
•; 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。
2 . 写一个“标准”宏MIN ,这个宏输入两个参数并返回较小的一个。


#define MIN(A,B) ((A) <= (B) ? (A) : (B))

这个测试是为下面的目的而设的:
•; 标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变为标准C的一部分,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
•; 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。
•; 懂得在宏中小心地把参数用括号括起来
•; 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?

least = MIN(*p++, b);


3. 预处理器标识#error的目的是什么?
如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。
死循环(Infinite loops)


4. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?
这个问题用几个解决方案。我首选的方案是:

while(1)
{
?}


一些程序员更喜欢如下方案:

for(;
{
?}


这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本答案是:“我被教着这样做,但从没有想到过为什么。”这会给我留下一个坏印象。
第三个方案是用 goto

Loop:
...
goto Loop;


应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。

数据声明(Data declarations)

5. 用变量a给出下面的定义
a) 一个整型数(An integer)
b)一个指向整型数的指针( A pointer to an integer)
c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an intege)r
d)一个有10个整型数的数组( An array of 10 integers)
e) 一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers)
f) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数 ( An array of ten pointers to functions that take an integer argument and return an integer )

答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
人 们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。但是当我被面试 的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或至少大部分答 案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?
Static
6. 关键字static的作用是什么?
这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:
•; 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
•; 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
•; 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。


Const

7.关键字const有什么含意?
我 只要一听到被面试者说:“const意味着常数”,我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有 用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你 从没有读到那篇文章,只要能说出const意味着“只读”就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的 答案,仔细读一下Saks的文章吧。)
如果应试者能正确回答这个问题,我将问他一个附加的问题:
下面的声明都是什么意思?

const int a;
int const a;
const int *a;
int * const a;
int const * a const;

/******/
前 两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型 数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数 是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键 字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:
•; 关键字const 的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃 圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)
•; 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
•; 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。
Volatile

8. 关键字volatile有什么含意?并给出三个不同的例子。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
•; 并行设备的硬件寄存器(如:状态寄存器)
•; 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
•; 多线程应用中被几个任务共享的变量

回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
•; 一个参数既可以是const还可以是volatile吗?解释为什么。
•; 一个指针可以是volatile 吗?解释为什么。
•; 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}

下面是答案:
•; 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
•; 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
•; 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:


int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}


由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}

位操作(Bit manipulation)

9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。
对这个问题有三种基本的反应
•; 不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
•; 用 bit fields。Bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用 的。我最近不幸看到 Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来 实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。
•; 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:


#define BIT3 (0x1 << 3)
static int a;

void set_bit3(void) {
a |= BIT3;
}
void clear_bit3(void) {
a &= ~BIT3;
}

一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=和&=~操作。
访问固定的内存位置(Accessing fixed memory locations)

10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:

int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;

A more obscure approach is:
一个较晦涩的方法是:

*(int * const)(0x67a9) = 0xaa55;

即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。

中断(Interrupts)

11. 中 断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键 字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。

__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf("\nArea = %f", area);
return area;
}

这个函数有太多的错误了,以至让人不知从何说起了:
•; ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
•; ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
•; 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
•; 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。

*****
代码例子(Code examples)

12 . 下面的代码输出是什么,为什么?

void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts("> 6") : puts("<= 6");
}
这 个问题测试你是否懂得C语言中的整数自动转换原则,我发现有些开发者懂得极少这些东西。不管如何,这无符号整型问题的答案是输出是 ”>6”。原因 是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。 这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。如果你答错了这个问题,你也就到了得不到这份工作的边缘。
13. 评价下面的代码片断:

unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
/*1's complement of zero */

对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下:

unsigned int compzero = ~0;

这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里,好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避免的烦恼。
到 了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那么我就 扔出下面的追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,而不是答案。不管如 何,你就当是这个娱乐吧…

动态内存分配(Dynamic memory allocation)
14. 尽管不像非嵌入式计算机那么常见,嵌入式系统还是有从堆(heap)中动态分配内存的过程的。那么嵌入式系统中,动态分配内存可能发生的问题是什么?
这 里,我期望应试者能提到内存碎片,碎片收集的问题,变量的持行时间等等。这个主题已经在ESP杂志中被广泛地讨论过了(主要 是 P.J. Plauger, 他的解释远远超过我这里能提到的任何解释),所有回过头看一下这些杂志吧!让应试者进入一种虚假的安全感觉后,我拿出这 么一个小节目:
下面的代码片段的输出是什么,为什么?

char *ptr;
if ((ptr = (char *)malloc(0)) ==
NULL)
else
puts("Got a null pointer");
puts("Got a valid pointer");

这 是一个有趣的问题。最近在我的一个同事不经意把0值传给了函数malloc,得到了一个合法的指针之后,我才想到这个问题。这就是上面的代码,该代码的输 出是“Got a valid pointer”。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要, 但解决问题的方法和你做决定的基本原理更重要些。
Typedef
:
15 Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:

#define dPS struct s *
typedef struct s * tPS;

以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?
这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是:typedef更好。思考下面的例子:

dPS p1,p2;
tPS p3,p4;

第一个扩展为

struct s * p1, p2;

.
上面的代码定义p1为一个指向结构的指,p2为一个实际的结构,这也许不是你想要的。第二个例子正确地定义了p3 和p4 两个指针。

晦涩的语法

16 . C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么?

int a = 5, b = 7, c;
c = a+++b;

这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成:

c = a++ + b;

因此, 这段代码持行后a = 6, b = 7, c = 12。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题。
好了,伙计们,你现在已经做完所有的测试了。这就是我出的C语言测试题,我怀着愉快的心情写完它,希望你以同样的心情读完它。如果是认为这是一个好的测试,那么尽量都用到你的找工作的过程中去吧。天知道也许过个一两年,我就不做现在的工作,也需要找一个。

点击此处查看原文 >>

系统分类: 单片机   |    用户分类: 无分类    |    来源: 转贴

评论(0) | 阅读(330)
发表于:2008-4-17 11:06:07
标签:H桥  电机驱动  

0

我的一个H桥,欢迎各位指点

TD340+IRF3205做的,请高手指点

 点击看大图

点击此处查看原文 >>

系统分类: 单片机   |    用户分类: 无分类    |    来源: 无分类

评论(2) | 阅读(183)
发表于:2008-4-17 10:42:15
标签:smt  贴片电阻  

0

常规贴片电阻(部分)


常规的贴片电阻的标准封装及额定功率如下表:
英制(mil) 公制(mm) 额定功率(W)@ 70°C
0201 0603 1/20
0402 1005 1/16
0603 1608 1/10
0805 2012 1/8
1206 3216 1/4
1210 3225 1/3
1812 4832 1/2
2010 5025 3/4
2512 6432 1

国内贴片电阻的命名方法:
1、5%精度的命名:RS-05K102JT
2、1%精度的命名:RS-05K1002FT
R -表示电阻
S -表示功率0402是1/16W、0603是1/10W、0805是1/8W、1206是1/4W、 1210是1/3W、1812是1/2W、2010是3/4W、2512是1W。
05 -表示尺寸(英寸):02表示0402、03表示0603、05表示0805、06表示1206、1210表示1210、1812表示1812、10表示1210、12表示2512。
K -表示温度系数为100PPM,
102-5%精度阻值表示法:前两位表示有效数字,第三位表示有多少个零,基本单位是Ω,102=10000Ω=1KΩ。1002是1%阻值表示法:前三位表示有效数字,第四位表示有多少个零,基本单位是Ω,1002=100000Ω=10KΩ。
J -表示精度为5%、F-表示精度为1%。
T -表示编带包装

 

点击此处查看原文 >>

系统分类: 单片机   |    用户分类: 无分类    |    来源: 无分类

评论(0) | 阅读(260)
发表于:2008-4-17 10:25:20
标签:单片机  程序设计  

0

如何优化单片机C语言代码

1、选择合适的算法和数据结构 ig 5Ce;P8R  
应该熟悉算法语言,知道各种算法的优缺点,具体资料请参见相应的参考资料,有
Y YA/QLJ6
 
很多计算机书籍上都有介绍。将比较慢的顺序查找法用较快的二分查找或乱序查找
[\U`si
 
法代替,插入排序或冒泡排序法用快速排序、合并排序或根排序代替,都可以大大
WNC["b[BFc
 
提高程序执行的效率。.选择一种合适的数据结构也很重要,比如你在一堆随机存
MOF?ni
 
放的数中使用了大量的插入和删除指令,那使用链表要快得多。
_/l3W4~{
 
数组与指针语句具有十分密码的关系,一般来说,指针比较灵活简洁,而数组则比
IM<5NL~:
 
较直观,容易理解。对于大部分的编译器,使用指针比使用数组生成的代码更短,
X&7cuoyv
 
执行效率更高。但是在Keil中则相反,使用数组比使用的指针生成的代码更短。。
,Lf)9$j+
 
+__%=}) 7k
 
@e #eL=
 
3、使用尽量小的数据类型
|+: <K
 
能够使用字符型(char)定义的变量,就不要使用整型(int)变量来定义;能够使用
["x w&d
 
整型变量定义的变量就不要用长整型(long int),能不使用浮点型(float)变量就
PrtTK#
 
不要使用浮点型变量。当然,在定义变量后不要超过变量的作用范围,如果超过变
_US\&h3
 
量的范围赋值,C编译器并不报错,但程序运行结果却错了,而且这样的错误很难
`i!Uner
 
发现。
{!7TN< Be
 
在ICCAVR中,可以在Options中设定使用printf参数,尽量使用基本型参数(%c、
>s=1zLx
 
%d、%x、%X、%u和%s格式说明符),少用长整型参数(%ld、%lu、%lx和%lX格式说明
{Br.Uz{
 
符),至于浮点型的参数(%f)则尽量不要使用,其它C编译器也一样。在其它条件不
g#\]JL@
 
变的情况下,使用%f参数,会使生成的代码的数量增加很多,执行速度降低。
< B5E&
 
 [  >~+
 
4、使用自加、自减指令
p&v))
 
通常使用自加、自减指令和复合赋值表达式(如a-=1及a+=1等)都能够生成高质量的
B?afPV(
 
程序代码,编译器通常都能够生成inc和dec之类的指令,而使用a=a+1或a=a-1之类
nQ_E]Tr3
 
的指令,有很多C编译器都会生成二到三个字节的指令。在AVR单片适用的ICCAVR、
w6c=Q%
 
GCCAVR、IAR等C编译器以上几种书写方式生成的代码是一样的,也能够生成高质量
;<{sTX
 
的inc和dec之类的的代码。
)g eTt
 
B#OKQ~(v
 
5、减少运算的强度
P|HL<\ur:W
 
可以使用运算量小但功能相同的表达式替换原来复杂的的表达式。如下:
ynq#9)O
 
(1)、求余运算。
h. $vMae
 
  a="a"%8;
U#j&$Vp1
 
可以改为:
qwezf(gMO
 
  a="a"&7;
}(:V[IwY
 
说明:位操作只需一个指令周期即可完成,而大部分的C编译器的“%”运算均是调
Yc*Hh n?[
 
用子程序来完成,代码长、执行速度慢。通常,只要求是求2n方的余数,均可使用
dzFID/2
 
位操作的方法来代替。
'}u+mMk#
 
qF:u,?7y
 
(2)、平方运算
=e3qj8cC<
 
  a="pow"(a,2.0);
AAz2,53
 
可以改为:
^2 $-4
 
  a="a"*a;
0a0HCra
 
说明:在有内置硬件乘法器的单片机中(如51系列),乘法运算比求平方运算快得多
fT"tHK0ER-
 
,因为浮点数的求平方是通过调用子程序来实现的,在自带硬件乘法器的AVR单片
Q7X Zh@
 
机中,如ATMega163中,乘法运算只需2个时钟周期就可以完成。既使是在没有内置
HQqb*.w
 
硬件乘法器的AVR单片机中,乘法运算的子程序比平方运算的子程序代码短,执行
 4(7@GW
 
速度快。
h3](wcZl?
 
如果是求3次方,如:
:g6'Q,|Z6q
 
  a="pow"(a,3.0);
-!!% 3S
 
更改为:
-faLjMp
 
  a="a"*a*a;
DFRn$tY0^
 
则效率的改善更明显。
l"T+d$Z
 
>K:\E?Ak@
 
(3)、用移位实现乘除法运算
l2H:$TXP[
 
  a="a"*4;
yAgD_(U'
 
  b="b/4";
 ~48S
 
可以改为:
kE8"j[
 
  a="a"<<2;
xuA9/
 
  b="b">>2;
T+F7oz y=
 
说明:通常如果需要乘以或除以2n,都可以用移位的方法代替。在ICCAVR中,如果
W]m&Pn,'
 
乘以2n,都可以生成左移的代码,而乘以其它的整数或除以任何数,均调用乘除法
y<8) .2]^7
 
子程序。用移位的方法得到代码比调用乘除法子程序生成的代码效率高。实际上,
XQWO<2wo:&
 
只要是乘以或除以一个整数,均可以用移位的方法得到结果,如:
]]B wl{
 
  a="a"*9
yDZtHp. mM
 
可以改为:
8s:$ez0!
 
  a=(a<<3)+a
gV+.nsh6-
 
:,]H~4U
 
6、循环
8~e Saa
 
(1)、循环语
^-q^V cN
 
对于一些不需要循环变量参加运算的任务可以把它们放到循环外面,这里的任务包
]hzk+ou
 
括表达式、函数的调用、指针运算、数组访问等,应该将没有必要执行多次的操作
LZp6m
 
全部集合在一起,放到一个init的初始化程序中进行。
:-jBeg<
 
7U ^X tO7
 
(2)、延时函数:
*/{4>W
 
通常使用的延时函数均采用自加的形式:
ODnWPT{x!
 
  void delay (void)
g^dQ[=(74
 
  {
r;= W66?r
 
unsigned int i;
d'V@_HZ_
 
  for (i=0;i<1000;i++)
JffvW{5cK
 
  ;
Dt?"xs5
 
  }
4:&7+8h
 
将其改为自减延时函数:
-5prF.!
 
  void delay (void)
KBCY@'
 
  {
|}M4cL+
 
unsigned int i;
Sm*Kg\Acdx
 
    for (i=1000;i>0;i--)
zLdL/T-n
 
  ;
x w!u'g
 
  }
Qk
 
两个函数的延时效果相似,但几乎所有的C编译对后一种函数生成的代码均比前一
L BM*=g1{
 
种代码少1~3个字节,因为几乎所有的MCU均有为0转移的指令,采用后一种方式能
~\7DL,9
 
够生成这类指令。
"D>h
 
在使用while循环时也一样,使用自减指令控制循环会比使用自加指令控制循环生
@Y`mR`]|:
 
成的代码更少1~3个字母。
BL\XjYQv
 
但是在循环中有通过循环变量“i”读写数组的指令时,使用预减循环时有可能使
w5b`sR\7`
 
数组超界,要引起注意。
<)+V]-B6Zv
 
: JN xAr
 
(3)while循环和do…while循环
)w=>|2pT
 
用while循环时有以下两种循环形式:
[:+ `
 
unsigned int i;
:\)w
 
  i="0";
YU.4AFxub
 
  while (i<1000)
kX9{0k
 
  {
l7X%@srn
 
    i++;
mRyh$LtQ`
 
  //用户程序
9pjyjp%#
 
  }
|"Rl\G*%
 
或:
f] Ng
 
unsigned int i;
m:!<$w8
 
  i="1000";
7adIfAI
 
  do
=&$EB{6=%
 
  i--;
W @.oUfo9
 
  //用户程序
<b; Olb83
 
  while (i>0);
<;7CXe*z
 
在这两种循环中,使用do…while循环编译后生成的代码的长度短于while循环。
a>7N~L'.G
 
D^>i\h%
 
7、查表
=2d87b)
 
在程序中一般不进行非常复杂的运算,如浮点数的乘除及开方等,以及一些复杂的
~y!vq2z &
 
数学模型的插补运算,对这些即消耗时间又消费资源的运算,应尽量使用查表的方
C\3Je/D
 
式,并且将数据表置于程序存储区。如果直接生成所需的表比较困难,也尽量在启
;{!Vt&4;W
 
了,减少了程序执行过程中重复计算的工作量。
*$Y(r:Af<
 
77cUy,C
 
8、其它
C;D>k lJN
 
比如使用在线汇编及将字符串和一些常量保存在程序存储器中,均有利于优化

点击此处查看原文 >>