20世纪30年代,英国送奶公司送奶到订户门口,没盖子也没封口,麻雀和红襟鸟可以很容易的喝到上层的奶皮。后来,牛奶公司把瓶口用锡箔纸封装起来防止鸟的偷食。20年后,英国的麻雀都学会了用嘴把奶瓶的锡箔纸啄开,继续偷吃它们喜欢的奶皮。然而,同样是20年,红襟鸟却一直没学会这种方法。生物学家对这两种鸟进行了研究,从生理角度看它们没多大区别,但在进化上却如此的不同。后来发现这于它们的生活习性有关,麻雀是群居的鸟类,当某只发现了啄破锡箔纸的方法,就可以教会别的麻雀。而红襟鸟则喜欢独居,它们圈地为主,沟通仅止于求偶和对侵犯者的驱逐,因此,就是有某只发现了那个方法,别的鸟也无法知晓。对于人类也是如此,进步需要交流和行动,这样,新技能才可以发扬光大。于是,我写了一下这些学习硬件编程中的感受。
一、 初学汇编
研究生课程结束时,我知道毕设应该是硬件相关的方向,当时对硬件的认识是一片空白。看着同学们早就投入到自己的课题中,自己很着急。忙着去图书馆借了很多关于硬件的书,和电子、电路、单片机等相关的,五花八门,只要觉得里面有想知道的,就拿回来啃,像一只忙碌的没头的苍蝇一样乱撞。对单片机类的,没有“型号”的概念,天书一般,只好理解一些硬件基本知识,很多东西觉得很好,下功夫理解记忆,但没多久就忘了一干二净。现在想想很正常,因为哪些东西是需要实践的。那个学期前后借了几十本书,但看懂的很少。当时做的笔记,只限于对三态门、总线驱动器、锁存器、计数器之类的概念了解,很是低级的东西。后来就看DSP的书,实验室的书不管是写什么系列的,都被我浏览了一番,好像朦胧的明白了什么。脑袋里装了一堆不知前因后果的片断就到了新学期,DSP和操作系统更是没有头绪,好在当时有了确定的目标——TMS320F240。写开题报告前,努力的看了ucOS-II操作系统,仔细的读了有关240的原理的书。但仍然对这两者怎么联系起来的概念很模糊。在开题报告里,写了很多怎样在ucOS 里编程的问题,在当时的理解下,觉得写了很“充分”的东西,想象着以后编程就是那个样子的。但实践以后知道那时的理解有些本末倒置了。
开题后已是四月上旬了,“嵌入式”、“操作系统”、“移植”、“DSP”这些东西一直在脑子了盘旋,看书上网查资料都要朝着这些目标。比较蠢的做法是,像我一样只是努力的找书,企图把这些底层的东西都理解了,甚至想把汇编的指令都记住。直到五月中旬的一天,觉得这样埋头读书不能前进了,就准备动手。当时已经是非典隔离期了,大家都在拼命的运动发泄各种心情,没有学习的气氛。我也很怕,没心情学习,但不敢消沉下去。于是我拿过来板子、仿真器、电源这些很陌生的东西,试着把它们装起来,接着就是装软件、仿真器驱动,因为有安装步骤的说明,我很顺利的完成了。但测试软件时却显示没有成功,仿真器不能用,安装软件的能力我还是比较自信的,但就是找不到问题,请有经验的同学帮忙结果一样。忙了两天还没搞定仿真器,严重的打击了我本来就很迷茫的自信。正当我无所适从时,很幸运的突然发现了电脑CMOS设置里有并口设置的选项,我发现了“EPP”模式,我当时就知道了这次成功了。这个开头很难,但困难有多大,解决困难后就有多兴奋,兴奋之余浑身充满了前进的动力。
接下来就可以编程了,第一步要熟悉软件编程环境,我的第一个疑问就是“Simulator”和“Emulator”的区别。我上网到清华的BBS上发现有很多人在讨论DSP,我在别人的贴子中隐约知道了我用仿真器就是“Emulator”(Simulator是在软件中模拟,开始我还想试试,但有仿真器,最终没去理会)。论坛人气很旺,很多问题我都不知所云,大开眼界,原来问题有这么多!我的第一个程序是最简单的加法。由于我之前还是努力的看了书,所以用到的简单指令不用很费力就可以写出来,但一个完整的程序不止这些,要知道cmd文件怎么写,知道它的作用(当时不能完全理解,按照大家一贯的写法写),还有中断向量表、头文件等。这些文件的作用开始是我不能完全理解的,不太明白为什么那么写。大多书中只是稍微提一下,不能足以帮一个初学者建立一个很明确的概念和编程框架。因为程序很简单,我仿照师姐留下的一个加法程序写了出来。这个加法程序用了三天时间,其中大部分时间花费在一个小问题上:第一次写程序太随意,可能是写高级语言程序的毛病,一个标号的第一个字母我没有写在第一列,而是随意的打了个空格(当时没有意识到后果),这个空格害我找了调试了一天时间!找出错误以后,我又总结了一下,哪些格式可以随意写,哪些要严格遵守,这样再写程序就不那么不自在了。这样简单的“1+1=2”,毕竟不能解决我的大部分疑问,只是稍微了解了编程环境,还有很多个疑问不能找到答案,所以就把CC‘C2000的帮助文件很仔细的看了一遍,很费劲,当时觉得没有很大收获,但从此对帮助文件的内容有了大致的了解,在以后的编程过程中,很习惯的查看帮助。
完成了第一个程序,以后的就按照这个结构写其他一些简单的程序,逐渐的用到了寄存器、中断等。这样就熟悉了以前看书时想努力记住但没有成功的一些指令和寄存器的配置,还逐步的有了一些调试经验。逐渐的我不像以前那样急切,很多问题是要细心的体会,试验一次不能反映全部问题,经验是在不断更正错误的基础上积累起来的。我的一个体会是:不要试图在最初知道所有问题的答案,即使找到的答案也只是纸上谈兵,还要在实践中深化理解,大部分答案都在实践中自然浮出水面。最初编程很死,不敢越雷池半步,这是因为很多东西不完全理解,只有按部就班的把它做出来,不敢加以随意的变化,变了就不知道对不对。我把这些不理解的东西就当作学习的目标,带着这些问题从练习中逐渐找出答案。这个过程中,我养成了一个习惯:因为问题很多,我随时都有可能明白了一些,也可能又有了一个新的疑问,我就把这些想法随时记下来,等待以后验证或寻找答案。写下来对我加深印象真的很有用。后来,如果有了什么重要的经验,等一个程序成功后,都会把它们总结出来,算是对自己的一种肯定,很有成就感。总结的多了,就了解了很多细节的东西,哪怕是以前看起来很简单的指令,也有运用是很巧妙的地方。举几个例子:
1.对形如:y=a1*x1+a2*x2+a3*x3的多项式编程,240指令的装载临时寄存器的指令有LT、LTD、LTP、LTA、LTS,乘法指令有MPY、MPYS、MPYA、MPYU,这些指令中有很多可以同时执行几步,如果能巧妙的结合利用,程序很简洁、效率很高,但要很好的运用,不是很容易(这些是最能体现DSP特点的指令,还有块移动指令,它们和流水线有关,所以效率很高)。自己写程序不要求很高,但知道它们之间有区别即使不用、记不大清楚,看别人的程序也能充分的领会其中的巧妙。有一条指令BANZ,我的程序中最初肯定不会利用它,偶尔一次看到有人用,仔细的体会了它的用法,发现用在循环中真是个不错的选择。
2.有一次看一个程序,涉及到了定标问题,我几乎是看着程序抄下来的试验的。其中有几条非常常见的指令MPY、MPYA、ADD、OR在编译时提示有错误,程序中有这么两句:
MPY #7FFBH
MPYA #0H
我看不出有问题,而且和书上是一模一样的呀。我就查软件中的帮助,发现原来书上用错了!那个错误实在是非常容易犯的!对MPY #k指令,操作数为立即数时为只能是 “13-bit short immediate value”。对MPYA指令根本就没有立即数寻址方式,只有直接或间接寻址方式。还有ADD和OR的用法都是类似的想当然地用,而不注意它地特殊之处。比较幸运的是,这种错误编译器可以发现的,但有些隐含的错误它可能发现不了,自己又觉得不可能会错,结果出来后错误很难排除。
3.最初面对240众多的寄存器,初始化时总觉得多写了没有用到,不然就是少了一些配置,这些要和240内部结构结合起来记忆理解。开始是对CPU寄存器、系统配置寄存器、时钟模块寄存器熟悉,其次熟悉了定时器、比较模块的寄存器配置,上手后就慢慢熟悉其他的,比较麻烦的就是EV模块。大多寄存器只要在程序的最初配置一次,就可以不用管了,个别的比较特殊。如等待状态发生寄存器WSGR在IO空间中,对它赋值就要用out指令而不是常用的splk。还有如 COMCON,要配置为PWM模式,为保证全比较单元的正确操作,需要对它连续两次写操作。还有时钟控制寄存器CKCR0、CKCR1,编程时,必须先使 CKCR0的CLKMD1=0,禁止PLL,然后根据要求设置CKCR1设置其他位,最后使CLKMD1=1,允许PLL工作(如果使用PLL的话)。还有定时器的控制寄存器TxCON有时也需要写两次,第一次配置,第二次启动。总之,对一些需要比较“特殊”的做法,如果注意总结会对整体有个清晰的把握。
二、 移植系统
练习多了,就有点柳暗花明的感觉。于是跃跃欲试,开始试着做我的重要任务——移植uCOS II,看了绍贝贝的书,明白了我要做的是什么,虽然这时还是雾里看花,理解也很朦胧,但已经有了前所未有的自信。最简单的方法是去www.ucos.com网站下载已经移植的代码,当时我查的时候,对TI的DSP,只有C31和C54的移植代码下载(不知现在有没有更新)。我试着从从这两个例程中学习我需要的东西。首先是要明白这么多文件的组织关系,最初面对一大堆文件,根本不知它们是何种关系,为什么存在?大多文献里的都是对几个移植文件做了详细的说明,而对怎样组织的好像是不言而瑜的事情,对一个资深的程序员确实没有必要教怎么做,可是我没有开发大程序的经验,没有清晰的把握,因为自己做的汇编程序都是十几行的小程序,而且还是对cmd文件、向量表、头文件这些不是程序“核心”的东西没有深刻的认识,我对这些零散的文件研究了很久,才意识到我不止要改几个函数那么简单,还要要写一个有main.c的文件、一个 cmd文件、中断向量表、一些必要的头文件,还要象写其他简单的程序一样做一个框架,操作系统当成普通的用户程序一样和这个框架结合起来,然后再写程序和普通的不同的是我有这么多别人都已经做了的东西,我要实现那样的机制不用自己去写,只需拿来用。
明白这些算是思想上的重大突破,不然连 DSP程序和操作系统的关系都不知道。这样就动手写移植部分代码,代码中的一些是参照一个例子,那个应该是240的移植,也只是移植部分的代码,不是一个完整的代码。最初我对ucos认识不深的时候,借鉴了那个程序中的一些做法,如任务切换函数是用软件中断INT31,当时对中断的认识都很浅,不用说软件中断了。对于什么是硬件中断什么是软件中断的问题也困扰我很长时间,曾经问过一个比我早入手的同学,在他的编程中也没有用过软件中断,可以说没有意识到这两种中断只是汇编中普通的中断。至于为什么用INT31就不知道了,现在知道了可以用任何一个软中断的。还有一个就是调用库函数I$$SAVE和I$$ REST用于移植,最初我在傻傻的想,我是不是可以直接用这两个函数?可是它们在什么地方?我怎样找到它们看看代码?还有很多别的疑问,但我还是建立了一个.mak把那些觉得需要的文件加了进去并按照以前的做法把它们“归位”。然后编译,当然有很多很多错误,除了语法错误,当然有我不懂的错误。我尽可能的改了一些,但不能完全正确。问题是出在用C编程上,所以我还要熟悉用C编程的方法。
(一) 用C编程
最初用C写没有什么可以参考的书,我还是从最简单的加法开始,写一个纯粹的C程序,同学说可以用输出语句输出结果,我的即使运行正确也不能输出,找了人帮忙看,还是于事无补。毕竟用C的人很少,于是我自己开始仔细的找原因。看生成的map文件找结果所在的地址,有结果而且正确,看交*列表,C语句对应的汇编语句,没什么错误,就是不明白为什么有错,和别人的不一样。这当然影响我的自信心,觉得为什么倒霉的总是我?抱怨当然不是办法,还是要继续找错了。于是我拿起了放了很久都没看的TI的C编译器文档,虽是全英文的,还是坚持了三天看完了,好像没有找到答案,但更坚定了我的想法,错误是肯定有的,因为文档上有printf函数,但好像不影响程序的运行结果,于是在以后的编程中只好暂时放弃用输出语句(后来调试基本成功后换了板子就可以输出了),这对调试当然很不方便,要在映射的地址中看结果。看文档的好处是,更清楚的知道了用C和汇编编程的不同,如cmd文件、中断向量表的写法。因为要涉及到混合编程,就要对在C中和汇编中的函数、变量互相调用问题弄明白,这是个难点,那个文档看了很多遍,有的问题还是不能完全明白,最终在老师的帮助下对它有了比较清晰的理解,理解后就用编程来验证,结果是我们的理解是正确的(后来看到有些书上也有对此的讨论,我甚至能判断作者的理解是否完全正确)。下面是几点总结:
cmd文件写法可以参照CC‘C2000帮助文件中的例子,还可以查阅TI文档spru024D的2.8.3。
寄存器映射地址头文件,和汇编中的不同,要重新定义,定义方法如下:
#define CKCR0 (volatile unsigned int *) 0x702B
使用方法: *CKCR0=0x0041;
中断向量表的第一条语句应该跳转到_c_int0对于这点我最初不是很明白,因为我看到的程序都是以main开始的。后来逐渐明白了,_c_int0是程序真正开始的地方,只是这个开始不是开发者写出来的,而是编译器自动为我们做好的,你要配合它做的是就是在Build Option中对linker的C Initialization的选项选择ROM Autoinitialization Model或RAM Autoinitialization Model,而不是汇编中的No Autoinitialization,开发者的程序要以main函数开始,初始化结束后会跳转到main函数。在反汇编代码中可以看到这些过程。两种初始化的方式详见上面文档的同一节。
汇编代码中要用到的C的变量或符号,都要在前面加“_ ”,即C中的fun要用在汇编中写为“_fun”。当然互调前要声明为全局变量或外部函数。详细的说明见spru024D的4.2.2。在C中要嵌入汇编的格式为:
asm(“ clrc INTM”);
这个地方要注意的是引号里面第一个字符为空格或Tab键(还可以是别的记不大清了),不能直接写指令。为什么会有上面的一些规定,看看反汇编的代码就很清楚了,编译后编译器会为C中的符号都加以下划线,所以在汇编中用当然要写成“一样”的了,第二条规则也是和编译以后的程序格式有关,可以在你的程序中故意不正确的写,看看显示的错误就明白了。
比较难的是C调用汇编函数,汇编函数的写法。这时要在汇编函数的开始和结束加入一些语句。C中用三个寄存器管理堆栈和局部帧:AR1作为堆栈指针 SP,AR0作为帧指针FP,AR2最为局部变量指针LVP。调用函数时当前指针必须为AR1,首先要在软件堆栈中保存函数返回地址、FP,分配局部帧空间,空间的大小是局部变量的个数加1,如果被调函数中可能修改寄存AR6、AR7,也要保存(当编译器优化时它们被用作保存寄存器变量)。函数实现过程中注意调用它的函数传递的参数的存放次序:从右到左按照堆栈增长的方向放置。函数退出时和进入函数时的操作相反。这个规则的原因也可以从生成的交*列表中找到答案,和上面的原因大同小异,C语句编译后的汇编代码可以看出在任何一个函数调用前都会有这样的“保存 ”工作,结束时做相应的恢复。详见spru024D的4.2.2,4.2.4和4.3节。
用C时需要.lib库函数,这个格式的不能用文本的形式看,在它的同一目录下有rts.scr文件可以以文本形式打开。用一个命令可以提取某个库函数可以对它查看或是修改。我知道这个过程,但没有用过,所以不多说了。详见spru024D的4.1.3。
(二)系统调试及总结
明白了以上的规则就可以大胆的用C编程了,确实要比汇编方便了很多。现在还回到我的移植程序中,弄懂上面的东西后又修改了一些错误,到了六月上旬,整个程序编译通过了。我很兴奋,终于有了进展。接下来就是调试,调试是比编写程序痛苦的事,对那些隐藏的错误要能顺利的找出来实在是困难。系统不能正常工作,首先怀疑的就是移植代码部分,移植代码是按照ucOS提供的步骤写的,写的时候由于借鉴了别的例子并没有深刻理解,调试时就必须有个深刻地认识。
难点一:对任务堆栈初始初始化函数OSTaskStkInit()的作用的理解,方法是模拟TI公司的I$$SAVE库函数对任务堆栈初始化,按照库函数地保存顺序开辟栈空间,得到堆栈指针。这个函数的编写要充分理解“堆栈”的概念。芯片本身的堆栈只有 8 级,无法作为系统堆栈使用,这8级堆栈用来保存函数调用和中断的返回地址。C 编译器将寄存器AR0、AR1作为SP和FP管理系统堆栈和局部帧(上面有说明)。编译器使保存在硬件堆栈里的返回地址弹出保存在系统堆栈里,并保存其他寄存器,即保存了任务运行的现场,这些工作都由I$$SAVE来做。有了任务堆栈初始化函数OSTaskStkInit(),系统在进行初始化时,这个函数将任务地址放在堆栈中,然后用中断返回也就是I$$REST函数将寄存器和TOS初始化,将任务的起始地址弹回到TOS中,这样就能从中断的任务开始运行了。
难点二:时钟节拍。对节拍地作用是在看了操作系统的内核代码后有了深刻认识的,虽然移植是可能不太了解具体的代码,但没有操作系统的概念最好把这个比较易学的操作系统的代码看看,有个结构和原理的认识,我大概花了一周仔细的看了书和代码,这次和以前看不同,了解了代码实现地细节,看完会更加头脑清醒。最初地时钟节拍中断是用实时时钟中断RTI,看完代码知道了应该不可以用它来实现节拍,因为系统时钟地启动是在初始化结束后第一个任务开始前启动的,实时时钟不能这样控制,它在板子上电是就启动了,所以我改用定时器1实现。时钟节拍函数OSTickISR()的实现是定时器的硬件中断,因为在我修改过程中不断地出错,这使我熟练掌握了F240的中断编程方法。我对中断编程做了总结,包括用C编写中断,对初学者应该有帮助:
在UCOS中的中断编程和一般的中断编程稍有不同。共同的是:
1.中断矢量表。中断矢量表一定要定位在程序空间的地址0开始的地方,0000h~003Fh为中断矢量表。第0行跳转到代码开始的地方、第1到第6行是硬件中断跳转指令,除NMI中断其他是软件中断指令(INTk最大为INT31)。发生硬件中断后,处理器自动到前面查表跳转到相应位置。软件中断是在程序中执行了INTk指令才会发生,然后根据x查中断矢量表跳转到相应的ISR(移植系统时用到了软件中断指令,即任务切换函数)。
2.硬件中断系统。其中的可屏蔽中断INTk(k=1~6)对应了多个中断源,有系统中断和EV中断,它们都通过INTk和CPU相关(将INTk称为内核中断)。写系统中断或EV中断必须知道对应哪个内核中断。因为每个内核中断对应了几个中断源(具体对应查阅书籍)。有中断源复用问题,当一个内核中断中有多于一个的外部中断发生时,就要查外部中断矢量表,它是根据中断标识排列的。注意每条跳转指令占两个字节。外部中断矢量表的位置可以在程序空间的任何位置(表中有一个代表表的起始位置的符号)。对它的查表方式为基址+变址。基址为表的起始地址,变址为中断源的标识×2。
3.中断编程。本程序的 INT1有中断复用,如果用汇编编写,需要外部中断矢量表。但在ucOS中编写串口通讯时,发送任务没有采用中断方式,它要和接收任务通讯而有相应的动作(没有操作系统时就是查询方式)。即接收中断发生后要调用发送信号量函数(也可以使用别的通讯机制),在汇编中调用较麻烦,所以采用了C编程,这样在 INT1的ISR中用了switch语句,就不用外部中断矢量表。其他中断源没有复用问题。所以整个程序也不需要外部中断矢量表。
在ucOS中的中断编程要注意的是进入中断调用库函数I$$SAVE(C编写自动调用),而程序结束是调用OSINTExit()(最后调用了I$$REST)。
看了操作系统的源代码再加上对F240编程方法进一步掌握,感觉对这个系统的整体有很清晰的认识,很高兴自己在无数的错误中摸索出来了。不幸的是我的程序仍然不能成功地运行,但我相信应该问题不太大,应该不是在关键处。因为系统可以单步运行且结果正确。我很幸运的在网上遇到了一位做过在2407移植的人,网上还有完整地源码下载。我对比了程序,仍然不知错在哪里,然后我向他讨教,他给了一个建议是,查看map文件,找到两个常量表OSMapTbl和 OSUnMapTbl的映射地址,看看它们的值是否正确。我查看了一下没错,但偶然发现一个寄存器的映射地址是错的,再看其他寄存器也是错的,因为用C编程寄存器的映射头文件要重新定义,我采用的是赋值语句,定义指针,程序中用指针寻址,这种方法应该是没有问题的,但我知道有它的不方便的地方,不利于我的程序中在每个需要的文件中包含这个头文件,于是就改成宏定义寄存器,这样改过之后地址映射就正确了。我把程序从头到尾的看了很多遍,包括反汇编的代码都仔细去找错,倒是有点小bug被找出来,但都不是致命的那个。在我身心俱惫时,我决定放松一下。放风了两天,仍然心有不甘,就又着手我的程序。这次我把所有的寄存器配置从操作系统中拿开,在汇编中检验,终于找到了那个bug:SYSCR的配置错误,这个常用的寄存器不用看帮助就知道怎样配置,但我写错了,可能是最初的笔误,直接导致了系统的复位。系统终于可以运行了!从我的第一个程序到这时有两个多月的时间,我学业生涯中最不平坦的两个多月。
我换了一块板子,以前不能显示输出语句结果的问题也解决了,于是我又写了比较复杂的测试程序测试系统运行情况,虽然不是一次就可以写成功的,但这次调试用了很短的时间。还有下面的总结,是C语言几个关键字有关的内容,虽然较为基础,在操作系统中可能用到的,对透彻把握程序很有帮助。
1.volatile类型限定符。用它修饰的对象叫易变对象,用于告诉编译程序它所修饰的对象(可以是变量或常量)的值可能会以程序中未显式指定的方式发生变化,即不是由程序中的赋值、初始化等显式指定的方式发生变化。如,其变化可能式由中断程序或IO端口所施加的。再如,在程序中可能会把某个全局变量的地址传送给操作系统的时钟并用于存放系统的实际时间,尽管程序中没有对这个变量使用赋值语句赋值,但它的内容还是变了。举个例子:
volatile int ticks;
interrupt timer( )
{
ticks++;
}
wait (int time)
{
ticks = 0;
while (ticks 《 time ) ;
}
如果ticks没有声明为易失变量,编译程序可能会把它当作寄存器变量分配,从而wait函数执行永远不会中止。上例中的interrupt 关键字是修饰中断函数的,表示改函数与中断相联系。
2.typedef 定义新的数据类型。例:
typedef unsigned int INT16U;
定义了类型INT16U,这样做可以提高程序的可移植性。例如,有些C编译系统没有提供无符号短整数unsigned short int类型,这样,在其他编译系统整运行的使用了这种类型的编译器就要把程序中出现的unsigned short int都换成另一种合适的类型如unsigned int,这样改动比较大。使用类型定义可以解决这个问题,这时,在允许使用unsigned short int 的编译系统中编写程序时,先使用一个类型定义:
typedef unsigned short int USINT ;
以后在其他编译系统运行时,只要把改类型定义改成:
typedef unsigned int USINT ;
其他地方不动就可以了。
还有一点就是可以避免因不同编译系统实现上的差别而带来的可移植性问题。例在某些编译系统中char、short(有无符号)用8位表示,而在 TMS320C2xx的系统中,都用16位表示,为使在前者系统中运行的程序在C2xx中运行,必须把char、short改成int,这时,使用类型定义可以减少修改。
3.register存储类区分符。用它修饰说明的变量叫寄存器变量,它的用途一是与auto一样,使它所修饰的对象成为自动的,二是建议编译程序在存储分配使尽可能的把它分配到机器存储器中,以便用时能快速存取。使用寄存器变量最常见的情况时把循环控制变量说明成寄存器变量,因为这个变量在每次循环时都要至少访问一次,例:
register int I ;
fun( )
{
for ( I =0 ;I 《1000;I ++)
{ 。..。.. }
}
理论上,一个程序中可说明任意多个寄存器变量,实际上,由于寄存器数目的物理限制,编译系统只把最前面的有限数目的寄存器变量分配在寄存器中,而把其余的当作普通自动变量处理。在F240中,编译系统允许把AR6、AR7用了存储寄存器变量,使用优化选项是还允许使用AR5(见 spru024D4.2.4)。
以上是我学习中的一些总结。我相信有很多像我这样从零开始的后来者,希望能给他们一点信心和勇气,让他们知道有我这样一个人也经历许多郁闷的日子,最终解决了当时觉得解决不了的问题。如果看到我用了“幸运”这个词,不要羡慕我的运气,不要怪自己得问题为什么不能“幸运”的解决(我就曾这样痛苦的想过),因为可能你更幸运,根本就不会像我这么“倒霉”遇到它。但总会有问题发生,如果解决了,你更幸运的获得了一个宝贵的经验、非常难得的财富。万事开头难,入了门脚下的路就就会逐步平坦了。
20世纪30年代,英国送奶公司送奶到订户门口,没盖子也没封口,麻雀和红襟鸟可以很容易的喝到上层的奶皮。后来,牛奶公司把瓶口用锡箔纸封装起来防止鸟的偷食。20年后,英国的麻雀都学会了用嘴把奶瓶的锡箔纸啄开,继续偷吃它们喜欢的奶皮。然而,同样是20年,红襟鸟却一直没学会这种方法。生物学家对这两种鸟进行了研究,从生理角度看它们没多大区别,但在进化上却如此的不同。后来发现这于它们的生活习性有关,麻雀是群居的鸟类,当某只发现了啄破锡箔纸的方法,就可以教会别的麻雀。而红襟鸟则喜欢独居,它们圈地为主,沟通仅止于求偶和对侵犯者的驱逐,因此,就是有某只发现了那个方法,别的鸟也无法知晓。对于人类也是如此,进步需要交流和行动,这样,新技能才可以发扬光大。于是,我写了一下这些学习硬件编程中的感受。
一、 初学汇编
研究生课程结束时,我知道毕设应该是硬件相关的方向,当时对硬件的认识是一片空白。看着同学们早就投入到自己的课题中,自己很着急。忙着去图书馆借了很多关于硬件的书,和电子、电路、单片机等相关的,五花八门,只要觉得里面有想知道的,就拿回来啃,像一只忙碌的没头的苍蝇一样乱撞。对单片机类的,没有“型号”的概念,天书一般,只好理解一些硬件基本知识,很多东西觉得很好,下功夫理解记忆,但没多久就忘了一干二净。现在想想很正常,因为哪些东西是需要实践的。那个学期前后借了几十本书,但看懂的很少。当时做的笔记,只限于对三态门、总线驱动器、锁存器、计数器之类的概念了解,很是低级的东西。后来就看DSP的书,实验室的书不管是写什么系列的,都被我浏览了一番,好像朦胧的明白了什么。脑袋里装了一堆不知前因后果的片断就到了新学期,DSP和操作系统更是没有头绪,好在当时有了确定的目标——TMS320F240。写开题报告前,努力的看了ucOS-II操作系统,仔细的读了有关240的原理的书。但仍然对这两者怎么联系起来的概念很模糊。在开题报告里,写了很多怎样在ucOS 里编程的问题,在当时的理解下,觉得写了很“充分”的东西,想象着以后编程就是那个样子的。但实践以后知道那时的理解有些本末倒置了。
开题后已是四月上旬了,“嵌入式”、“操作系统”、“移植”、“DSP”这些东西一直在脑子了盘旋,看书上网查资料都要朝着这些目标。比较蠢的做法是,像我一样只是努力的找书,企图把这些底层的东西都理解了,甚至想把汇编的指令都记住。直到五月中旬的一天,觉得这样埋头读书不能前进了,就准备动手。当时已经是非典隔离期了,大家都在拼命的运动发泄各种心情,没有学习的气氛。我也很怕,没心情学习,但不敢消沉下去。于是我拿过来板子、仿真器、电源这些很陌生的东西,试着把它们装起来,接着就是装软件、仿真器驱动,因为有安装步骤的说明,我很顺利的完成了。但测试软件时却显示没有成功,仿真器不能用,安装软件的能力我还是比较自信的,但就是找不到问题,请有经验的同学帮忙结果一样。忙了两天还没搞定仿真器,严重的打击了我本来就很迷茫的自信。正当我无所适从时,很幸运的突然发现了电脑CMOS设置里有并口设置的选项,我发现了“EPP”模式,我当时就知道了这次成功了。这个开头很难,但困难有多大,解决困难后就有多兴奋,兴奋之余浑身充满了前进的动力。
接下来就可以编程了,第一步要熟悉软件编程环境,我的第一个疑问就是“Simulator”和“Emulator”的区别。我上网到清华的BBS上发现有很多人在讨论DSP,我在别人的贴子中隐约知道了我用仿真器就是“Emulator”(Simulator是在软件中模拟,开始我还想试试,但有仿真器,最终没去理会)。论坛人气很旺,很多问题我都不知所云,大开眼界,原来问题有这么多!我的第一个程序是最简单的加法。由于我之前还是努力的看了书,所以用到的简单指令不用很费力就可以写出来,但一个完整的程序不止这些,要知道cmd文件怎么写,知道它的作用(当时不能完全理解,按照大家一贯的写法写),还有中断向量表、头文件等。这些文件的作用开始是我不能完全理解的,不太明白为什么那么写。大多书中只是稍微提一下,不能足以帮一个初学者建立一个很明确的概念和编程框架。因为程序很简单,我仿照师姐留下的一个加法程序写了出来。这个加法程序用了三天时间,其中大部分时间花费在一个小问题上:第一次写程序太随意,可能是写高级语言程序的毛病,一个标号的第一个字母我没有写在第一列,而是随意的打了个空格(当时没有意识到后果),这个空格害我找了调试了一天时间!找出错误以后,我又总结了一下,哪些格式可以随意写,哪些要严格遵守,这样再写程序就不那么不自在了。这样简单的“1+1=2”,毕竟不能解决我的大部分疑问,只是稍微了解了编程环境,还有很多个疑问不能找到答案,所以就把CC‘C2000的帮助文件很仔细的看了一遍,很费劲,当时觉得没有很大收获,但从此对帮助文件的内容有了大致的了解,在以后的编程过程中,很习惯的查看帮助。
完成了第一个程序,以后的就按照这个结构写其他一些简单的程序,逐渐的用到了寄存器、中断等。这样就熟悉了以前看书时想努力记住但没有成功的一些指令和寄存器的配置,还逐步的有了一些调试经验。逐渐的我不像以前那样急切,很多问题是要细心的体会,试验一次不能反映全部问题,经验是在不断更正错误的基础上积累起来的。我的一个体会是:不要试图在最初知道所有问题的答案,即使找到的答案也只是纸上谈兵,还要在实践中深化理解,大部分答案都在实践中自然浮出水面。最初编程很死,不敢越雷池半步,这是因为很多东西不完全理解,只有按部就班的把它做出来,不敢加以随意的变化,变了就不知道对不对。我把这些不理解的东西就当作学习的目标,带着这些问题从练习中逐渐找出答案。这个过程中,我养成了一个习惯:因为问题很多,我随时都有可能明白了一些,也可能又有了一个新的疑问,我就把这些想法随时记下来,等待以后验证或寻找答案。写下来对我加深印象真的很有用。后来,如果有了什么重要的经验,等一个程序成功后,都会把它们总结出来,算是对自己的一种肯定,很有成就感。总结的多了,就了解了很多细节的东西,哪怕是以前看起来很简单的指令,也有运用是很巧妙的地方。举几个例子:
1.对形如:y=a1*x1+a2*x2+a3*x3的多项式编程,240指令的装载临时寄存器的指令有LT、LTD、LTP、LTA、LTS,乘法指令有MPY、MPYS、MPYA、MPYU,这些指令中有很多可以同时执行几步,如果能巧妙的结合利用,程序很简洁、效率很高,但要很好的运用,不是很容易(这些是最能体现DSP特点的指令,还有块移动指令,它们和流水线有关,所以效率很高)。自己写程序不要求很高,但知道它们之间有区别即使不用、记不大清楚,看别人的程序也能充分的领会其中的巧妙。有一条指令BANZ,我的程序中最初肯定不会利用它,偶尔一次看到有人用,仔细的体会了它的用法,发现用在循环中真是个不错的选择。
2.有一次看一个程序,涉及到了定标问题,我几乎是看着程序抄下来的试验的。其中有几条非常常见的指令MPY、MPYA、ADD、OR在编译时提示有错误,程序中有这么两句:
MPY #7FFBH
MPYA #0H
我看不出有问题,而且和书上是一模一样的呀。我就查软件中的帮助,发现原来书上用错了!那个错误实在是非常容易犯的!对MPY #k指令,操作数为立即数时为只能是 “13-bit short immediate value”。对MPYA指令根本就没有立即数寻址方式,只有直接或间接寻址方式。还有ADD和OR的用法都是类似的想当然地用,而不注意它地特殊之处。比较幸运的是,这种错误编译器可以发现的,但有些隐含的错误它可能发现不了,自己又觉得不可能会错,结果出来后错误很难排除。
3.最初面对240众多的寄存器,初始化时总觉得多写了没有用到,不然就是少了一些配置,这些要和240内部结构结合起来记忆理解。开始是对CPU寄存器、系统配置寄存器、时钟模块寄存器熟悉,其次熟悉了定时器、比较模块的寄存器配置,上手后就慢慢熟悉其他的,比较麻烦的就是EV模块。大多寄存器只要在程序的最初配置一次,就可以不用管了,个别的比较特殊。如等待状态发生寄存器WSGR在IO空间中,对它赋值就要用out指令而不是常用的splk。还有如 COMCON,要配置为PWM模式,为保证全比较单元的正确操作,需要对它连续两次写操作。还有时钟控制寄存器CKCR0、CKCR1,编程时,必须先使 CKCR0的CLKMD1=0,禁止PLL,然后根据要求设置CKCR1设置其他位,最后使CLKMD1=1,允许PLL工作(如果使用PLL的话)。还有定时器的控制寄存器TxCON有时也需要写两次,第一次配置,第二次启动。总之,对一些需要比较“特殊”的做法,如果注意总结会对整体有个清晰的把握。
二、 移植系统
练习多了,就有点柳暗花明的感觉。于是跃跃欲试,开始试着做我的重要任务——移植uCOS II,看了绍贝贝的书,明白了我要做的是什么,虽然这时还是雾里看花,理解也很朦胧,但已经有了前所未有的自信。最简单的方法是去www.ucos.com网站下载已经移植的代码,当时我查的时候,对TI的DSP,只有C31和C54的移植代码下载(不知现在有没有更新)。我试着从从这两个例程中学习我需要的东西。首先是要明白这么多文件的组织关系,最初面对一大堆文件,根本不知它们是何种关系,为什么存在?大多文献里的都是对几个移植文件做了详细的说明,而对怎样组织的好像是不言而瑜的事情,对一个资深的程序员确实没有必要教怎么做,可是我没有开发大程序的经验,没有清晰的把握,因为自己做的汇编程序都是十几行的小程序,而且还是对cmd文件、向量表、头文件这些不是程序“核心”的东西没有深刻的认识,我对这些零散的文件研究了很久,才意识到我不止要改几个函数那么简单,还要要写一个有main.c的文件、一个 cmd文件、中断向量表、一些必要的头文件,还要象写其他简单的程序一样做一个框架,操作系统当成普通的用户程序一样和这个框架结合起来,然后再写程序和普通的不同的是我有这么多别人都已经做了的东西,我要实现那样的机制不用自己去写,只需拿来用。
明白这些算是思想上的重大突破,不然连 DSP程序和操作系统的关系都不知道。这样就动手写移植部分代码,代码中的一些是参照一个例子,那个应该是240的移植,也只是移植部分的代码,不是一个完整的代码。最初我对ucos认识不深的时候,借鉴了那个程序中的一些做法,如任务切换函数是用软件中断INT31,当时对中断的认识都很浅,不用说软件中断了。对于什么是硬件中断什么是软件中断的问题也困扰我很长时间,曾经问过一个比我早入手的同学,在他的编程中也没有用过软件中断,可以说没有意识到这两种中断只是汇编中普通的中断。至于为什么用INT31就不知道了,现在知道了可以用任何一个软中断的。还有一个就是调用库函数I$$SAVE和I$$ REST用于移植,最初我在傻傻的想,我是不是可以直接用这两个函数?可是它们在什么地方?我怎样找到它们看看代码?还有很多别的疑问,但我还是建立了一个.mak把那些觉得需要的文件加了进去并按照以前的做法把它们“归位”。然后编译,当然有很多很多错误,除了语法错误,当然有我不懂的错误。我尽可能的改了一些,但不能完全正确。问题是出在用C编程上,所以我还要熟悉用C编程的方法。
(一) 用C编程
最初用C写没有什么可以参考的书,我还是从最简单的加法开始,写一个纯粹的C程序,同学说可以用输出语句输出结果,我的即使运行正确也不能输出,找了人帮忙看,还是于事无补。毕竟用C的人很少,于是我自己开始仔细的找原因。看生成的map文件找结果所在的地址,有结果而且正确,看交*列表,C语句对应的汇编语句,没什么错误,就是不明白为什么有错,和别人的不一样。这当然影响我的自信心,觉得为什么倒霉的总是我?抱怨当然不是办法,还是要继续找错了。于是我拿起了放了很久都没看的TI的C编译器文档,虽是全英文的,还是坚持了三天看完了,好像没有找到答案,但更坚定了我的想法,错误是肯定有的,因为文档上有printf函数,但好像不影响程序的运行结果,于是在以后的编程中只好暂时放弃用输出语句(后来调试基本成功后换了板子就可以输出了),这对调试当然很不方便,要在映射的地址中看结果。看文档的好处是,更清楚的知道了用C和汇编编程的不同,如cmd文件、中断向量表的写法。因为要涉及到混合编程,就要对在C中和汇编中的函数、变量互相调用问题弄明白,这是个难点,那个文档看了很多遍,有的问题还是不能完全明白,最终在老师的帮助下对它有了比较清晰的理解,理解后就用编程来验证,结果是我们的理解是正确的(后来看到有些书上也有对此的讨论,我甚至能判断作者的理解是否完全正确)。下面是几点总结:
cmd文件写法可以参照CC‘C2000帮助文件中的例子,还可以查阅TI文档spru024D的2.8.3。
寄存器映射地址头文件,和汇编中的不同,要重新定义,定义方法如下:
#define CKCR0 (volatile unsigned int *) 0x702B
使用方法: *CKCR0=0x0041;
中断向量表的第一条语句应该跳转到_c_int0对于这点我最初不是很明白,因为我看到的程序都是以main开始的。后来逐渐明白了,_c_int0是程序真正开始的地方,只是这个开始不是开发者写出来的,而是编译器自动为我们做好的,你要配合它做的是就是在Build Option中对linker的C Initialization的选项选择ROM Autoinitialization Model或RAM Autoinitialization Model,而不是汇编中的No Autoinitialization,开发者的程序要以main函数开始,初始化结束后会跳转到main函数。在反汇编代码中可以看到这些过程。两种初始化的方式详见上面文档的同一节。
汇编代码中要用到的C的变量或符号,都要在前面加“_ ”,即C中的fun要用在汇编中写为“_fun”。当然互调前要声明为全局变量或外部函数。详细的说明见spru024D的4.2.2。在C中要嵌入汇编的格式为:
asm(“ clrc INTM”);
这个地方要注意的是引号里面第一个字符为空格或Tab键(还可以是别的记不大清了),不能直接写指令。为什么会有上面的一些规定,看看反汇编的代码就很清楚了,编译后编译器会为C中的符号都加以下划线,所以在汇编中用当然要写成“一样”的了,第二条规则也是和编译以后的程序格式有关,可以在你的程序中故意不正确的写,看看显示的错误就明白了。
比较难的是C调用汇编函数,汇编函数的写法。这时要在汇编函数的开始和结束加入一些语句。C中用三个寄存器管理堆栈和局部帧:AR1作为堆栈指针 SP,AR0作为帧指针FP,AR2最为局部变量指针LVP。调用函数时当前指针必须为AR1,首先要在软件堆栈中保存函数返回地址、FP,分配局部帧空间,空间的大小是局部变量的个数加1,如果被调函数中可能修改寄存AR6、AR7,也要保存(当编译器优化时它们被用作保存寄存器变量)。函数实现过程中注意调用它的函数传递的参数的存放次序:从右到左按照堆栈增长的方向放置。函数退出时和进入函数时的操作相反。这个规则的原因也可以从生成的交*列表中找到答案,和上面的原因大同小异,C语句编译后的汇编代码可以看出在任何一个函数调用前都会有这样的“保存 ”工作,结束时做相应的恢复。详见spru024D的4.2.2,4.2.4和4.3节。
用C时需要.lib库函数,这个格式的不能用文本的形式看,在它的同一目录下有rts.scr文件可以以文本形式打开。用一个命令可以提取某个库函数可以对它查看或是修改。我知道这个过程,但没有用过,所以不多说了。详见spru024D的4.1.3。
(二)系统调试及总结
明白了以上的规则就可以大胆的用C编程了,确实要比汇编方便了很多。现在还回到我的移植程序中,弄懂上面的东西后又修改了一些错误,到了六月上旬,整个程序编译通过了。我很兴奋,终于有了进展。接下来就是调试,调试是比编写程序痛苦的事,对那些隐藏的错误要能顺利的找出来实在是困难。系统不能正常工作,首先怀疑的就是移植代码部分,移植代码是按照ucOS提供的步骤写的,写的时候由于借鉴了别的例子并没有深刻理解,调试时就必须有个深刻地认识。
难点一:对任务堆栈初始初始化函数OSTaskStkInit()的作用的理解,方法是模拟TI公司的I$$SAVE库函数对任务堆栈初始化,按照库函数地保存顺序开辟栈空间,得到堆栈指针。这个函数的编写要充分理解“堆栈”的概念。芯片本身的堆栈只有 8 级,无法作为系统堆栈使用,这8级堆栈用来保存函数调用和中断的返回地址。C 编译器将寄存器AR0、AR1作为SP和FP管理系统堆栈和局部帧(上面有说明)。编译器使保存在硬件堆栈里的返回地址弹出保存在系统堆栈里,并保存其他寄存器,即保存了任务运行的现场,这些工作都由I$$SAVE来做。有了任务堆栈初始化函数OSTaskStkInit(),系统在进行初始化时,这个函数将任务地址放在堆栈中,然后用中断返回也就是I$$REST函数将寄存器和TOS初始化,将任务的起始地址弹回到TOS中,这样就能从中断的任务开始运行了。
难点二:时钟节拍。对节拍地作用是在看了操作系统的内核代码后有了深刻认识的,虽然移植是可能不太了解具体的代码,但没有操作系统的概念最好把这个比较易学的操作系统的代码看看,有个结构和原理的认识,我大概花了一周仔细的看了书和代码,这次和以前看不同,了解了代码实现地细节,看完会更加头脑清醒。最初地时钟节拍中断是用实时时钟中断RTI,看完代码知道了应该不可以用它来实现节拍,因为系统时钟地启动是在初始化结束后第一个任务开始前启动的,实时时钟不能这样控制,它在板子上电是就启动了,所以我改用定时器1实现。时钟节拍函数OSTickISR()的实现是定时器的硬件中断,因为在我修改过程中不断地出错,这使我熟练掌握了F240的中断编程方法。我对中断编程做了总结,包括用C编写中断,对初学者应该有帮助:
在UCOS中的中断编程和一般的中断编程稍有不同。共同的是:
1.中断矢量表。中断矢量表一定要定位在程序空间的地址0开始的地方,0000h~003Fh为中断矢量表。第0行跳转到代码开始的地方、第1到第6行是硬件中断跳转指令,除NMI中断其他是软件中断指令(INTk最大为INT31)。发生硬件中断后,处理器自动到前面查表跳转到相应位置。软件中断是在程序中执行了INTk指令才会发生,然后根据x查中断矢量表跳转到相应的ISR(移植系统时用到了软件中断指令,即任务切换函数)。
2.硬件中断系统。其中的可屏蔽中断INTk(k=1~6)对应了多个中断源,有系统中断和EV中断,它们都通过INTk和CPU相关(将INTk称为内核中断)。写系统中断或EV中断必须知道对应哪个内核中断。因为每个内核中断对应了几个中断源(具体对应查阅书籍)。有中断源复用问题,当一个内核中断中有多于一个的外部中断发生时,就要查外部中断矢量表,它是根据中断标识排列的。注意每条跳转指令占两个字节。外部中断矢量表的位置可以在程序空间的任何位置(表中有一个代表表的起始位置的符号)。对它的查表方式为基址+变址。基址为表的起始地址,变址为中断源的标识×2。
3.中断编程。本程序的 INT1有中断复用,如果用汇编编写,需要外部中断矢量表。但在ucOS中编写串口通讯时,发送任务没有采用中断方式,它要和接收任务通讯而有相应的动作(没有操作系统时就是查询方式)。即接收中断发生后要调用发送信号量函数(也可以使用别的通讯机制),在汇编中调用较麻烦,所以采用了C编程,这样在 INT1的ISR中用了switch语句,就不用外部中断矢量表。其他中断源没有复用问题。所以整个程序也不需要外部中断矢量表。
在ucOS中的中断编程要注意的是进入中断调用库函数I$$SAVE(C编写自动调用),而程序结束是调用OSINTExit()(最后调用了I$$REST)。
看了操作系统的源代码再加上对F240编程方法进一步掌握,感觉对这个系统的整体有很清晰的认识,很高兴自己在无数的错误中摸索出来了。不幸的是我的程序仍然不能成功地运行,但我相信应该问题不太大,应该不是在关键处。因为系统可以单步运行且结果正确。我很幸运的在网上遇到了一位做过在2407移植的人,网上还有完整地源码下载。我对比了程序,仍然不知错在哪里,然后我向他讨教,他给了一个建议是,查看map文件,找到两个常量表OSMapTbl和 OSUnMapTbl的映射地址,看看它们的值是否正确。我查看了一下没错,但偶然发现一个寄存器的映射地址是错的,再看其他寄存器也是错的,因为用C编程寄存器的映射头文件要重新定义,我采用的是赋值语句,定义指针,程序中用指针寻址,这种方法应该是没有问题的,但我知道有它的不方便的地方,不利于我的程序中在每个需要的文件中包含这个头文件,于是就改成宏定义寄存器,这样改过之后地址映射就正确了。我把程序从头到尾的看了很多遍,包括反汇编的代码都仔细去找错,倒是有点小bug被找出来,但都不是致命的那个。在我身心俱惫时,我决定放松一下。放风了两天,仍然心有不甘,就又着手我的程序。这次我把所有的寄存器配置从操作系统中拿开,在汇编中检验,终于找到了那个bug:SYSCR的配置错误,这个常用的寄存器不用看帮助就知道怎样配置,但我写错了,可能是最初的笔误,直接导致了系统的复位。系统终于可以运行了!从我的第一个程序到这时有两个多月的时间,我学业生涯中最不平坦的两个多月。
我换了一块板子,以前不能显示输出语句结果的问题也解决了,于是我又写了比较复杂的测试程序测试系统运行情况,虽然不是一次就可以写成功的,但这次调试用了很短的时间。还有下面的总结,是C语言几个关键字有关的内容,虽然较为基础,在操作系统中可能用到的,对透彻把握程序很有帮助。
1.volatile类型限定符。用它修饰的对象叫易变对象,用于告诉编译程序它所修饰的对象(可以是变量或常量)的值可能会以程序中未显式指定的方式发生变化,即不是由程序中的赋值、初始化等显式指定的方式发生变化。如,其变化可能式由中断程序或IO端口所施加的。再如,在程序中可能会把某个全局变量的地址传送给操作系统的时钟并用于存放系统的实际时间,尽管程序中没有对这个变量使用赋值语句赋值,但它的内容还是变了。举个例子:
volatile int ticks;
interrupt timer( )
{
ticks++;
}
wait (int time)
{
ticks = 0;
while (ticks 《 time ) ;
}
如果ticks没有声明为易失变量,编译程序可能会把它当作寄存器变量分配,从而wait函数执行永远不会中止。上例中的interrupt 关键字是修饰中断函数的,表示改函数与中断相联系。
2.typedef 定义新的数据类型。例:
typedef unsigned int INT16U;
定义了类型INT16U,这样做可以提高程序的可移植性。例如,有些C编译系统没有提供无符号短整数unsigned short int类型,这样,在其他编译系统整运行的使用了这种类型的编译器就要把程序中出现的unsigned short int都换成另一种合适的类型如unsigned int,这样改动比较大。使用类型定义可以解决这个问题,这时,在允许使用unsigned short int 的编译系统中编写程序时,先使用一个类型定义:
typedef unsigned short int USINT ;
以后在其他编译系统运行时,只要把改类型定义改成:
typedef unsigned int USINT ;
其他地方不动就可以了。
还有一点就是可以避免因不同编译系统实现上的差别而带来的可移植性问题。例在某些编译系统中char、short(有无符号)用8位表示,而在 TMS320C2xx的系统中,都用16位表示,为使在前者系统中运行的程序在C2xx中运行,必须把char、short改成int,这时,使用类型定义可以减少修改。
3.register存储类区分符。用它修饰说明的变量叫寄存器变量,它的用途一是与auto一样,使它所修饰的对象成为自动的,二是建议编译程序在存储分配使尽可能的把它分配到机器存储器中,以便用时能快速存取。使用寄存器变量最常见的情况时把循环控制变量说明成寄存器变量,因为这个变量在每次循环时都要至少访问一次,例:
register int I ;
fun( )
{
for ( I =0 ;I 《1000;I ++)
{ 。..。.. }
}
理论上,一个程序中可说明任意多个寄存器变量,实际上,由于寄存器数目的物理限制,编译系统只把最前面的有限数目的寄存器变量分配在寄存器中,而把其余的当作普通自动变量处理。在F240中,编译系统允许把AR6、AR7用了存储寄存器变量,使用优化选项是还允许使用AR5(见 spru024D4.2.4)。
以上是我学习中的一些总结。我相信有很多像我这样从零开始的后来者,希望能给他们一点信心和勇气,让他们知道有我这样一个人也经历许多郁闷的日子,最终解决了当时觉得解决不了的问题。如果看到我用了“幸运”这个词,不要羡慕我的运气,不要怪自己得问题为什么不能“幸运”的解决(我就曾这样痛苦的想过),因为可能你更幸运,根本就不会像我这么“倒霉”遇到它。但总会有问题发生,如果解决了,你更幸运的获得了一个宝贵的经验、非常难得的财富。万事开头难,入了门脚下的路就就会逐步平坦了。
举报