一只鱼 的个人资料EE小站照片日志列表更多 工具 帮助

日志


9月22日

小述电磁兼容

 
我必须写点什么证明这个Blog还在我的维护之下,呵呵。
 
先赞一下我公司的领导,终于请专业咨询公司为我们进行了为期两天的电磁兼容知识的培训;收获还真的不小,困扰我很久的一些问题有了明确的理论答案,下面就要针对我的实际工作应用这些理论了,呵呵。
为了防止我自己忘记,同时也给需要的同学们提供个参考,我决定把这两天培训的要点和我工作以来的体会写下来。转载请注明来自我是一只鱼的EE小站,邮件cosine@126.com,欢迎有问题在此留言或来信交流。
 
  • 什么是电磁兼容

对于还没有毕业的、刚毕业的同学们可能对电磁兼容体会不深。但不论老师的指导、业界的传言,都给我们指出了一些电磁兼容的蛛丝马迹:每个IC加一个104电容、数字地模拟地需要分割等等。先不讨论这些做法的对错,至少说明,我们需要做些什么事情,才能保证我们的设计能很好的工作。
电磁兼容性(EMC)是描述一个电子设备的两方面性能:对别的设备的干扰严重程度(EMI)和对自然界或别的设备的干扰的抗受程度(EMS)。有很多很牛X的人做了很多很牛X的工作,用标准的形式把电磁兼容分成了若干个可以用测试衡量的项目,例如IEC61000-4-X系列;国标GB17626.X系列,就是抄IEC的。我不想像教课书一样给大家列出这些标准,就用几个简单的、我们经常能遇到现象来对应下:
1. 很多人遇到过把示波器地线夹到板子上的时候,板子复位的现象吧,这个是热插拔“浪涌(Surge)”的效果;
2. 冬天的时候,北方的同学们经常对自己的笔记本电脑或者台式机过电吧,这个是“静电放电(ESD)”干扰;
3. 使用电钻的时候听收音机,有杂音,这是“快速瞬变脉冲群(EFT)”的效果;
注意这几个用引号括起来的词,就是几个典型的电磁兼容测试项目。当然还有两个非常重要的项目“辐射”和“传导”,但我觉得只是为了让单板正常工作,这两个项目不用考虑;我目前也没有这方面的实践经验,本文就简略些。据我头说我们要买个频谱仪,有那个之后我再玩玩这两个项目,看看理论的东西是不是胡说。吐舌
再说说对电磁兼容的范畴界定:以我的认识,电磁兼容性应算产品的性能,不能划归可靠性范畴;因为可靠性主要研究产品能在什么应力下能用多久不坏,不关心干扰的问题。因此,电磁兼容性的提高,不代表可靠性提高,反而由于引入了串联的对策器件(这个词下面就解释了),对策器件选择不当会降低系统可靠性。但研究所、实验室科研就不用理清这个概念了,反正课题验收只要好使一次,没有人关心可靠性。
很多人觉得电子设计必须考虑电磁兼容各种规则,其实有些情况不用,或者说不用考虑全部;因为请一定记住,电磁兼容性的提高不是随便就能达到的,是有成本的,这包括几个方面:电磁兼容PCB设计往往要求高,层数和面积都会增大;增加对策器件;增加测试成本。山寨手机往往电磁兼容做的不好,但卖的好才是真的好。但对于工程师,必须懂得这些知识,中国人不能永远搞差异化产品的。呵呵,扯远了。

  • 重新认识PCB和基本器件

这个小节讲的全部是一件事情:分布参数。分布参数就是分布在PCB或者器件中的电容、电感、电阻,这些东西数值很小,但是在很多情况下,他们造成的影响狠严重。给出一个经验数值,一般1厘米长的PCB走线的电感量是6nH。按照经典的复阻抗计算公式Z=ωL,在100MHz下,1厘米导线的复阻抗达到3.76Ω,稍微长一些的导线对高频信号就会表现出很大的衰减。
而我们常用的电容、电感因为存在分布参数,在高频下也不能用简单的符号来表示。以电容的模型为例,请看我们常用的X7R型的0603封装50V耐压0.1μF电容的模型,其中L1和L2影响电容的等效串联电感(ESL),R1、R2、R3影响电容的等效串联电阻(ESR)。

 有这么多分布参数,导致这个104电容的复阻抗-频率曲线变成了这样:

 

可以看出,这种电容在11M左右的时候,复阻抗最小,大约为100mΩ。随后由于等效串联电感的存在,复阻抗会逐渐增大。但刚才已经提到了,1厘米的PCB走线有6nH的电感,当这种电容焊接在电路板上的时候,其复阻抗特性曲线会向左移动一些;所以,这种电容比较适合用于若干MHz到20MHz频率下的滤波。很多单片机的工作频率都在这个范围,因此104电容可以用于单片机系统的电源滤波。但如今的ARM、DSP、PowerPC系统频率都不下百兆,用104电容就不合适了。
说说等效串联电阻的作用:等效串联电阻小的电容,其复阻抗特性曲线整体下移,因此,在很大频率范围内复阻抗都很小,所以等效串联电阻越小的电容,滤波效果越好。

接下来说说电感,电感也存在等效串联电阻、匝间电容等分布参数。除了高频下,电感特性会变化之外,电感还受到直流电流的影响——因为电流会让电感的磁芯饱和。另外,有的磁芯磁导率还会受到温度的影响。用TDK公司的某电感的参数特性曲线为例子说明:

  •  认识电磁兼容对策器件

下面来说说专门为了电磁兼容而存在的器件。
主要可以分为滤波和瞬态抑制两个类别。起滤波作用的有:共模电感、磁珠、三端电容等;起瞬态抑制作用的有:TVS二极管、压敏电阻、气体放电管等。这些器件的原理、结构、基本作用等我不说了,百度一下一大片。说些不容易搜索到的:
1. 磁珠到底是什么
很多人搞不清楚磁珠、电感、0欧姆电阻的区别。先看看村田制作所(好多年了,一直觉得和春田花花幼儿园有关系吐舌)提供的磁珠内部结构:

原来就是用线在里面绕圈啊,可以肯定这和0欧姆电阻不一样,因为它有一定电感。那么磁珠和电感有什么区别呢?我觉得是磁心材料的区别,电感的磁芯材料是用来储能的,而磁珠的磁性材料是用来耗能的,在高频的时候产生很大的涡流,来把交变信号的能量消耗掉。所以,在高频下,磁珠表现出很大的纯电阻值,而不是复阻抗值。可以看看磁珠的阻值频率特性曲线:

那么,什么地方用磁珠呢?建议的应用是:电源滤波、信号滤波。强烈不建议将磁珠用于模拟、数字地之间的跨接,因为这样容易引起地电位差,对AD、DA的精度产生很严重的影响;这种情况用0欧姆电阻或者干脆不区分模拟和数字地,只区分模拟和数字电源。目前我尝试过两个地之间用磁珠有效的只有一种:防止热插拔时的浪涌。

2.TVS、压敏电阻、气体放电管到底用那个
这三个器件都是用来抑制瞬态电压变化的,不同的是TVS响应最快(0.1ns级别),但通流量小;压敏电阻响应比较快(1ns级别),通流量大;气体放电管响应慢(1us级别),通流量大。因此,TVS和压敏电阻可以用来抑制ESD放电,气体放电管和压敏电阻可以用来抑制浪涌。

  • 怎样提高电磁兼容性

之前我提出电磁兼容是产品的性能,但对于概念不清的人来说,“提高电磁兼容性”这个提法很模糊,只好通俗解释下——就是怎样通过电磁兼容测试。
请一定建立起这个概念:提高电磁兼容性要从板级设计开始。在产品的板级设计不太差的前提下,可以通过增加一些简单的电磁兼容对策器件实现电磁兼容性的提高——但这完全是治标不治本。给我上课的老师就举了某电力测量设备的制造商例子,他们的产品为了通过2kV的快瞬脉冲群,共模电感、Y电容的加了一大堆,还修改采样电路板PCB,搞得很麻烦;原因就在于他们的单片机系统设计太垃圾了。2kV的快瞬对低速的单片机系统来说就和玩一样,轻松对付。那么具体该怎么做呢,第一,要注意PCB和基本器件的分布参数,正确选择各种器件;第二,要了解各种电磁兼容对策器件的特性和选型方法;第三,注意各种滤波设计;第四,要注意PCB设计。前面两条已经在上文种有所涉及了,但我写出来的就是个导读,要是全写出来我干脆写书去了。说说后面两条。

  • 电磁兼容滤波设计

这个叫法和一般的信号滤波设计不同,是有针对性的。再次说明,本文不涉及产品对外辐射的控制,但这些滤波器一般可以起双向滤波的作用,通俗点说,里面的干扰出不来,外面的干扰进不去。
要设计这种有针对性的滤波器,首先必须了解将要面对的是什么信号:以快瞬脉冲群、浪涌、静电放电为例。
1.快瞬脉冲群
快瞬脉冲群是用来模拟感性负载在电路上的频繁接入和断开的情况,比如电机、继电器等。它的电压波形是这样的:

 

快瞬脉冲群可以加在电源上,如果AC/DC电源的传导衰减不够,就会在设备的直流电源上产生相似波形的干扰信号。快瞬脉冲也可以通过耦合夹耦合在信号线上耦合出相似波形的干扰信号,通过信号端口传入设备。另外,由于快瞬脉冲群的辐射能量很大,附近的线缆也会感应产生相似波形的干扰信号。如果是单个干扰脉冲,数字电路一般有施密特触发器,不会误触发。但连续的脉冲群会使施密特触发器失效,而扰乱数字电路。
快瞬脉冲群单个脉冲的上升沿,决定了其能量大致分布在十几MHz~几百MHz的范围内,因此滤波器在这个区间内要有滤波效果。
另外,快瞬脉冲群干扰是共模的,不论你选则的是L、N,L、PE,N、PE,L、N、PE那一种耦合方式,快瞬脉冲群都是共模的。有必要解释什么叫共模干扰,共模干扰指的是相对于某个共同的外部参考源,例如大地的干扰。我目前理解的脉冲群共模干扰的模型如下:

脉冲群的干扰就像河道里的水波一样,一波一波的涌入设备,如果被测设备完全悬空,那么仅仅是其电势被抬高,不会产生影响;但是,由于被测设备和大地之间存在耦合电容,高频干扰信号就能找到回流路径,因此,设备的正常工作受到影响。
对于脉冲群,可以使用如下图所示的对策:

我仅对电源线对策举例,信号线的对策大同小异。被测设备最好要有个金属屏蔽外壳,并与大地有良好的电气连接,这样PE信号的干扰就可以直接导入大地。交流供电线上的干扰使用共模电感抑制,注意应选择滤波区间在脉冲群能量频率范围内(十几MHz~几百MHz)的共模电感,通过共模电感之后共模干扰会大幅衰减,图中的箭头大小表示能量的大小。光有共模电感有些情况下还不够,还应在进线端增加对大地的Y电容,让干扰能量尽可能早的回流到大地,而不干扰到后级的电源模块和功能电路。

2.浪涌

浪涌就是模拟雷击的,当然还有热插拔浪涌,这个暂时不讨论。 浪涌测试的电压波形如下:

电流波形就不贴出了,长的和这个波形差不多。浪涌的特点是电流上升沿较缓,但峰值电流很大,可以达到数百到上千安培;同时,浪涌的电压较高,可以达到数千伏。浪涌能在很短的时间内令设备内部器件或空气击穿,损坏设备。由于浪涌的能量很大,完全堵住浪涌是不可能的;因此,浪涌的对策是疏导。在电源线上放置瞬态抑制器件,如TVS、压敏电阻、气体放电管等等。典型的对策电路如下所示:

这种形式的电路,放置在设备的电源输入端,一旦电源出现浪涌,压敏电阻和气体放电管就会导通,将浪涌能量导走,令其不进入后部处理电路。而在没有浪涌时,由于气体放电管几乎没有漏电流,可以满足安规要求。

3.静电

静电放电测试用来模拟人体对设备的放电。其波形如下图所示:

静电放电结合了脉冲群和浪涌的特点,由于静电放电电流上升沿非常陡峭,使放电时产生宽达数GHz的干扰频谱;另外静电放电的电压一般可以达到1万伏特,峰值电流可以达到数十安培,虽然放电的时间短、能量较小,但也有可能使设备损坏或造成可积累的损坏。静电的对策是脉冲群对策和浪涌对策的结合:一方面要加强屏蔽和共模滤波,另一方面也要对放电电流进行疏导,避免其进入电路。

  • 如何设计电磁兼容性良好的PCB

设计PCB学问就多而乱了,什么层叠设置,阻抗匹配,串扰预防等等等等。其实要提高电磁兼容性,最重要的原则是:回路面积最小。
回路面积最小指的是信号路径和回流电流路径所围成的截面积最小。用一个信号穿越地分割的例子来说明:

图中红色的是某双面板顶层信号线,蓝色是底层地平面。当信号线在完整的地平面上时,黄箭头所示的信号电流和绿箭头所示的信号回流电流基本重合,二者回路面积最小;当地平面存在分割时,回流电流就会绕开地平面分割行进,使回路面积增大。回路面积越大,电路感应出的干扰信号幅度也就越大,向外的辐射也越多。相关知识可以查看PCB方面书籍。
最符合回路面积最小原则的PCB设计是所有信号线都有完整地平面陪伴,最不符合的例子是电缆中独行的信号线。

PCB设计虽然没有太高的技术含量,但确是很需要经验的。

6月14日

再谈SDRAM的布线——有关Mentor WG、DxDesinger、Expedition、CES

 

  • 前言的前言
这篇文章我写了很久很久,因为最近很忙很忙。现在我逐渐开始接触开关电源和可靠性设计的东西,好像离原来我定义的EE越来越远了。也许以后我要向模电或管理人员发展了……我还是纯朴地希望自己能一直保持做一个不断钻研的EE工程师。不说了,做人要厚道,转载请注明来自我是一只鱼同学的EE小站,邮件地址cosine@126.com
  • 前言
最近一个多月都在研究Mentor WG,已经对DxDesigner + Expedition的画板流程有了比较清醒的认识,我对Mentor WG评价可以套用对目前国产汽车的评价——配置齐全、做工粗糙。虽然WG有很强的功能,但是BUG实在是数不胜数,而且有些BUG可能导致你的工程彻底报废,所以建议使用时辅以自动备份软件,减小工程崩溃带来的损失。
 
今天要谈的话题都是基于WG的,因为PADS、Protel / DXP之类的软件没有这样的功能或功能不完整。不过,也可以使用其他软件进行PCB前仿、手动完成线长匹配等工作;工具只是人的技巧的辅助和延伸,要是没有高速PCB设计的知识,同样完不成高速数字PCB的设计。本文为我是一只鱼同学EE小站的原创文章,转载请注明出处;本文对初学者而言,技术难度较高,如果有不明白的地方,可以留言。另外继续废话几句,事实上SDRAM对布线的要求是很低的,DDR才是真正有挑战的东西,可惜我目前没有DDR的项目,也没有办法验证我对理论的理解,希望以后有机会和大家分享我的心得。下面正式开始:
  • 什么是高速数字PCB,怎么入手?
高速数字PCB简单来说可以理解为关键部分如存储器总线的工作频率高于数十至一百MHz的PCB,更严格的定义应该用传输线来描述,当PCB上的信号的传输延迟大于上升时间的1/10时,这个信号的传输路径就应该视为传输线;即应当用与传统低速数字电路不同的方法对待。那么怎么入手?我是学机械出身的,电路原理和模电都是三脚猫知识;我个人认为High-Speed Digital System Design是本不错的书。首先看书,弄明白在频率高了以后会出现什么样的现象,有什么东西需要考虑之后,再继续后面的设计。不过我可以做个简单的概括,高频数字电路设计的大部分工作是解决传输线中信号反射问题和延迟问题。BTW,很久以前我还很菜的时候写了一篇文章http://xianzilu.spaces.live.com/blog/cns!4201FDC93932DDAF!171.entry,这是关于PCB后仿的(这个词下面马上解释),大家有兴趣可以看看。
  • 高速PCB设计的流程
元件布局——〉前仿真——〉布线——〉后仿真——〉出CAM文件
其他不多说了,就解释下前仿真和后仿真。
前仿真就是在器件IBIS模型、网络拓扑结构和器件分布的基础上做的对PCB可实现性仿真。举个例子解释前仿真的作用,如果器件、板子的机械结构都已经定下来了,CPU和SDRAM插座相隔10000mil,那么在布完这个板子之前,怎么知道这个板子能不能正常工作?关于如何使用WG进行前仿真,后面再说。
后仿真就是在板子走线已经成型之后,对布线结果进行验证而作的仿真。后仿真会在前仿真基础上加上过孔模型、串扰、电磁兼容性等仿真内容。刚才提到的我的菜菜鸟文章http://xianzilu.spaces.live.com/blog/cns!4201FDC93932DDAF!171.entry,说的就是后仿真。
  • SDRAM对布线有什么要求?
首先必须明白SDRAM是一种什么样的存储器,搞清其接口工作的逻辑时序。SDRAM是一种同步动态存储器,所有接口信号都是通过时钟同步和采样的。这就对SDRAM的布线提出了要求——保证采样的正确性。于是,应用高速数字电路的知识结合某种具体SDRAM器件和你的PCB进行分析,发现在正常工作频率(如100MHz)下,在PCB走线上的信号传输时间大于其上升时间1/10。于是,接下来考虑高速数字电路两大问题反射和延迟:反射造成SDRAM时钟线信号出现振铃,多次穿越门限造成误触发;数据线和时钟线的传输延迟不相同,造成时钟上升沿采样不到所需要的数据。接下来应用解决方法:时钟线串联电阻做阻抗匹配;布线时控制数据线和时钟线的长度差在一定范围内。当然,我这里说的是一个很简单的演绎过程,还有拓扑结构、最大布线长度等重要问题没有考虑,请大家仔细阅读我是一只鱼同学刚才推荐的课本。提示下,拓扑结构和最大布线长度的选择可以通过前仿真进行验证。
  • 进一步的问题,SDRAM布线用什么拓扑结构好?
这个问题困扰了我很久很久,终于在学会前仿真后解决了,哈哈。其实大家已经很清楚SDRAM要尽量使用Y型分支结构(也叫T型分支),因为链式结构会产生两个问题:一、两片SDRAM的传输延迟不一样,影响CPU对数据输出进行采样;二、链式结构的节点处阻抗不连续,是一个反射点,而且反射点和源的距离太大,反射效果明显。但是,如果使用Y型分支结构,到底是先分支好呢还是后分支好呢?
 

 
经过前仿真的验证,分支点靠近CPU的时候效果稍微好那么一点点。我想这是因为分支点本身是一个阻抗不连续点,也是会发生反射的。如果分支点靠近源端CPU,反射就会因为传输线的缩短而显得不太明显。我给分枝点靠近CPU的这种拓扑结构起个名字,叫短桩Y型分支结构。
  • 怎样用WG进行PCB前仿真?
WG中PCB的前仿真的步骤:DxDesigner画原理图——〉Expedition布局——〉CES指定IBIS模型——〉CES指定网络的拓扑结构——〉ICX Pro前仿真。我们来看图说话。
对于下面这样一个已经完成布局的PCB,从Expedition的工具栏中选择CES
 

 
在CES的窗口中见的选项卡中,选择Parts
 
 
 
例如对SDRAM的走线进行前仿真,就需要制定CPU、SDRAM以及其他连接在数据总线上的器件的IBIS模型。相应的模型可以在器件的官方网站上下载到。在Parts里选中要指定模型的器件。
 
 
 
在弹出的窗口中按照步骤1、2、3(后续图片中的1、2、3、4亦表示步骤)选择IBIS模型文件
 
 
 
选好后就OK,重复以上步骤直到把要进行仿真的信号所连接的所有器件的IBIS模型都选上。有的IBIS文件中含有多个器件的模型,选择你需要的
 
 
 
在SDRAM信号设计的时候往往会使用电阻对信号进行阻抗匹配,这时候SDRAM布线的拓扑结构就会变成下面这样
 
 
 
这个电阻也必须包含到前仿真中去。但是在实际设计中,往往有很多需要匹配的信号,所以一般是使用排阻的。但是默认情况下,CES是不认识排阻的,这需要设置。选择Setup菜单下的Settings…
 
 
 
在弹出的窗口中选择Discrete Component Prefixes选项页,将你所用的排阻前缀输入(如RM),然后确认
 
 
 
随后你就会发现CES把你的排阻认为是串行器件了。顺便提下,如上面这张图所示,CES会把这些已经定义前缀的器件识别为串行器件。识别为串行器件有好处也有坏处,好处是对于真正用于阻抗匹配等目的的器件,前后的网络会被归为一个网络进行识别(CES在原来的网络名后面加上“^^^”符号,将两个网络合并);坏处是很多功能性质的电阻,如运放中设定放大比例的电阻两端的网络也会被归为一个网络识别,这时候就需要把下面这张图中所示的钩号去掉。
 
 
 
接下来,点击Parts边上的Nets选项卡,选择SDRAM的信号
 
 
 
随后配置网络拓扑结构。对于SDRAM的控制线来说,它们不连接在SDRAM之外的其他上,因此其拓扑结构一般都是之前描述的这种:
 
 
 
对于一般的地址线而言,往往需要连接除SDRAM芯片之外的其他器件,如NOR Flash,其拓扑结构可能是这样的
 
 
 
我建议将NOR Flash连接在一个SDRAM的之后,因为如果再搞短桩Y型分支,那么将会有3组分支线,布线就很困难了。当然,连接在哪里要根据前仿真的结果来调整,我提供的这种连接对于某些器件应该会有问题。

下面要把SDRAM的信号定义成上面这2种拓扑结构中的一种,在Nets列表里找到Topology这一列,点击下拉列表。对于SDRAM时钟信号SDCK这种串联阻抗匹配电阻的拓扑结构而言,选择Complex。
 
 
 
随后点击工具栏上的Netline order按钮(这个按钮左边的几个按钮可以定义其他不同的拓扑结构,与上图列表中对应,依次是MST、Chained、T、Star、HTree、Custom,这些不同拓扑结构的含义可以Google下或者参看Mentor WG CES的手册)
 
 
 
出来这样一个对话框
 
 
 
之前已经提到SDRAM的地址和控制信号应当是短桩Y型分支,如果还有别的器件就连接在SDRAM之后。先说明下,因为定义了排阻为串行期间,所以CES自动的将CPU到排阻的连接识别出来并列在管脚对列表里了;管脚列表里蓝色背景的部分是已经在管脚对列表中存在的管脚。随后就需要定义Y型分支,点击Y型分支拓扑结构图标,再依次点击信号源管脚和两个负载管脚,如下图所示
 
 
 
在管脚列表中就会出现如下显示
 
 
 
这说明已经定义了一个Y型分支。定义之后的Y型分支可以删除和修改,具体细节请看CES手册。随后点击对话框下面的那个复选框,“Automatically create pin pairs from from-tos”,确定,CES的Nets列表中,刚才定义的Net下就出现如下图所示的内容
 
 
 
那些L:VP_T_1_1_1181, L:D19-38之类的东西就是产生的拓扑结构描述。
刚才已经提到SDRAM的地址线因为还需连接其他器件,拓扑结构的设置还需要有一步添加自定义管脚对。以地址线A2举例,同样选择其为Complex结构
 
 
 
然后选择工具栏上的Netline order,在出现的对话框中将CPU和SDRAM之间的连接配置为Y型分支,如下图所示
 
 
 
随后配置和NOR Flash芯片的连接,先点击“From pin / pin set”下面的文本框,然后依次点击SDRAM和NOR Flash的管脚,再点击右边的下箭头,如下图
 
 
 
于是一个自定义的Pin Pair就出现了,同时管脚列表中对应的管脚背景色也会变蓝,显示这个管脚被指派过了。
 
 
 
需要注意的是,NOR Flash连接在那个SDRAM器件的管脚上是没有要求的,但是为了走线方便,还是建议连接在理NOR Flash相应管脚较近的SDRAM器件上。
接下来同样选中“Automatically create pin pairs from from-tos”复选框,确定,A2的拓扑结构就配置好了。
 
 
 
这里还需要提一个概念——Virtual Pin(虚拟管脚),这是WG为了方便对拓扑结构的管理而设定的一种虚拟的控制点。还是用图来说明,对于SDRAM的连接拓扑结构
 
 
 
WG把图中红圈标示的那个分支点分立出来,当作一个可以控制的元素Virtual Pin,这个元素可以移动、定位;一个Y型分支的拓扑结构就拆分成了各个元件到这个Virtual Pin连接的结构。刚才我们看到的“VP_T_1_1_1181”就是Virtual Pin,它在PCB设计的时候是这样显示的:
 
 
 
Protel / DXP、PADS是没有这种功能的,PADS只能通过Matchlength Pin Pair来实现类似的功能,但对于SDRAM不太适用。不仅WG,强大Alergo也有这个功能。说了这么多,Virtual Pin的作用其实就一句话,用来做Y型分支的两个分支等长——因为没有Virtual Pin,去哪里设置等长规则,怎么检验等长的情况?
接下来设定PCB层叠,不指定PCB的层叠,前仿真中的阻抗参数的计算是根本没有意义的。选择CES工具栏上的Stackup Editor
 
 
 
出现下面的窗口,这个东西比较傻瓜式了,我就不做保姆式指导了。需要指定的东西有,PCB每一层的铜厚,相临两层之间的绝缘层厚度和介电常数,参考面(电源层、地层)是哪几层等。PCB的厚度和介电常数等参数需要向PCB厂家索取,参考面的位置可以Google下,看看大多数人用的层叠方法。
 
 
 
有了以上的工作,前仿真就可以进行了,右键选择需要仿真的网络,例如SDCK,在弹出菜单中选择“Display Net in”——〉“ICX Pro Explorer”
 
 
 
出现以下窗口
 
 
 
这就是我们进行前仿的软件了。可以看到,在中间的黑色背景区域是器件管脚、传输路径以及其他器件(如阻抗匹配电阻)的连接拓扑结构——这也就是我们刚才设定的那些东西。图中绿色那些短棒就是传输路径,双击它可以修改长度;这个长度应由PCB布局决定。在CES中选择菜单“Data”——〉“Actuals”——〉“Upadate All”命令将PCB的数据导入CES之后
 
 
 
在Nets的列表里可以看到Manhattan Length这一项的数值出现了
 
 
 
这个数值的含义是布局完成之后目前PCB上这一网络所有飞线(用直线连接管脚的线)长度之和。一般来说把这个数值乘以1~2之间的一个数,就是最后的布线长度。你可以把这些数值输入到ICX Pro Explorer中去。对于含有拓扑结构的网络,应当把拓扑结构的每一段都输入到相应的传输线上去。你可以在PCB上试着移动Y型分支点即Virtual Pin的位置,然后更新Manhattan Length,看看是先分支的拓扑结构前仿的效果好,还是后分支的。
ICX Pro Explorer也是一个比较傻瓜式的软件,用一用很快就会了,我就不介绍了,给大家看个仿真结果
 
 
 
这是AT91SAM9263和K4S561632K-UC75组成的存储器系统,阻抗匹配电阻为22欧,SDRAM时钟在133MHz下的仿真波形。
  • WG中怎么设置SDRAM的线长匹配规则
这是一个非常麻烦的部分,我研究了很久,转载请注明来自EE小站。在介绍规则设置之前,先介绍下SDRAM线长匹配的原则。
一般来说,SDRAM的地址线是这样的拓扑结构
 
 
 
SDRAM的控制线是这样的拓扑结构
 
 
 
不同地址线之间,需要保证单路分支的长度匹配,即AB+BC或AB+BD相等;同时,需要保证同一地址线的两个分支相等,即BC=BD。不同控制线之间,需要保证单路分支的长度匹配,即ab+cd+de或ab+cd+df相等;同时,需要保证同一控制线的两个分支相等,即de=df。在控制线和地址线之间,同样需要保证单路分的支长度匹配,即AB+BC=ab+cd+de或AB+BC=ab+cd+df或AB+BD=ab+cd+de或AB+BD=ab+cd+df。

而软件统计的结果,地址线的长度为AB+BC+BD+D’E;控制线的长度为ab+cd+de+df。统计的内容都不一样,怎么做匹配呢?有2种方法,使用公式、使用Pin pair。使用公式的好处是无论有没有阻抗匹配电阻,都可以很清晰的看到线长匹配的误差,这对手工布线非常有帮助,但是过程极其繁琐。使用Pin Pair的好处是很简单,不用输入很长的公式,但是对于有阻抗匹配电阻的信号,不能看到匹配误差,需要自己计算。

先说公式方法
 
记住,软件统计的是所有线段长度之和;我们需要控制的仅仅是所有地址线的AB+BC、所有控制线的ab+cd+de这些长度都相等(BC=BD、de=df这两个条件在设定Y型分支拓扑结构之后,布线器会自动地做到,于是将上面的那些表达式简化成等价的AB+BC和ab+cd+de这两个)。因为SDRAM的时钟是最重要的信号,选择时钟线的长度作为参考,时钟线的单路分支长度为ab[时钟]+cd[时钟]+de[时钟](Blog没有办法表示下标,用[]来修饰)。则对于其他信号,如地址线,理论上约束的内容应该是AB[地址]+BC[地址] = ab[时钟]+cd[时钟]+de[时钟]。软件统计的是所有线段长度之和,对于某一地址线,其长度应该是AB[地址]+BC[地址]+BD[地址]+D’E[地址]。因此,公式约束的内容就变为 AB[地址]+BC[地址] = ab[时钟]+cd[时钟]+de[时钟]+ BD[地址]+D’E[地址]。

很复杂,来看个例子,对于一个含有匹配电阻的时钟信号,EBI0_SDCK,其拓扑结构如下所示
 
 
 
其中VP_T_3_5_1336为Virtual Pin。它的单路分支长度按WG CES的语法,应写成\D1\-\C5\@\RM2\-\7\ + \RM2\-\2\@\VP_T_3_5_1336\-\VP_T_3_5_1336\ + \RM2\-\2\@\D19\-\38\,当然最后一项是\RM2\-\2\@\D20\-\38\也可以。
确定了时钟的长度,接下来用它来约束其他地址线、控制线的长度。以EBI0_A3为例,其拓扑结构如下所示
 
 
 
由于软件统计EBI0_A3的长度为上面4个Pin Pair线段长度之和,因此在EBI0_A3单段分支的基础上(这段长度和EBI0_SDCK的单段分支长度相等),需要加上\D19\-\24\@\D21\-\25\ + \D1\-\C9\@\D20\-\24\这段长度,当然第二项是\D1\-\C9\@\D19\-\24\也可以。所以限制EBI0_A3的长度为,\D1\-\C5\@\RM2\-\7\ + \RM2\-\2\@\VP_T_3_5_1336\-\VP_T_3_5_1336\ + \RM2\-\2\@\D19\-\38\ + \D19\-\24\@\D21\- \25\ + \D1\-\C9\@\D20\-\24\ +/-200th,当然倒数第二项是\D1\-\C9\@\D19\-\24\也可以,最后的+/-200th是控制的误差长度。然后把这个公式填到EBI0_A3后面的Formula中去。
 
 
 
对于有阻抗匹配电阻的信号,如nEBI0_CAS
 
 
 
由于软件统计nEBI0_CAS的长度为上面4个Pin Pair线段长度之和,因此在nEBI0_CAS^^^单段分支的基础上(这段长度和EBI0_SDCK的单段分支长度相等),需要加上\RM1\-\3\@\D20\-\17\或\RM1\-\3\@\D19\-\17\的长度。所以限制nEBI0_CAS的长度为,\D1\-\C5\@\RM2\-\7\ + \RM2\-\2\@\VP_T_3_5_1336\-\VP_T_3_5_1336\ + \RM2\-\2\@\D19\-\38\ + \RM1\-\3\@\D19\-\17\ +/-200th,当然倒数第二项是\RM1\-\3\@\D20\-\17\也可以,最后的+/-200th是控制的误差长度。
按照上面的步骤把所有SDRAM信号线都操作一遍,是不是会崩溃?
 
下面介绍用Pin Pair的方法
 
给地址线、控制线建立这样的Pin Pair,例子如下:
EBI0_SDCK^^^信号
 
 
 
EBI0_A3信号
 
 
 
这和之前的Pin Pair不同,这些Pin Pair是手工生成的,也就是说,在制定这些信号的拓扑结构的时候,不选择“Automatically create pin pairs from from-tos”复选框。然后,选中这一信号,通过菜单Edit>Pin Pair>Add Pin Pairs来手工添加,如下面两张图片所示。
 
 
 
 
 
这样,只需要关心信号最开始是从哪个芯片的哪个管脚出来的,最后到哪个芯片的哪个管脚里去,有多少个单路分支,添加多少个Pin Pair,中间的阻抗匹配电阻、Virtual Pin全部可以忽略;最后再在Pin Pair的Match列用同一名字约束就可以了,如下所示
 
 
 
控制信号也一样
 
 
 
需要说明的是,上面这张图头三个Pin Pair是用来方便手工布线的。因为Pin Pair如果穿越了器件(对于信号路径穿越阻抗匹配电阻这种情况而言),Pin Pair的长度在CES里是显示不出来的——至少目前我还没有找到什么办法可以让它显示出来——这对手工布线来说非常不方便;但是自动布线却没有任何问题,Expedition可以正常的完成Tune(自动长度调整)操作。

也许你有些疑惑,既然Pin Pair这么定义了,怎样才能看到自定义拓扑结构呢?把菜单中Filters>Levels>FromTos选项钩起来,就可以看见了,如下图所示。需要说明的是,Pin Pair仅仅是一种虚拟的连接关系,拓扑结构是用FromTos这种物理连接关系确定的。
 
 
 转载请注明来自EE小站,以方便后人查询。至于其他的布局、布线这些简单的东西就没有什么好说的了。就写到这里。
2月3日

半个猪年总结

07年7月十多号来到北京,转眼半年了。今天公司放假了,写半个猪年的总结。
首先介绍下我干活的公司,中国本土数控行业第二,当然和Fanuc、Siemens没法比,呵呵;我们以中低端产品为主打。刚才说的是销量,我觉得技术水平我们绝对国内第一;我拆解过主要竞争对手的产品,发现他们用的CPU已经停产了,但那可是他们的高端主打产品……我们公司研发的力量还是很雄厚的,这也头儿们的领导风格决定的。头儿们都是做技术出身,行为做事都很低调,在中国现在的拜金气氛下,这样不浮躁的头真的不多。但是中国市场是复杂的,技术好不能保证最好的销量,但是可以保证企业的长久生存。哈哈,扯远了,我这样的小兵,还是专心做好自己的事情先。
反正这半年混的还凑合,不过把自己热爱的事业作为职业,业余时间也没有什么追求了,关心技术内容的人一定发现技术文章次了些,哈哈。其实我也不能多写了,现在签了卖身契,开源就等于自杀——如果要我付保密违约金,那干脆就杀了我吧。怪不得国内有些Linux大牛的文章都太监了,估计也是不想和钱过不去吧……现在有空我就游戏,我的QQ牌升级已经从来北京的时候的300分小房车变成现在600多分的小坦克了,最近打腻了,开始捡起研究生时候练的WOW小牧师,也许我该开个类别介绍我的游戏经验了。也许我有些宅,但是在北京这种地方,我也就能在看丹桥的冠京隆市场撑回款爷,出门玩,那就是大把大把的RMB啊。现在住的是公司的房子,要想买房,以我和GF的工资加起来,除去花销,一年也就能买四环外半个厕所。
不知不觉在北方已经生活了6年半了,我觉得自己从口音、思维习惯到饮食口味已经都没有福州人的风格了,唯一剩下的是一张福州人脸,到哪儿人都一下认出我是南方人……我现在特别羡慕有口音的人,那说话多有味道啊。哦,其实我也有口音,说福州话有东北口音尴尬
好了,废话说完放一些图片,先是我们公司前台,刚搬新楼,装修不错,关键是前台MM好漂亮,真是我情窦初开以来遇到的最漂亮的MM,每天我都要多看几眼,哈哈。更加幸运的是,这张照片里MM走光了。
 
 
 
第一张图是我的猪窝办公桌,呵呵,我的口号是,做硬件的就得要有做硬件的样子。接下来的是我在学校的桌子,相比之下现在的我还是整洁许多的。
 
 
 
 
 
我们公司的产品,这台是目前已经上市的产品里第二高端的,4轴系统;第一高端的是8轴的,我还没有见过实物。业内人士一定知道我在哪里工作了,刚才还有一点忘了说了,我觉得我们公司产品的外形是国内最酷的,这是唯一和Fanuc、Siemens不相上下的地方,哈哈。这台是我们用来调试的,面板有些旧。
 
 
 
园区夜景,拍的时候没有发现那根电线,真烦人……手持相机,无三脚架,快门时间2秒,能拍出这样的效果我是不是很牛X?估计是焊接0603练出来的,哈哈。
 
 
 
1月21日

AT91SAM9263内部ROM时钟初始化代码反汇编

AT91SAM9263是个不错的CPU,我这段时间在上面跑起了U-Boot、Linux。过程就没有什么好写的了,都一样,就是U-Boot的移植有些麻烦,但我的设计的板子和9263ek相差太大,写出来没有什么参考价值,就都不写了。我拿到的CPU样品是Rev A的,而现在的手册是Rev B的,需要注意的是Rev A不支持NAND和SD卡启动。为了从NAND启动,需要自己写启动代码了。我的板子上有EPROM,并且把它放在nCS0上,我移植了AT的Bootstrap到ADS下面,然后做添加了上电初始化代码。但是我修改过的代码不能公开,抱歉了。刚开始我修改的代码总是Boot不成功,似乎时钟不稳定,也没有好的样例,无奈之下,我只好把AT的Boot ROM关于时钟部分给反汇编了,照着反汇编的结果写了一个,好使了。这个代码在0x400000能看到,我应该不算破解吧,写出来给其他写启动代码的人一些参考。

 

行标号        原始指令                           更易阅读的指令         注释

---------------+---------------------------------------+-------------------------+----------

                b        0x20                           b reset                   ; 复位向量

                b        0x4

                b        0x124

                b        0xc

                b        0x10

                b        0x14

                b        0x18

                b        0x1c

reset           mov      r13,#0x314000                  mov sp, #0x31400          ; 初始化堆栈

                ldr      r1,0x00000160 ; = #0xfffffc00  ldr r1, =AT91C_BASE_PMC

                mov      r0,#2                          mov r0, #OCSBYPASS

                str      r0,[r1,#0x20]                  str r0, [r1, #PMC_MOR]    ; 设置主振荡器为旁路模式

                mov      r4,#0x10000                    mov r4, #MAINRDY

                mov      r0,#0x20                       mov r0, #TIMEOUT          ; 设置超时时间(注意:现在运行于慢时钟)

loop1           ldr      r3,[r1,#0x24]                  ldr r3, [r1, #PMC_MCFR]

                and      r3,r4,r3

                cmp      r3,#0x10000

                beq      0x50                           beq exclkdetected        ; 查看PMC_MCFR,外部时钟输入是否稳定

                subs     r0,r0,#1

                bne      0x38                           bne loop1

exclkdetected  ldr      r4,0x00000164 ; = #0x0000ffff  ldr r4, #MANIFMASK

                ldr      r3,[r1,#0x24]                  ldr r3, [r1, #PMC_MCFR]   ; 读出外部时钟频率

                and      r3,r4,r3

                cmp      r3,#0x10

                bgt      0x8c                           bgt clkdetected           ; 如果外部时钟频率小于32.768KHz,则

                mov      r0,#0                          mov r0, #MORCLEAR         ; 复位主时钟控制器

                str      r0,[r1,#0x20]                  str r0, [r1, #PMC_MOR]

                ldr      r1,0x00000160 ; = #0xfffffc00

                ldr      r0,0x00000168 ; = #0x00004001

                str      r0,[r1,#0x20]                  str r0, [r1, #PMC_MOR]    ; 设置主时钟振荡器使能

                mov      r4,#1                          mov r4, #MOSCS

loop2           ldr      r3,[r1,#0x68]                  ldr r3, [r1, #PMC_SR]     ; 读取PMC状态

                and      r3,r4,r3

                cmp      r3,#1

                bne      0x7c                           bne loop2                 ; 循环等待主时钟振荡器稳定

clkdetected     mov      r0,#1                          mov r0, #AT91C_PMC_CSS_MAIN_CLK

                str      r0,[r1,#0x30]                  str r0, [r1, #PMC_MCKR]   ; 切换芯片时钟到主时钟

                mov      r4,#8                          mov r4, #MCKRDY

loop3           ldr      r3,[r1,#0x68]                  ldr r3, [r1, #PMC_SR]     ; 读取PMC状态

                and      r3,r4,r3

                cmp      r3,#8

                bne      0x98                           bne loop3                 ; 等待芯片时钟稳定

                add      r2,pc,#0x2c ; #0xdc                                      ; 开始别的代码

                ldmia    r2,{r0,r1,r3,r4}

                ……

BGA封装手工焊接攻略改进版

又是好长时间没有更新了,最近工作的事情比较忙,也比较挫折。最近的那块板子出现了多年以来没有的第一次制板就因严重设计错误嗝屁的现象……
后来经过痛苦的BGA飞线,终于好使了;随后也发现其他部分没有任何错误,唉……
看看我飞的板子,我的口号是,没有不能飞线的板子和器件,呵呵。
 
 
 
飞线的过程中,我发现,之前焊接BGA的方法对于较大较厚的电路板是不行的。因为电路板背面一般有较大面积的铺铜,散热很快,导致板子上下受热不均而翘曲。我焊的第一块板子就因为这个,BGA下的过孔断了,废掉了。经过冥思苦想和长时间的搜索,对之前的焊接方法进行一些修改,电路板底面加热不使用风机,而用加热面积较大的预热台。而价格为广大劳苦EE工程师们可以接受的预热台,好像也只有傲月的853A,只要500大洋。下面是853A的图片,这个东东在中发都不太好买到,估计国内的二线城市就有些困难了。
 
 
 
这个东东中间那个网里面是加热盘,两边是两条滑轨,上面有两个用于固定电路板的滑块。把要焊接的电路板放上去,关于芯片的对正什么的还是一样,一般常用的BGA都有0.8mm以上的球间距,而且锡球溶化以后有表面张力,只要大致放上,锡球溶化就能自动拉正芯片。助焊剂什么的和过去一样,我就不说了。看下面这张图。
 
 
 
电路板放在预热台发热盘正上方,尽量让有BGA的区域在发热盘的中心,然后打开预热台电源,我开的温度是300度,等上一会儿,用红外测温仪测量电路板温度,等温度到150度左右,打开风机,温度设定到300度左右,吹个1、2分钟就差不多了。如果不放心,可以参照我过去的方法,用摄像头来看。红外测温仪看下面这张图。
 
 
 
这个东西是东莞造的三无产品,220大洋就能买下,不过个体差异非常大,我买的时候用我太阳穴的35.x度的标准温度对比了好几个才挑出个准的……
这篇就到这里。
6月17日

BGA封装芯片手工焊接攻略

我毕设的很多板上都有BGA芯片,刚开始我觉得这东西实在是没有办法焊接。幸运的是我们研究所的另外一个研究室花了30多万买了个BGA焊接设备,我去蹭了2次,可惜要看人家的脸色,说还好你是一个研究所的,不然要收100块/片呢,心里暗暗不爽。回来问老板,老板说现在就你一个人用,何况就在我们楼上,你就去蹭吧,买了不值得。没办法,自己研究办法焊吧。现在算起来我一共手焊了7~8片了,省了不少钱啊,呵呵。
 
这里给大家提供的手焊方法已经是我最初方法的改进版,我现在基本可以保证100%的焊接成功率。但是有局限性,我焊接的最大芯片尺寸为16mm x 16mm,更大的像TI C6000那样的BGA我觉得会有问题,不建议采用我这种方法。
 
进入正题,你需要的工具:台虎钳一个,热风机两台,红外测温器一个(可选),摄像头一个(可选)。台虎钳是用来夹持电路板的;热风机是用来加热的,如果你的条件有限只有一台,那么最好不要焊接,因为成功率很低,尤其是板子和芯片尺寸都比较大的时候;红外测温器可以测量加热后的温度,没有也可以,用摄像头代替;摄像头是用来观察焊接的进行情况的,没错,这个摄像头就是我们用来QQ视频的那种,当然你熟练了以后,没有也可以。
 
首先,将你的电路板装夹在台虎钳上,调整摄像头的位置,使镜头的中心与电路板的平面一样高。看下面的两个图很容易就明白。有个很严重的问题,芯片怎么和焊盘对上?其实这个用担心,一般手持设备使用的BGA的Pitch都有0.8mm,更大的BGA都是1.0mm,你只要根据丝印层差不多对上就可以了,因为锡球溶化时的表面张力会把芯片拉正。
 
 
 
然后,调节摄像头的焦距,使它能够拍摄到BGA的锡球。为什么要拍BGA的锡球啊?呵呵,我从那台30万元的机器上学习的,当锡球全部加热溶化时,你能看到芯片在重力作用下落下一段距离。很有意思的过程,请看我附上的视频。
 
 
接下来就可以加热了,把两台风机的喷嘴都对准芯片所在的位置,如下图。风机的温度设定在300度左右(这个就需要你的经验了,含铅、不含铅芯片需要的温度不一样,不同公司的芯片需要的温度也不一样)。
 
 
 
加热的同时请仔细观察摄像头捕捉到的画面,或者同时用红外测温器测量电路板的温度,一般温度达到240~250度就一定可以让锡球溶化了。
 
 
值得一提的是,事先在电路板上涂上助焊剂,至于用什么样的就看你个人经验了。另外建议如果你对你的助焊剂没有信心,请在做板的时候给焊盘吹锡,加强可焊性。
 
最后是焊接时芯片下落的视频。
  
6月14日

ADSP-BF531/532/533的SPI Flash Boot

昨天写这篇文章写的差不多的时候,改了Windows Explorer的文件夹选项,气愤的是IE居然也跟着刷新了,我写的还没有保存……今天重新来。
 
到昨天为止,我毕设的几个关键技术的研究,包括超声测距、无刷电机驱动、ARM+FPGA控制器和ARM Linux还有FPGA的驱动、嵌入式图像处理、无线通信模块(这个是我师弟做的)基本上都完事了。最麻烦的是图像处理这块,花了我老师1万多块钱,呵呵,我比较能造。板子做了2次,第一次3700元,4层PCB+摄像头的FPC,不好使;第二次6400元,4层PCB+摄像头的软硬结合FPC,终于好使。还有开发板、芯片什么的3000多,好在我们机器人所有钱哈哈。由于花钱太多,ADSP-BF531/532/533+CMOS摄像头嵌入式图像处理这块是不能写在这里了,如果有需要,可以和我们研究室联系(我要毕业了,但是研究室马上会申请专利,0451-86414462-22,李老师)。
 
今天要写的是嵌入式图像处理部分的最后10cm的工作,将程序写入SPI Flash,并且Boot。其实这个部分挺麻烦的,我看了很多资料,也没有成功Boot。感谢http://bbs.21ic.com上id为yqyte的这位大虾的帖子http://bbs.21ic.com/club/bbs/list.asp?boardid=51&t=2269103,帮助我越过了最后1cm的障碍。
 
开始正文。SPI Flash Boot是ADSP-BF531/532/533一个很好的功能,这可以大大减小电路板的体积。目前可以使用在BF531/532/533上的SPI Flash至少可以达到2M Bytes,对于一般的Linux系统来说也够了。但是SPI Flash的速度还是比较慢的,启动加载需要比较长的过程,这点需要注意。
 
要实现SPI Boot,你必须要看的文档有:ADSP-BF533 Blackfin® Processor Hardware Reference(3 OPERATING MODES AND STATES->Booting Methods和18 SYSTEM DESIGN->Booting the Processor),VisualDSP++ Help(Loader and Utilities Manual)。阅读了这些,加上yqyte大虾的帖子,基本上就知道怎么设计硬件和怎么SPI Boot了。但是,SPI Flash烧写驱动还是个问题,一般的开发板如果支持SPI Boot都会带有SPI烧写驱动;如果你和我一样买的是个劣质的便宜货,没有这个驱动也不要紧,我下面会介绍。
 
从头说起吧:
1. ADSP-BF531/532/533支持如下几种Boot方式:
A. 外部非同步存储器直接启动,启动地址0x20000000,要求管脚BMODE[1:0]为00
B. 外部非同步存储器启动,使用片上Loader,将外部非同步存储器中的程序加载到L1 Cache,启动地址0xFFA08000(BF533为0xFFA00000),要求管脚BMODE[1:0]为01
C. SPI Flash Slave模式启动,即可以使用别的CPU向ADSP-BF531/532/533输入程序,启动地址同B,要求管脚BMODE[1:0]为10
D. SPI Flash Master模式启动,使用片上Loader,将SPI Flash中的程序加载到L1 Cache,启动地址同B,要求管脚BMODE[1:0]为11
 
着重说下方式D,除了以上要求外,还必须注意的是:芯片0.3版本后可以支持8、16或24位寻址的SPI Flash,0.2及以前只支持8位和16位的;PF2管脚用于SPI_CS,MISO管脚必须上拉,0.2版本及以前需要将FP2管脚上拉;除了通用SPI Flash之外,ADSP-BF531/532/533还支持AT45DB041B、 AT45DB081B和AT45DB161B这些器件。另外,芯片内部的Boot ROM地址是0xEF000000,有兴趣的话可以去反汇编一下,看看它的工作原理。
 
2. Loader文件的生成
用仿真器调试程序的时候,生成的是可执行程序的映像(*.dxe),如果要进行Boot,就应该生成Loader文件。这需要对工程进行设置。
 
 
选择VisualDSP++的菜单Project->Project Options,出现如上图所示的窗口,在Type中选择Loader file。然后点击左边的Load,出现如下图所示的窗口。
 
 
选择Boot Mode为SPI,Boot Format为Intel Hex,然后一定要选择一个Initialization File(不选的话不能启动),这个Initialization File是需要编译的,一会儿说。再选择一个Output File即可。
 
3. Initialization file的编译
我也不知道这个Initialization file用来干什么的,呵呵,只是放上了好使。VisualDSP++提供了这个Initialization file的源代码,默认位置在C:\Program Files\Analog Devices\VisualDSP 4.5\Blackfin\ldr\init_code\ADSP-BF533 INIT CODE,这个Init code是给BF533用的,BF531需要修改。其实修改也很简单,就是2句话。
在Init_code.asm中,修改
#include <defBF533.h>为#include <defBF531.h>
在ADSP-BF533.ldf中,修改
 PROGRAM  { TYPE(RAM) START(0xFFA00000) END(0xFFA07fff) WIDTH(8) } //L2为
 PROGRAM  { TYPE(RAM) START(0xFFA08000) END(0xFFA0bfff) WIDTH(8) } //L2
然后进入菜单Project->Project Options,修改Processor为ADSP-BF531,重新编译,生成的DXE文件就是需要的Initialization file。
 
4. SPI Flash的烧写
有了Initialization file之后,重新编译工程,得到的LDR文件就是要烧写的文件。SPI Flash烧写需要:仿真器、VisualDSP++、烧写驱动。很遗憾,我不知道怎么用JTAG下载电缆烧写SPI,前文已经提到,如果你买的是合格的开发板,那么一定会提供SPI烧写的驱动。如果没有也没有关系,我在www.blackfin.org上找到了一个,地址是http://www.blackfin.org/tools.php?id=16。这个驱动是给BF533写的,要给BF531使用,同样需要修改。另外,这个驱动的开发环境是VisualDSP++3.5,其工程文件与4.5版本的似乎不兼容,需要删除SPIFlash.dpj重新建立一个,建立过程如下。
 
A. 建立一个新的工程
 
B. 选择处理器类型
 
C. 加入Startup Code/LDF
 
D. 选上你需要的支持,其实不选SDRAM支持也可以
 
E. 一路Next到Finish。
F. 在工程中加入SPIFlash.c、stflash.c。
G. 修改stflash.c
找到
#include <cdefBF533.h>
修改为
#include <cdefBF531.h>
 
找到
#define CLKIN 27    // CLKIN frequency is 27 MHz on the BF533 EZ-Kit
修改为
#define CLKIN 20    // CLKIN frequency is 20 MHz on the BF531
当然你的晶振是多少就写多少
 
重新编译,生成的DXE文件就是SPI Flash的驱动。
 
H. 加载驱动
加载驱动时,仿真器必须连接目标板。选择VisualDSP++的菜单Tools->Flash Programmer。出现以下窗口:
 
 
点击Browse选择刚才编译好的驱动文件,然后点击Load Driver。注意,此时SPI Flash必须已经与BF531相连接,否则会提示“Out of memory”。
 
 
点选Programming选项卡,就可以进行编程了。
 
就这些了。
5月15日

发现TI 2407A的一个BUG

今天调串口程序,发现我的2407串口程序居然在有定时器中断时无法正常工作。进而研究发现TI的一个小BUG。赶快写下来,不然过几天我又忘记了。
我试过的中断优先级如下:
INT2 --- T3PINT
INT5 --- SCIRX,SCITX
INT1 --- SCIRX,SCITX
INT2 --- T3PINT
以及
INT1 --- SCIRX
INT2 --- T3PINT
INT5 --- SCITX
都出现一个问题——SCITX的中断有可能无法进入。而且如果SCIRX和SCITX处于同一个优先级,整个这个级别的中断(如INT1或INT5)都无法进入;这样造成的后果是SCIRX中断也无法进入,进而触发SCIRXST中的RX Err和OE位,使得SCI模块需要重启才能正常工作。
 
于是分析2407的中断结构,发现出现问题时外设中断请求PIRQ1中的SCITX请求位为1,按TI的Datasheet这个请求是发生中断时被内核自动清除的,而且正常情况下也是如此。于是只好人为在SCITX中断程序里向PIACKR1里的SCITX位写1来响应中断,且最好在中断程序的首尾都加上对这个位的操作。
 
哈哈,原理很抽象啊,说说这个BUG的表象吧,如果你遇到:发送串口数据后,同一优先级的中断,如CAN,SPI,SCI发送,PDPINTA/B,ADCINT(高优先模式),XINT1/2等这些中断无法响应,就是这个BUG造成的。
 
当然,这可能只是一批芯片的问题,我遇到问题的芯片的DINR = 0x0521,芯片型号批号TMS320LF2407APGEA CA-66A3TKW G4。
解决方法举例(不是100%的解决):
void interrupt INT?_handler()
{
    PIACKR1 = 0x0200;
    // 你的中断服务代码
    PIACKR1 = 0x0200;
}
当然别忘了清除相应的IFR位。
 
这样做仍然有非常低的概率出现中断无法响应的情况,请注意!以我的串口程序为例,以115200波特率传输,T3PR为20ms,目前测试到结果是,传输字节数大约500kb就会出一次错。
4月26日

我的串行外设控制协议,以及在2407上运行的串口界面程序。(续)

这个BLOG有BUG,在简体中文下显示的每个Blog的行数不能太多。我把程序分开来贴。
 
serial.h如下:
 
#ifdef _SERIAL_C_
#define SERIAL_RX_BUFFER_LENGTH    0x80
#define SERIAL_TX_BUFFERA_LENGTH   0x40
#define SERIAL_TX_BUFFERB_LENGTH   0x40
#define SERIAL_TEMP_RX_BUFFER_LENGTH  0x04
#define SERIAL_TEMP_TX_BUFFER_LENGTH  0x04
#define SERIAL_TOKEN      0x55
#define SERIAL_TOKEN_SENT     0x05
#define SERIAL_DATALENGTH_SENT    0x06
#define SERIAL_DATA_SENT     0x07
#define SERIAL_PACKAGE_SENT     0x08
#define SERIAL_COMMAND_QUEUE_LENGTH   0x10
#define SERIAL_PACKAGE_OK     0x00
#define SERIAL_ONLY_TOKEN     0x01
#define SERIAL_ZERO_PACKAGE     0x02
#define SERIAL_PACKAGE_TOO_LARGE   0x03
#define SERIAL_PACKAGE_NOT_COMPLETE   0x04
#define SERIAL_CHECKSUM_MISMATCH   0x05
#define ERR_NONE       0x00
#define ERR_NOT_ENOUGH_MEMORY    0x06
#define ERR_NO_PACKAGE_TO_SEND    0x07
#define ERR_SERIAL_NOT_READY    0x08
#define ERR_NO_COMMAND      0x09
struct SERIAL_RX_STRUCT
{
 unsigned int buf_pos;
 unsigned int buf[SERIAL_RX_BUFFER_LENGTH];
 unsigned int flag_parsing_data;
 unsigned int temp_buf_pos;
 unsigned int temp_buf[SERIAL_TEMP_RX_BUFFER_LENGTH];
};
struct SERIAL_TX_STRUCT
{
 unsigned int bufa_write_pos;
 unsigned int bufa_read_pos;
 unsigned int bufa[SERIAL_TX_BUFFERA_LENGTH];
 unsigned int bufb_write_pos;
 unsigned int bufb_read_pos;
 unsigned int bufb[SERIAL_TX_BUFFERB_LENGTH];
 unsigned int buf_on_duty;
 unsigned int is_sending;
};
struct SERIAL_COMMAND_STRUCT
{
 unsigned int cmd_id;
 unsigned int cmd;
 unsigned int parameter_a;
 unsigned int parameter_b;
 unsigned int parameter_c;
};
struct SERIAL_COMMAND_QUEUE_STRUCT
{
 unsigned int length;
 struct SERIAL_COMMAND_STRUCT item[SERIAL_COMMAND_QUEUE_LENGTH];
};
struct SERIAL_STRUCT
{
 struct SERIAL_RX_STRUCT rx;
 struct SERIAL_TX_STRUCT tx;
 struct SERIAL_COMMAND_QUEUE_STRUCT cmd_queue;
};
struct SERIAL_STRUCT serial;
#else
extern int serial_is_rx_buffer_empty();
extern void serial_parse_rx_data();
extern int serial_is_tx_buffer_empty();
extern void serial_package_tx_data(unsigned char cmd_id, unsigned char cmd_status, unsigned char data_a, unsigned char data_b, unsigned char data_c);
extern int serial_tx_trigger();
extern int serial_read_command();
#endif
 
serial.c如下:
 
#ifndef _SERIAL_C_
#define _SERIAL_C_
#endif
#include "basdef.h"
#include "serial.h"
  
void serial_initiate()
{
 serial.rx.buf_pos = 0;
 serial.rx.flag_parsing_data = 0;
 serial.rx.temp_buf_pos = 0;
 serial.tx.bufa_write_pos = 0;
 serial.tx.bufa_read_pos = 0;
 serial.tx.bufb_write_pos = 0;
 serial.tx.bufb_read_pos = 0;
 serial.tx.buf_on_duty = 'A';
 serial.tx.is_sending = 0;
 serial.cmd_queue.length = 0;
 
 //跟CPU有关的串口初始化程序,移植请修改这里
 sys_regs.SCSR1.bits.SCI_CLKEN = 1;
 sci_regs.SCICCR.all = 0;
 sci_regs.SCICCR.bits.SCI_CHAR = 7;
 sci_regs.SCICTL2.all = 0;
 sci_regs.SCICTL2.bits.TX_INT_ENA = 1;
 sci_regs.SCICTL2.bits.RX_BK_INT_ENA = 1;
 sci_regs.SCIHBAUD = (unsigned char)((global_vars.crystal_freq * global_vars.osc_prescale / (global_vars.sci_baudrate * 8) - 1) / 256);
 sci_regs.SCILBAUD = (unsigned char)(((unsigned int)(global_vars.crystal_freq * global_vars.osc_prescale / (global_vars.sci_baudrate * 8) - 1)) % 256);
 sci_regs.SCIPRI.all = 0;
 sci_regs.SCIPRI.bits.SCIRX_PRIORITY = 1;
 sci_regs.SCIPRI.bits.SCITX_PRIORITY = 1;
 sci_regs.SCICTL1.all = 0;
 sci_regs.SCICTL1.bits.SW_RESET = 1;
 sci_regs.SCICTL1.bits.RXENA = 1;
 sci_regs.SCICTL1.bits.TXENA = 1;
 gpio_regs.MCRA.all = 0;
 gpio_regs.MCRA.bits.IOPA0_or_SCITXD = 1;
 gpio_regs.MCRA.bits.IOPA1_or_SCIRXD = 1;
 cpu_regs.IMR.bits.INT5_mask = 1;
 EINT();

}
//这是放在串口接收和发送中断里的两个handler
void serial_rx_handler(unsigned char serial_buf)
{
 if (!serial.rx.flag_parsing_data)
 {
  if (serial.rx.buf_pos < SERIAL_RX_BUFFER_LENGTH)
   serial.rx.buf[serial.rx.buf_pos++] = serial_buf;
 }
 else
 {
  if (serial.rx.temp_buf_pos < SERIAL_TEMP_RX_BUFFER_LENGTH)
   serial.rx.temp_buf[serial.rx.temp_buf_pos++] = serial_buf;
 }
}
void serial_tx_handler()
{
 if (serial.tx.buf_on_duty == 'A')
  if (serial.tx.bufa_read_pos < serial.tx.bufa_write_pos)
   sci_regs.SCITXBUF = serial.tx.bufa[serial.tx.bufa_read_pos++];
  else
  {
   serial.tx.bufa_read_pos = 0;
   serial.tx.bufa_write_pos = 0;
   serial.tx.is_sending = 0;
  }
 else
  if (serial.tx.buf_on_duty == 'B')
   if (serial.tx.bufb_read_pos < serial.tx.bufb_write_pos)
    sci_regs.SCITXBUF = serial.tx.bufb[serial.tx.bufb_read_pos++];
   else
   {
    serial.tx.bufb_read_pos = 0;
    serial.tx.bufb_write_pos = 0;
    serial.tx.is_sending = 0;
   }
}
//这是串口接收中断服务程序,可以看出serial_rx_handler()和serial_tx_handler()的用法,移植请修改这里
void interrupt INT5_handler()
{
 switch (sys_regs.PIVR)
 {
  case 6:
   //接收中断
   serial_rx_handler(sci_regs.SCIRXBUF);
   break;
  case 7:
   //发送中断
   serial_tx_handler();
   break;
 };
 cpu_regs.IFR.bits.INT5_flag = 1;
}

unsigned int serial_parse_rx_data()
{
 unsigned int i = 0, j, k, sum, pos, length, new_pos = 0;
 unsigned int package_status = SERIAL_PACKAGE_OK;
 unsigned int increase;
 struct SERIAL_COMMAND_STRUCT temp_cmd;
 
 serial.rx.flag_parsing_data = 1;
 
 while (i < serial.rx.buf_pos && !new_pos)
 {
  if (serial.rx.buf[i++] == SERIAL_TOKEN)
  {
   package_status = SERIAL_PACKAGE_OK;
   if (i >= serial.rx.buf_pos)
   {
    package_status = SERIAL_ONLY_TOKEN;
    goto finish_pre_check;
   }
   if (!serial.rx.buf[i])
   {
    package_status = SERIAL_ZERO_PACKAGE;
    goto finish_pre_check;
   }
   if (serial.rx.buf[i] > SERIAL_RX_BUFFER_LENGTH - 3)
   {
    package_status = SERIAL_PACKAGE_TOO_LARGE;
    goto finish_pre_check;
   }
   if (serial.rx.buf[i] + i + 2 > serial.rx.buf_pos)
   {
    package_status = SERIAL_PACKAGE_NOT_COMPLETE;
    goto finish_pre_check;
   }
   sum = 0;
   for (j = i + 1; j <= i + serial.rx.buf[i]; j++)
    sum += serial.rx.buf[j];
   if ((sum & 0x00ff) != serial.rx.buf[j])
    package_status = SERIAL_CHECKSUM_MISMATCH;
   
finish_pre_check:
   switch (package_status)
   {
    case SERIAL_PACKAGE_OK:
     length = serial.rx.buf[i];
     pos = i;
     if (serial.cmd_queue.length < SERIAL_COMMAND_QUEUE_LENGTH)
      while (++pos <= i + length)
      {
       // Add command parsing here
       if (!((serial.rx.buf[pos] & 0x00c0) ^ 0x00c0))
       {
        increase = 0;
        temp_cmd.cmd_id = serial.rx.buf[pos];
        temp_cmd.cmd = 0;
        temp_cmd.parameter_a = 0;
        temp_cmd.parameter_b = 0;
        temp_cmd.parameter_c = 0;
        if (pos + 1 <= i + length)
        {
         temp_cmd.cmd = serial.rx.buf[pos + 1];
         increase++;
        }
        else
         goto finish_package_parsing;
       
        if (temp_cmd.cmd >= 0x30)
         if (pos + 2 <= i + length)
         {
          increase++;
          temp_cmd.parameter_a = serial.rx.buf[pos + 2];
         }
         else
          goto finish_package_parsing;
       
        if (temp_cmd.cmd >= 0x60)
         if (pos + 3 <= i + length)
         {
          temp_cmd.parameter_b = serial.rx.buf[pos + 3];
          increase++;
         }
         else
          goto finish_package_parsing;
        if (temp_cmd.cmd >= 0x90)
         if (pos + 4 <= i + length)
         {
          temp_cmd.parameter_c = serial.rx.buf[pos + 4];
          increase++;
         }
         else
          goto finish_package_parsing;
       
        serial.cmd_queue.item[serial.cmd_queue.length].cmd = temp_cmd.cmd;
        serial.cmd_queue.item[serial.cmd_queue.length].cmd_id = temp_cmd.cmd_id;
        serial.cmd_queue.item[serial.cmd_queue.length].parameter_a = temp_cmd.parameter_a;
        serial.cmd_queue.item[serial.cmd_queue.length].parameter_b = temp_cmd.parameter_b;
        serial.cmd_queue.item[serial.cmd_queue.length].parameter_c = temp_cmd.parameter_c;
        serial.cmd_queue.length++;
        pos += increase;
finish_package_parsing:
       ;
       }
      };
     i += (length + 2);
     break;
    case SERIAL_ONLY_TOKEN:
    case SERIAL_PACKAGE_NOT_COMPLETE:
     if (i - 1)
     {
      // Copy incomplete package to the head of the buffer
      for (j = i - 1, k = 0; j < serial.rx.buf_pos; j++, k++)
       serial.rx.buf[k] = serial.rx.buf[j];
      new_pos = k;
     }
     else
      new_pos = serial.rx.buf_pos;
     break;
    case SERIAL_ZERO_PACKAGE:
     i++;
     break;
    case SERIAL_PACKAGE_TOO_LARGE:
     break;
    case SERIAL_CHECKSUM_MISMATCH:
     break;
   }
  }
 }
 
 for (i = 0; i < serial.rx.temp_buf_pos; i++)
  serial.rx.buf[i + new_pos] = serial.rx.temp_buf[i];
 serial.rx.buf_pos = i + new_pos;
 if (i) serial.rx.temp_buf_pos = 0;
 serial.rx.flag_parsing_data = 0;
 
 return(ERR_NONE);
}
int serial_package_tx_data(unsigned char cmd_id, unsigned char cmd_status, unsigned char data_a, unsigned char data_b, unsigned char data_c)
{
 if (serial.tx.buf_on_duty == 'A')
 {
  if ((cmd_status < 0x30) && (serial.tx.bufb_write_pos + 5 < SERIAL_TX_BUFFERB_LENGTH))
  {
   serial.tx.bufb[serial.tx.bufb_write_pos++] = SERIAL_TOKEN;
   serial.tx.bufb[serial.tx.bufb_write_pos++] = 0x02;
   serial.tx.bufb[serial.tx.bufb_write_pos++] = cmd_id;
   serial.tx.bufb[serial.tx.bufb_write_pos++] = cmd_status;
   serial.tx.bufb[serial.tx.bufb_write_pos++] = (unsigned char)(cmd_id + cmd_status);
  }
  else
   if ((cmd_status < 0x60) && (serial.tx.bufb_write_pos + 6 < SERIAL_TX_BUFFERB_LENGTH))
   {
    serial.tx.bufb[serial.tx.bufb_write_pos++] = SERIAL_TOKEN;
    serial.tx.bufb[serial.tx.bufb_write_pos++] = 0x03;
    serial.tx.bufb[serial.tx.bufb_write_pos++] = cmd_id;
    serial.tx.bufb[serial.tx.bufb_write_pos++] = cmd_status;
    serial.tx.bufb[serial.tx.bufb_write_pos++] = data_a;
    serial.tx.bufb[serial.tx.bufb_write_pos++] = (unsigned char)(cmd_id + cmd_status + data_a);
   }
   else
    if ((cmd_status < 0x90) && (serial.tx.bufb_write_pos + 7 < SERIAL_TX_BUFFERB_LENGTH))
    {
     serial.tx.bufb[serial.tx.bufb_write_pos++] = SERIAL_TOKEN;
     serial.tx.bufb[serial.tx.bufb_write_pos++] = 0x04;
     serial.tx.bufb[serial.tx.bufb_write_pos++] = cmd_id;
     serial.tx.bufb[serial.tx.bufb_write_pos++] = cmd_status;
     serial.tx.bufb[serial.tx.bufb_write_pos++] = data_a;
     serial.tx.bufb[serial.tx.bufb_write_pos++] = data_b;
     serial.tx.bufb[serial.tx.bufb_write_pos++] = (unsigned char)(cmd_id + cmd_status + data_a + data_b);
    }
    else
     if (serial.tx.bufb_write_pos + 8 < SERIAL_TX_BUFFERB_LENGTH)
     {
      serial.tx.bufb[serial.tx.bufb_write_pos++] = SERIAL_TOKEN;
      serial.tx.bufb[serial.tx.bufb_write_pos++] = 0x04;
      serial.tx.bufb[serial.tx.bufb_write_pos++] = cmd_id;
      serial.tx.bufb[serial.tx.bufb_write_pos++] = cmd_status;
      serial.tx.bufb[serial.tx.bufb_write_pos++] = data_a;
      serial.tx.bufb[serial.tx.bufb_write_pos++] = data_b;
      serial.tx.bufb[serial.tx.bufb_write_pos++] = data_c;
      serial.tx.bufb[serial.tx.bufb_write_pos++] = (unsigned char)(cmd_id + cmd_status + data_a + data_b + data_c);
     }
     else
      return(ERR_NOT_ENOUGH_MEMORY);
  return(ERR_NONE);
 };
 if (serial.tx.buf_on_duty == 'B')
 {
  if ((cmd_status < 0x30) && (serial.tx.bufa_write_pos + 5 < SERIAL_TX_BUFFERA_LENGTH))
  {
   serial.tx.bufa[serial.tx.bufa_write_pos++] = SERIAL_TOKEN;
   serial.tx.bufa[serial.tx.bufa_write_pos++] = 0x02;
   serial.tx.bufa[serial.tx.bufa_write_pos++] = cmd_id;
   serial.tx.bufa[serial.tx.bufa_write_pos++] = cmd_status;
   serial.tx.bufa[serial.tx.bufa_write_pos++] = (unsigned char)(cmd_id + cmd_status);
  }
  else
   if ((cmd_status < 0x60) && (serial.tx.bufa_write_pos + 6 < SERIAL_TX_BUFFERA_LENGTH))
   {
    serial.tx.bufa[serial.tx.bufa_write_pos++] = SERIAL_TOKEN;
    serial.tx.bufa[serial.tx.bufa_write_pos++] = 0x03;
    serial.tx.bufa[serial.tx.bufa_write_pos++] = cmd_id;
    serial.tx.bufa[serial.tx.bufa_write_pos++] = cmd_status;
    serial.tx.bufa[serial.tx.bufa_write_pos++] = data_a;
    serial.tx.bufa[serial.tx.bufa_write_pos++] = (unsigned char)(cmd_id + cmd_status + data_a);
   }
   else
    if ((cmd_status < 0x90) && (serial.tx.bufa_write_pos + 7 < SERIAL_TX_BUFFERA_LENGTH))
    {
     serial.tx.bufa[serial.tx.bufa_write_pos++] = SERIAL_TOKEN;
     serial.tx.bufa[serial.tx.bufa_write_pos++] = 0x04;
     serial.tx.bufa[serial.tx.bufa_write_pos++] = cmd_id;
     serial.tx.bufa[serial.tx.bufa_write_pos++] = cmd_status;
     serial.tx.bufa[serial.tx.bufa_write_pos++] = data_a;
     serial.tx.bufa[serial.tx.bufa_write_pos++] = data_b;
     serial.tx.bufa[serial.tx.bufa_write_pos++] = (unsigned char)(cmd_id + cmd_status + data_a + data_b);
    }
    else
     if (serial.tx.bufa_write_pos + 8 < SERIAL_TX_BUFFERA_LENGTH)
     {
      serial.tx.bufa[serial.tx.bufa_write_pos++] = SERIAL_TOKEN;
      serial.tx.bufa[serial.tx.bufa_write_pos++] = 0x04;
      serial.tx.bufa[serial.tx.bufa_write_pos++] = cmd_id;
      serial.tx.bufa[serial.tx.bufa_write_pos++] = cmd_status;
      serial.tx.bufa[serial.tx.bufa_write_pos++] = data_a;
      serial.tx.bufa[serial.tx.bufa_write_pos++] = data_b;
      serial.tx.bufa[serial.tx.bufa_write_pos++] = data_c;
      serial.tx.bufa[serial.tx.bufa_write_pos++] = (unsigned char)(cmd_id + cmd_status + data_a + data_b + data_c);
     }
     else
      return(ERR_NOT_ENOUGH_MEMORY);
  return(ERR_NONE);
 }
}
int serial_is_rx_buffer_empty()
{
 if (serial.rx.buf_pos)
  return(0);
 else
  return(1);
}
int serial_is_tx_buffer_empty()
{
 if (serial.tx.bufa_write_pos || serial.tx.bufb_write_pos)
  return(0);
 else
  return(1);
}
int serial_tx_trigger()
{
 if (!serial.tx.is_sending)
 {
  if (serial.tx.buf_on_duty == 'A' && !serial.tx.bufa_write_pos && serial.tx.bufb_write_pos)
  {
   serial.tx.buf_on_duty = 'B';
   //移植请修改这里,发送触发
   sci_regs.SCITXBUF = serial.tx.bufb[serial.tx.bufb_read_pos++];

   serial.tx.is_sending = 1;
   return (ERR_NONE);
  }
  if (serial.tx.buf_on_duty == 'B' && !serial.tx.bufb_write_pos && serial.tx.bufa_write_pos)
  {
   serial.tx.buf_on_duty = 'A';
   //移植请修改这里,发送触发
   sci_regs.SCITXBUF = serial.tx.bufa[serial.tx.bufa_read_pos++];

   serial.tx.is_sending = 1;
   return (ERR_NONE);
  }
 }
}
int serial_read_command(unsigned int * cmd, unsigned int * cmd_id, unsigned int * parameter_a, unsigned int * parameter_b, unsigned int * parameter_c)
{
 unsigned int i;
 if (serial.cmd_queue.length)
 {
  * cmd = serial.cmd_queue.item[0].cmd;
  * cmd_id = serial.cmd_queue.item[0].cmd_id;
  * parameter_a = serial.cmd_queue.item[0].parameter_a;
  * parameter_b = serial.cmd_queue.item[0].parameter_b;
  * parameter_c = serial.cmd_queue.item[0].parameter_c;
  for (i = 0; i < serial.cmd_queue.length - 1; i++)
  {
   serial.cmd_queue.item[i].cmd = serial.cmd_queue.item[i+1].cmd;
   serial.cmd_queue.item[i].cmd_id = serial.cmd_queue.item[i+1].cmd_id;
   serial.cmd_queue.item[i].parameter_a = serial.cmd_queue.item[i+1].parameter_a;
   serial.cmd_queue.item[i].parameter_b = serial.cmd_queue.item[i+1].parameter_b;
   serial.cmd_queue.item[i].parameter_c = serial.cmd_queue.item[i+1].parameter_c;
  };
  serial.cmd_queue.length--;
  return (ERR_NONE);
 }
 else
  return (ERR_NO_COMMAND);
}
4月23日

我的串行外设控制协议,以及在2407上运行的串口界面程序。

相信很多做机电控制的人都遇到这个问题:怎样写一个相当稳定的上位机和下位机的通信界面,我给大家提供一个。
这个串口界面的基本思想是使用双接收和发送缓冲,避免在解析接收到的数据和发送数据的同时,新的数据写入缓冲对界面稳定性的影响。
本界面已经通过2小时连续数据包轰炸式的压力测试(串口通信速度115200),对一般干扰、错误数据包的抵抗能力尚可,已经完成的测试是轰炸30分钟不出错,唯一会造成问题的干扰是连续出现的数据包头令牌,大约在50~100个连续出现的令牌后界面崩溃。
另外附加于这个界面的协议如下:
1.通信方式
一对一通信、全双工异步串行口、二进制方式数据流、无数据流方向控制
2.数据包基本格式
[令牌] [数据包长度] [数据1] ... [数据n] [校验和]
 
[令牌] = 0x55
[数据包长度] = 0x01 ~ 0xFF
[校验和] = ( [数据1] + [数据2] + ... + [数据n] ) 取除以0x100的余数
例如:
0x55 0x01 0x01 0x01 就是一个合法的数据包
0x55 0x02 0x01 0x02 0x01 就是一个校验和错误的数据包
3.暂且叫做Lxz Serial Command Protocol 0.3 (LSCP 0.3)的指令格式
上位机发往下位机的指令格式:
 
[指令ID] [指令] [[参数1] [参数2] [参数3]]
 
[指令ID] 为上位机为某个指令分配的ID,指令ID的范围在0xC0 ~ 0xFF,之后我会解释使用指令ID的意图。
[指令] 为表示者某项操作的一个数,范围为0x00 ~ 0xFF
[参数1] 、[参数2] 、[参数3] 为执行某个指令所必需的参数,范围为0x00 ~ 0xFF。
不同的指令参数个数是不同的,[指令]在0x00到0x2F之间时,无参数;[指令]在0x30到0x5F之间时,有且只有1个参数,以此类推,最多3个参数。
 
下位机发往上位机的指令格式:
 
[返回的指令ID] [指令执行状态] [参数1] [参数2] [参数3]
[返回的指令ID] 为与某个上位机已经发出的指令对应的,上位机分配的指令ID。
[指令执行状态] 为向上位机返回的表示某个指令执行状态的一个数,范围为0x00 ~ 0xFF。
[参数1] 、[参数2] 、[参数3] 为执行某个指令返回的状态所必需的参数,范围为0x00 ~ 0xFF。
不同的执行状态返回参数个数是不同的,这和上述指令参数的情况相同。
 
数据包和命令协议合起来,举些例子:
 
上位机 -> 下位机: 0x55 0x02 0xC0 0x01 0xC1 为一个指令ID为0xC0的没有参数的控制指令。
下位机 -> 上位机: 0x55 0x05 0xC8 0x92 0x0A 0x1F 0x88 0x0B为一个返回指令ID为0xC8的返回执行状态为0x92的含有三个参数0x0A,0x1F,0x88的控制指令的返回。
 
说说使用指令ID的意义:指令ID就是一个上位机为了检测指令执行情况而挂的“号”,对于那种必须在一定时限内对上位机回复的下位机指令,判断接收到的指令ID可以用来监测指令执行情况。当然指令ID的意义不仅仅在此,对于可以同时执行的N条指令,上位机可以一口气发出去,让下位机执行,就算其中有一条是有时限的指令,也只要监测是否在一定时间内有相应的指令ID接收到即可,不用让上位机等下位机。
下面是相应的程序了,这个程序针对TMS320LF2407A写的,其中还使用了我过去写的2407BT,能使用结构体的方式访问寄存器。一共有2个文件:serial.h和serial.c,在开发环境中只需要将serial.c加到编译文件列表里,然后在使用到下述几个函数的文件中包含上serial.h就可以了。有兴趣的话可以把它移植到各种CPU上,移植需要修改的代码位置我用红色注出来了。
 
可以调用的函数申明(包含在serial.h)中:
extern void serial_initiate();
extern int serial_is_rx_buffer_empty();
extern void serial_parse_rx_data();
extern int serial_is_tx_buffer_empty();
extern void serial_package_tx_data(unsigned char cmd_id, unsigned char cmd_status, unsigned char data_a, unsigned char data_b, unsigned char data_c);
extern int serial_tx_trigger();
extern int serial_read_command(unsigned int * cmd, unsigned int * cmd_id, unsigned int * parameter_a, unsigned int * parameter_b, unsigned int * parameter_c);
 
serial_initiate() 用来初始化;
serial_is_rx_buffer_empty() 用来判断接收缓冲是否有数据;
serial_parse_rx_data() 用来以LSCP 0.3 (就是上面写的那个命令协议) 的标准解析串口缓冲里的数据;
serial_is_tx_buffer_empty() 用来判断发送缓冲是否有数据;
serial_package_tx_data() 用来以LSCP 0.3的标准封装送到发送缓冲里的数据;
serial_tx_trigger() 用来触发串口发送;
serial_read_command() 用来读取以LSCP 0.3标准解析出来的串口命令;
 
注意,串口会自动接收数据,但是不会自动解析和自动触发数据发送(一旦触发之后就会连续发送缓冲中的数据),你需要在程序的主循环里加入判断缓冲是否为空、解析和触发发送的指令,例如这样的一个main.c
 
#ifndef _MAIN_C_
#define _MAIN_C_
#endif
#include "basdef.h"
#include "serial.h"
void initiate()
{
 global_vars.crystal_freq = 10000000;
 global_vars.osc_prescale = 4;
 global_vars.sci_baudrate = 115200;
 pre_initiate();
 sys_regs.SCSR1.bits.CLKPS = 0;
 cpu_regs.IMR.all = 0;
 cpu_regs.IFR.all = 0xffff;
 serial_initiate();
}
void main()
{
 unsigned int i;
 unsigned int cmd, cmd_id, parameter_a, parameter_b, parameter_c;
 initiate();
 while(1)
 {
  if (!serial_is_rx_buffer_empty()) serial_parse_rx_data();
  while (!serial_read_command(&cmd, &cmd_id, & parameter_a, & parameter_b, & parameter_c))
   serial_package_tx_data(cmd_id, cmd, parameter_a, parameter_b, parameter_c);
  if (!serial_is_tx_buffer_empty()) serial_tx_trigger();
 };
 
}
 
程序请看下一篇日志。
4月6日

S3C2410的布线。

之前写了那么多关于操作系统的东东,一直没有涉及到作为一个硬件工程师的实质——PCB的制作。
花了大约五天才把S3C2410的SDRAM部分搞定,有些吐血的感觉。说说我的布线:线路板尺寸为30mm*100mm左右的长条形,线长匹配在5mm范围内,主要的SDRAM控制信号做阻抗匹配。关于阻抗和线长匹配的内容请看“高速PCB资料”。
 
出来的板子大概是这样的,第一次画6层板,也不知道这样的能不能加工。
 
 

图中的蛇形线用途就是平衡各个NET的长度。从CPU出来到SDRAM的拓扑结构大约是CPU输出分支接2个SDRAM。这种分支结构在Protel里好像是没有办法自动做线长匹配的(应该是我比较笨,希望高手看到了予以指教),或者以后有空了学学专业的PCB软件,看看别的行不行。

但是事实上,这块板子似乎没有必要做这么复杂的事情,我的大牛师兄的板子是这样的: 

呵呵,他的拓扑结构是CPU->SDRAM1->SDRAM2,没有阻抗匹配。但是这样走线少,布线容易的多;再提供一个数据,他的最长和最短NET失配长度差达到37mm。但是人家的板子就是好使。我有个同学做了个CycloneII+SRAM的板,读写时钟也很高,啥匹配没有,就是好使。看来课本是不是应该改改了,哈哈。

下面来抱怨下三星这个白痴公司,这是Analog Device的ADSPBF531的电源管脚分布

这是Altera的EP1C6F256的电源管脚分布

 这是三星的S3C2410的电源管脚分布

希望我的原理图和封装都没有画错……真是无语了,一点也不为PCB工程师枉死的脑细胞考虑。看来韩国人毕竟是科技的暴发户,细节上完全比不上美国公司。不过回头来想想中国……我们连像样的单片机都生产不了,学IC设计的兄弟们,加油吧!

今天到这里。

4月2日

MC33035和有刷/无刷电机控制。

这篇文章主要留给我的师弟们看的。
 
首先,无刷直流电机的工作原理。无刷电机的优点、原理请你自己百度下。原理一句话概括就是用电子换向器换向的类似同步电机的电机。
无刷电机有转子位置传感器,电子换向器可以知道转子和绕组的相对位置,而进行换向。但是有的无刷电机没有转子位置传感器,这是通过定子线圈的反电动势来确定转子的位置的,有专用的控制芯片。本文使用的是比较常用的有转子位置传感器的无刷电机控制芯片MC33035。关于它的资料也很多,33035自己的DATASHEET上就有一套用模拟电压输入控制电机的图,就是这个:
 
 
按照这个图进行开环控制有问题,调速的范围很窄。可以看10脚的波形,这是个由MC33035内部振荡器产生的上不挨VCC下不挨GND的三角波,而11、12脚后MC33035内含的的那个运放被接成了电压跟随,11脚输入电压的范围是0~6V左右,实际上只在10脚的三角波幅值覆盖的范围(大约为2~4V)内有调速效果,因此,调速的范围很窄。
 
由于我们实验室一直有用DSP产生PWM控制电机的传统,呵呵。所以我采用了这样的连接方法:
 
 
注意这只是我完整原理图的一部分,有些连接如9脚的没有画;R29和R30起到的是“跳线”的作用,如果需要22脚电平为低,请在PCB上焊下R29保留R30。11脚和13脚为PWM输入,12脚接大约1V电压,这样就可以PWM信号输入调速了。请注意PWM的频率和电机的低速稳定性有关系,频率请看我的程序。其他东西不需要太多解释,原理图就能明白。如果需要模拟电压方式的调速,建议使用MC33039进行闭环调速,具体的电路可以参考张明辉师兄的。如果想用33035控制有刷电机也是可以的,这就需要把33035的霍尔传感器三个输入依次固定接为“高、低、低”,在A相和C相的构成H桥上连接有刷电机的绕组就可以了。这样接的原因请看33035的DATASHEET里的那个真值表。不过这样用还不如使用专用的MC33887呢。
 
另外有一个需要注意的地方是选择器件的时候一定要考虑MOSFET的Drain-Source和Gate-Source极限电压和供电电压的大小关系,否则会导致MOSFET烧毁;为了购买和选型方便,可以在力源www.icbase.com网站上寻找你需要的MOSFET器件。
3月15日

高速PCB资料

最近在布Blackfin视觉的板子,开始看了一些高速板的资料,感觉收获挺大的。贴些东西来。
我看了些资料,主要是一篇叫High-Speed Digital System Design的文章,PCBBBS(www.pcbbbs.com)的大侠们翻译的。可以从http://www.pcbbbs.com/dispbbs.asp?boardID=4&ID=122837&page=1下载到。如果这个链接失效了,请从PCBBBS首页进,分支路线为:“中国PCB论坛网 → 线路设计 → SI高速设计 → 帖子列表”,搜索High-Speed Digital System Design。
 
其实我也是刚入门,本文的作用仅仅是给大家提供入门级HOWTO:
 
什么样的板子需要考虑高速所造成的效应?用数字信号的上升时间来衡量,任何长度超过信号在其上升时间内走过距离的十分之一的导线都必须考虑所谓的“传输线效应”。说形象的,如果是133MHz的SDRAM(前几年最常见的PC133SDRAM内存条上的芯片,上升时间1ns),15mm以上距离,就必须考虑传输线效应。这个距离怎么计算出来的呢,如果你算算,实际上电磁波在100ps里能够走上30mm呢,怎么是15mm呢?实际上电信号在导体中的速度和导体周围的介质有关,公式为(BLOG没法写公式,我按照MATLAB符号数学格式写了)v=c/sqrt(Er),c是光速,Er是导线周围导体的介电常数,一般电路板使用一种叫FR4的玻璃纤维,Er=4.X。好的,当你知道你所要面对的情况之后请仔细阅读我上面提到的这个文章。
 
对于实际引用,我们会遇到以下3个问题:
1.布线问题,怎么在我们熟知的PCB设计软件中解决
2.阻抗匹配,这个词很经常听到啊,举例来说明吧。请你找一根杂牌的示波器探针线,测量数字电路管脚输出的波形,也许你会看到方波边沿出现了过冲,这有部分是因为探针的阻抗和芯片、示波器输入阻抗不匹配造成的。这个需要你阅读文章了,实际上完全匹配是不可能的,问题是怎样将阻抗不匹配造成的影响减少到最小。
3.仿真问题,怎么样验证自己对电路板进行的修改是正确的
 
问题2,我目前也没有整太明白,这个需要看的书更多。我上面提供的材料里就有一些简单的描述。
 
问题1,我用的是Altium Designer 6.5,就是大家所熟知的Protel,后来又叫DXP,再后来就叫了Altium Designer。打开菜单Design>Rules,里面的High Speed和Signal Integrity是管高速布线的,大家可以找相关的书解决。
这里我提个我用到的,SDRAM地址/控制和数据线分组等长问题的解决。以Altium Designer 6.5为例(其实Protel 99se也差不多,DXP就更不用说了)。首先Design>Netlist>Edit Nets,新建SDRAM_AC(地址和控制)和SDRAM_D(数据)两个Netclass,把相应的Net添加进来,Design>Rules,选择High Speed>Matched Net Length>新建两个规则SDRAM_AC和SDRAM_D,在Where the first object matches里选择Net Class,然后分别选中刚才建立的两个Net Class。下面的Constraints是最神奇的蛇形线规则设置(电脑主板上经常可以见到的那些弯弯曲曲的绕着走的线,目的就是让短的线绕着走,增加长度以和长线匹配),Tolerance是同组线长最大差距值,我设的是7.62mm,就是0.3inch,具体数值有的强人可以控制在正负50mil以内,由于我们同学有做10mm成功的例子,我放弃了50mil的想法。Style是蛇形线的风格,可以选着玩玩看看,其他的设置可以自己试,决定了蛇形线的形状。这些规则是为了自动生成蛇形线准备的,鉴于自动生成糟糕的结果,我不建议这么做。这些规则仅仅是用来进行DRC(Design Rule Check,在Tools菜单中的命令)的。另外告诉大家怎么自动生成,菜单Tools>Equalize Net Length,注意这个命令:1、仅仅对头尾都连接上的线起作用即针对Routed Nets,2、如果板子太密没有生成蛇形线的地方,执行后没有反应,你可以修改Rules来缩小蛇形线的尺寸看看,3、可以多次调用这个命令,使线长逐步趋于一致,4、如果同组线本身走线长度差距太大,或者你的要求太高(哈哈50mil),那么可能会存在永远也没有办法一致长度的线,这个在执行命令后生成报告中会告诉你。
说说手动怎么做(仅仅针对DXP以上版本),右下角的弹出菜单站里点PCB>PCB,你可以看到你刚才设置的Net Class,选中后可以看到每个Net(无论是Routed还是Unrouted的)长度,在布线时注意调整,如果出现了长度差距太大的线,可以在短线周围预留空间,手工用圆弧连接的方法画出蛇形线。
贴个图看看,你可以先画一个图中那样的波浪形状,然后Ctrl+C和Ctrl+V。当然PADs好像做这个更加专业,但是我看同学用的好像没有Altium Designer操作这么方便。
 
 
问题3,我没有做太深入的研究,随便找了个软件HyperLynx 7.5,哈哈原因很单纯,因为Altium Designer可以直接把PCB存成HyperLynx格式的,其实PADs里带的就是HyperLynx。随便说下,HyperLynx作PCB分析需要几个东西:1、PCB图(废话,哈哈);2、电路中用到的元件的仿真模型,这个可以在器件厂商的主页上面找到,如SDRAM的厂家Micron(www.micron.com),如果没有模型也可以根据器件的DATASHEET自己做,这个可是高级操作;器件模型里规定了器件的管脚输入输出阻抗等一系列的参数;3、电路板的参数,比如板厚、铜厚、多层板每层间距离、介电常数等,这个需要问你委托加工电路板的厂家。简单仿真步骤如下:1、PCB,Altium Designer可以生成,但是一些细节参数可能会错,需要修改下,一般HyperLynx也会自动修改,菜单Edit>Stackup可以修改并输入从电路板制造商那儿得到的数据,2、仿真模型,Edit菜单中的.Ref IC Automapping file等指令就是用来映射器件和模型的,可以尝试下就知道了,操作很简单,3、开始仿真,菜单Simulate>Run interactive simulation就可以看到电信号上升和下降时输入和输出管脚波形的变化了。另外,Report菜单里的Net Statistics可以看到选中Net的特征阻抗等数据。详细的操作请阅读相关书籍。
 
正如我开始说的,在SI(Signal Integrity)方面,我也是新手,希望有大虾看到后对我的文章予以斧正。
 
刚开始的时候我一直想知道,电路板仿真软件到底是什么样子的,能看到什么样的方针结果,为了给和我一样的菜鸟一个直观的印象,我再贴个仿真过程图解,本例使用HyperLynx 7.5,如图这样一个简单的PCB,用来转接直插的FPGA配置接头到FPC软线插座,顶层线路底层地。
 

 先将其另存为HyperLynx可以打开的文件,在Altium Designer中,菜单File>Save as,然后选择保存类型为Export HyperLynx,确认。在HyperLynx中,菜单File>Open Boardsim Board,选择刚才保存的文件,有Warning先OK,然后菜单Edit>Stackup,设定板厚,介电常数等基本参数,如图所示:1、修改底层为参考平面,事实上仿真需要一个参考平面,否则无法进行,很抱歉,我是新手不知道双层的没有地平面的板子该怎么仿真。2、修改板厚,此板1.0mm厚。3、按照电路板制造商给你的介电常数设置Er。

 

接下来设定Net的输入输出属性,选择一个Net,菜单Select>Net by Name,随便选择一个Net,点确定,如图所示

然后菜单Select>Component Models or Edit Values,弹出的窗口里选Select,然后点EASY.MOD,再选CMOS,3.3V,FAST(这仅仅是个演示,实际要选择与Net相对应的器件,可以点Find Model按钮)

接下来Assign Models窗口中的内容变了,先将Pin J1.1配置为Output,然后再选择Pin J3.20做一次同样的选择过程,将J3.20配置为CMOS,3.3V,FAST(当然实际上不是),方向为Input,然后就可以进行仿真了。

菜单Simulate>Run Interactive Simulation,例如要看一个上升沿在这个线上传输的效果,在Driver waveform里选择Rising edge,然后点Start Simulation,在左边的示波窗口里就可以看见波形了。黄线是经过传输后的波形,由于阻抗不匹配出现了振铃现象。

 

那么传输线阻抗匹配的怎么实现呢,波形又是什么样的呢?我们可以用菜单Reports>Net Statistics找到传输线的特性阻抗Z0,此时Z0是120.3欧姆。而通过查找Pin J1.1的模型可以发现输出阻抗为5欧姆(操作命令为Select>Component Models or Edit Values,在弹出窗口中选择Edit Model File按钮,就可以看见输出阻抗),根据传输线原理要消除振铃,可以进行源匹配、负载匹配等,简单点说就是在导线上串联或者并联电阻,阻止信号反弹的发生,具体细节请看我上面推荐的文章。为了看看匹配的效果,这里我来个变态的,直接修改传输线本身的属性——这在实际中是不可选的操作。菜单Edit>Stackup,将板厚修改为0.27mil,此时计算出的传输线特征阻抗恰好是5欧姆,再来看仿真

 

哈哈,这回的结果不错了吧,这就是阻抗匹配的作用。其实你可以仔细观察电脑主板的内存条边上,有很多的贴片阻排,就是用来匹配传输线阻抗的。今天就到这里。