《电子技术应用》
您所在的位置:首页 > 模拟设计 > 设计应用 > ucos ii嵌入式操作系统的分析和移植
ucos ii嵌入式操作系统的分析和移植
摘要: ucosii是由Labrosse先生编写的一个开放式内核,最主要的特点就是源码公开。这一点对于用户来说可谓利弊各半,好处在于,一方面它是免费的,另一方面用户可以根据自己的需要对它进行修改。缺点在于它缺乏必要的支持...
Abstract:
Key words :

  ucos ii的特点

  1.ucos ii是由Labrosse先生编写的一个开放式内核,最主要的特点就是源码公开。这一点对于用户来说可谓利弊各半,好处在于,一方面它是免费的,另一方面用户可以根据自己的需要对它进行修改。缺点在于它缺乏必要的支持,没有功能强大的软件包,用户通常需要自己编写驱动程序,特别是如果用户使用的是不太常用的单片机,还必须自己编写移植程序。

  2.ucos ii是一个占先式的内核,即已经准备就绪的高优先级任务可以剥夺正在运行的低优先级任务的CPU使用权。这个特点使得它的实时性比非占先式的内核要好。通常我们都是在中断服务程序中使高优先级任务进入就绪态(例如发信号),这样退出中断服务程序后,将进行任务切换,高优先级任务将被执行。拿51单片机为例,比较一下就可以发现这样做的好处。假如需要用中断方式采集一批数据并进行处理,在传统的编程方法中不能在中断服务程序中进行复杂的数据处理,因为这会使得关中断时间过长。所以经常采用的方法是置一标志位,然后退出中断。由于主程序是循环执行的,所以它总有机会检测到这一标志并转到数据处理程序中去。但是因为无法确定发生中断时程序到底执行到了什么地方,也就无法判断要经过多长时间数据处理程序才会执行,中断响应时间无法确定,系统的实时性不强。如果使用μC/OS-II的话,只要把数据处理程序的优先级设定得高一些,并在中断服务程序中使它进入就绪态,中断结束后数据处理程序就会被立即执行。这样可以把中断响应时间限制在一定的范围内。对于一些对中断响应时间有严格要求的系统,这是必不可少的。但应该指出的是如果数据处理程序简单,这样做就未必合适。因为ucos ii要求在中断服务程序末尾使用OSINTEXIT函数以判断是否进行任务切换,这需要花费一定的时间。

  3.ucos ii和大家所熟知的Linux等分时操作系统不同,它不支持时间片轮转法。ucos ii是一个基于优先级的实时操作系统,每个任务的优先级必须不同,分析它的源码会发现,ucos ii把任务的优先级当做任务的标识来使用,如果优先级相同,任务将无法区分。进入就绪态的优先级最高的任务首先得到CPU的使用权,只有等它交出CPU的使用权后,其他任务才可以被执行。所以它只能说是多任务,不能说是多进程,至少不是我们所熟悉的那种多进程。显而易见,如果只考虑实时性,它当然比分时系统好,它可以保证重要任务总是优先占有CPU。但是在系统中,重要任务毕竟是有限的,这就使得划分其他任务的优先权变成了一个让人费神的问题。另外,有些任务交替执行反而对用户更有利。例如,用单片机控制两小块显示屏时,无论是编程者还是使用者肯定希望它们同时工作,而不是显示完一块显示屏的信息以后再显示另一块显示屏的信息。这时候,要是ucos ii即支持优先级法又支持时间片轮转法就更合适了。

  4.ucos ii对共享资源提供了保护机制。正如上文所提到的,ucos ii是一个支持多任务的操作系统。一个完整的程序可以划分成几个任务,不同的任务执行不同的功能。这样,一个任务就相当于模块化设计中的一个子模块。在任务中添加代码时,只要不是共享资源就不必担心互相之间有影响。而对于共享资源(比如串口),ucos ii也提供了很好的解决办法。一般情况下使用的是信号量的方法。简单地说,先创建一个信号量并对它进行初始化。当一个任务需要使用一个共享资源时,它必须先申请得到这个信号量,而一旦得到了此信号量,那就只有等使用完了该资源,信号量才会被释放。在这个过程中即使有优先权更高的任务进入了就绪态,因为无法得到此信号量,也不能使用该资源。这个特点的好处显而易见,例如当显示屏正在显示信息的时候,外部产生了一个中断,而在中断服务程序中需要显示屏显示其他信息。这样,退出中断服务程序后,原有的信息就可能被破坏了。而在μC/OS-II中采用信号量的方法时,只有显示屏把原有信息显示完毕后才可以显示新信息,从而可以避免这个现象。不过,采用这种方法是以牺牲系统的实时性为代价的。如果显示原有信息需要耗费大量时间,系统只好等待。从结果上看,等于延长了中断响应时间,这对于未显示信息是报警信息的情况,无疑是致命的。发生这种情况,在μC/OS-II中称为优先级反转,就是高优先级任务必须等待低优先级任务的完成。在上述情况下,在两个任务之间发生优先级反转是无法避免的。所以在使用ucos ii时,必须对所开发的系统了解清楚,才能决定对于某种共享资源是否使用信号量。

  ucos ii在单片机使用中的一些特点

  1.在单片机系统中嵌入ucos ii将增强系统的可靠性,并使得调试程序变得简单。以往传统的单片机开发工作中经常遇到程序跑飞或是陷入死循环。可以用看门狗解决程序跑飞问题,而对于后一种情况,尤其是其中牵扯到复杂数学计算的话,只有设置断点,耗费大量时间来慢慢分析。如果在系统中嵌入 ucos ii的话,事情就简单多了。可以把整个程序分成许多任务,每个任务相对独立,然后在每个任务中设置超时函数,时间用完以后,任务必须交出 CPU的使用权。即使一个任务发生问题,也不会影响其他任务的运行。这样既提高了系统的可靠性,同时也使得调试程序变得容易。

  2.在单片机系统中嵌入ucos ii将增加系统的开销。现在所使用的51单片机,一般是指87C51或者89C51,其片内都带有一定的RAM和 ROM。对于一些简单的程序,如果采用传统的编程方法,已经不需要外扩存储器了。如果在其中嵌入ucos ii的话,在只需要使用任务调度、任务切换、信号量处理、延时或超时服务的情况下,也不需要外扩ROM了,但是外扩RAM是必须的。由于ucos ii是可裁减的操作系统,其所需要的RAM大小就取决于操作系统功能的多少。举例来说,μC/OS-II允许用户定义最大任务数。由于每建立一个任务,都要产生一个与之相对应的数据结构TCB,该数据结构要占用很大一部分内存空间。所以在定义最大任务数时,一定要考虑实际情况的需要。如果定得过大,势必会造成不必要的浪费。嵌入ucos ii以后,总的RAM需求可以由如下表达式得出:

  RAM总需求=应用程序的RAM需求+内核数据区的RAM需求+(任务栈需求+最大中断嵌套栈需求)·任务数

  所幸的是,μC/OS-II可以对每个任务分别定义堆栈空间的大小,开发人员可根据任务的实际需求来进行栈空间的分配。但在RAM容量有限的情况下,还是应该注意一下对大型数组、数据结构和函数的使用,别忘了,函数的形参也是要推入堆栈的。

  3.ucos ii的移植也是一件需要值得注意的工作。如果没有现成的移植实例的话,就必须自己来编写移植代码。虽然只需要改动两个文件,但仍需要对相应的微处理器比较熟悉才行,最好参照已有的移植实例。另外,即使有移植实例,在编程前最好也要阅读一下,因为里面牵扯到堆栈操作。在编写中断服务程序时,把寄存器推入堆栈的顺序必须与移植代码中的顺序相对应。

  4.和其他一些著名的嵌入式操作系统不同,ucos ii在单片机系统中的启动过程比较简单,不像有些操作系统那样,需要把内核编译成一个映像文件写入ROM中,上电复位后,再从ROM中把文件加载到RAM中去,然后再运行应用程序。ucos ii的内核是和应用程序放在一起编译成一个文件的,使用者只需要把这个文件转换成HEX格式,写入ROM中就可以了,上电后,会像普通的单片机程序一样运行。

  结语

  由以上介绍可以看出,ucos ii具有免费、使用简单、可靠性高、实时性好等优点,但也有移植困难、缺乏必要的技术支持等缺点,尤其不像商用嵌入式系统那样得到广泛使用和持续的研究更新。但开放性又使得开发人员可以自行裁减和添加所需的功能,在许多应用领域发挥着独特的作用。当然,是否在单片机系统中嵌入ucos ii应视所开发的项目而定,对于一些简单的、低成本的项目来说,就没必要使用嵌入式操作系统了。

  --摘自INTERNET

  44B0下ucos-ii的移植

  要保证ucos Ⅱ移植到微处理器后能正确运行;处理器需具备如下特性:

  1) 处理器的c编译器支持可重入函数

  可重入的代码指的是一段代码(如一个函数)可以被多个任务同时调用,而不必担心会破坏数据。也就是说,可重入型函数在任何时候都可以被中断执行,过一段时间以后又可以继续运行,而不会因为在函数中断的时候被其他的任务重新调用,影响函数中的数据。下面的两个例子可以比较可重入型函数和非可重入型函数:

  程序1:可重入型函数

  void swap(int *x, int *y)

  int temp;

  temp=*x;

  *x=*y;

  *y=temp;

  程序2:非可重入型函数

  int temp;

  void swap(int *x, int *y)

  temp=*x;

  *x=*y;

  *y=temp;

  程序1 中使用的是局部变量temp 作为变量。通常的C 编译器,把局部变量分配在栈中。

  所以,多次调用同一个函数,可以保证每次的temp 互不受影响。而程序2 中temp 定义的是全局变量,多次调用函数的时候,必然受到影响。代码的可重入性是保证完成多任务的基础,除了在C 程序中使用局部变量以外,还需要C 编译器的支持。笔者使用的是ARM SDT 以及ADS 的集成开发环境,均可以生成可重入的代码。

  2)在程序中可以打开和关闭中断

  在ucos Ⅱ中,可以通过OS_ENTER_CRITICAL()或者OS_EXIT_CRITICAL()宏来控制

  系统关闭或者打开中断。这需要处理器的支持,在ARM7TDMI 的处理器上,可以设置相应的寄存器来关闭或者打开系统的所有中断。

  3)处理器支持中断,并且能产生定时器中断(ucos Ⅱ是通过定时器中断来实现多任务的调度,即时间片的产生 )ucos Ⅱ 是通过处理器产生的定时器的中断来实现多任务之间的调度的。在ARM7TDMI 的处理器上可以产生定时器中断。

  4)处理器要具有一定的硬件堆栈数量

  5)处理器要有将堆栈指针和其他cpu寄存器存储和读出堆栈(或者内存)的指令(如51的pop,push指令)。

  ucos Ⅱ进行任务调度的时候,会把当前任务的CPU 寄存器存放到此任务的堆栈中,然后,再从另一个任务的堆栈中恢复原来的工作寄存器,继续运行另一个任务。所以,寄存器的入栈和出栈是ucos Ⅱ多任务调度的基础。

  ARM7TDMI 处理器完全满足上述要求。

 

  ucos ii的特点

  1.ucos ii是由Labrosse先生编写的一个开放式内核,最主要的特点就是源码公开。这一点对于用户来说可谓利弊各半,好处在于,一方面它是免费的,另一方面用户可以根据自己的需要对它进行修改。缺点在于它缺乏必要的支持,没有功能强大的软件包,用户通常需要自己编写驱动程序,特别是如果用户使用的是不太常用的单片机,还必须自己编写移植程序。

  2.ucos ii是一个占先式的内核,即已经准备就绪的高优先级任务可以剥夺正在运行的低优先级任务的CPU使用权。这个特点使得它的实时性比非占先式的内核要好。通常我们都是在中断服务程序中使高优先级任务进入就绪态(例如发信号),这样退出中断服务程序后,将进行任务切换,高优先级任务将被执行。拿51单片机为例,比较一下就可以发现这样做的好处。假如需要用中断方式采集一批数据并进行处理,在传统的编程方法中不能在中断服务程序中进行复杂的数据处理,因为这会使得关中断时间过长。所以经常采用的方法是置一标志位,然后退出中断。由于主程序是循环执行的,所以它总有机会检测到这一标志并转到数据处理程序中去。但是因为无法确定发生中断时程序到底执行到了什么地方,也就无法判断要经过多长时间数据处理程序才会执行,中断响应时间无法确定,系统的实时性不强。如果使用μC/OS-II的话,只要把数据处理程序的优先级设定得高一些,并在中断服务程序中使它进入就绪态,中断结束后数据处理程序就会被立即执行。这样可以把中断响应时间限制在一定的范围内。对于一些对中断响应时间有严格要求的系统,这是必不可少的。但应该指出的是如果数据处理程序简单,这样做就未必合适。因为ucos ii要求在中断服务程序末尾使用OSINTEXIT函数以判断是否进行任务切换,这需要花费一定的时间。

  3.ucos ii和大家所熟知的Linux等分时操作系统不同,它不支持时间片轮转法。ucos ii是一个基于优先级的实时操作系统,每个任务的优先级必须不同,分析它的源码会发现,ucos ii把任务的优先级当做任务的标识来使用,如果优先级相同,任务将无法区分。进入就绪态的优先级最高的任务首先得到CPU的使用权,只有等它交出CPU的使用权后,其他任务才可以被执行。所以它只能说是多任务,不能说是多进程,至少不是我们所熟悉的那种多进程。显而易见,如果只考虑实时性,它当然比分时系统好,它可以保证重要任务总是优先占有CPU。但是在系统中,重要任务毕竟是有限的,这就使得划分其他任务的优先权变成了一个让人费神的问题。另外,有些任务交替执行反而对用户更有利。例如,用单片机控制两小块显示屏时,无论是编程者还是使用者肯定希望它们同时工作,而不是显示完一块显示屏的信息以后再显示另一块显示屏的信息。这时候,要是ucos ii即支持优先级法又支持时间片轮转法就更合适了。

  4.ucos ii对共享资源提供了保护机制。正如上文所提到的,ucos ii是一个支持多任务的操作系统。一个完整的程序可以划分成几个任务,不同的任务执行不同的功能。这样,一个任务就相当于模块化设计中的一个子模块。在任务中添加代码时,只要不是共享资源就不必担心互相之间有影响。而对于共享资源(比如串口),ucos ii也提供了很好的解决办法。一般情况下使用的是信号量的方法。简单地说,先创建一个信号量并对它进行初始化。当一个任务需要使用一个共享资源时,它必须先申请得到这个信号量,而一旦得到了此信号量,那就只有等使用完了该资源,信号量才会被释放。在这个过程中即使有优先权更高的任务进入了就绪态,因为无法得到此信号量,也不能使用该资源。这个特点的好处显而易见,例如当显示屏正在显示信息的时候,外部产生了一个中断,而在中断服务程序中需要显示屏显示其他信息。这样,退出中断服务程序后,原有的信息就可能被破坏了。而在μC/OS-II中采用信号量的方法时,只有显示屏把原有信息显示完毕后才可以显示新信息,从而可以避免这个现象。不过,采用这种方法是以牺牲系统的实时性为代价的。如果显示原有信息需要耗费大量时间,系统只好等待。从结果上看,等于延长了中断响应时间,这对于未显示信息是报警信息的情况,无疑是致命的。发生这种情况,在μC/OS-II中称为优先级反转,就是高优先级任务必须等待低优先级任务的完成。在上述情况下,在两个任务之间发生优先级反转是无法避免的。所以在使用ucos ii时,必须对所开发的系统了解清楚,才能决定对于某种共享资源是否使用信号量。

  ucos ii在单片机使用中的一些特点

  1.在单片机系统中嵌入ucos ii将增强系统的可靠性,并使得调试程序变得简单。以往传统的单片机开发工作中经常遇到程序跑飞或是陷入死循环。可以用看门狗解决程序跑飞问题,而对于后一种情况,尤其是其中牵扯到复杂数学计算的话,只有设置断点,耗费大量时间来慢慢分析。如果在系统中嵌入 ucos ii的话,事情就简单多了。可以把整个程序分成许多任务,每个任务相对独立,然后在每个任务中设置超时函数,时间用完以后,任务必须交出 CPU的使用权。即使一个任务发生问题,也不会影响其他任务的运行。这样既提高了系统的可靠性,同时也使得调试程序变得容易。

  2.在单片机系统中嵌入ucos ii将增加系统的开销。现在所使用的51单片机,一般是指87C51或者89C51,其片内都带有一定的RAM和 ROM。对于一些简单的程序,如果采用传统的编程方法,已经不需要外扩存储器了。如果在其中嵌入ucos ii的话,在只需要使用任务调度、任务切换、信号量处理、延时或超时服务的情况下,也不需要外扩ROM了,但是外扩RAM是必须的。由于ucos ii是可裁减的操作系统,其所需要的RAM大小就取决于操作系统功能的多少。举例来说,μC/OS-II允许用户定义最大任务数。由于每建立一个任务,都要产生一个与之相对应的数据结构TCB,该数据结构要占用很大一部分内存空间。所以在定义最大任务数时,一定要考虑实际情况的需要。如果定得过大,势必会造成不必要的浪费。嵌入ucos ii以后,总的RAM需求可以由如下表达式得出:

  RAM总需求=应用程序的RAM需求+内核数据区的RAM需求+(任务栈需求+最大中断嵌套栈需求)·任务数

  所幸的是,μC/OS-II可以对每个任务分别定义堆栈空间的大小,开发人员可根据任务的实际需求来进行栈空间的分配。但在RAM容量有限的情况下,还是应该注意一下对大型数组、数据结构和函数的使用,别忘了,函数的形参也是要推入堆栈的。

  3.ucos ii的移植也是一件需要值得注意的工作。如果没有现成的移植实例的话,就必须自己来编写移植代码。虽然只需要改动两个文件,但仍需要对相应的微处理器比较熟悉才行,最好参照已有的移植实例。另外,即使有移植实例,在编程前最好也要阅读一下,因为里面牵扯到堆栈操作。在编写中断服务程序时,把寄存器推入堆栈的顺序必须与移植代码中的顺序相对应。

  4.和其他一些著名的嵌入式操作系统不同,ucos ii在单片机系统中的启动过程比较简单,不像有些操作系统那样,需要把内核编译成一个映像文件写入ROM中,上电复位后,再从ROM中把文件加载到RAM中去,然后再运行应用程序。ucos ii的内核是和应用程序放在一起编译成一个文件的,使用者只需要把这个文件转换成HEX格式,写入ROM中就可以了,上电后,会像普通的单片机程序一样运行。

  结语

  由以上介绍可以看出,ucos ii具有免费、使用简单、可靠性高、实时性好等优点,但也有移植困难、缺乏必要的技术支持等缺点,尤其不像商用嵌入式系统那样得到广泛使用和持续的研究更新。但开放性又使得开发人员可以自行裁减和添加所需的功能,在许多应用领域发挥着独特的作用。当然,是否在单片机系统中嵌入ucos ii应视所开发的项目而定,对于一些简单的、低成本的项目来说,就没必要使用嵌入式操作系统了。

  --摘自INTERNET

  44B0下ucos-ii的移植

  要保证ucos Ⅱ移植到微处理器后能正确运行;处理器需具备如下特性:

  1) 处理器的c编译器支持可重入函数

  可重入的代码指的是一段代码(如一个函数)可以被多个任务同时调用,而不必担心会破坏数据。也就是说,可重入型函数在任何时候都可以被中断执行,过一段时间以后又可以继续运行,而不会因为在函数中断的时候被其他的任务重新调用,影响函数中的数据。下面的两个例子可以比较可重入型函数和非可重入型函数:

  程序1:可重入型函数

  void swap(int *x, int *y)

  int temp;

  temp=*x;

  *x=*y;

  *y=temp;

  程序2:非可重入型函数

  int temp;

  void swap(int *x, int *y)

  temp=*x;

  *x=*y;

  *y=temp;

  程序1 中使用的是局部变量temp 作为变量。通常的C 编译器,把局部变量分配在栈中。

  所以,多次调用同一个函数,可以保证每次的temp 互不受影响。而程序2 中temp 定义的是全局变量,多次调用函数的时候,必然受到影响。代码的可重入性是保证完成多任务的基础,除了在C 程序中使用局部变量以外,还需要C 编译器的支持。笔者使用的是ARM SDT 以及ADS 的集成开发环境,均可以生成可重入的代码。

  2)在程序中可以打开和关闭中断

  在ucos Ⅱ中,可以通过OS_ENTER_CRITICAL()或者OS_EXIT_CRITICAL()宏来控制

  系统关闭或者打开中断。这需要处理器的支持,在ARM7TDMI 的处理器上,可以设置相应的寄存器来关闭或者打开系统的所有中断。

  3)处理器支持中断,并且能产生定时器中断(ucos Ⅱ是通过定时器中断来实现多任务的调度,即时间片的产生 )ucos Ⅱ 是通过处理器产生的定时器的中断来实现多任务之间的调度的。在ARM7TDMI 的处理器上可以产生定时器中断。

  4)处理器要具有一定的硬件堆栈数量

  5)处理器要有将堆栈指针和其他cpu寄存器存储和读出堆栈(或者内存)的指令(如51的pop,push指令)。

  ucos Ⅱ进行任务调度的时候,会把当前任务的CPU 寄存器存放到此任务的堆栈中,然后,再从另一个任务的堆栈中恢复原来的工作寄存器,继续运行另一个任务。所以,寄存器的入栈和出栈是ucos Ⅱ多任务调度的基础。

  ARM7TDMI 处理器完全满足上述要求。

 

  接下来将介绍如何把ucos Ⅱ移植到Samsung公司的一款ARM7TDMI 的嵌入式处理器——S3C44B0X 上。

  ucos Ⅱ中与处理器有关的代码:os_cpu.h os_cpu_a.asm os_cpu_c.c

  ucos Ⅱ的设置 : os_cfg.h inludes.h

  ucos Ⅱ在44b0上的移植

  1)设置inludes.h中与处理器及编译器有关的代码

  FORADS

  #include “os_cpu.h”

  #include “os_cfg.h”

  #include “ucos_ii.h”

  这里未做处理 取默认的数据类型。

  FOR SDT

  #include 》stdio.h《

  #include 》stdlib.h《

  #include 》string.h《

  #include “os_cpu.h”

  #include “os_cfg.h”

  #include “ucos_ii.h”

  #ifdef EX3_GLOBALS

  #define EX3_EXT

  #else

  #define EX3_EXT extern

  #endif

  typedef struct {

  char TaskName[30];

  INT16U TaskCtr;

  INT16U TaskExecTime;

  INT32U TaskTotExecTime;

  } TASK_USER_DATA;

  EX3_EXT TASK_USER_DATA TaskUserData[10];

  void DispTaskStat(INT8U id);

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

  其他人的应用修改事例:

  #define INT8U unsigned char

  #define INT16U unsigned short

  #define INT32U unsigned long

  #define OS_STK unsigned long

  #define BOOLEAN int

  #define OS_CPU_SR unsigned long

  #define INT8S char

  extern int INTS_OFF(void);

  extern void INTS_ON(void);

  #define OS_ENTER_CRITICAL() { cpu_sr = INTS_OFF(); }

  #define OS_EXIT_CRITICAL() { if(cpu_sr == 0) INTS_ON(); }

  #define OS_STK_GROWTH 1

  #define STACKSIZE 256

  因为不同的微处理器有不同的字长,所以ucos Ⅱ的移植包括了一系列的类型定义以确

  保其可移植性。尤其是ucos Ⅱ代码从不使用C 的short,int 和long 等数据类型,因为它们是与编译器相关的,不可移植。相反的,我们定义的整形数据结构既是可移植的又是直观的。为了方便,虽然ucos Ⅱ不是用浮点数据,但我们还是定义了浮点数据类型。

  例如,INT16U 数据类型总是代表16 位的无符号整数。现在,ucos Ⅱ和用户的应用程序就可以估计出声明为该数据类型的变量的取值范围是0~65535。将ucos Ⅱ移植到32 位的处理器上也就意味着INT16U 实际被声明为无符号短整形数据结构而不是无符号整数数据结构。但是,μC/OS-Ⅱ所处理的仍然是INT16U。用户必须将任务堆栈的数据类型告诉给μC/OS-Ⅱ。这个过程是通过为OS_STK 声明正确的C 数据类型来完成的。我们的处理器上的堆栈成员是16 位的,所以将OS_TSK 声明为无符号整形数据类型。所有的任务堆栈都必须用OS_TSK 声明数据类型。

  2)OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()

  与所有的实时内核一样,μC/OS-Ⅱ需要先禁止中断再访问代码的临界区,并且在访问完

  毕后重新允许中断。这就使得μC/OS-Ⅱ能够保护临界区代码免受多任务或中断服务例程

  (ISR)的破坏。在S3C44B0X 上是通过两个函数(OS_CPU_A.S)实现开关中断的。

  INTS_OFF

  mrs r0, cpsr ; 当前CSR

  mov r1, r0 ; 复制屏蔽

  orr r1, r1, #0xC0 ; 屏蔽中断位

  msr CPSR, r1 ; 关中断(IRQ and FIQ)

  and r0, r0, #0x80 ; 从初始CSR 返回FIQ 位

  mov pc,lr ; 返回

  INTS_ON

  mrs r0, cpsr ; 当前CSR

  bic r0, r0, #0xC0 ; 屏蔽中断

  msr CPSR, r0 ; 开中断(IRQ and FIQ)

  mov pc,lr ; 返回

  3)OS_STK_GROWTH

  绝大多数的微处理器和微控制器的堆栈是从上往下长的。但是某些处理器是用另外一种方式工作的。μC/OS-Ⅱ被设计成两种情况都可以处理,只要在结构常量OS_STK_GROWTH中指定堆栈的生长方式就可以了。

  置OS_STK_GROWTH 为0 表示堆栈从下往上长。

  置OS_STK_GROWTH 为1 表示堆栈从上往下长。

  用c语言编写6个与操作系统相关的函数(OS_CPU_C.C)

  1. OsTaskStKInit()

  OSTaskCreate()和OSTaskCreateExt()通过调用OSTaskStkInit()来初始化任务的堆

  栈结构。因此,堆栈看起来就像刚发生过中断并将所有的寄存器保存到堆栈中的情形一样。

  图12-2 显示了OSTaskStkInt()放到正被建立的任务堆栈中的东西。这里我们定义了堆栈是

  从上往下长的。

  在用户建立任务的时候,用户传递任务的地址,pdata 指针,任务的堆栈栈顶和任务的

  优先级给OSTaskCreate()和OSTaskCreateExt()。一旦用户初始化了堆栈,OSTaskStkInit

  ()就需要返回堆栈指针所指的地址。OSTaskCreate()和OSTaskCreateExt()会获得该地

  址并将它保存到任务控制块(OS_TCB)中。

  OS_STK * OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)

  {

  unsigned int * stk;

  stk = (unsigned int *)ptos;

  opt++;

  *--stk = (unsigned int) task;

  *--stk = (unsigned int) task;

  *--stk = 12;

  *--stk = 11;

  *--stk = 10;

  *--stk = 9;

  *--stk = 8;

  *--stk = 7;

  *--stk = 6;

  *--stk = 5;

  *--stk = 4;

  *--stk = 3;

  *--stk = 2;

  *--stk = 1;

  *--stk = (unsigned int) pdata;

  *--stk = (SUPMODE);

  *--stk = (SUPMODE);

  return ((OS_STK *)stk);

  }

  2).OSTaskCreateHook

  当用OSTaskCreate()和OSTaskCreateExt ()建立任务的时候就会调用OSTaskCreateHook

  ()。该函数允许用户或使用移植实例的用户扩展μC/OS-Ⅱ功能。当μC/OS-Ⅱ设置完了自己的内部结构后,会在调用任务调度程序之前调用OSTaskCreateHook()。该函数被调用的时候中断是禁止的。因此用户应尽量减少该函数中的代码以缩短中断的响应时间。当OSTaskCreateHook()被调用的时候,它会收到指向已建立任务的OS_TCB 的指针,这样它就可以访问所有的结构成员了。

  函数原型:

  void OSTaskCreateHook (OS_TCB *ptcb)

  3).OsTaskDelHook()

  当任务被删除的时候就会调用OSTaskDelHook()。该函数在把任务从μC/OS-Ⅱ的内部

  任务链表中解开之前被调用。当OSTaskDelHook()被调用的时候,它会收到指向正被删除任务的OS_TCB 的指针,这样它就可以访问所有的结构成员了。OSTaskDelHook()可以来检验TCB 扩展是否被建立(一个非空指针)并进行一些清除操作。

  函数原型:

  void OSTaskDelHook (OS_TCB *ptcb)

  4.OsTaskSwHook()

  当发生任务切换的时候就会调用OSTaskSwHook()。OSTaskSwHook()可以直接访问

  OSTCBCur 和OSTCBHighRdy,因为它们是全局变量。OSTCBCur 指向被切换出去的任务OS_TCB,而OSTCBHighRdy 指向新任务OS_TCB。注意在调用OSTaskSwHook()期间中断一直是被禁止的。因此用户应尽量减少该函数中的代码以缩短中断的响应时间。

  函数原型:

  void OSTaskSwHook (void)

  5.OsTaskStatHook()

  OSTaskStatHook()每秒钟都会被OSTaskStat()调用一次。用户可以用OSTaskStatHook

  ()来扩展统计功能。例如,用户可以保持并显示每个任务的执行时间,每个任务所用的CPU 份额,以及每个任务执行的频率等。

  函数原型:

  void OSTaskStatHook (void)

  6.OsTimeTickHook()

  OSTimeTickHook()在每个时钟节拍都会被OSTaskTick()调用。实际上,OSTimeTickHook

  ()是在节拍被μC/OS-Ⅱ真正处理,并通知用户的移植实例或应用程序之前被调用的。

 

  函数原型:

  void OSTimeTickHook (void)

  后5 个函数为钩子函数,可以不加代码。只有当OS_CFG.H 中的OS_CPU_HOOKS_EN

  被置为1 时才会产生这些函数的代码。

  用汇编语言编写4个与处理器相关的函数(OS_CPU_a.asm)

  (1)OsStartHighRdy();运行优先级最高的就绪任务

  OSStartHighRdy

  LDR r4, addr_OSTCBCur ; 得到当前任务TCB 地址

  LDR r5, addr_OSTCBHighRdy ; 得到最高优先级任务TCB 地址

  LDR r5, [r5] ; 获得堆栈指针

  LDR sp, [r5] ; 转移到新的堆栈中

  STR r5, [r4] ; 设置新的当前任务TCB 地址

  LDMFD sp!, {r4} ;

  MSR SPSR, r4

  LDMFD sp!, {r4} ; 从栈顶获得新的状态

  MSR CPSR, r4 ; CPSR 处于SVC32Mode 摸式

  LDMFD sp!, {r0-r12, lr, pc } ; 运行新的任务

  (2)OS_TaSK_SW();任务级的任务切换函数

  OS_TASK_SW

  STMFD sp!, {lr} ; 保存pc

  STMFD sp!, {lr} ; 保存lr

  STMFD sp!, {r0-r12} ; 保存寄存器和返回地址

  MRS r4, CPSR

  STMFD sp!, {r4} ; 保存当前的PSR

  MRS r4, SPSR

  STMFD sp!, {r4} ; 保存SPSR

  ; OSPrioCur = OSPrioHighRdy

  LDR r4, addr_OSPrioCur

  LDR r5, addr_OSPrioHighRdy

  LDRB r6, [r5]

  STRB r6, [r4]

  ; 得到当前任务TCB 地址

  LDR r4, addr_OSTCBCur

  LDR r5, [r4]

  STR sp, [r5] ; 保存sp 在被占先的任务的TCB

  ; 得到最高优先级任务TCB 地址

  LDR r6, addr_OSTCBHighRdy

  LDR r6, [r6]

  LDR sp, [r6] ; 得到新任务堆栈指针

  ; OSTCBCur = OSTCBHighRdy

  STR r6, [r4] ; 设置新的当前任务的TCB 地址

  ;保存任务方式寄存器

  LDMFD sp!, {r4}

  MSR SPSR, r4

  LDMFD sp!, {r4}

  MSR CPSR, r4

  ; 返回到新任务的上下文

  LDMFD sp!, {r0-r12, lr, pc}

  (3)OSINTCTXSW();中断级的任务切换函数

  OSIntCtxSw

  add r7, sp, #16 ; 保存寄存器指针

  LDR sp, =IRQStack ;FIQ_STACK

  mrs r1, SPSR ; 得到暂停的PSR

  orr r1, r1, #0xC0 ; 关闭IRQ, FIQ.

  msr CPSR_cxsf, r1 ; 转换模式(应该是SVC_MODE)

  ldr r0, [r7, #52] ; 从IRQ 堆栈中得到IRQ‘s LR (任务PC)

  sub r0, r0, #4 ; 当前PC 地址是(saved_LR - 4)

  STMFD sp!, {r0} ; 保存任务PC

  STMFD sp!, {lr} ; 保存LR

  mov lr, r7 ; 保存FIQ 堆栈ptr in LR (转到nuke r7)

  ldmfd lr!, {r0-r12} ; 从FIQ 堆栈中得到保存的寄存器

  STMFD sp!, {r0-r12} ;在任务堆栈中保存寄存器

  ;在任务堆栈上保存PSR 和任务PSR

  MRS r4, CPSR

  bic r4, r4, #0xC0 ; 使中断位处于使能态

  STMFD sp!, {r4} ; 保存任务当前PSR

  MRS r4, SPSR

  STMFD sp!, {r4} ; SPSR

  ; OSPrioCur = OSPrioHighRdy // 改变当前程序

  LDR r4, addr_OSPrioCur

  LDR r5, addr_OSPrioHighRdy

  LDRB r6, [r5]

  STRB r6, [r4]

  ; 得到被占先的任务TCB

  LDR r4, addr_OSTCBCur

  LDR r5, [r4]

  STR sp, [r5] ; 保存sp 在被占先的任务的TCB

  ; 得到新任务TCB 地址

  LDR r6, addr_OSTCBHighRdy

  LDR r6, [r6]

  LDR sp, [r6] ; 得到新任务堆栈指针

  ; OSTCBCur = OSTCBHighRdy

  STR r6, [r4] ; 设置新的当前任务的TCB 地址

  LDMFD sp!, {r4}

  MSR SPSR, r4

  LDMFD sp!, {r4}

  BIC r4, r4, #0xC0 ; 必须退出新任务通过允许中断

  MSR CPSR, r4

  LDMFD sp!, {r0-r12, lr, pc}

  完成了上述工作以后,μC/OS-Ⅱ就可以正常运行在ARM 处理器上了。

  我们的板子上已经有移植成功的简单应用,移植部分不须多大改动就可以直接复制到您的应用中去。

  文件系统的建立:

  文件系统相关的API函数功能解释:

  void initosfile();

  功能:初始化文件管理,为文件结构分配空间,在系统初始化时调用

  FILE* OPENOSFILE(char filename[],u32 open mode);

  功能:以读取方式或写入方式指定打开的文件,并创建FILE结构,为文件读取分配缓冲区,返回当前指向文件结构的指针。

  参数说明:

  filename 打开的文件名

  openmode 打开文件的方式:FILEMODE_READ 1

  FILEMODE_WRITE 2

  U32 Readosfile(FILE* pfile,u8* readbuffer,u32 nreadbyte);

  功能: 读取已经打开的文件到制定的缓冲区,成功则返回读取的字节数

  参数说明:

  pfile : 指向打开文件的指针

  readbuffer :读文件的目的缓冲区。

  Nreadbyte: 读文件的字节数

  U32 linereadosfile(FILE* pfile,char str[]);

  功能:读取之定文件的一行,返回读取文件的字节数。

  参数说明:

  Pfile: 指向打开文件的指针

  Str: 读取的字符窜数组

  U8 writeosfile(FILE* pfile,u8* writebuffer,u32 nreadbyte);

  功能:把缓冲区写入指定的文件,如果成功就返回true 否则false.

  参数说明:

  pfile: 指向打开文件的指针

  writebuffer :写入文件的目的缓冲区。

  Nreadbyte: 写入文件的字节数

  Void closeosfile()

  功能:关闭打开的文件,释放文件缓冲区

  参数说明:

  pfile: 指向打开文件的指针

  u8 getnextfilename(u32 *filepos,char filename[]);

  功能:得到文件目录分配表中的指定位置的文件名(包括扩展名),文件位置自动下移。

  若文件有效则返回true ,否则flase

  filepos: 文件的位置,范围从0~511;

  filename: 返回的文件名

  u8 listnextfilename(u32 *filepos,char fileexname[],char filename[]);

  功能:列出当前位置开始第一个制定扩展名的文件,如果没有,返回flase

  参数说明:

  filepos: 文件的位置,范围从0~511;

  fileexname:指定的文件扩展名

  filename:返回的文件名

  外设计驱动程序

  1) 串口接口函数

  void Uart_Init(int uartnum,int mclk,int baud);

  功能:初始化串口,设置通讯的波特率

  参数说明:

  uartnum :所设定的串行口号

  mclk: 系统的主时钟频率,如果为0则为默认值 60

  baud:所设定的串口通讯波特率

  void uart_printf(char *fmt,…)

  功能:输出字符到串口0

  参数说明:

  fmt:输出到串口的字符串

  char uart_getch(char *revdatq,int uartnum,int timeout);

  功能:接收指定的串口的数据,收到数据是返回true 否则flase

  参数说明:

  revdatq: 输入缓冲区

  uartnum:所设定得串口号

  timeout: 等待超时时间

  void uart_sendbyte(int uartnum,u8 data);

  功能:向指定串口发送数据

  参数说明:

  uart_num : 所设定得串口号

  data: 发送的数据

  例子:

  当操作系统启动时,将自动初始化各串行口,所以应用程序调用串行口资源将变得非常

  容易。值的注意的是,应用程序往往是多任务系统,为了实时监测串行口信息,在本操作环

  境中必须单开一个串行口扫描任务,保证信息不丢失。

  ⑴ 打开一个已有的工程文件,在其中的主函数MAIN 中添加串行口的寄存器初始化

  代码,并添加串行口和键盘扫描任务,串行口扫描任务的代码如下:

  void Uart_Scan_Task1(void *Id)

  {

  char c1;

  POSMSG pmsg1;

  for (;;){

  if(Uart_Getch(&c1,0,1))

  {

  pmsg1=OSCreateMessage(NULL,OSM_SERIAL,0,c1);

  if(pmsg1)

  SendMessage(pmsg1);

  }

  }

  }//Uart_Scan_Task

 

  (2)当系统收到串行口信息时,将会自动向主任务发送一个串行口消息。主任务接收

  到该消息,将会调用响应函数,响应该消息。添加消息响应函数的代码如下:

  void onSerial(int portn, char c)

  {

  Uart_SendByte(0,c);

  ⑶ 添加主任务

  void Main_Task(void *Id) //Main_Test_Task

  {

  POSMSG pMsg=0;

  ClearScreen();

  //消息循环

  for(;;){

  pMsg=WaitMessage(0); //等待消息

  switch(pMsg-{

  case OSM_SERIAL:

  onSerial(pMsg-break;

  }

  DeleteMessage(pMsg);//删除消息,释放资源

  }

  }

  2) 键盘扫描驱动4*4

  u32 GetKey();

  功能:1 有效。此函数位死锁函数,调用以后,除非有键按下 否则不返回

  void setfunctionkey();

  功能:设定功能键扫描码,1 有效。类似计算机的ctrl/alt ,可以提供复合键

  u32 getnotaskkey();

  功能:1 有效。此函数位死锁函数,调用以后,除非有键按下 否则不返回, 与u32 GetKey()的区别诗词函数不会释放此任务的控制权,除非有更高级的任务运行

  例子

  1)在主函数中定义键盘映射表,定义键盘扫描函数,定义键盘驱动函数。

  (2)定义键盘响应函数,将得到的键值在液晶屏上显示。

  void onKey(int nkey, int fnkey)//键盘响应函数

  {

  char temp[3];//转换成ASC-II 的键值数组

  if(nkey《9)

  {

  temp[0]=0x31;

  temp[1]=(nkey-10)|0x30;

  temp[2]=0;

  }

  else

  {

  temp[0]=nkey+0x30;

  temp[1]=0;

  }

  LCD_printf(temp);//在液晶平上显示键值

  LCD_printf(“\n”);

  }

  (3)定义键盘扫描任务。

  OS_STK My_Key_Scan_Stack[STACKSIZE]={0, }; //定义键盘扫描任务的堆栈大小

  void My_Key_Scan_Task(void *Id); //定义键盘扫描任务

  #define MyKey_Scan_Task_Prio 58 //定义键盘扫描任务的优先级

  OSTaskCreate(My_Key_Scan_Task,(void*)0,(OS_STK*)&My_Key_Scan_Stack[STACKSIZE-1],

  MyKey_Scan_Task_Prio );//在主函数中创建键盘扫描任务

  void My_Key_Scan_Task(void *Id)//键盘扫描任务

  {

  U32 key;

  u32 tempkey=0;

  POSMSG pmsg;//创建消息

  Uart_Printf(“begin key task \n”);

  for (;;)

  {

  key=MyGetKey();

  key&=0x000f;

  if(key《9)

  {

  Uart_SendByte(0,0x31);

  32

  tempkey=key-10;

  Uart_SendByte(0,tempkey|=0x0030);

  }

  else

  Uart_SendByte(0,key|0x0030);

  Uart_Printf(“,”);

  pmsg=OSCreateMessage(NULL, OSM_KEY,key,key);//创建键盘消息

  if(pmsg)

  SendMessage(pmsg);//发送键盘消息

  }

  }

  (4)定义主任务。

  OS_STK Main_Stack[STACKSIZE*8]={0, };//定义主任务的堆栈大小

  void Main_Task(void *Id); //定义主任务

  #define Main_Task_Prio 12 //定义主任务的优先级

  OSTaskCreate(Main_Task,(void*)0,(OS_STK*)&Main_Stack[STACKSIZE*8-1],

  Main_Task_Prio);//在主函数重创建主任务

  void Main_Task(void *Id) //主任务

  {

  POSMSG pMsg=0;//创建消息

  LCD_ChangeMode(DspTxtMode);//将液晶屏设为文本显示摸式

  LCD_Cls();//清屏

  for(;;)

  {

  pMsg=WaitMessage(0); //等待消息

  switch(pMsg-{

  case OSM_KEY:

  onKey(pMsg-break;

  Delay(200);

  }

  DeleteMessage(pMsg);//删除消息,释放资源

  } }

  注意:以上API接口函数只是原型 ;例子只作为参考

此内容为AET网站原创,未经授权禁止转载。