NXP

NXP ARM7 必须知道的例子!

2019-07-12 11:14发布

寄存器和工作模式:

7种工作模式:
fiq/irq/abt/und/sys/usr/svc。通过"MSR cpsr_c,#0xdx"切换。上电时进入svc模式。
svc和usr的区别是:svc可以通过"MSR cpsr_c,#0xdx"自由切换到其它任何模式,但是usr不可以。
各模式下有自己的堆栈。要在程序启动后依次进入各个模式分别设置自己的堆栈,最后进入usr模式。

好多个寄存器:
r0 - r7 (a1 - a4 / v1 - v4),r15(pc) 在所有模式下都可见。
r8(v5),r9(sb,v6),r10(sl,v7),r11(fp,v8),r12(ip) fiq模式下有一组独立的映射。
r13(sp)/R14(lr) 在usr和sys模式下用同一组映射,其它模式下各有自己的映射。
cpsr在所有模式下可见。
spsr在usr和sys模式下没有映射。

cpsr是一个最特殊的寄存器,结构如下:

31 30 29 28 27~8 7 6 5 4 3 2 1 0
N Z C V 保留 I F T M4 M3 M2 M1 M0

其中,N/Z/C/V分别为负/零/进位/溢出的标志位。在所有模式下都可以进行读操作。
I/F为中断/快中断禁止位,M4~M0是工作模式控制位,它们在USR模式下都不可操作。
T为Thum/ARM模式位,在所有模式下不可直接操作,否则会天下大乱,预取址错误中断可以捕获这种乱局。只能用BX指令进行Thum/ARM的状态切换。
总之,USR模式很不方便。在该模式下只可以通过软中断控制I/F位。cpsr只能够用MSR/MRS指令来操作。

各工作模式下的spsr: 在由突发事件引起的模式切换发生时,新模式的spsr自动保存cpsr的值,以备该模式退出时还原cpsr。在程序的控制下进入某模式时,cpsr不会自动保存到相应的spsr。

Prefetch Abort和Data Abort模式:
Prefetch Abort通常会发生在自修改指令之后。而Data Abort发生于向无效内存中取操作数时,通常是数据指针越过边界了。如果在scatter文件中不指定边界,若编译时内存分配超过了实际物理内存,一定会有Data Abort或Prefetch Abort发生。

对特殊功能寄存器的操作:
通常都有两个寄存器操作同一个特殊功能。一个是负责置位,另一个是负责清除。如VICIntEnable和VICIntEnClr, IOxSET和IOxCLR等。这样就使得单独操作某一位或某几位变得非常容易,如:IO0SET = 0x00001100;IO0CLR = 0x00000011等,只变动特定的位,而不影响其他位。当然也可以用IO0PIN = 0x00001100直接设置所有的位。


--------------------------------------------------------------------------------

存储器映射:
0-1G(0x0000,0000 - 0x3fff,ffff): 片内Flash.
1-2G(0x4000,0000 - 0x7fff,ffff): 片内RAM.
2-3.5G(0x8000,0000 - 0xbfff,ffff - 0xdfff,ffff): 片外存储器。
3.5G - 3.75G(0xe000,0000 - 0xefff,ffff): VPB外设。
3.75G - 4G(0xf000,0000 - 0xffff,ffff): AHB外设。

虽然ARM7的寻址空间为4G,但是LPC2200系列只提供A0~A23总共16M的地址。片选信号CS0 - CS3是A24和A25的译码输出,将片外存储区0x8000,0000 - 0x83ff,ffff划分为bank0 - bank3,共16M*4=64M. 这4个bank可以被分别配置为8/16/32位总线宽度。复位时,bank0的总线宽度由Boot1:0引脚决定, bank1为32位,bank2为16位,bank3为8位。

字节定位信号(BLS0 - BLS3)协调总线宽度和外存芯片数据线宽度。
当Memory由“字节宽度器件”(如62256)或者“未按照字节区分的多字节器件”组成时,应将RBLE设置为"0"。此时,读访问时EMC将BLS0~BLS3拉高。
当Memory由“含有字节选择输入的16位或32位器件”组成时,应将RBLE设置为"1"。此时,读访问时EMC将BLS0~BLS3拉低。
所以,当Memory由62256组成时,由于不需要“片内字节选择输入”,故令RBLE = '0',则BLS0~BLS3只会与nWR同步,可以代替nWR使用。
但是,当Memory由IS61LV25616AL组成时,由于该芯片有"nLB"和"nUB"控制低/高8位的输入,故令RBLE = '1',则BLS0~BLS3与nRD和nWR都会同步,此时,不可以使用BLS0~BLS3代替nWR信号。

地址数据总线:D0 - D31, A0 - A23, OE, WE, CS0 - CS3, BLS0 - BLS3
启动后由P2.7/P2.6控制引导方式,然后由程序设置MEMMAP决定中断向量的映射。
BCFG0 - BCFG3控制读写延时和总线宽度。注意复位后的默认值。
PINSEL2控制引脚功能。

Boot Block
LPC2114/2214的BootBlock被固化在最高的Flash块中,运行时被映射到0x7FFF,E000 - 0x7FFFF,FFFF的区域。而LPC2210没有片内Flash,但它有8K片内ROM存储了BootBlock,也被映射到0x7FFF,E000处。


--------------------------------------------------------------------------------

VPB只是ARM内部使用的总线,它通过桥与AHB总线连接,对用户透明。所以,不必考虑它的存在,只要知道0xe000 - 0xffff,ffff 是外设控制器的地址就可以了。


--------------------------------------------------------------------------------

VIC:
ARM有19个中断源,为其分配了0 - 18号VIC通道。
向量控制寄存器VICVectCntl0-15记录了各个通道号及其使能位。
当中断发生时,VICVectAddr0-15中的一个值会被copy到VICVectAddr.
如果是非向量中断则VICdefaultAddr被copy到VICVectAddr.
程序跳转到VICVectorAddr指向的地址。
中断返回时,写0x00到VICVectAddr.

非向量中断是指那些虽然已经打开(允许),但是没有在相应的VICVectorCntl0~15和VICVectorAddr0~15中设置的中断。

关于中断设置:
1、首先,硬复位后所有的Special Function Registor都有默认值。不必考虑设置的顺序问题。可以先设置好中断,再开通模块功能。
2、软中断(SWI)与非向量中断不同,它的入口是0x0000,0008。进入软中断后,系统变为管理模式。而非向量中断入口是0x0000,0018。它引导系统进入fiq/irq模式。
3、VIC设置实例:
    VICIntSelect = 0x00000000;    //所有中断都是IRQ
    VICVectCntl0 = 0x20 | 15;    //EINT1为向量中断,使用Slot0
    VICVectAddr0 = (uint32)EINT1_Exception;    //EINT1中断地址
    VICDefVectAddr = (uint32)Default_Entry;    //非向量中断地址
    VICIntEnable = 0x00018000;    //使能EINT1和EINT2
    由于在管理向量中断的VICVectCntl0~15和VICVectAddr0~15中只设置了EINT1,故EINT2中断发生时,要进入非向量中断处理程序Default_Entry。

关于优先级:
在调试C语言的多中断程序时,在中断中设断点,调了几个循环就全乱了。开始时怀疑编译器有问题,后来发现是Debugger不够好。
测试如下:
1、修改C Compiler的优化级别为最低。重新编译。
2、令Timer0定时0.25秒中断,在中断中将Test++。
   令EINT3(外部RTC)1秒中断,在中断中用LCD显示Test的值并将一个LED取反。然后再用软件延时,占据CPU0.9秒或0.5秒后才退出中断。
3、令Timer0的优先级小于EINT3。LED为1秒断续,LCD显示的Test值一秒加一。
4、令Timer0的优先级大于EINT3。LED为1秒断续,LCD显示的Test值一秒加一或加二或加四。
结论:Timer0中Test加一/加二/加四是在EINT3退出后的间隙中完成的。所以,IRQ中可能没有传说中的优先级机制。

关于Spurious Interrupts:
http://www.arm.com/support/faqip/3677.html的介绍。其中Solution1、2、3皆可行。当然,最好的办法是不要老是关中断。
在18App的主循环中不断关闭/开启UART的中断以读取其缓冲区的长度及数据,出现以下现象:程序经常走进VICDefaultAddress!
解释:当UART中断产生时,Core latches the IRQ status,此时,UART的中断被关闭。然后,Core loads IRQ Address from VIC. the VIC will not be able to clearly identify the interrupt that generated the interrupt request, and as a result the VIC will return the default interrupt VICDefVectAddr.


--------------------------------------------------------------------------------

关于周立功EasyJTAG设置:
计算机并口要设为SPP模式。
弹出Fatal Axd Error时,点击Connect mode...选择ATTACH...然后Restart即可。


--------------------------------------------------------------------------------

编译和调试:

Project->Remove Object Code...删除当前运行代码,然后可以重新编译。
Axd Debugger运行时会产生C:/documents and settings/username/default-1-2-0-0.sec.如果在打开Axd Debugger时不能自动调入Image, 可以删除此文件,重新打开Axd Debugger, 然后调入Image即可。

单步执行时对特殊功能寄存器的监视:
要监视Timer0寄存器组,从0xE000,4000开始。打开"Processor View"->"Memory"定位在0xE0004000。刚开始运行时监测结果正常,几个循环后,0xE0004000(T0PR)开始不断变化,而这个寄存器除了初始化时,不应该变化的。本应该变化的寄存器如0xE0004008(T0TC)反而不变了。于是打开"Processor Views"->"Watch",在里面加入 *((unsigned long*)0xE0004000)至 *((unsigned long*)0xE000403C)进行观察。发现Watch窗口的内容与Memory中的不同,似乎是Memory中的内容与其对应的地址产生了4个字节的错位。因为Memory中0xE000400C的内容总是与Watch中0xE0004008的内容相近,两者的差别大概是JTAG两次读RAM的时间差。另外,RAM的0xE000401C与Watch中0xE0004018内容相同。所以,Watch窗口的内容是正确的。

Debug:
Axd的Disassembly窗口:有单三角箭头的两个按钮,用于在小范围内(页内)滚动汇编程序。也有双三角箭头的两个按钮,用于在整个汇编程序的范围内移动。
Axd的"Low level symbles"窗口可用于观察各变量的地址,此窗口在辅助阅读Disassembly时有用。


--------------------------------------------------------------------------------

分散加载描述文件.scf的设置

Metrowerks Code Warrior V1.2的"Edit->Debug In ExRAM Settings",然后在"Linker->ARM Linker"的Output页中,选中Scatter选项。在Scatter的编辑框中选择写好的.scf文件。(Scatter-Loading description file).

简单应用时可以不写.scf文件。而在"Output"页中选择"Simple".然后填写"RO Base"和"RW Base"的起始地址。在"Lay Out"页中,填写Object/Symble: Startup.o, Section: Start.编写启动文件:Startup.s.

在"Option"页里的"Image Entry Point"填入起始地址。


--------------------------------------------------------------------------------

Scatter-Load Description File的结构:

".scf"文件中的"+RW"对应".s"源文件中的"READWRITE".
".scf"文件中的"+ZI"对应".s"源文件中的"NOINIT".
".scf"文件中的"+RO"对应".s"源文件中的"READONLY".

在".s"源文件中有:
AREA area_name CODE/DATA,READONLY/NOINIT/READWRITE
END

".scf"的例子:

内容   注解
ROM_LOAD 0x80000000
{   Name of Load Region, Start Address for Load Region and Maximum size of Load Region(省略了)
  ROM_EXEC 0x80000000 0x20000
{ 片外存储区,从0x80000000开始,最多0x20000字节。
  Startup.o(Vector,+First) Startup模块的Vector段放在最前面。注1
  *(+RO) 其他所有模块中的所有代码和只读的数据放在这里。
  }  
  IRAM 0x40000000 0x00004000
{ 片内RAM区,从0x40000000开始,最多0x4000字节
  Startup.o(MyStacks,+first) 指定Startup.o中MyStacks放在最前面。
  Startup.o(+RW,+ZI) Startup.o中的其他+RW/+ZI段。注1
  os_cpu_a.o(+RW,+ZI)  
  }  
  STACKS 0x40004000 UNINIT
{ 片内16K RAM的顶端,存放不需要被"C library"初始化的段。
  Stack.o(+ZI) 注2
  }  
  ERAM 0x80040000
{  
  *(+RW,+ZI)  
  }  
  HEAP +0 UNINIT
{ "+0"表示接着上一段"ERAM"的结尾,继续安排存储区。
  Heap.o(+ZI) 注3
  }  
}    

下面是在scf文件中引用过的源文件示意:

"Startup.s"
  code 32
  area Vectors,CODE,READONLY
  entry
  ...
  end 注1:在"Startup.o"里面会生成名为"Vectors"的段,段的属性为"READONLY"
"Stack.s"
  area Stacks, DATA, NOINIT
  export StackUsr
StackUsr  SPACE 1
  end 注2: 在"Stack.o"里面会生成名为"Stacks"的段,段的属性为"NOINIT",该属性对应scf文件中的"+ZI". 该段不需要初始化或者可以被初始化为"0".
"Heap.s"
  area Heap,DATA,NOINIT
  export bottom_of_heap
bottom_of_heap  SPACE 1
  end 注3: "Heap.o"里面名为"Heap"的段。

在Scatter文件中最好每一个Region都加一个Maximum参数,这样当编译时如果实际使用的空间大于Maximum Size,会有Error:16220E: Excution region xxx size (xxx bytes) exceeds limit (xx bytes)。如果地址有重复,会有Error: 16221E: Excution region xxx overlaps with excution region xxx。前一个Region的首地址 + Maximum > 后一个Region的首地址时不一定有Error。只有当一分配的内存出现覆盖时才会有Error。

Region的"UNINIT"之类的参数要放在"Maximum size"参数之前。

在一个Region中,RAM的分配不是按照罗列的顺序来的。要想让汇编中使用的变量有固定的位置,可以把所有汇编文件产生的".o"放在同一个Region中。如:
IRAM1 0x40000000
{
  startup.o(+RW,+ZI)
  ASMSourceCode1.o(+RW,+ZI)
  ASMSourceCode2.o(+RW,+ZI)
}
IRAM2 +0
{
  CSourceCode1.o(+RW,+ZI)
  CSourceCode2.o(+RW,+ZI)
}
这样,所有汇编中定义的变量地址就相对集中了。
如果只有一个汇编文件如startup.s,也可以这样:
IRAM 0x40002000 0x1000
{
  startup.o (Mystack,+first)
  *(+RW,+ZI)
}
用一个"+first"强行将startup.s中的Mystack放在0x40002000位置。

在"Edit -> DebugRel Settings...->ARM Linker"中选中"Image map"。编译后在Error & Warnings窗口会显示出详细的内存分配情况。如果在"List file name"中指定一个输出文件名,该祥单会直接存在制定文件中以供多次研究。


--------------------------------------------------------------------------------

关于JTAG接口:
P1.20/TRACESYNC应该加上拉电阻以禁止TRACE功能。PINSEL2一定要在程序开始时初始化一下。

LPC2210
JTAG


  1,2/VDD3.3V  
P1.31/nTRST, input 3/nTRST, output EasyJTAG中有上拉电阻。
P1.28/TDI, input 5/TDI, output EasyJTAG中有上拉电阻。
P1.30/TMS, input 7/TMS,output EasyJTAG中有上拉电阻。
P1.29/TCK, input/output 9/TCK, input/output EasyJTAG中有上拉电阻。
P1.26/RTCK, input 11/RTCK, output P1.26外接下拉电阻。
P1.26有内部上拉电阻,故测量时该引脚会呈现高电平。但是在复位时,它的上拉电阻不起作用,只有外部的下拉电阻起作用,P1.26 = 0V, 所以上电后PINSEL2的D3~D0会是0x04(B0100),JTAG有效。
若将P1.26接到3.3V再复位,此时PINSEL2的D3~D0将会是0x00,JTAG无效。
P1.27/TDO, output 13/TDO, input EasyJTAG中有上拉电阻。
nRESET, input 15/nRST, output EasyJTAG中有上拉电阻。
  4,6,8,10,12,14,16,18,20/GND  
  17,19/NC  

G18控制板采用LPC2114,每次运行Axd都不会正确调入程序。原因如下:

有一次是因为已经有一个Axd在运行了,打开第二个Axd, 当然不会正确调入程序。
还有一次是重新编译了一下,就好了。
以上两次都不奇怪,奇怪的是下面几次:
在"Config Target -> Config -> Easy JTag Setup"随便点两下"Halt Mode"中的选项,然后一路点击"OK",会出现"Reload the last Image?",点击"Yes"。有时会有正确的程序被调入,但有时候不成功。要检验是不是已经成功调入了,只要按下"Ctrl-D"显示Disassembly窗口,即可看到芯片中的程序是否正确。
在"Option -> Config Interface -> Session File -> Session file Options"中选择"Reload Images",之后每次启动Axd都会提示"The processor ARM_1 already has image(s) loaded. Continue the operation will replace the currently loaded images(s).... Do you wish to continue?" 选择"Yes", 有时候也可以成功调入程序。
当然,在"Easy JTag Setup -> Aux Option"中要选中"Erase Flash when need".
固化的程序中有禁止JTag调试端口的语句(操作PINSEL2的语句),连不上时用LPC2000 Flash Utility擦除了Flash。偶尔可行。
注意使用LPC2000 Flash Utility时要先将电路复位,再点"OK".

当然最根本的解决办法是将计算机并口设置为"EPP"模式。其他地方都按照"Default"就可以了。


--------------------------------------------------------------------------------

有效用户代码:
ARM把“向量表所有32位数据累加和为0”作为有效用户代码的条件,只适用于使用片内程序存储器的时候,片外程序存储器无此限制。

C语言程序通常需要一段用于初始化的汇编代码,通常存储为"Startup.s",它实现的任务通常是:
1、做好中断向量表
2、初始化外部总线控制器/堆栈/目标板基本模块。
3、给库函数使用的"__user_inital_stackheap"。
4、除数为零时的死循环"__rt_div0: B __rt_div0"。
5、支持加密功能的语句。
6、定义堆栈空间:AREA MyStacks, DATA,NOINIT,ALIGN = 2
要定义栈的起始地址:IrqStackSpace SPACE ...
和栈的头部:StackIrq DCD IrqStackSpace + Length*4。因为栈是向下生长的。

与目标板有关的初始化程序可以放在一个名为"Target.c"的文件里。
1、定义中断处理入口:void IRQ_Exception(void), void FIQ_Exception(void), void Timer0_Exception(void)。
2、向量中断控制器初始化。
3、remap,系统时钟,实时时钟,存储器加速。


--------------------------------------------------------------------------------

C语言中的延时:
__asm{
  nop;
  nop;}
即可。

关注C编译器:"=="的优先级确实比"&"的高,所以,凡牵扯到逻辑的东西,用"()"确认优先级,以避免出现低级错误。


--------------------------------------------------------------------------------

对定时器的操作:
void Timer0Init(uint8 VICSlot, uint32 fdiv)
{
  T0PR = 0; //Prescaling = 0
  T0PC = 0; //Prescalar Counter
  T0TC = 0; //T0 Counter
  T0MR0 = Fpclk / fdiv; //计数周期
  T0MCR = 0x03;  //计数达到T0MR0则置位中断,计数器复位并继续运行。
  T0CCR = 0x00;  //不用捕获模式
  T0TR = 0xffffffff; //清中断
  T0TCR = 0x01;  //运行

  if(VICSlot <= 15){
    *((uint32*)(&VICVectAddr0 + VICSlot)) = (uint32)Timer0_Exception;
    *((uint32*)(&VICVectCntl0 + VICSlot)) = 0x20 | 0x04;
    VICIntEnable = 1 << 0x04;
  }
}


注意:
1、"*((uint32*)(&VICVectAddr0 + VICSlot)) = ..."中,&VICVectAddr0作为基址,VICSlot作为偏移量。由于前面已经有(uint32*)声明这是一个指向uint32的指针,故偏移量每变化一个数字代表地址变化了4个字节,在基址与偏移量相加的时候,系统自动将VICSlot乘以4。如果程序中写成"... + 4 * VICSlot"就错了。
2、一定要用"Fpclk / fdiv"设置,以延时1/fdiv秒。该参数不可以以uS为单位。若"Fpclk * us / 1000000"在计算中会乘法溢出,不易避免,又无警告,故不可用。


--------------------------------------------------------------------------------

对I2C占空比的设置:
I2SCLH = (Fpclk / fi2c + 1) / 2;
I2SCLL = (Fpclk / fi2c) / 2;
妙哉!无论"Fpclk / fi2c"是奇是偶,单方面的"Fpclk / fi2c + 1"使得I2C总周期"Fpclk / fi2c = I2SCLH + I2SCLL"在方法上没有误差。

I2C必须工作在中断模式。因为:"When the "SI" flag is reset, no serial interrupt is requested, and there is no stretching of the serial clock on the SCL line."

I2C的资料在http://www.semiconductors.philip ... _562OVERVIEW_2.pdf.


--------------------------------------------------------------------------------

宏的应用:

在片内外设如I2C,UART,T0,T1,SPI的设置过程中,都需要根据Fpclk计算出一些设定值。我讨厌用ARM做除法,所以就用宏来实现,除法在编译时就可以完成。

首先,所有片内外设的初始化程序都名为:"void _xxxInit();"。之所以在正式函数名之前加一个"_",是为了与宏区别开,不至于误写函数。因为宏的名字与函数名相同,只是全部大写,并且前面没有"_"。如:

#define TIMER0INIT(VICSlot,ms) _Timer0Init(VICSlot,Fpclk/100*ms/10);

void _Timer0Init(uint8 VICSlot,uint32 ClockCycle);
在函数中,直接"T0MR0 = ClockCycle"即可。

注意宏里面的表达式,不可写成"Fpclk*ms/1000",因为如果这样写,当mS太大时,比如mS=1000, Fpclk*mS=(11059200/4)*1000=0xA4CB8000,算到这一步,编译器认为是溢出(它把计算结果看作是有符号数),只要有溢出的警告出现,设置就不正确。
也不可以先做除法,以防止吃掉精度,使计算结果为"0"而令定时器死掉。
总之,既要保证计算精度,又不可以出现溢出警告。


--------------------------------------------------------------------------------

关于C编译器使用的堆栈设置:
1、在Startup.s中有一句:
  MSR CPSR_C,#0x5f    //系统模式
  LDR SP, =UsrStack  //用户栈
2、在Scatter文件中,有
  STACKS 0x40004000 UNINT
  {
    UsrStack.o(+ZI)
  }
3、在UsrStack.s中有
  AREA Stacks, DATA, NOINT
  EXPORT UsrStack
  UsrStack SPACE 1
  END
定义一个UsrStack,大小都无所谓,把它放在可用物理内存的最顶端。C编译器在编译子程序调用时,会将要保护的寄存器压栈,如:
  stmfd r13!,{r3-r7,r14}
其中,r13的别名是SP。
这是一个满递减堆栈。即SP指向的单元内的数据是有效的,入栈时先减SP再存数据。


--------------------------------------------------------------------------------

C编译器的全局变量和局部变量:
全局变量从Scatter文件规定的起始地址向地址增加的方向生长。
局部变量从堆栈中规划,向地址减小的方向生长。

实验如下:
1、在".c"中定义全局变量:
  uint8 ucData2[17]="__/__ hh:mm:ss";
2、在main()函数中定义局部变量:
  uint8 ucdata1[17]="abcdefghijklmnop";
3、运行程序,至"void main(){"处停下。观察0x40003000处为"__/__ hh:mm:ss"。堆栈指针SP(r13)=0x40004000。
4、观察地址0x40003c00处,没有蛛丝马迹。按"Step In"单步执行,指针停在"uint8 ucdata1[17]="abcdefghijklmnop";"处。 SP变为0x40003FC8。
5、再按"Step In"单步执行,进入Disassembly窗口。当前程序标号为"__rt_memcpy",应该是初始化局部变量的。
6、按"Step Out"退出Disassembly窗口。0x40003FCC~0x40003FDB被初始化为"abcdefghijklmnop"。
  此时,SP(r13)仍旧为0x40003FC8,与在第4步时的内容相比,没有变化。说明在刚刚进入"void main()"以后,即在第3、4步中间,局部变量的地址就已经分配好了。

在安排全局变量的时候,若某一模块有被初始化的需求,则该模块放在前面,Scatter文件中的"+first"被移到所有有初始值得变量后面。"first"只是没有初始化要求的数据块的"first"。


--------------------------------------------------------------------------------

建立结构:
typedef struct {
  uint16 Bottom;
  uint16 Top;
  uint16 MaxLength;
  unsigned char* Pointer;
} FIFOCONTROL;
以支持一个FIFO环。
在UART中断中向里面添加数据。在主循环里读出FIFO的长度及里面的数据。

static uint16 FIFOGetLength(FIFOCONTROL* pFifo)
{
  if(pFifo->Bottom <= pFifo->Top)
    return (pFifo->Top - pFifo->Bottom);
  else
    return (pFifo->Top + pFifo->MaxLength - pFifo->Bottom);
}
这段程序看起来很简单,但是,如果在"if"和"return"之间被UART中断,并且恰好是在pFifo->Top越过pFifo->MaxLength时,返回的结果就错了,导致访问到非法地址,而进入Data aboart。
解决:不要直接用指针指向的单元作判断和计算。修改如下:
static uint16 FIFOGetLength(FIFOCONTROL* pFifo)
{
  uint16 m,n,r;
  m = pFifo->Bottom;
  n = pFifo->Top;
  if(m <= n)
    r = n - m;
  else
    r = n + pFifo->MaxLength - m;
  return r;
}
只要把临界区的内容倒入m、n中就大吉了。


--------------------------------------------------------------------------------

莫名其妙:
在主循环前面加上初始化的内容如下:
  SPI0ReadWrite(ucData,1);
  ucIOStatus[0] = ucData[0];
  ucIOCurrent[0] = ucData[0];
  ucIOChange[0] = 0;
每次运行都会进入SWIService;
改为:
  m = ucData[0];
  ucIOStatus[0] = m;
  ucIOCurrent[0] = m;
一切正常,未详查原因。其中,ucIOStatus[]、ucIOCurrent[]和ucIOChange[]虽然写成了数组的样式,但是他们都只包含一个元素,即数组长度是1,不知是否有关系