示例定义了两个任务 task1 和 task2。task1 获取一个信号灯后,根据结果翻转 PB0 或 PB1,而 task2 按 10 个 tick 间隔时间,周期性地释放信号灯。 当然,这个示例过于简单,某些喜欢裸奔的朋友可能会不屑(其实裸奔一样可以实现),但这只是教程的第一步,不好用个太复杂的示例。
这个示例包含了 3 个文件:config.h, sample1.cc, arch.cc
config.h 文件: #ifndef __CONFIG_H #define __CONFIG_H
const unsigned long FOSC = 14745600L; // 晶振频率 const unsigned int TICKS_PER_SEC = 100; // 每秒 tick const int SIZE_INTERRUPT_STACK = 32; // 中断栈长度
#endif
sample1.cc 文件: #include "look.h" // 必须在第一行 #include <new> #include <avr/io.h> #include "config.h"
#define SIZE_TASK1_STACK 64 // 定义 task1 的栈长度 #define SIZE_TASK2_STACK 64 // 定义 task2 的栈长度 #define PRIO_TASK1 1 // 定义 task1 的优先级 #define PRIO_TASK2 2 // 定义 task2 的优先级
class task1_t : public task_t { public: task1_t (size_t stack_size) __attribute__ ((always_inline)); // 构造函数 void notify () __attribute__ ((always_inline));
private: virtual void process (void* arg); // 任务函数 sema_t sema; // semaphore,这里将semaphore定义在task1对象里,就相当于task1的per-thread data。 };
uint8_t task1[sizeof (task1_t) + SIZE_TASK1_STACK]; // 为task1分配空间,由于task1_t的对象将创建在这里,所以除了task1的栈空间外,还要加上task1_t的大小。
// task1的构造函数 inline task1_t::task1_t (size_t stack_size) : task_t (stack_size), // 初始化基类 task_t,参数为任务空间大小。 sema (0) // 初始化 sema,参数为 semaphore 的初始值。 { // 用户可以在此对task1的其它方面进行初始化,但不要在此创建其它任务对象。 DDRB |= _BV (PB0) | _BV (PB1); }
// task_t::sema的访问函数 inline void task1_t::notify () { sema.put (); // 释放 semaphore(V操作) }
class task2_t : public task_t { public: task2_t (size_t stack_size) __attribute__ ((always_inline));
private: virtual void process (void* arg); };
uint8_t task2[sizeof (task2_t) + SIZE_TASK2_STACK]; // 为task2分配空间,同task1。
inline task2_t::task2_t (size_t stack_size) : task_t (stack_size) // 同 task1。 { }
// task1的任务函数 void task1_t::process (void* arg) { // 在函数主循环之前,可以进行一些任务刚进入时所要做的工作,也可以创建其它任务对象。 // 本例中,就在此创建了task2对象。 sched_t::add (new (task2) task2_t (sizeof (task2)), PRIO_TASK2); // 在 task2 空间上创建 task2_t 的对象 while (true) { // 任务循环 if (sema.get () == sync_t::DONE) // 获取 semaphore(P操作) PORTB ^= _BV (PB0); // 成功,翻转 PB0 表示,用户可以测量。 else PORTB ^= _BV (PB1); // 失败,任务意外退出阻塞状态(应该不会运行到这个地方),翻转 PB1 表示,用户可以测量。 } }
// task2的任务函数 void task2_t::process (void* arg) { while (true) { // 任务循环 delay (10); // 延时 10 ticks task1_t *task = static_cast<task1_t *> (sched_t::get_task (PRIO_TASK1)); // 调用 sched_t::get_task 取得task1的句柄(指针) task->notify (); // 通知 task1 } }
LOOK_CONFIG (_VECTORS_SIZE, SIZE_INTERRUPT_STACK); // 配置中断系统,此为定式,不要修改
LOOK_START () // 系统入口点。相当于 main { // 在这里加入任务。可以创建所有的任务,也可以只创建第一个任务(其它任务由第一个任务创建)。 sched_t::add (new (task1) task1_t (sizeof (task1)), PRIO_TASK1); // 在 task1 空间上创建 task1_t 的对象 }
arch.cc 文件,这个文件是与系统节拍定时器的定义,用户可以根据具体项目修改,目前这个文件是为 atmega8 写的,系统节拍定时器使用了 timer2: #include "look.h" #include <avr/io.h> #include "config.h"
#undef _VECTOR #define _VECTOR(n) (n - 1)
void systick_t::init () { attach (SIG_OUTPUT_COMPARE2); // 将系统节拍中断绑定到 SIG_OUTPUT_COMPARE2。 // 设定 timer2 为 10ms 周期性中断。 OCR2 = (FOSC / 1024 / TICKS_PER_SEC) - 1; TIMSK |= _BV (OCIE2); TCCR2 = _BV (WGM21) | _BV (CS22) | _BV (CS21) | _BV (CS20); }
此示例展示了 LOOK 系统的一般结构,信号灯,延时和 per-thread data。
为了简便,我就不写 makefile 了,直接给出编译命令行(假定 look 系统的所有头文件在当前目录): avr-gcc -c -Os -g -D__AVR_ATmega8__ -fshort-enums -pipe -mmcu=avr4 -fno-rtti -fno-exceptions -Wa,-ahlms=sample3.lst sample1.cc -o sample1.o avr-gcc -c -Os -g -D__AVR_ATmega8__ -fshort-enums -pipe -mmcu=avr4 -fno-rtti -fno-exceptions arch.cc -o arch.o
链接命令行(假定liblook.a 在当前目录): avr-gcc -mmcu=atmega8 -nostartfiles -L. -o sample1.elf sample1.o arch.o -llook -Wl,-Map=sample1.map,--cref
生成的程序空间占用情况: avr-size sample1.elf text data bss dec hex filename 2110 70 343 2523 9db sample1.elf
可以看出此程序占用 flash 大小为 2180bytes,占用 ram 大小为 413bytes。
然后可以在 avr studio 中调试。
运行一段时间后,message 框中就会出现下列文字: AVR Simulator: PORTB - 001477586:01 002952146:00 004426706:01 005901266:00 007375826:01
大家可以在网上查一下 avr studio 的 stimuli 格式。然后分析一下上面的文字。就可以得出结果了。 |