一只鱼 的个人资料EE小站照片日志列表更多 ![]() | 帮助 |
|
|
9月22日 小述电磁兼容我必须写点什么证明这个Blog还在我的维护之下,呵呵。
先赞一下我公司的领导,终于请专业咨询公司为我们进行了为期两天的电磁兼容知识的培训;收获还真的不小,困扰我很久的一些问题有了明确的理论答案,下面就要针对我的实际工作应用这些理论了,呵呵。
为了防止我自己忘记,同时也给需要的同学们提供个参考,我决定把这两天培训的要点和我工作以来的体会写下来。转载请注明来自我是一只鱼的EE小站,邮件cosine@126.com,欢迎有问题在此留言或来信交流。
6月14日 再谈SDRAM的布线——有关Mentor WG、DxDesinger、Expedition、CES
转载请注明来自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.数据包基本格式
3.暂且叫做Lxz Serial Command Protocol 0.3 (LSCP 0.3)的指令格式
下面是相应的程序了,这个程序针对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欧姆,再来看仿真 哈哈,这回的结果不错了吧,这就是阻抗匹配的作用。其实你可以仔细观察电脑主板的内存条边上,有很多的贴片阻排,就是用来匹配传输线阻抗的。今天就到这里。 |
|
|