1)实验平台:正点原子开拓者FPGA 开发板
2)摘自《开拓者 Nios II开发指南》关注官方微信号公众号,获取更多资料:正点原子
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/index.html
第十六章创建第一个uC/OSII系统
从本章开始,将带领大家进入RTOS(Real Time Operating System,实时多任务操作系统)
的世界,关于RTOS类操作系统有很多,本教程选取的是非常有名的uCOS操作系统(Nios中写作
MicroC/OS-II)。本章包括以下几个部分:
16.1 uCOS简介
16.2 实验任务
16.3 硬件设计
16.4 软件设计
16.5 下载验证
uCOS简介
在正式介绍uCOS系统之前,我们先来了解一下前后台系统和RTOS系统。
早期嵌入式开发没有嵌入式操作系统的概念 ,直接操作裸机,在裸机上写程序,比如用
51单片机基本就没有操作系统的概念。通常把程序分为两部分:前台系统和后台系统。
简单的小系统通常是前后台系统,这样的程序包括一个死循环和若干个中断服务程序。应
用程序是一个无限循环,循环中调用API函数完成所需的操作,这个大循环就叫做后台系统。
中断服务程序用于处理系统的异步事件,也就是前台系统。前台是中断级,后台是任务级。前
后台系统流程图如下图所示:
图 16.1.1 前后台系统流程图
由上图可知,程序按照任务的顺序开始执行。当中断来临时,会打断当前程序的执行,然
后执行中断函数,中断函数执行完成后,才会继续执行被打断的任务;同样的,中断函数也会
被更高级别的中断打断,更高级别的中断函数执行完成后,才会继续执行被打断的中断函数。
其中,任务的无线循环称为后台系统,而中断服务程序称为前台系统。
在介绍完前后台系统之后,接下来再来熟悉下RTOS,即实时操作系统。
RTOS全称为:Real Time Operating System,即实时操作系统。顾名思义,实时操作系统
强调的是实时性。实时系统又分为硬实时和软实时,硬实时要求在规定的时间内必须完成操作,
硬实时系统不允许超时,而对于软实时系统里面,超时的后果就没有那么严格。
在实时操作系统中,我们可以把要实现的功能划分为多个任务,每个任务负责实现其中的
一部分,每个任务都是一个很简单的程序,通常是一个死循环。
RTOS的内核负责管理所有的任务,内核决定了运行哪个任务,何时停止当前任务切换到其
他任务,这个是内核的多任务管理能力。多任务管理给人的感觉就好像芯片有多个CPU,多任
务管理实现了CPU资源的最大化利用,有助于实现程序的模块化开发,能够实现复杂的实时应
用。RTOS的流程图如下图所示:
图 16.1.2 RTOS系统流程图
由上图可知,RTOS系统每个任务都是一个无限循环,高优先级的任务可以打断低优先级的
任务,而中断服务程序拥有最高级别的优先级,系统总是在运行优先级最高的那个任务。
这里要注意,RTOS不是指某一个确定的系统,而是指一类系统。比如uCOS、FreeRTOS、RTX、
RT-Thread等这些都是RTOS类操作系统。本章我们学习的是RTOS中的经典代表作:uCOS,而uCOS
的内核是可剥夺型的,即允许剥夺其他任务的CPU使用权,它总是运行就绪任务中的优先级最
高的那个任务。RTOS操作系统的核心内容在于实时内核。
接下来我们开始熟悉下uCos系统。
uCOS是Micrium公司出品的RTOS类实时操作系统,uCOS目前有两个版本:uCOSII和uCOSIII。
uCOSII是用C和汇编来写的,其中绝大部分都是用C语言编写的,只有极少数的与处理器密切相
关的部分代码才是用汇编写的,uCOSII结构简洁,可读性很强!最主要的是非常适合初次接触
嵌入式实时操作系统学生、嵌入式系统开发人员和爱好者学习。
uCOSII作为RTOS提供的服务有:多线程任务(Tasks)、事件标志组(Event flags)、消
息传递(Message passing)、内存管理(Memory management)、信号量(Semaphores)、时
间管理(Time management)等。操作系统允许多个任务同时运行,这个叫做多任务,实际上,
一个处理器核心在某一时刻只能运行一个任务。操作系统中任务调度器的责任就是决定在某一
时刻究竟运行哪个任务,任务调度在各个任务之间的切换非常快!这就给人们造成了同一时刻
有多个任务同时运行的错觉。
操作系统的分类方式可以由任务调度器的工作方式决定,比如有的操作系统给每个任务分
配同样的运行时间,时间到了就轮到下一个任务,Unix操作系统就是这样的。RTOS的任务调度
器被设计为可预测的,而这正是嵌入式实时操作系统所需要的,实时环境中要求操作系统必须
对某一个事件做出实时的响应,因此系统任务调度器的行为必须是可预测的。像uCOS,FreeRTOS
这种传统的RTOS类操作系统是由用户给每个任务分配一个任务优先级,任务调度器就可以根据
此优先级来决定下一刻应该运行哪个任务。
需要注意的是:uCOS是要收费的,学习RTOS系统的话uCOS是首选,但是做产品的话就要考
虑一下成本了。显而易见的,FreeRTOS在这个时候就是一个很好的选择,当然了也可以选择其
他的免费的RTOS系统。
Altera公司把uCOSII系统当做HAL(硬件抽象层)的一个扩展,移植到Nios II处理器上。
因此,在Nios II处理器使用uCOSII系统时,不需要繁琐的移植工作,只需要创建uCOSII系统
的模板就可以了。同时,选择uCOSII实时系统,具有以下好处:
1. uCOSII的资料很多,尤其是中文资料;
2. 该系统源代码开放、整洁、一致,注释详尽,适合系统开发;
3. 高可移植性,代码主要C语言编写;
4. uCOSII是一个基于优先级的实时操作系统,实时性比较好;
5. 适用于多种微处理器,微控制器和数字处理芯片(已经移植到超过100种以上的微处理
器应用中)。
目前最新的uCOS版本是uCOSIII,但是现在使用最为广泛的还是uCOSII,本章我们主要针
对uCOSII进行介绍。uCOSII是一个可以基于ROM运行的、可裁减的、抢占式、实时多任务内核,
具有高度可移植性,特别适合于微处理器和控制器,是和很多商业操作系统性能相当的实时操
作系统(RTOS)。实时操作系统指的是能快速地处理外界的事件或数据,并能依据处理的结果在
规定的时间范围内做出快速响应,调动资源协调一致地完成实时任务的系统。为了提供最好的
移植性能,uCOSII最大程度地使用ANSI C语言进行开发,并且已经移植到近40多种处理器体系上,涵盖了从8位到64位各种CPU(包括DSP)。
uCOSII是专门为计算机的嵌入式应用设计的, 绝大部分代码是用C语言编写的。CPU硬件
相关部分是用汇编语言编写的、总量约200行的汇编语言被压缩到最低限度,为的是便于移植
到任何一种其它的CPU上。用户只要有标准的ANSI C交叉编译器,有汇编器、连接器等软件工
具,就可以将uCOSII嵌人到开发的产品中。uCOSII具有执行效率高、占用空间小、实时性能优
良和可扩展性强等特点,最小内核可编译至2KB 。uCOSII已经移植到了几乎所有知名的CPU上。
uCOSII构思巧妙、结构简洁精练、可读性强,又具备了实时操作系统的全部功能,非常适
合初次接触嵌入式实时操作系统的朋友,可以说是麻雀虽小,五脏俱全。uCOSII体系结构如图
16.1.3所示:
图 16.1.3 uCOSII体系结构图
(1) Application Software:这部分为用户的应用程序,即使用uCOSII完成的应用层代码。
(2) Processor-Independent Code:这部分是uCOSII的核心源码,它们是与处理器无关的
代码,都是由高度可移植的ANSI C编写的。
(3) Application-Specific:这部分是系统配置文件,用来配置所需的系统功能,比如需要
用到的uCOSII的模块、时钟频率等等。
(4) Processor-Specific Code:这部分的文件可以根据不同的CPU架构去做修改,也就是移
植的过程。不过在Nios里面,不需要修改这些文件。
想要在类似于ARM等其他的微处理器上搭建、移植uCOSII系统,过程是比较繁琐的。但在
Nios中搭建uCOSII系统却是非常简单的。这是因为Altera把uCOSII系统当做HAL的的一个扩展,
移植到Nios II处理器上。在实际使用中,我们不需要修改文件以及配置操作,只需要使用
Eclipse 软件就能够轻松搭建uCOSII系统。
在Quartus II 13.1软件中,Nios使用的uCOSII版本是v2.86。Nios II处理器的uCOSII结
构如下图所示:
图 16.1.4 Nios中的uCOS架构
硬件层与软件层之间为中间层,也称为硬件抽象层(Hardware Abstract Layer,HAL)或
板级支持包(Board Support Package,BSP),它将系统上层软件与底层硬件分离开来,使系
统的底层驱动程序与硬件无关,上层软件开发人员无需关心底层硬件的具体情况,根据BSP层提供的接口即可进行开发。该层一般包含相关底层硬件的初始化、数据的输入/输出操作和硬
件设备的配置功能。图中的API,也就是应用程序编程接口(Application Programming
Interface),可以帮助应用程序达到开启视窗、描绘图形、使用周边设备的目的。
uCOSII内核(kernel)运行于硬件抽象层(HAL)的上层,而这个硬件抽象层是Nios II处
理器的板级支持包(BSP)。由于这种结构,在Nios II处理器上开发uCOSII系统,具有以下优
势:
?
程序可在Nios II硬件系统间进行移植
?
程序不用顾虑底层硬件的变化
?
程序能够连接所有的硬件抽象层服务,调用UNIX类硬件抽象层的应用程序编程接口
(API)
?
较易实现中断(ISR)
uCOSII早期版本只支持64个任务,但是从v2.80版本开始,支持任务数提高到255个。不过
一般很难用到这么多个任务,uCOSII保留了最高4个优先级和最低4个优先级用于拓展使用,总
共8个任务。但实际上,uCOSII一般只占用了最低2个优先级,分别用于空闲任务(倒数第一)
和统计任务(倒数第二),所以剩下给我们使用的任务最多可达255-2=253个(V2.92)。
所谓的任务,其实就是一个死循环函数,该函数实现一定的功能,一个工程可以有很多这
样的任务(最多255个)。uCOSII对这些任务进行调度管理,让这些任务可以并发工作(注意
不是同时工作,并发只是各任务轮流占用CPU,而不是同时占用,任何时候还是只有1个任务能
够占用。uCOSII的任何任务都是通过一个叫任务控制块(TCB)的东西来控制的,每个任务管
理块有3个最重要的参数:1,任务函数指针;2,任务堆栈指针;3,任务优先级;任务控制块
就是任务在系统里面的身份证(uCOSII通过优先级识别任务),任务控制块我们就不再详细介
绍了,详细介绍请参考任哲老师的《嵌入式实时操作系统uCOSII原理及应用》一书第二章。
在uCOSII系统中,使用CPU的时候,优先级高(数值小)的任务比优先级低的任务具有优
先使用权,即任务就绪表中总是优先级最高的任务获得CPU使用权,只有高优先级的任务让出
CPU使用权(比如延时)时,低优先级的任务才能获得CPU使用权。uCOSII不支持多个任务优先
级相同,也就是每个任务的优先级必须不一样。
任务的调度其实就是CPU运行环境的切换,即:PC指针、SP指针和寄存器组等内容的存取
过程,关于任务调度的详细介绍,请参考《嵌入式实时操作系统uCOSII原理及应用》一书第三
章相关内容)。
接下来我们将详细介绍用来创建任务的函数,它的重要性不言而喻。此外,我们在软件设
计部分会通过一个简单的程序来带领大家掌握它的用法。
INT8U OSTaskCreateExt (void (*task)(void *p_arg),
void *p_arg,
OS_STK *ptos,
INT8U prio,
INT16U id,
OS_STK *pbos,
INT32U stk_size,
void *pext,
INT16U opt);
OSTaskCreateExt()函数用于创建uCOSII中的任务。这个函数的早期版本是OSTaskCreate()
函数,但是OSTaskCreateExt()能够允许用户给任务明确更多的信息。利用这个函数,可以通
过运行中的任务建立新的任务,且新建的任务可以比初始的多任务(multitask)优先级更高。
此外不能通过中断来新建任务。强烈建议用户使用 OSTaskCreateExt()函数,而不是
OSTaskCreate(),因为前者使用起来更为灵活。
声明:
task:指向任务代码的指针。
p_arg:指向可选的数据区域的指针,用于给创建中的任务传递常量(parameters)。不过,
这个指针并不常用。
ptos:指向任务堆栈栈顶的指针。当任务处于中断状态时,堆栈用于储存的任务当地变量、
函数常量、返回地址、以及CPU中的寄存器的值。堆栈的大小由任务的需求以及参与的中断嵌
套(interrupt nesting)决定。如果配置常量OS_STK_GROWTH被设置为1,堆栈被设置为向下
进栈(从高存储空间到低存储空间),ptos需要指向堆栈的最高有效地址。如果OS_STK_GROWTH
被设置为0,堆栈的进栈方向则相反(从低存储空间到高存储空间)。
prio:任务的优先级。每一个任务都必须拥有它独特的优先级数字(不能重复)。优先级
的数字越低,任务的重要性(优先度)越高。
id: 任务的 ID 。 这 个 版 本 里 ID 并 没 有 被 其 他 的 函 数 运 用 , 只 是 被 简 单 地 加 入
OSTaskCreateExt()函数,用于未来的扩展。用户应该设置ID的值和任务优先级(prio)的值
一样。
pbos:指向任务堆栈栈底的指针。如果配置常量OS_STK_GROWTH被设置为1,堆栈被设置为
向下进栈(从高存储空间到低存储空间)。pbos因此需要指向堆栈的最低有效地址。如果
OS_STK_GROWTH被设置为0,堆栈的进栈方向则相反(从低存储空间到高存储空间)。因此,pbos
则必须指向堆栈的最高有效地址。pbos被栈检验函数OSTaskStkChk()使用。
stk_size:任务堆栈的大小。如果OS_STK被设置为INT8U,那么stk_size对应于堆栈中可以
使用的字节数(bytes)。如果OS_STK被设置为INT16U,那么stk_size指示栈里有多少个16bit
的存储空间。如果OS_STK被设置为INT32U,那么stk_size指示栈里有多少个32bit的存储空间。
pext:指向用户提供的存储空间(通常为数据结构)的指针,被作为任务控制块(TCB)的
扩展。
opt:包含任务说明选项。低8位被uCOSII保留,高8位可用于操作说明(application
specific)选项。每个选项包含一位或多位,当某一位被置1的时候,相应选项就被选择了。
当前版本的uCOSII提供了以下选项。
OS_TASK_OPT_NONE: 说明没有选项。
OS_TASK_OPT_STK_CHK: 说明堆栈检验是否被允许。
OS_TASK_OPT_STK_CLR: 说明堆栈是否需要被清空。
OS_TASK_OPT_SAVE_FP:说明是否要保存浮点寄存器。只有当你的处理器中包含浮点硬件,
以及处理器配置码(processor-specific code)中保存了浮点寄存器,这个选项才会有效。
更多的选项请查阅uCOS_II.H文件。
返回值:
OSTaskCreateExt()函数会返回以下错误码中的一种:
OS_ERR_NONE: 如果函数正确
OS_ERR_PRIO_EXIST: 如果要求的优先级已经存在
OS_ERR_PRIO_INVALID: 如果prio的值比OS_LOWEST_PRIO的值大
OS_ERR_NO_MORE_TCB: 如果uCOSII没有更多的任务控制块(OS_TCBs)用于赋值
OS_ERR_TASK_CREATE_ISR: 用户试图通过中断来建立任务
提示:
1. 堆栈必须被声明为OS_STK类型的。
2. 一个任务必须调用uCOSII提供的一种服务进入睡眠状态,来暂停任务。或者等待一个
事件发生(邮箱(mailbox)、队列(queue)、信号(semaphore))。这样使得其他
的任务能够获得CPU的控制权。
实验任务
本节实验任务是通过一个“Hello MicroC/OS-II”实验,让大家掌握创建uCOSII系统的步
骤和方法。
硬件设计
Qsys系统的搭建如图 16.3.1所示,本次实验所使用到的IP核在前面的章节中都有介绍过,
这里不再赘述。需要注意的是,在搭建Qsys系统的时候,必须添加timer IP核,来为uCOSII系
统提供时钟节拍,timer IP核的设置保持默认即可。
图 16.3.1 搭建的qsys系统
接下来我们来看下顶层模块的代码,如下所示:
1 module qsys_ucosii_hello(
2 //module clock
3 input sys_clk , //系统时钟,50Mhz
4 input sys_rst_n , //系统复位,低电平有效
5
6 //SDRAM interface
7 output sdram_clk , //SDRAM 芯片时钟
8 output sdram_cke , //SDRAM 时钟有效
9 output sdram_cs_n , //SDRAM 片选
10 output sdram_ras_n, //SDRAM 行有效
11 output sdram_cas_n, //SDRAM 列有效
12 output sdram_we_n , //SDRAM 写有效
13 output [ 1:0] sdram_ba , //SDRAM Bank地址
14 output [12:0] sdram_addr , //SDRAM 行/列地址
15 inout [15:0] sdram_data , //SDRAM 数据
16 output [ 1:0] sdram_dqm , //SDRAM 数据掩码
17
18 //EPCS FLASH interface
19 output epcs_dclk , // EPCS 时钟信号
20 output epcs_sce , // EPCS 片选信号
21 output epcs_sdo , // EPCS 数据输出信号
22 input epcs_data0 // EPCS 数据输入信号
23 );
24
25 //wire define
26 wire clk_100m; //SDRAM 控制器时钟
27 wire rst_n ; //系统复位信号
28 wire locked ; //PLL输出稳定标志
29
30 //*****************************************************
31 //** main code
32 //*****************************************************
33
34 //待PLL输出稳定之后,停止系统复位
35 assign rst_n = sys_rst_n & locked;
36
37 //例化PLL
38 pll_clk u_pll_clk(
39 .areset (~sys_rst_n),
40 .inclk0 (sys_clk ),
41 .c0 (clk_100m ),
42 .c1 (sdram_clk ),
43 .locked (locked )
44 );
45
46 //例化Qsys系统
47 system_qsys u_qsys(
48 .clk_clk (clk_100m ), // 时钟100M
49 .reset_reset_n (rst_n ), // 复位信号
50 .sdram_addr (sdram_addr ), // SDRAM 行/列地址
51 .sdram_ba (sdram_ba ), // SDRAM Bank地址
52 .sdram_cas_n (sdram_cas_n), // SDRAM 列有效
53 .sdram_cke (sdram_cke ), // SDRAM 时钟有效
54 .sdram_cs_n (sdram_cs_n ), // SDRAM 片选
55 .sdram_dq (sdram_data ), // SDRAM 数据
56 .sdram_dqm (sdram_dqm ), // SDRAM 数据掩码
57 .sdram_ras_n (sdram_ras_n), // SDRAM 行有效
58 .sdram_we_n (sdram_we_n ), // SDRAM 写有效
59 .epcs_dclk (epcs_dclk ), // EPCS 时钟信号
60 .epcs_sce (epcs_sce ), // EPCS 片选信号
61 .epcs_sdo (epcs_sdo ), // EPCS 数据输出信号
62 .epcs_data0 (epcs_data0 ) // EPCS 数据输入信号
63 );
64
65 endmodule
顶层模块的代码只是例化了qsys系统和PLL IP核,硬件设计部分和我们前面的实验区别不
大。对于uCOSII系统来说,重要的是软件设计的部分,接下来我们就来学习一下,如何创建一
个uCOSII系统。
软件设计
创建uCOSII系统和我们前面创建Nios工程的操作步骤基本一致,只是在进入【Nios II
Application and BSP from Template】界面的时候有所区别,需要在Project template下面
单击选择Hello MicroC/OS-II,其余操作和搭建Nios工程一致。如图 16.4.1所示:
图 16.4.1 创建uCOSII系统
创建完工程后,我们双击打开工程下的hello_ucosii.c文件,如下图所示:
图 16.4.2 hello_ucosii代码
图中hello_ucosii.c文件是软件为我们自动生成的,用于实现打印字符的功能。而图中
UCOSII文件夹里存放了uCOSII系统的源代码,感兴趣的朋友可以打开了解一下。
工程默认的代码如下所示:
1 #include <stdio.h>
2 #include "includes.h"
3
4 /* Definition of Task Stacks */
5 #define TASK_STACKSIZE 2048
6 OS_STK task1_stk[TASK_STACKSIZE];
7 OS_STK task2_stk[TASK_STACKSIZE];
8
9 /* Definition of Task Priorities */
10
11 #define TASK1_PRIORITY 1
12 #define TASK2_PRIORITY 2
13
14 /* Prints "Hello World" and sleeps for three seconds */
15 void task1(void* pdata)
16 {
17 while (1)
18 {
19 printf("Hello from task1\n");
20 OSTimeDlyHMSM(0, 0, 3, 0);
21 }
22 }
23 /* Prints "Hello World" and sleeps for three seconds */
24 void task2(void* pdata)
25 {
26 while (1)
27 {
28 printf("Hello from task2\n");
29 OSTimeDlyHMSM(0, 0, 3, 0);
30 }
31 }
32 /* The main function creates two task and starts multi-tasking */
33 int main(void)
34 {
35 OSTaskCreateExt(task1,
36 NULL,
37 (void *)&task1_stk[TASK_STACKSIZE-1],
38 TASK1_PRIORITY,
39 TASK1_PRIORITY,
40 task1_stk,
41 TASK_STACKSIZE,
42 NULL,
43 0);
44
45 OSTaskCreateExt(task2,
46 NULL,
47 (void *)&task2_stk[TASK_STACKSIZE-1],
48 TASK2_PRIORITY,
49 TASK2_PRIORITY,
50 task2_stk,
51 TASK_STACKSIZE,
52 NULL,
53 0);
54 OSStart();
55 return 0;
56 }
程序中定义了三个参数,分别是TASK_STACKSIZE、TASK1_PRIORITY和TASK2_PRIORITY。
TASK_STACKSIZE表示堆栈的大小,堆栈的作用就是用来保存局部变量,在uCOS中,每一个任务
都有一个独立的任务堆栈,使用OS_STK来定义堆栈;TASK1_PRIORITY和TASK2_PRIORITY分别定
义了任务1和任务2的优先级,任务优先级的数值越小,表示任务的优先级越高。
程序中的task1和task2为创建的两个任务,可以看到,每一个任务都是一个无限循环。任
务1和任务2都是实现输出字符串的功能,其中OSTimeDlyHMSM()函数实现延时的功能,同时释
放CPU的使用权,这个函数输入的参数分别表示延时小时、分钟、秒和毫秒,因此
OSTimeDlyHMSM(0,0,3,0)表示延时3秒,如代码中第14行至第31行所示。
程序中的main函数创建两个任务,分别是task1和task2,创建任务的函数是OSTaskCreateExt。
在创建完任务后,通过调用OSStart函数启动uCOSII,如代码中第32行至第56行所示。此时开始执
行任务,首先执行的是任务1,即打印字符“Hello from task1”;然后延时3秒后,执行任务
2,打印“Hello from task2”;然后再延时3秒,执行任务1,循环往复。
下载验证
接下来编译工程,稍等片刻后console界面会显示Build Finished,即编译成功,此时就
可以下载程序了。开发板连接电源线和下载器,并打开电源开关。
首先下载sof文件,然后点击菜单栏的【Run】→【Run Configurations】,进入下图所示
的界面:
图 16.5.1 Run Configurations界面
接着点击Target connection,如下图所示:
图 16.5.2 Target connection界面
最后点击Run,就开始下载程序了。如果无法点击Run,可以先点击Refresh Connections。
需要提醒的是在下载elf文件之前,需要先下载Quartus工程的sof文件。
程序下载完成,就会看到Nios II Console界面一直在打印“Hello from task1”和“Hello
from task2”。
图 16.5.3 Nios II Console界面
本文暂时没有评论,来添加一个吧(●'◡'●)