菜鸟学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的模板文件。好在找到了ZLG的LPC2141模板,将分散加载文件修改了就好了。找一个LPC2148的手册,查查它的内存映射,修改分散文件中的堆栈地址到对应CPU的RAM最高端地址就可以搞定了。其中IRAM地址——片内RAM调试代码空间结束地址没有修改,因为我认为既然2141调试都没有问题,那么,2148的代码也不会多到哪里去吧。如果出现问题,再来修改。如果使用LPC2131或者LPC2000的模板,除了堆栈大小,还需要修改系统晶振设置和分散加载文件等,多一些步骤,不过都是相似的。
修改完了,能不能用?跑一个小程序,点点LED就可以了。
程序当然能够跑!
其实,这些设置不过是让CPU物尽其用而已,如果你将RAM顶端地址修改为0x40000100,程序还是能够跑,因为程序代码只占用了那么点空间,如果代码多起来了,那可能就有点问题了。我将它修改为0x40000010程序就跑不动了。而代码调试结束空间地址也可以适当加大,这里我设的是0x40002000。
这一块还是比较生疏,不过现在的目的是拉通一遍去移植uC/OS,那个时候才必须熟悉系统存储器结构等,所以,现在还不忙去深入。
突然发现个问题,就是我修改的是我ADS程序中的模板,需要注意如果以后直接运行例程,出现问题要找这里的问题——分散文件的加载。另外,还有uC/OS_II的模板也没有改,需要注意。
USB好像没有做驱动,所以XP一直提示找不到硬件——
使用LPC2148GPIO的注意事项:
1)如何让引脚输出并行的0和1
要让引脚输出并行的数据,通过IOSET和IOCLR是不行的,因为存在延时,那么就可以通过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.h和INCLUDES.h文件中的路径设置不同,结构也当然不同。
然后来看文件夹中这些文件的组织方式:
既然是针对工程应用的,那么先从从Beep文件夹开始——
很明显,这里面有这样几种文件:
l ARM的启动代码:target.c、targe.h、Startup.s、LPC2294及IRQ.s
l 操作系统配置文件:INCLUDES.H、OS_CFG.H
l 工程应用文件及配置:main.c、config.h
l 编译器分散加载文件:mem_a、mem_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工程的组织文件(如右图):
在target文件夹中有target.c、IRQ.s和Startup.s三个文件,这三个文件针对的是目标处理器;其中IRQ.s包含了文件IRQ.inc,此文件定义IRQ汇编接口代码宏;既然是针对CPU的,就放在ARM文件夹中。
这样,所有相关文件就组织起来了。
这里注意,由于周工给的工程模板组织形式,将INCLUDES.H和OS_CFG.H放在了工程文件中,起先我个人认为这样不是很清晰(虽然简化了路径),可以将其单独放在一个操作系统配置文件夹中。但是,我这样做了一次实验之后,发现更不方便,因为很多操作系统文件用到了INCLUDES.H的,路径都在工程当前路径下,要是一个个来改,很累;不过将其做成模板之后还是可以用的。
现在来一个个注释代码——
不得不再次回到启动代码:
基于ARM的芯片多数为复杂的片上系统,这种复杂系统里的多数硬件模块都是可配置的,需要由软件来设置其需要的工作状态。因此在用户的应用程序之前,需要由专门的一段代码来完成对系统的初始化。由于这类代码直接面对处理器内核和硬件控制器进行编程,一般都是用汇编语言。
通用的内容包括:
l 中断向量表
l 初始化存储器系统
l 初始化堆栈
l 初始化有特殊要求的断口,设备
l 初始化用户程序执行环境
l 改变处理器模式
l 呼叫主应用程序
这几天被汇编搞昏了头,不得不仔细去看看ARM7指令系统,并研究LPC2000的User Manual等,这些手册说的比较清晰,有问题也好找出来——
再次闭关中………………………………
知识点1:做移植规划的时候,模式选择要求不能使用异常模式,因此,默认模式使用用户模式,而可选模式为系统模式。
知识点2:用户模式不能使用MSR和MRS,但是系统模式可以使用;其他地方两个模式是相似的。
知识点3:存储器映射再理解:
这里借用一位网友的博文。我在看LPC2000手册的时候,有一段关于存储器映射的解释,当时已经大略懂了。看了这篇博文之后,不但印证了我的理解,而且其他有些地方也豁然开朗……
『存储器映射是指把芯片中或芯片外的FLASH,RAM,外设,BOOTBLOCK等进行统一编址。即用地址来表示对象。这个地址绝大多数是由厂家规定好的,用户只能用而不能改。用户只能在挂外部RAM或FLASH的情况下可进行自定义。
ARM7TDMI的存储器映射可以有0X00000000~0XFFFFFFFF的空间,即4G的映射空间,但所有器件加起来肯定是填不满的。一般来说,0X00000000依次开始存放FLASH——0X00000000,SRAM——0X40000000,BOOTBLOCK(重新映射之前),外部存储器0X80000000,VPB(低速外设地址,如GPIO,UART)——0XE0000000,AHB(高速外设:向量中断控制器,外部存储器控制器)——从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 (快速中断),而这个异常向量表是总表,还包括许多分散的异常向量表,比如在外部存储器,BOOTBLOCK,SRAM中固化的,不可能都由用户直接定义,所以还是需要重映射把那些异常向量表的地址映到总表中。』
知识点4:看了半天,总是找不到OSTickISR()在哪里处理的;偶然间看见程序初始化了一个Timer0,为什么要单单初始化Timer0呢?分析一下其调用过程,发现它是跟中断联系在一起的。而在Target.c中被初始化的Timer0又去调用了Timer0_Exception()函数,这个函数中,调用了系统的OSTimeTick()函数,这不就是OSTickISR()的处理过程么?也就是说,模板是利用Timer0作为时钟节拍中断源的。在IRQ.S中声明的中断函数句柄会通过IRO.INC中的宏得到处理。这里需要复习一下OSTimeTick()和OSTickISR()了。
知识点5:在TargetInit()函数中进行了时钟节拍使用的Timer0初始化,其他中断初始化也放在这里进行,也就是说,如果要使用中断,那么就要先在IRQ.S中声明中断句柄,然后在TargetInit()中的VICInit()中初始化向量,将程序与芯片的相关中断源挂接,使芯片在产生相应的中断后会调用相关的处理程序。例如,模板就没有初始化UART,可以做一做实验。
知识点6:在软中断中,软中断号是放在LR的0-23位的(ARM状态),要取得中断号,只需要取得这几位的值就可以了。
知识点7:看了这么久代码,也一一进行了注释,但是还是在整体结构上有一些模糊,所以,花一点时间去整理函数调用关系,整理之后,发现整个代码规模并不是很大。就移植文件而言,也就是OS_CPU.H、OS_CPU_C.C、OS_CPU_A.S以及ARM启动相关的Startup.s、Target.c、IRQ.S和IRQ.INC了。
有些地方可能有些偏差,暂时还没有发现
下面是这两天研究代码的时候对一些文件作的注释,虽然有些地方不是很明白,不过还是有一定的参考价值,先写在这里,可温故知新。
<由于太多,放在“LPC2131FORuCOS模板文件注释.doc”文件中>
接下来做一个外部中断和信号量结合的实验,并记录经过——
实验内容:
创建了三个任务,TaskStart进行初始化,TaskSemSend发送信号量,TaskLed等待信号量并点亮相应的LED,也让BEEP进行一定动作;
第一阶段:
开始觉得这个实验很简单(其实本来就很简单,但是没有经验还是会走很多弯路),我就直接写下了大框架,结果好像没有什么反应。郁闷……
第二阶段:
这回我谨慎一些,先调通LED和BEEP,毕竟这些不是主要,要是在这些地方出了问题找不出来,就很冤了。
然后,我再写中断接口:
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
**********************************************************************************************
(待续)
文章评论(0条评论)
登录后参与讨论