打印
[AVR单片机]

LOOK 教程1(示例 1 详解)

[复制链接]
4655|11
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
John_Lee|  楼主 | 2008-8-8 19:30 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
在此篇中,我会向大家展示如何创建 LOOK 系统中的任务,我将使用上一篇的“示例 1”来做讲解。在示例 1 中,有些语句是无关紧要的,如: __attribute__ ((always_inline)) 等等,你可以先忽略掉这些。

在 LOOK 系统中,除了系统初始化外的所有工作都应该放在任务(task)里来做(其它操作系统也是如此)。那么首先遇到的问题就是:在 LOOK 中如何创建一个任务并让它运转起来。

创建一个任务分为三步:

一、声明(declare)和定义(define)任务类


在 LOOK 系统中,任务是以一个 C++ 类来表达的(与其它操作系统的任务控制块概念相当)。并且必须从 task_t 类或 task_t 类的派生类继承,task_t 类是 LOOK 系统声明的一个抽象类。在示例 1 中,我们声明了两个任务(类):task1_t 和 task2_t。

task1_t 的声明(task2_t 也是同样的):
class task1_t : public task_t        //    此 C++ 语法表示:声明一个以 task1_t 为名称的类,该类从 task_t 中派生,或者说,该类继承 task_t 类。
然后就需要声明该类的成员了,有两个成员是必须声明的:

1、构造函数


task1_t (size_t stack_size) __attribute__ ((always_inline));

任务类的构造函数必须声明的理由是:它的基类 task_t,没有缺省的构造函数。事实上,task_t 类的构造函数如下所示:
task_t (size_t stack_size, void* arg = 0);

task_t 类的构造函数需要两个参数,第一个:该任务所占的 RAM 空间大小,这个必须由它的子类——任务 (task1_t) 类构造函数来传递(见后)。第二个:任务函数的参数,类似于 uC/OS,eCos 之类的操作系统,这个参数可以省略。
而任务类的构造函数的参数就全由编程者自己考虑了,省略也可以,只要传给 task_t 一个合适的 stack_size 值就可以了。构造函数在此还必须初始化类成员 seam(见后)。

2、任务函数


virtual void process (void* arg);

这个就是任务的工作函数了,这个是由 task_t 类声明的纯虚函数,所以 task_t 是个抽象类,不能实例化(定义类对象),这些概念都是 C++ 的,其实不用我在这里废话了。void* arg 参数就是 task_t 构造函数的第二个参数传递来的。
至于工作函数里应该怎么定义(怎么写),那就是你说了算。在示例 1 中,先创建了 task2,然后进入任务主循环。

其它的成员的声明:
1、sema_t sema;
信号灯。sema_t 是 LOOK 系统声明的一个同步对象。sema_t 没有缺省的构造函数。所以,sema 成员的初始化也必须由 task1_t 类的构造函数来执行。sema_t 的构造函数声明:
sema_t (unsigned int count);

参数 count 是提供给信号灯的初始值。信号灯的使用方法以后详细说明。

2、void notify () __attribute__ ((always_inline));
这个函数简单地对 sema 进行 V 操作。它存在的必要仅仅是为了数据隐藏(C++ 程序设计的原则之一)。

二、创建任务类的对象(类实例化)


这里只推荐一个方法:定义一个 RAM 区,然后用 placement new 在此 RAM 区上 new 一个类对象:

task_t *new_task = new (task1) task1_t (sizeof (task1));

这里存在一个非常重要的问题:

如何确定 RAM 区的大小?

LOOK 系统设计,任务类的对象和任务栈一起被放置在用户定义的 RAM 区中,所以 RAM 区的大小由任务类的大小任务栈的大小共同决定。对象的大小可以用 sizeof (类名) 来得到,而任务栈的大小由两部分组成:①保存任务上下文所需要的空间。这个空间大小是个固定数,在 LOOK 系统中,这个值是 19 字节。②任务调用函数的层数和各函数的局部变量大小,这个值不太好确定,但用户(基于 LOOK 系统的应用程序设计者、编程者)必须估算出一个安全的最小值。还有 LOOK 系统调用自己也需要一些空间,但非常少,不超过 10 字节。

确定好了任务栈大小后,就可以定义 RAM 区了:uint8_t task1[sizeof (task1_t) + SIZE_TASK1_STACK];SIZE_TASK1_STACK 就是用确定好的任务栈大小定义的宏。

三、任务类对象加入调度器


这个用 sched_t 类的方法 add 就可以了,sched_t 类也是 LOOK 系统声明的。add 方法的声明:
static void add (task_t* task, unsigned char prio);

第一个参数是任务类对象,第二个参数是任务的优先级。

任务对象一旦加入调度器,就立刻处于就绪态。

待续...

相关帖子

沙发
kanprin| | 2008-8-9 00:59 | 只看该作者

这样看来就明朗多了。

占个沙发,好好听课,谢谢老师。

使用特权

评论回复
板凳
John_Lee|  楼主 | 2008-8-9 12:51 | 只看该作者

教程 1 续

系统启动


正如示例 1 中看到的,系统入口点位于 LOOK_START。这是一个 LOOK 系统定义的宏函数,你必须在这个函数中创建至少一个任务。如果没有任务被创建,LOOK 系统将跳转到 exit 函数,exit 函数通常由 libc 提供,但你也可以自己定义一个同名的函数来取代它。

初始化系统节拍定时器


任务 task1 被创建后,下一个观察点是 arch.cc 文件的 systick_t::init 函数。系统会调用此函数进行系统节拍定时器的初始化,首先要选择一个硬件定时器,这个工作要根据项目的硬件设计而定,然后就需要对定时器编程已达到周期性中断的目的。重要的一点是要调用 attach 方法绑定硬件定时器的中断:

attach (中断向量号)

中断向量号在与硬件对应的系统头文件(在 WinAVR 安装目录的 avrincludeavr 目录)中定义,通过 io.h 可以查询到具体的头文件名。

下一个观察点就是 task1_t 的任务函数 process 了。首先 process 函数创建了 task2 任务(task2_t 类的实例并加入调度器),此时有两个任务都处于就绪态,但是由于 task1 的优先级比 task2 优先级高,所以 task2 创建后,并不会立刻投入执行,而 task1 将会继续执行,进入了主循环。

同步对象之信号灯


在 task1 的主循环中,任务主要依靠信号灯 sema 进行操作同步。LOOK 中信号灯类(sema_t)已定义的方法有:
A.    unsigned int get_count ();
获取信号灯计数。

B.    unsigned char tryget ();
非阻塞方式获取。

C.    unsigned char tryput ();
非阻塞方式释放。

D.    unsigned char get (unsigned int timeout = 0);
阻塞方式获取(信号灯的 P 操作)。timeout 参数为阻塞超时设置,以系统节拍(tick)为单位。如果在 timeout 时间内获取到了信号灯,方法返回 DONE;如果未获取到,则返回 BREAK。
DONE 和 BREAK 都是 LOOK 系统中 sync_t 类中声明的常量。所以要写成 sync_t::DONE 或 sync_t::BREAK。如示例 1 中。

E.    unsigned char put (unsigned int timeout = 0);
阻塞方式释放(信号灯的 V 操作)。LOOK 系统的信号灯设计成为 V 操作可以阻塞的模式:当信号灯的计数值已经达到了上限,如果再对此信号灯进行 V 操作,信号灯将阻塞当前任务,直到其它任务对信号灯进行 P 操作后,信号灯才会解除该任务的阻塞。

除了上述的 5 个外,还有两个和中断有关,以后在讲到中断系统时再详细讲解。

任务使用信号灯 get 方法获取信号灯:sema.get ()。由于 sema 的初始值为 0(见task1_t 的构造函数),当前任务(task1)就会在 sema 对象上挂起而进入阻塞状态。这时,系统中只有 task2 是处于就绪态的,所以调度器会将 task2 投入运行。

下一个观察点在 task2_t 的任务函数 process。这个函数立刻进入主循环:

首先,函数调用 delay 方法进行延时,时间为 10 个系统节拍。delay 是 task_t 定义的方法,所以 delay 只能在任务类成员函数(非静态)中调用。如果某个函数是非任务类成员函数,又被类成员函数调用,并且其中又希望延时的话,可以先调用 sched_t::get_task ()函数,获得当前任务对象的句柄(指针),然后使用这个指针调用 delay (),示例如下:

void foo ()                //    非任务类成员函数
{
    task_t* cur_task = sched_t::get_task ();    //    获得当前任务对象指针
    cur_task->delay ();                            //    调用 delay
}

void task_foo::do_something ()    //    任务类(task_foo,从 task_t 派生)成员函数
{
    foo ();            //    调用非任务类成员函数
}

你可以在 delay 前先记下当前系统已运行时间(AVR Studio 中的 Stop Watch)以备验证。

task2 进入延时后,实际上已经被挂起了,此时,系统内没有用户创建的任务处于就绪态了,调度器将运行系统空闲任务。空闲任务是一个死循环,不调用任何函数(不会发生阻塞),所以除了中断发生外,没有办法进行再调度的。目前,由于系统内只有系统节拍定时器在工作,所以,在 10 个系统节拍后,定时器将解除 task2 的挂起状态,而重新转入就绪态。

调度器再次检查系统内任务状态,发现 task2 已经就绪,就会立即将 task2 投入运行,注意:系统空闲任务总是就绪的,只不过它的优先级最低,所以一旦有其它任务就绪,调度器就会中断系统空闲任务的运行,而转入以就绪的最高优先级的任务。

下一个观察点在 delay 的下一句,在这个观察点,我们可以发现系统已运行时间已经过去了 100ms,正好是 10 个 ticks。

其实细心的话可以发现是有一些误差的,以后再详细说明,在这里你只需要知道:使用系统节拍定时器的精度最大误差可能接近 1 个 tick(只可能是负误差)。

接下来 task2 要对 task1 发出一个信号,这是通过调用 task1_t 类提供的 notify 方法来完成的。但这里有一个前提:task2 必须知道 task1 的位置,才能对其进行操作,这是因为 notify 是 task1_t 的成员函数,要调用 notify 必须要有一个指向 task1_t 类对象的指针。获取这个指针可以通过调度器的 get_task 方法,如示例 1 所示。也许我们将这步拆开为两步可能更容易理解:

task_t* task = sched_t::get_task (PRIO_TASK1);
task1_t* task1 = static_cast<task1_t*> (task);

第一步,调用 sched_t::get_task 获取 task_t 指针,参数为希望获取的任务的优先级。获取后的指针为 task_t 类指针(sched_t::get_task 只能返回 task_t 类指针),而不是我们希望的 task1_t 类指针(虽然实际上它就是),所以需要第二步。
第二步,静态转换 task 为 task1_t 类的指针。

现在,我们得到了 task1 的指针,那么就可以调用其 notify 方法了。至于 notify 里怎样给 task1 发出信号,这不是 task2 所关心的,这也正是 C++ 程序设计的基本规则之一:提供接口,隐藏实现细节

在 task1 的 notify 中,仅仅简单地调用了 sema 的 put 方法,sema_t::put 释放信号灯,因为 sema 上还有一个 task1 任务还在因为获取信号灯而阻塞,所以 sema 会解除 task1 的阻塞状态,并使 task1 获取到刚才被释放的信号灯,然后通知调度器重新调度。此刻,系统内 task1 和 task2 都处于就绪态。但当前任务是 task2,并且 task1 的优先级要高于 task2,所以调度器会将 task1 投入运行。

下一个观察点在 task1_t::process 函数的 sema.get 之后,因为 get 返回了 DONE,所以 PB0 的电平被翻转。然后 task1 又回到了循环起点,再去获取下一个信号灯。

下一个观察点回到了 notify。因为 task1 又被阻塞,所以 task2 被调度器投入运行,从 sema.put 返回。再返回到 task2_t::process。

周而复始,永不停息。

教程 1 结束

使用特权

评论回复
地板
testcode| | 2008-8-9 13:38 | 只看该作者

板凳~~~

使用特权

评论回复
5
li923661521| | 2011-5-12 08:31 | 只看该作者
学习!

使用特权

评论回复
6
hotpower| | 2011-5-25 16:53 | 只看该作者
看明白了

使用特权

评论回复
7
无法想象| | 2011-7-11 17:22 | 只看该作者
好东西,谢谢搂主分享,希望以后多发一些,对一些新人还是很有帮助的.

使用特权

评论回复
8
hotpower| | 2011-7-13 08:49 | 只看该作者
有时间测试一下。

使用特权

评论回复
9
小涛xty| | 2011-8-12 10:58 | 只看该作者
学习学习

使用特权

评论回复
10
小涛xty| | 2011-8-12 10:58 | 只看该作者
呵呵

使用特权

评论回复
11
yinlitansuo| | 2011-12-4 10:32 | 只看该作者
刚开始学avr的路过:)

使用特权

评论回复
12
天命风流| | 2014-4-14 17:52 | 只看该作者
顶起!!!

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

33

主题

1466

帖子

21

粉丝