原创 菜鸟学uC/OS_II(12)

2008-9-12 10:58 4903 6 6 分类: MCU/ 嵌入式

菜鸟学uC/OS_II(12)<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />


By <?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />Norman


2008-7-28


 


环境搭建:


今天将上册基本跑完了,一定程度地熟悉了ARM7基本体系结构,为下册的学习(主要是uC/OS_II的移植)作准备,将整个结构看了看,首先还是要搭建环境。其他的没有什么,就是需要修改LPC2148的模板文件。好在找到了ZLGLPC2141模板,将分散加载文件修改了就好了。找一个LPC2148的手册,查查它的内存映射,修改分散文件中的堆栈地址到对应CPURAM最高端地址就可以搞定了。其中IRAM地址——片内RAM调试代码空间结束地址没有修改,因为我认为既然2141调试都没有问题,那么,2148的代码也不会多到哪里去吧。如果出现问题,再来修改。如果使用LPC2131或者LPC2000的模板,除了堆栈大小,还需要修改系统晶振设置和分散加载文件等,多一些步骤,不过都是相似的。


修改完了,能不能用?跑一个小程序,点点LED就可以了。


程序当然能够跑!


其实,这些设置不过是让CPU物尽其用而已,如果你将RAM顶端地址修改为0x40000100,程序还是能够跑,因为程序代码只占用了那么点空间,如果代码多起来了,那可能就有点问题了。我将它修改为0x40000010程序就跑不动了。而代码调试结束空间地址也可以适当加大,这里我设的是0x40002000


这一块还是比较生疏,不过现在的目的是拉通一遍去移植uC/OS,那个时候才必须熟悉系统存储器结构等,所以,现在还不忙去深入。


突然发现个问题,就是我修改的是我ADS程序中的模板,需要注意如果以后直接运行例程,出现问题要找这里的问题——分散文件的加载。另外,还有uC/OS_II的模板也没有改,需要注意。


USB好像没有做驱动,所以XP一直提示找不到硬件——


使用LPC2148GPIO的注意事项:


1)如何让引脚输出并行的01


要让引脚输出并行的数据,通过IOSETIOCLR是不行的,因为存在延时,那么就可以通过IOPIN寄存器来写。慢速和快速GPIO口实现方式有所不同。


<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />



 


点击看大图


 


点击看大图


2)如果要体现快速GPIO口的优势,需要使用汇编语言编写相应的代码,并且在ARM模式下,使用MAM配置,在Flash中运行,才有比较好的效果。


 


对于其他特点,对移植uC/OS并没有帮助,所以跳过,直接进入主题。


 


ARM7会跑了,但是对于移植还是不够的。需要进一步深入熟悉其汇编编程和系统管理等。接下来主要的任务就是不遗余力地去搞定……


 


首先还是跑程序,毕竟先跑起来才知道例程是否可用,然后去研读代码应该就可以事半功倍了。程序写好了,环境搭建也很简单。由于程序是LPC2131的模板,而我还没有研究过模板之间的区别(只知道加载文件如何改),但是其他系统方面的不知道。所以还是换回LPC2131的跑。


真不好意思啊^_^  ……


 


程序跑起来了,接下来的工作就是潜心研究代码……


闭关中……………………………………


 


 


花了一天时间去了解了ARM的指令结构,现在开始研读代码。


计划是先弄清楚代码的组织结构,然后逐个注释,再形成系统认识,最后进行模仿修改。


 


1、代码组织结构


我截了一个能够运行的工程模板的图。从途中可以看出,我们要移植所需要的处理器相关文件ARM,操作系统相关文件SOURCE,功能文件Arm_Pc(不是必须)和工程文件Beep是平级的——FlashOutput文件是为了方便我放在这里的Hex输出文件夹——这样的组织结构比较清晰。当然,如果你在你的配置文件config.hINCLUDES.h文件中的路径设置不同,结构也当然不同。



 


f86bb1a6-1e09-473d-9266-250d19aa41a2.JPG


然后来看文件夹中这些文件的组织方式:


既然是针对工程应用的,那么先从从Beep文件夹开始——



 


6b681281-1e83-414b-99a9-dee817dc7b74.JPG


很明显,这里面有这样几种文件:


l         ARM的启动代码:target.ctarge.hStartup.sLPC2294IRQ.s


l         操作系统配置文件:INCLUDES.HOS_CFG.H


l         工程应用文件及配置:main.cconfig.h


l         编译器分散加载文件:mem_amem_c


既然是针对工程的,我们先看main.c


#include "config.h"


#include "stdlib.h"


——这是main.c中包含的头文件,很明显,应用文件通过config.h来配置其所需要的资源;stdlib.h只是一个库文件(也可以放在config.h中吧)。


顺藤摸瓜,看看config.h中有些什么东西:


#include "Includes.h"


——操作系统资源


#include    "..\..\Arm_Pc\pc.h"


——功能性部件


#include    "target.h"


#include    "LPC2294.h"


——ARM目标文件


#include    <stdio.h>


#include    <ctype.h>


#include    <stdlib.h>


#include    <setjmp.h>


#include    <rt_misc.h>


——工程应用需要的一些库文件


这里主要配置了操作系统和处理器目标启动相关资源


再往下看:


首先是INCLUDES.H文件——


#include    "..\..\arm\os_cpu.h"


#include    "os_cfg.h"


#include    "..\..\source\ucos_ii.h"


<?xml:namespace prefix = w ns = "urn:schemas-microsoft-com:office:word" />——都是操作系统相关的文件


不过这里放了一个#include    <string.h>让人费解干嘛放在这里config.h中不行么?改一下!


编译给出警告:原来在OSTask.c中用到了string.h中的sizeof,所以这里需要这个库文件。


然后看target.h——


咦,没什么内容?


那我们就来看看ADS工程的组织文件(如右图):


c48f58f9-ab08-4536-8315-34b309f4f33f.JPG


target文件夹中有target.cIRQ.sStartup.s三个文件,这三个文件针对的是目标处理器;其中IRQ.s包含了文件IRQ.inc,此文件定义IRQ汇编接口代码宏;既然是针对CPU的,就放在ARM文件夹中。


这样,所有相关文件就组织起来了。


这里注意,由于周工给的工程模板组织形式,将INCLUDES.HOS_CFG.H放在了工程文件中,起先我个人认为这样不是很清晰(虽然简化了路径),可以将其单独放在一个操作系统配置文件夹中。但是,我这样做了一次实验之后,发现更不方便,因为很多操作系统文件用到了INCLUDES.H的,路径都在工程当前路径下,要是一个个来改,很累;不过将其做成模板之后还是可以用的。


现在来一个个注释代码——


 


不得不再次回到启动代码:


基于ARM的芯片多数为复杂的片上系统,这种复杂系统里的多数硬件模块都是可配置的,需要由软件来设置其需要的工作状态。因此在用户的应用程序之前,需要由专门的一段代码来完成对系统的初始化。由于这类代码直接面对处理器内核和硬件控制器进行编程,一般都是用汇编语言。


通用的内容包括:


l         中断向量表


l         初始化存储器系统


l         初始化堆栈


l         初始化有特殊要求的断口,设备


l         初始化用户程序执行环境


l         改变处理器模式


l         呼叫主应用程序


 


这几天被汇编搞昏了头,不得不仔细去看看ARM7指令系统,并研究LPC2000User Manual等,这些手册说的比较清晰,有问题也好找出来——


 


再次闭关中………………………………


 


知识点1做移植规划的时候,模式选择要求不能使用异常模式,因此,默认模式使用用户模式,而可选模式为系统模式。


知识点2用户模式不能使用MSRMRS,但是系统模式可以使用;其他地方两个模式是相似的。


知识点3存储器映射再理解:


这里借用一位网友的博文。我在看LPC2000手册的时候,有一段关于存储器映射的解释,当时已经大略懂了。看了这篇博文之后,不但印证了我的理解,而且其他有些地方也豁然开朗……


『存储器映射是指把芯片中或芯片外的FLASHRAM,外设,BOOTBLOCK等进行统一编址。即用地址来表示对象。这个地址绝大多数是由厂家规定好的,用户只能用而不能改。用户只能在挂外部RAMFLASH的情况下可进行自定义。


ARM7TDMI的存储器映射可以有0X00000000~0XFFFFFFFF的空间,即4G的映射空间,但所有器件加起来肯定是填不满的。一般来说,0X00000000依次开始存放FLASH——0X00000000SRAM——0X40000000BOOTBLOCK(重新映射之前),外部存储器0X80000000VPB(低速外设地址,如GPIOUART)——0XE0000000AHB(高速外设:向量中断控制器,外部存储器控制器)——从0XFFFFFFFF回头。他们都是从固定位置开始编址的,而占用空间又不大,如AHB只占2MB,所以从中间有很大部分是空白区域,用户若使用这些空白区域,或者定义野指针,就可能出现取指令中止或者取数据中止。


由于系统在上电复位时要从0X00000000 开始运行,而第一要运行的就是厂家固化在片子里的BOOTBLOCK,这是判断运行哪个存储器上的程序,检查用户代码是否有效,判断芯片是否加密,芯片是否IAP(在应用编程),芯片是否ISP(在系统编程),所以这个BOOTBLOCK要首先执行。而芯片中的BOOTBLOCK不能放在FLASH的头部,因为那要存放用户的异常向量表的,以便在运行、中断时跳到这来找入口,所以BOOTBLOCK只能放在FLSAH尾部才能好找到,呵呵。而ARM7的各芯片的FLASH大小又不一致,厂家为了BOOTBLOCK在芯片中的位置固定,就在编址的2G靠前编址的位置虚拟划分一个区域作为BOOTBLOCK区域,这就是重映射,这样访问<2G<0X80000000的位置时,就可以访问到在FLASH尾部的BOOTBLOCK区了。


BOOTBLOCK运行完就是要运行用户自己写的启动代码了,而启动代码中最重要的就是异常向量表,这个表是放在FLASH的头部首先执行的,而异常向量表中要处理多方面的事情,包括复位、未定义指令、软中断、预取指中止、数据中止、IRQ(中断) ,FIQ (快速中断),而这个异常向量表是总表,还包括许多分散的异常向量表,比如在外部存储器,BOOTBLOCKSRAM中固化的,不可能都由用户直接定义,所以还是需要重映射把那些异常向量表的地址映到总表中。』


知识点4看了半天,总是找不到OSTickISR()在哪里处理的;偶然间看见程序初始化了一个Timer0,为什么要单单初始化Timer0呢?分析一下其调用过程,发现它是跟中断联系在一起的。而在Target.c中被初始化的Timer0又去调用了Timer0_Exception()函数,这个函数中,调用了系统的OSTimeTick()函数,这不就是OSTickISR()的处理过程么?也就是说,模板是利用Timer0作为时钟节拍中断源的。在IRQ.S中声明的中断函数句柄会通过IRO.INC中的宏得到处理。这里需要复习一下OSTimeTick()OSTickISR()了。


知识点5TargetInit()函数中进行了时钟节拍使用的Timer0初始化,其他中断初始化也放在这里进行,也就是说,如果要使用中断,那么就要先在IRQ.S中声明中断句柄,然后TargetInit()中的VICInit()中初始化向量,将程序与芯片的相关中断源挂接,使芯片在产生相应的中断后会调用相关的处理程序。例如,模板就没有初始化UART,可以做一做实验。


知识点6在软中断中,软中断号是放在LR0-23位的(ARM状态),要取得中断号,只需要取得这几位的值就可以了。


知识点7看了这么久代码,也一一进行了注释,但是还是在整体结构上有一些模糊,所以,花一点时间去整理函数调用关系,整理之后,发现整个代码规模并不是很大。就移植文件而言,也就是OS_CPU.HOS_CPU_C.COS_CPU_A.S以及ARM启动相关的Startup.sTarget.cIRQ.SIRQ.INC了。


 


点击看大图


 


有些地方可能有些偏差,暂时还没有发现


 


下面是这两天研究代码的时候对一些文件作的注释,虽然有些地方不是很明白,不过还是有一定的参考价值,先写在这里,可温故知新。


 


<由于太多,放在“LPC2131FORuCOS模板文件注释.doc”文件中>


 


 


接下来做一个外部中断和信号量结合的实验,并记录经过——


实验内容:


创建了三个任务,TaskStart进行初始化,TaskSemSend发送信号量,TaskLed等待信号量并点亮相应的LED,也让BEEP进行一定动作;


第一阶段:


开始觉得这个实验很简单(其实本来就很简单,但是没有经验还是会走很多弯路),我就直接写下了大框架,结果好像没有什么反应。郁闷……


第二阶段:


这回我谨慎一些,先调通LEDBEEP,毕竟这些不是主要,要是在这些地方出了问题找不出来,就很冤了。


然后,我再写中断接口:


l         添加句柄声明——EXT0_Handler       HANDLER     EXT0_Exception,完成汇编接口


l         完成中断向量控制器的初始化——


    extern void IRQ_Handler(void);


    extern void Timer0_Handler(void);


    extern void EXT0_Handler(void);


    VICIntEnClr = 0xffffffff;


    VICDefVectAddr = (uint32)IRQ_Handler;


    VICVectAddr0 = (uint32)Timer0_Handler;


    VICVectCntl0 = (0x20 | 0x04);


    VICVectAddr1 = (uint32)EXT0_Handler;


    VICVectCntl1 = (0x20 | 14);


    VICIntEnable = (1 << 4) | (1 << 14);


       其实这里还有一步,就是在TargetInit中完成外部中断的初始化;参照模板的风格,我们增加一个函数:


void    EXT0Init(void)


{


EXTMODE = 0x01;   


EXTINT = 0x01;                  


}


将这个函数添加到TargetInit()函数中就可以了。


这样,基本就完成了我们中断的汇编接口了。


接下来就是编写主程序,这个好说,按照uCOS的风格编写。


接下来就是调试……


调试情况比我想象的艰难多了。问题总是处在中断处:


首先看信号量工作正常与否:屏蔽掉中断代码,单独写一个信号量发送任务,处理LED,工作正常。


然后单独写中断处理程序,利用中断发送信号量,发现不工作。


为了测试到底发生中断的时候调用EXT0_Exception与否,在EXT0_Exception中设置一个BEEP。发现确实是调用了,只不过在发送信号量的时候可能发生了一些意外的情况,难道是发送时发生了等待(我在中断的同时加上了循环发送任务,发现这个任务也没有效果了,说明整个程序都跑飞了或者死在什么地方了)?而且如果按久一点KEY1,也会发生跑飞。


最终发现一个地方,我用了低电平触发模式,但是没有等待电平恢复就清了中断,就是前面提到过的这个情况。


改成边沿触发,再试试——


第三阶段:


OK


第四阶段:


为了进一步验证结果,再创建一个任务,单独来管理BEEP(利用外部中断),而原来这个任务和循环任务保留下来一直进行LED的操作。


我发现一直以来用的好好的这段程序不管用了!


           if(IO0SET & BEEP == 0)


                    IO0SET = BEEP;


           else   IO0CLR = BEEP;


这是为什么哩?


再喵一眼,天呐,我无语了,难道是上天再惩罚我?很久没有犯这种错误了:运算符优先级!


改了,这次当然是成功的了——


总结:在这个实验中,首先是验证了中断接口的安装方法,其次是验证了在这个接口下,uCOS的功能是能够正常发挥的。当然,这是个小规模的程序,不能验证到一些时序或者内存方面的问题,但也让我掌握了基本的应用过程,为进一步熟悉移植过程增加了感性认识(学到这步,我觉得我还是没有掌握到移植到全过程,只是在框架的高度有了认识,对于代码中的一些细节处理上我还不是很懂)。


感觉这几天挺浮躁的,可能是几天都没有点进展,心里面挺着急的。一定要克服这样的浮躁情绪,要知道,做硬件工程师最忌讳浮躁了,其实学什么都忌这个^_^


这个程序的主要部分贴在下面,以便日后参考。


main.c


**********************************************************************************************


#include "config.h"


#include "stdlib.h"


 


#define        TaskStkLength    64                        // 定义用户任务的堆栈长度


 


#define KEY (1 << 16)


#define BEEP (1 << 7)


#define LED         (1 << 18)


 


OS_STK     TaskStartStk [TaskStkLength];                //Define the Task0 stack 定义用户任务0的堆栈


OS_STK     TaskLedStk[TaskStkLength];


OS_STK     TaskSemStk[TaskStkLength];


OS_STK     TaskBeepStk[TaskStkLength];


 


OS_EVENT* ExtSemLed;                                  /*声明一般放在这里*/


OS_EVENT* ExtSemBeep;


 


void   TaskStart(void *pdata);                           //我的习惯是专门创建一个初始化的任务,好处是结构清楚,并且


void    TaskLed(void* pdata);                             //这个任务可以扩充一些用途的


void    TaskBeep(void* pdata);


void    TaskSemSend(void* pdata);


 


 


int main (void)


{


           OSInit ();


 


           ExtSemLed = OSSemCreate(1);/*创建一般放在这里*/              


           ExtSemBeep = OSSemCreate(1);   


 


         OSTaskCreate (TaskStart,(void *)0, &TaskStartStk[TaskStkLength - 1], 2);         


 


         OSStart ();


         return 0;                                                                                                                                       


}


/**********************************************************************************************


 


void TaskStart     (void *pdata)


{


         pdata = pdata;


 


         TargetInit ();



         PINSEL0 = 0x00000000;


         PINSEL1 = 0x00000001;


         PINSEL2 &= (~0x08);


         IO1DIR = LED;


         IO1SET = LED;


         IO0DIR = BEEP;


         IO0SET = BEEP;


 


         OSTaskCreate(TaskLed,(void*)0,&TaskLedStk[TaskStkLength - 1],3);


         OSTaskCreate(TaskBeep,(void*)0,&TaskBeepStk[TaskStkLength - 1],4);


         OSTaskCreate(TaskSemSend,(void*)0,&TaskSemStk[TaskStkLength - 1],5);


         while (1)


         {


                    OSTimeDly(5);


         }


}


 


void    TaskLed(void* pdata)


{


         uint8 err;


         pdata = pdata;


 


         for(;;)


         {


                  OSSemPend(ExtSemLed,0,&err);


          


                    OSTimeDlyHMSM(0,0,1,0);


                   IO1CLR = LED;


                  OSTimeDlyHMSM(0,0,0,500);


                    IO1SET = LED;


                  


                  


                   /*


                   if(IO1SET & LED == 0)                //令人无语的低级失误if((IO1SET & LED) == 0)


                   {


                            IO1SET = LED;


                   }


                   else


                   {


                            IO1CLR = LED;


                   }


                   */


         }


}


 


void    TaskBeep(void* pdata)


{


         uint8 err;


         pdata = pdata;


         for(;;)


         {


                   OSSemPend(ExtSemBeep,0,&err);


                   if((IO0SET & BEEP) == 0)


                   {


                            IO0SET = BEEP;


                   }


                   else


                   {


                            IO0CLR = BEEP;


                   }


         }


}


 


 


void    TaskSemSend(void* pdata)


{


         int8 err;


         pdata=pdata;


         for(;;)


         {


                   err = OSSemPost(ExtSemLed);


                   OSTimeDlyHMSM(0,0,1,0);


         }


}


 


Target.c


 


#define IN_TARGET


#include "config.h"


 


        void IRQ_Exception(void)


{


    while(1);                   // change it to your code  这一句替换为自己的代码


}


 


        void FIQ_Exception(void)


{


    while(1);                   // change it to your code  这一句替换为自己的代码


}


        void Timer0_Exception(void)


{


    T0IR = 0x01;


    VICVectAddr = 0;            //interrupt close 通知中断控制器中断结束


    OSTimeTick();


}


        void Timer0Init(void)


{


    T0IR = 0xffffffff;


    T0TC = 0;


    T0TCR = 0x01;


    T0MCR = 0x03;


    T0MR0 = (Fpclk / OS_TICKS_PER_SEC);


 }


void    EXT0Init(void)


{


EXTMODE = 0x01;


EXTINT = 0x01;


}


void    EXT0_Exception(void)


{        


uint8 err;


extern OS_EVENT *ExtSemBeep;


err = OSSemPost(ExtSemBeep);


EXTINT = 0x01;


VICVectAddr = 0x00;


}


        void VICInit(void)


{


    extern void IRQ_Handler(void);


    extern void Timer0_Handler(void);


    extern void EXT0_Handler(void);


    VICIntEnClr = 0xffffffff;


    VICDefVectAddr = (uint32)IRQ_Handler;


    VICVectAddr0 = (uint32)Timer0_Handler;


    VICVectCntl0 = (0x20 | 0x04);


//       VICIntEnable = (1 << 4);


    VICVectAddr1 = (uint32)EXT0_Handler;


    VICVectCntl1 = (0x20 | 14);


   


    VICIntEnable = (1 << 4) | (1 << 14);


 


 }


        void TargetInit(void)


{


    OS_ENTER_CRITICAL();


    srand((uint32) TargetInit);


    VICInit();


    Timer0Init();


    EXT0Init();


    OS_EXIT_CRITICAL();


}


        void InitialiseUART0(uint32 bps)               <>


        void TargetResetInit(void)                           <>


<>


/**********************************************************************************************


**                            End Of File


**********************************************************************************************


 


(待续)

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
6
关闭 站长推荐上一条 /3 下一条