引 言
随着技术的发展,嵌入式系统的设计及应用对人们的生活产生了很大的影响,并将逐渐改变人们未来的生活方式。在特定的操作系统上开发应用程序,可以使开发人员忽略掉很多底层硬件细节,使得应用程序调试更方便、易于维护、开发周期缩短并且降低开发成本,因而嵌入式操作系统深得开发人员的青睐。
AVR微处理器是Atmel公司开发的8位嵌入式RISC处理器,它具有高性能、高保密性、低功耗、非易失性等优点,而且程序存储器和数据存储器可独立编址,并具有独立访问的哈佛结构。AVR单片机内核有丰富的指令集,通过32个通用寄存器直接与逻辑运算单元相连接,允许在一个周期内一条单一指令访问两个独立的寄存器,这样的结构使代码的执行效率比传统的复杂指令集微处理器快了将近lO倍。
AVRX是由lbarello编写的源码公开的嵌入式操作系统,它专门针对AVR系列单片机的RTOS,具有免费和可以修改的特点。它的缺点是由于做为一种专用的操作系统很难移植到其他平台上。
1 AVRX系统的特点
AVRX做为AVR专用RTOS有如下的特点:
◆完全支持占先式、优先级驱动的任务调度算法;
◆16个优先级,相同的优先级的任务采用Round-robin调度算法轮流执行;
◆信号量可以用于信号传递、同步和互斥信号量,支持阻塞和非阻塞语法;
◆任务之间可以用消息队列相互传递信息,接收和确认消息可以用阻塞和非阻塞调用;
◆在中断子程序中,大部分非阻塞的中断服务程序可以使用;
◆支持单个定时器的时间队列管理,任何进程都可以设置一个定时器,并且任何一个任务都可以等待定时器时间到;
◆支持单步调试运行着的进程;
◆程序空间小,包含所有功能的版本占用700~1000字(words);
◆快速,若10MHz的晶振速律,内核扫描中断速律为1Khz(1ms),中断服务包括返回用211周期占用处理器20%(211/1000)左右的资源。
◆与AvrX内核定时器/计数器有关的一些事务可以用AVRX写成任务级代码。
1.1任务
AVRX2.6为了支持c语言,保存了所有的32个寄存器。最小的上下文是32个寄存器、SREG和PC,总共35个字节。AvrxInitTask()函数给所有的寄存器初始化为0X00。只有进程上下文保存在任务堆栈中,所有其他的使用(包括内核和中断)保存在内核堆栈。这样降低了第一个中断的上下文切换和进入内核API的SRAM消耗。随后的中断(如果允许中断嵌套)嵌入内核堆栈,API不进行上下文切换。
保持任务的信息(栈指针或上下文指针)存于进程ID块中(PID),PID用6字节SRAM构成 。额外的SRAM是进程队列,状态位和优先级的字节指针。
AvrXInitTask
AvrXRunTask
AvrXSuspend
AvrXResume
AvrXTaskExit
AvrXTerminate
AvrXHalt /*AvrXHalt:cli
rjmp AvrXHalt*/
1.2信号量
信号量是SRAM指针,它们有三种状态:PEND、WAITING和DONE。当一个进程被一个信号量阻塞时,它处于WAITING状态,多个任务可以排队等候一个信号量。在后一种情况下,信号量可以看作互斥信号量。提供的API函数如下:
AvrXSetSemaphore
AvrXIntsetSemaphore
AvrXWaitSemaphore
AvrXTestSemaphore
AvrXIntTestSemaphore
AvrxResetSemaphore
1.3定时器
定时器控制块(TCB)长度为4(或6)个字节。它们管理一个16位计数值。定时器队列管理器管理一个分类的定时器队列,每个都调整为所有计数器的和到其延时需要的值。提供的API函数如下:
AvrXStartTimer
AvrXTimerHandler
AvrXCancelTimer
AvrXWaitTimer//阻塞函数
AvrXTestTimer
AvrXDelay//阻塞函数
在AvrX2.6中,有个额外的可变的定时器队列块,TimerMessageBlock。定时器的消息用在了MessageTimer的列程中。简言之,定时器一到点,一个消息就被排列到消息队列中。任务以等消息队列的方法来等侯多个事件。
AvrXStartTimerMessage //阻塞函数
AvrXCancelTimerMessage
1.4消息队列
消息队列用消息控制块(MCB)做为队列首地址。任何进程、中断处理函数和多个进程都可以等待消息。MCB的长度是2或4个字节,消息可以认为是灵活性更大的信号量。提供的API函数如下:
AvrXSendMessage
AvrXIntSendMessage
AvrXRecvMessage
AvrXWaitMessage
AvrXAckMessage
AvrXTestMessage
AvrXWaitMessageAck
1.5单步运行支持
通过重新汇编内核AVRX,可以允许和禁止单步运行的支持。单步运行可以通过编译内核库时定义下面的变量:#define SIGNALSTEPSUPPORT。在能够单步运行以前,进程必须先暂停。有两种方法实现:一是仅仅初始化进程但不使能;二是用目标进程识别码调用
AvrXSuspend,一旦目标进程挂起,调试SPI就能使用了。提供的API函数有:
AvrxStepNext
AvrXSin-gleStepNext
1.6系统对象
AVRX是围绕系统对象的概念而构建的。系统对象包括一个链接和其后面的0个或者若干个字节的数据信号量。进程对象可以根据运行队列和信号量排队。计数器控制块只能根据计数器队列排队。消息控制块只能在消息队列排队。进程根据嵌入对象的信号量等待这些对象。进程堆栈中可用的SRAM是限制系统规模的主要因素,每个进程都需要至少10~35字节的空间来存储进程上下文。对中断进程只要使用AvrX的语句就无需额外堆栈。堆栈可在64k的SRAM任何地方开始。至少该内核的最好表现是在片内SRAM。提供的API函数如下:
AvrXSetObjectSamaphore
AvrXIntObjectSamaphore
AvrXResetObiectSamaphore
AvrXWaitObjectSamaphore
AvrXTestObjectSamaphore
AvrXIntTestObjectSamaphore
1.7系统堆栈
AVRX需要足够大的堆栈来处理所有可能的中断嵌套。每次进入内核将会把10~35字节压进堆栈(标准上下文和返回地址),中断处理可能压进去更多。AVRX的API会临时压入2个以上的字节。GCC或者汇编代码定义于SRAM的顶部,保证AVRX的堆栈在有效SRAM空间之内是设计者的工作。
原理:
AvrX完全支持占先式、优先级驱动的任务调度算法。口水话。
术语:
PID - 进程识别码,它是一个数据结构,包含了用来执行一个进程的所有信息。
任务切换:
任务切换逻辑的大部分是在_Prolog和_Epilog这两个程序中实现的,它用到了三个数据结构:
_RunQueue 指向运行队列中的第一个PID的指针
_Running 指向正在运行的PID的指针
_SysLevel 系统进入内核的次数计数器,-1 = 进程堆栈
_SysLevel 指示了系统正在运行的是用户代码还是内核代码。如果_SysLevel = -1,当前的堆栈是任务堆栈。当有中断发生,或者用户代码调用内核API时,_Prolog就首先保存当前的运行环境到堆栈,然后使_SysLevel加一,接下来如果_SysLevel == 0 就切换到内核堆栈上去。后来的再发生中断或者是内核API调用时,_Prolog将会把运行环境保存在内核堆栈上并对_SysLevel加一。切换到内核堆栈的过程包括读取当前SP(堆栈指针)并将其存储到_Running所指向的PID。然后向SP中装载指向内核堆栈栈顶的指针。_Epilog展开内核堆栈,它先弹出先前的运行环境,把_SysLevel减一。当_SysLevel变为负数时,就必须切换到进程堆栈上去,这时环境切换才真正发生:如果 _Running == _RunQueue,就意味着没有切换运行环境的必要,_Epilog就简单的恢复个寄存器的内容(通过指针_RunQueue)。如果_Running和_RunQueue不同的话,说明有高优先级的任务变为运行态等待运行了(不管是正在队列中的,或者是当前的任务给阻塞,比如被从_RunQueue中移除)。在这种情况下,_Epilog仍然是仅仅通过_RunQueue队首的指针来恢复运行环境。第三种情况是如果 _RunQueue == 0,或者为空,这意味着所有的任务都被阻塞了,CPU处在空闲状态。在这种情况下,_Epilog转向运行一个叫做空闲任务的特殊的任务,当前,空闲任务所做的事情就只是把CPU置于休眠状态,等待中断的唤醒。软件系统的设计者可能会选择创建一个低优先级的任务,该任务永远不会被阻塞或者调用空闲任务,它可以暂停CPU或者做任何你想做的事。只要它永远不会被阻塞,则系统内部的空闲任务就永远不会被执行。
Queue 队列
处在运行态的进程会被按照优先级反序放入_RunQueue队列中,优先级为0的将放在队列的头部。如果有多个进程拥有相同的优先级,则以进程的先后顺序运行。随着进程的执行和阻塞,他们将被重新置于相同优先级任务列表的尾部,从而进行有效的循环打鸣调度。 一个较低优先级的进程不能中断一个较高优先级的进程。_Epilog只是简单的从_RunQueue的头部取出一个进程来运行。然而,如果一个较低优先级的进程占用了较高优先级的进程需要的资源,比方说一个用来控制硬件访问的信号量,则较高优先级的进程将会阻塞低优先级的进程直到该资源可被访问为止。
一旦一个进程开始运行,它将一直运行直到被较高优先级的进程占先,或者是被较高优先级的进程阻塞等待某种资源。
Suspending a process 挂起一个进程
因为一个“正在运行”的进程可能被一个信号量阻塞,而信号量不被内核所知,所以挂起一个进程不是简单把它从运行队列中移除。而是用AvrXSuspend先标记一个准备挂起的进程,然后尝试把它从运行队列中移除。如果成功了,AvrXSuspend将标记该进程为“SUSPEND”并返回,如果没有成功,它将简单返回,稍后当该进程符合条件准备插入运行队列时,如果它标记为准备挂起,则通过_QueuePid手续不在将其列入队列,并把它标记为“SUSPEND”。根据定义,当_QueuePid试图把一个任务推入运行队列中时,该任务不能正在等待任何资源,并且与之关联的信号量将被清零。
Blocked Tasks 阻塞的任务
一个任务,不管它是被挂起或者是在等待某种信号量,都是被阻塞。换一句话说,就是没有被放入运行队列。因为AvrX是计划为小系统用的,所以只有一条前向链表。由于只需要一个指针,这种方式为系统节省了宝贵的SRAM空间。当要插入或者删除队列中的某个元素时,AvrX遍历队列寻找需要删除的对象或者是插入点。一般来讲,所有的队列(运行队列,定时队列,信号量,消息队列以及消息)都支持多元素排队等待。这样一来,一个信号量变成了一个互斥量(Mutex):如果它被一个进程所使用,所有其他的进程都排队等待它被使用者释放,然后队列头部的进程就可以从等待队列中释放出来。消息队列可以是多个消息等待一个接收者,或者是多个接收者等待一个消息。运行队列可以有很多的运行态任务排队等待运行。随着任务的阻塞或者退出或者被挂起,则运行队列中的下一个进程将把其运行环境交换到CPU中继续执行。(非作者注:阻塞的任务由运行阻塞函数实现的。)
Timer Queue 定时队列
定时队列是个比较特殊的情况。它是一个排过序的链表且链表中的超时设定都是调整过的,这样中断处理过程就只需要递减链表中第一个元素的计数器即可。当第一个元素的计数器变为0时,处理过程就设定该元素中的信号量。如果有一个任务在等待此信号量,则该任务就被放入运行队列中。TimerMessages是特殊的情况,TimerControlBlok不是直接发信号给任务,而是进入一个消息队列中。TimerHandler可以作为一个单独的任务运行,定时器可以作为消息。这实际上是个相当强大的功能但是并没有被采用,因为它要花费两次环境切换(一次是定时器处理任务,另一次是那个被通知进入运行的任务)并且还要占用额外的任务环境(~40字节的SRAM)。AvrX可以在512字节的SRAM上轻松运行3~4个任务:使用其中的一个任务做定时器句柄似乎太过于浪费。
Fifo Support 支持FIFO
AvrXFifo支持只是简单的字节性的FIFO。FIFO是通过申请字节数组,用一个正确类型的Const型指针映射到数组上生成的。FIFO可以是静态的也可以是自动的,或者是从堆中申请的。FIFO包括一些信号量,用来实现中断处理过程和任务之间的同步。AvrXSerialIO例程举例说明FIFO是怎样使用的。
怎样开始
开始的最简单的方式是找一个例程或者是测试程序和内核一起编译。当核实该例程或测试代码可以正常工作后,去掉例程文件,重名名文件并添加进你自己的代码。
顶层的文件一般需要包含下列部分:
AvrXTimerHandler中断句柄(处理程序)
一个或者多个内部或外部任务定义
CPU复位程序或者main(void)
为系统运行必须准备的所有任务必须执行最后一条设置。至少包含一些的条目,其中前三项都是由C编译器帮你完成的。
设置硬件堆栈指针
清零SRAM
清零所有寄存器
初始化任务结构(AvrXInitTask or AvrXRunTask)
初始化硬件(Timer0 or 1, ports, serialio, 其他)
由Epilog()跳转
必须由启动代码的最后指令Epilog()是跳转到任务。它将启动系统调度过程,切换运行环境到_RunQueue中的第一个任务上。如果monitor被包含在你的程序中,它应该拥有最高优先级(0),并且第一个被运行。
典型的用户代码应该独立放在各自的文件中,仅仅任务控制块(TCB)需要导入到顶层文件中。AvrX2.6中,宏AVRX_EXTERNALTASK完成了导入工作。请参考头文件(avrx.inc或avrx.h)获得宏工作的具体细节。
启动代码是在由C运行时库建立的堆栈或者是你用汇编语言建立的堆栈上运行的。这个堆栈地址基本上就是AvrX用来做内核堆栈的地方。因此,main()中所做的一切或者是复位程序都是在内核环境中运行的。在退出复位代码前至少要准备一个任务用AvrXRunTask()来运行。如果没有准备任务,Epilog()将会发现运行队列为空,就会使系统永久进入空闲任务。
尽管我还没有这样做,但是从空闲模式启动并且有一个中断处理程序通过AvrXRunTask()来创建一个任务是有可能的。更多的情况应该是运行你所有的任务,让他们做一些他们需要的初始化工作并通过某种东西(Timer,semaphore or message)来阻塞之。
可选的素材
在最小化下考虑,AvrX只是简单地支持任务初始化和信号量。单步,定时器队列管理,消息队列管理都是可选的服务,如果不需要的话可以被裁减掉。没有这些服务的内核是非常小的。这里给出不同内核功能下的粗略代码大小。
基本的任务调度和信号量队列管理: ~670字节(约670字节)
定时器队列管理: ~236
消息队列管理: 48
杂项(单步,高级任务调度): 200
调试监视器: ~1300字节
Fifo支持(用C编写) ~300bytes
不带调试监视器的总代码大小(对GCC版AvrX而言)为约600个字(1200字节),占8515代码空间的14%
定时器支持包含AvrX在你的应用中是毫无道理的。(非作者注:AvrX的内核用T0以1Khz(1ms)速律产生循环打鸣调度,一般来说应用中可用内核定时器。所以T0不得作为他用。)定时器队列管理仅是多任务竞争调度时延的一种实现机制而已。对简单的应用而言,可以简单地用实时中断发送一个信号量并用一个进程封装所有依赖时间要素。在每一个时间片到来时,该进程就运行,作业并有必要时置位其他的信号量来通知相应进程准备运行。另一种方式是发送消息。如果你的定时要求适当,一个简单的任务,甚至是中断处理程序,可能在代码空间和处理周期上都更加有效。(非作者注:若对定时有特殊要求可见列程Timers1.c,Timers1.c。)
消息队列管理之所以这么小是因为很多功能都已经在基本的任务和信号量模块中实现了。消息队列是个从信号量队列引申出的简单的概念。
任务结构
尽管不是绝对必要,一个任务一般是一个由入口,初始化和一个死循环组成的子程序。死循环中一般包括阻塞或等待一个信号量。它有可能是显式地调用AvrXWaitSemaphore,或者是非显式的调用AvrXWaitTimer或AvrXWaitMessage。后两种方式实际上被定时器结构或者消息结构嵌入的信号量给阻塞了。
还有一些数据结构也是需要在代码中定义的。不过大部分都可以用方便的宏定义来实现。这些宏定义在avrx.inc或avrx.h中,具体用哪个取决于你所使用的版本。
下面是一个简单的例子。这是一个任务简单地阻塞于一段定时之后发送一个信号量。这段代码使用的是AvrX2.5,GCC编译器。
Mutex Timeout;
AVRX_TASKDEF(myTask, 10, 3)
{
TimerControlBlock MyTimer;
while (1)
{
AvrXDelay(&MyTimer, 10); // 10ms delay
AvrXSetSemaphore(&Timeout);
}
}
AVRX_TASKDEF宏接收3个参数:任务(进程)名字,附加的堆栈字节数(在35个字节的标准环境基础上),优先级。它创建了所有需要的AvrX数据结构并声明了C语言任务进程。详情请参考AvrX.h。(非作者注:优先级在任务间根据需要可以通过编程来改变。)
中断句柄结构
中断处理程序需要有一个特定的名字以便GCC编译器为之设定一个适当的中断向量。查看avr-gcc文档sig-avr.h中的向量列表。AvrX完全处理了保存和恢复中断环境,所以你不用使用GCC程序的限定词SIGNAL或INTERRUPT,取而代之使用下面的语法:
AVRX_SIGINT(SIG_OVERFLOW0)
{
IntProlog(); // Switch to kernel stack/context
EndCriticalSection(); // Re-enable interrupts
outp(TCNT0_INIT, TCNT0); // Reset timer overflow count
AvrXTimerHandler(); // Call Time queue manager
Epilog(); // Return to tasks
}
IntProlog()在中断处理程序中没有重新开启中断(这一点和AvrX2.3不同),所以如果你想允许中断嵌套的话,你需要在中断处理程序中显式的开启中断。此外要小心,有些中断源没有被中断服务程序清除,所以你要在允许中断前清除它们,否则你将无止境地重入你的中断代码并耗尽你的堆栈。串口的处理程序便是这种情况的一个很好的例子。查看Serial I/O文件看看它是怎么处理的。
AvrX2.3的IntProlog()中默认是允许中断的,所以你要在调用IntProlog()前清除所有等待的中断。
你的main()代码的结构:
main()代码中先是初始化硬件和各个任务,然后跳转到Epilog()。这里是例程message.c中的main()函数(请参考实际例程,因为这里的是以前写的,所以代码的风格已经变了)
void main(void) // Main runs under the AvrX Stack
{
outp((1<<SE) , MCUCR); // Enable "Sleep" instruction for idle loop
outp(TCNT0_INIT, TCNT0); // TCNT0_INIT defined in "hardware.h"
outp(TMC8_CK256 , TCCR0); // Set up Timer0 for CLK/256 rate
outp((1<<TOIE0), TIMSK); // Enable0 Timer overflow interrupt
outp(-1, LED-1); // Make PORTB output and
outp(-1, LED); // drive high (LEDs off)
AvrXRunTask(TCB(task1));
AvrXRunTask(TCB(task2));
AvrXRunTask(TCB(Monitor));
InitSerialIO(UBRR_INIT); // Initialize USART baud rate generator
Epilog(); // Switch from AvrX Stack to first task
}
决定各种堆栈的大小
AvrX2.6的堆栈大小是35字节+任务所需的额外堆栈。C编译器在调用子程序时要使用大量的堆栈。这主要决定于子程序中有多少自动变量。最好的方式就是申请足够的堆栈(比方说70字节),然后把你的应用程序跑上一会儿看看堆栈使用了多少。由于GCC在启动时清零了所有的存储器,所以很容易知道到底用了多少。然而,保险起见,使用仿真器或者调试监视器往接近堆栈顶的地方写入几个0xFFFF来核实到底使用了多少。然后在此基础上多申请几个字节(最少一个或连个字)以保证你不会跑到其他的堆栈或数据结构上去。
决定内核堆栈要稍微简单点。AvrX普通操作时不会消耗很多的堆栈空间。可能最多4-6个字节。然而,内核是可重入的,并且会在中断嵌套时保存全部的环境到堆栈中。所以,你需要用中断源数乘以35然后加上中断服务程序代码用到的堆栈数再加上4-6个AvrX使用的堆栈,以此来估算一个大概的堆栈大小。当然,如果你的应用中有一个中断没有使用AvrX(例如 没有IntProlog()/Epilog()),这样它只需要把它用到的变量存储到内核堆栈或用户堆栈上去。在任何一种情况下,使用上述的方法来决定确切的大小:申请足够的空间,运行你的应用程序一段时间(模拟所有的中断源),然后看看到底用了多少堆栈。你可能会推断出最大可能用到的堆栈深度,因为有些中断可能不会常常发生,所以这种测试也不能覆盖所有的情况。对高速的素材(例如 串行通信产生的大量的数据,基本的定时时钟等)一般会在很短的时间内覆盖所有的组合情况。
2.1 任务(task) Avrx的编程是对任务(task)进行编程。任务的属性个人可分为:同步,互斥,独立,关联。一个完整的AvrX程序由
AVRX_SIGINT(SIG_OVERFLOW0)
{
IntProlog(); // Switch to kernel stack/context
TCNT0 = TCNT0_INIT;
AvrXTimerHandler(); // Call Time queue manager
Epilog(); // Return to tasks
}
int main(void) // Main runs under the AvrX Stack
{
AvrXSetKernelStack(0);
MCUCR = 1<<SE;
TCNT0 = TCNT0_INIT;
TCCR0 = TMC8_CK256;
TIMSK = 1<<TOIE0;
LEDDDR = 0xFF;
LED = 0xFF;
PORTC=0xff;
DDRC=0xf0;
PINC=0xFF;
AvrXRunTask(TCB(task1));
AvrXRunTask(TCB(task2));
AvrXRunTask(TCB(Monitor));
InitSerialIO(UBRR_INIT); // Initialize USART baud rate generator
Epilog(); // Switch from AvrX Stack to first task
while(1);
}
和至少两个以上的任务(task)组成。
任务类似于子程序又不同于子程序,在一个任务中可完成一项工作或多项工作。任务是采用线性方式编程,既不要编写子程序再在任务中调用,而是在任务中直接编写你需要的程序,做什么-等什么-做什么。
AvrX是实时多任务操作系统,可把每个任务看着是一个完整的“CPU”。因此用它可降低编程难度,提高编程效率。
另外,可在任务(task)中操作其他任务(task)。
2.2 变量任务中的常驻变量在任务中的While(1)之前声明且初始化,临时变量在任务中的While(1)之后声明且初始化。任务与任务间,任务与中断服务程序间的共用变量在int main()之前用volatile声明且初始化。与AvrX内核事务有关的变量和宏如:
AVRX_GCC_TASK(task1, 20, 2);//任务的声明(Declare)见jiaotong.c
/*宏:
AVRX_GCC_TASKDEF(start,c_stack,priority)
AVRX_GCC_TASK(start,c_stack,priority)
AVRX_SIGINT(vector)
*/
TimerControlBlock mytimer;
TimerMessageBlock Timer;
MUTEX mySemaphore;
MessageControlBlock myMessage;
MessageQueue MyQueue;
等在int main()之前声明。不要再另外声明所谓的位变量和判断用的变量,将与AvrX内核有关的一切事务交给AvrX内核去做(任务的操作,定时器(Timer),信号(Semaphore)和消息(Message) )。这是要点。
2.3 信号(Semaphore)和消息(Message) 信号(Semaphore)类似FLAGE,用MUTEX修饰,用于两个互斥任务或单条件分支。它们有三种状态:
SEM_PEND(0) //清零等待信号
SEM_DONE(1) //信号触发
SEM_WAIT(2) //除以上以外的状态
API函数如下:
AvrXSetSemaphore /
置互为反逻辑
AvrXIntSetSemaphore/
AvrXWaitSemaphore//类似while(...)死等,阻塞函数
AvrXTestSemaphore //测试(SEM_WAIT,SEM_PEND,SEM_DONE三态之一)
AvrXIntTestSemaphore //(=AvrXTestSemaphore+AvrXIntSetSemapore)
AvrxResetSemaphore//迫使=SEM_PEND
消息(Message)是信号(Semaphore)的扩展,消息可以认为是灵活性更大的信号量。API函数如下:
AvrXSendMessage /
发送互为反逻辑
AvrXIntSendMessage/
AvrXRecvMessage//接收
AvrXWaitMessage//类似while(...)死等,阻塞函数。
AvrXAckMessage//发送握手
AvrXTestMessage//测试(SEM_WAIT,SEM_PEND,SEM_DONE三态之一)
AvrXWaitMessageAck//等握手 类似while(...)死等,阻塞函数。
有如下用法:
发送端AvrXSendMessage//发送消息
AvrXWaitMessageAck//等握手
接收端AvrXWaitMessage//等消息
AvrXAckMessage//发送握手
另外可见列程Timer3.c
2.4 中断在实时系统中,实时系统的实时性表现在系统对外部事件的响应能力上。系统通过中断来响应外部事件的发生,并且在用户中断程序中做的事要尽量少,把大部分工作留给任务去做,只是通过信号量或者消息机制来通知任务运行。
jiaotong.c
/*这是个交通灯的程序,有两个互斥的任务*/
#include <C:/avrx/avrx-io.h>
#include <C:/avrx/avrx-signal.h>
#include "C:/avrx/serialio.h"
#include "C:/avrx/avrx.h"
#include "hardware.h"
TimerControlBlock timer1; // Declare the control blocks needed for timers
AVRX_GCC_TASK(task1, 20, 2); // Declare the Task
AVRX_GCC_TASK(task2, 20, 2);
AVRX_MUTEX(TimerSemaphore1);
int main(void)
{
AvrXSetKernelStack(0);
MCUCR = _BV(SE); // Initialize Timer Hardware
TCNT0 = TCNT0_INIT;
TCCR0 = TMC8_CK256;
TIMSK = _BV(TOIE0); // Enable Timer overflow interrupt
DDRB = 0xFF;
PORTB = 0xFF;
AvrXRunTask(&task1Tcb);
AvrXRunTask(&task2Tcb);
AvrXSetSemaphore(&TimerSemaphore1);
PORTB &=~ (_BV(PORTB0)|_BV(PORTB3));
InitSerialIO(UBRR_INIT);
//Initialize USART baud rate generator
Epilog(); // Switch from AvrX Stack to first task
while(1);
}
AVRX_SIGINT(SIG_OVERFLOW0)
{
IntProlog();
TCNT0 = TCNT0_INIT;
AvrXTimerHandler();
Epilog();
}
NAKEDFUNC(task1)
{
uint8_t i;
while(1)
{
AvrXWaitSemaphore(&TimerSemaphore1);//类似While(...)
PORTB &=~ _BV(PORTB0); //
PORTB |= _BV(PORTB1);
AvrXStartTimer(&timer1, 800); // 800 ms delay非阻塞函数
AvrXWaitTimer(&timer1); //阻塞函数
PORTB &=~ (_BV(PORTB0)|_BV(PORTB1));//
for(i=0;i<3;i++)
{ AvrXStartTimer(&timer1, 200);//非阻塞函数
AvrXWaitTimer(&timer1);//阻塞函数
PORTB |= _BV(PORTB0)|_BV(PORTB1);
AvrXStartTimer(&timer1, 200); // 200 ms delay非阻塞函数
AvrXWaitTimer(&timer1);//阻塞函数
PORTB &=~ (_BV(PORTB0)|_BV(PORTB1));
}
PORTB &=~ _BV(PORTB1);
PORTB |= _BV(PORTB0);
AvrXSetSemaphore(&TimerSemaphore1);
}
}
NAKEDFUNC(task2)
{uint8_t i;
while(1)
{
AvrXWaitSemaphore(&TimerSemaphore1);
PORTB &=~ _BV(PORTB2);
PORTB |= _BV(PORTB3);
AvrXDelay(&timer1, 800); // 800 ms delay 阻塞函数
/*AvrXDelay(&timer1, 800)=AvrXStartTimer(&timer1, 800)+ AvrXWaitTimer(&timer1);*/
PORTB &=~ (_BV(PORTB2)|_BV(PORTB3));
for(i=0;i<3;i++)
{
AvrXDelay(&timer1, 200); // 200 ms delay阻塞函数
PORTB |= _BV(PORTB2)|_BV(PORTB3);
AvrXDelay(&timer1, 200);//阻塞函数
PORTB &=~ (_BV(PORTB2)|_BV(PORTB3));
}
PORTB &=~ _BV(PORTB3);
PORTB |= _BV(PORTB2);
AvrXSetSemaphore(&TimerSemaphore1);
}
}
Timers1.c
/*这是运动记时器,俗称“跑表”,记时范围0~99.99秒,用LED数码管显示。用M16的定时器1的CTC方式定时,256分频,10ms单位。PC0为启动/停止钮,PC1为复位钮。Timers1.c和Timer3.c完成相同功能,Timer3.c更符合AvrX的风格*/
#include <c:/avrx/avrx-io.h>
#include <c:/avrx/avrx-signal.h>
#include "c:/avrx/avrx.h"
#include "c:/avrx/serialio.h" // From AvrX...
#include <avr/pgmspace.h>
#include <stdint.h>
#include "hardware.h"
prog_uint8_t LEDD[27]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x27,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x76,0x38,0x7c,0x73,0x67,0x50,0x78,0x3e,0x00,0x40,0x54};//共阳
/*unsigned char LEDD[27] =
{ 0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,
0x88,0x83,0xc6,0xa1,0x86,0x8e,0x89,0xc7,0x83,0x8c,
0x98,0xaf,0x87,0xc1,0xff,0xbf,0xab};
//{0123456789
// AbcdEFHLoP
// QrtU -n} LED段码表*/ //共阴
//AVRX_IAR_TASK(Monitor, 0, 20, 0); // External Task: Debug Monitor
//AVRX_GCC_TASK(Monitor, 20, 0); // External Task: Debug Monitor
volatile uint16_t Myclock;
volatile uint8_t disp[4]={0x3f,0x3f,0x3f,0x3f};
TimerControlBlock timer1; // Declare the control blocks needed for timers
AVRX_MUTEX(TimerSemaphore);
AVRX_SIGINT(SIG_OVERFLOW0)
{
IntProlog(); // Switch to kernel stack/context
TCNT0 = TCNT0_INIT;
AvrXTimerHandler(); // Call Time queue manager
Epilog(); // Return to tasks
}
SIGNAL(SIG_OUTPUT_COMPARE1A)
{
sei();
if((Myclock+=1)>=10000)Myclock=0;
AvrXSetSemaphore(&TimerSemaphore);
}
//AVRX_IAR_TASKDEF(task1, 0, 6, 3)
AVRX_GCC_TASKDEF(task1, 12, 3)
{ uint8_t Key[3];
//TCCR1B|=(_BV(WGM12)|_BV(CS12));//控制寄存器B,CTC模式256分频
while (1)
{uint8_t i;
Key[0]=PINC&03;
if(Key[0]!=Key[1])
{ i=Key[0];
if((~i&03)==3||(~i&03)==2)
{TCCR1B&=~(_BV(WGM12)|_BV(CS12));
Myclock=0;
disp[0]=disp[1]=disp[2]=disp[3]=0x3f;
TIMSK&=~_BV(OCIE1A);
TCNT1=0;
Myclock=0;
}
else if((~i&03)==1)
{ if((Key[2]^=~i&Key[1])==1)
{TCCR1B|=(_BV(WGM12)|_BV(CS12));
TIMSK|=_BV(OCIE1A);
}
else {TCCR1B&=~(_BV(WGM12)|_BV(CS12));
TIMSK&=~_BV(OCIE1A);
}
}
Key[1]=Key[0];
}
}
}
//AVRX_IAR_TASKDEF(task2, 0, 6, 2)
AVRX_GCC_TASKDEF(task2, 12, 1)
{uint8_t Tcount=0;
while (1)
{
uint8_t i;
AvrXDelay(&timer1, 4);
PORTC=0xff;
PINC|=0xF0;
if((Tcount+=1)==4)
Tcount=0;
switch(Tcount&0x3)
{
case 0x0:PORTC&=0xef;break;
case 0x1:PORTC&=0xdf;break;
case 0x2:PORTC&=0xbf;break;
case 0x3:PORTC&=0x7f;break;
}
if(((Tcount&0x3)==0)&&(disp[Tcount]==0x3f))
LED=0xff;
// else LED = disp[Tcount];
else if((Tcount&0x3)==1)
LED = disp[Tcount]|0x80;//LED ^ 0x02;
else LED = disp[Tcount];
// outp((inp(LED) ^ 0x02), LED);
if(AvrXIntTestSemaphore(&TimerSemaphore)==SEM_DONE)//测试相等清除
{ uint16_t div=1000,t;
uint8_t dd=0;
t=Myclock;
for(i=0;i<4;i++)
{
while(t>=div){dd+=1;t-=div;};
disp[i]=pgm_read_byte(&LEDD[dd]);div/=10;dd=0;}
}
}
}
int main(void) // Main runs under the AvrX Stack
{
AvrXSetKernelStack(0);
MCUCR = 1<<SE;
TCNT0 = TCNT0_INIT;
TCCR0 = TMC8_CK256;
TIMSK = 1<<TOIE0;
LEDDDR = 0xFF;
LED = 0xFF;
PORTC=0xff;
DDRC=0xf0;
PINC=0xFF;
OCR1A=F_CPU*10/256/1000;//10ms
TCNT1=0;
AvrXRunTask(TCB(task1));
AvrXRunTask(TCB(task2));
/// AvrXRunTask(TCB(Monitor));
InitSerialIO(UBRR_INIT); // Initialize USART baud rate generator
Epilog(); // Switch from AvrX Stack to first task
while(1);
}
Timer3.c
#include <c:/avrx/avrx-io.h>
#include <c:/avrx/avrx-signal.h>
#include "c:/avrx/avrx.h"
#include "c:/avrx/serialio.h" // From AvrX...
#include <avr/pgmspace.h>
#include "hardware.h"
prog_uint8_t LEDD[27]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x27,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x76,0x38,0x7c,0x73,0x67,0x50,0x78,0x3e,0x00,0x40,0x54};//共阳
/*unsigned char LEDD[27] =
{ 0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,
0x88,0x83,0xc6,0xa1,0x86,0x8e,0x89,0xc7,0x83,0x8c,
0x98,0xaf,0x87,0xc1,0xff,0xbf,0xab};
//{0123456789
// AbcdEFHLoP
// QrtU -n} LED段码表*/ //共阴
//AVRX_IAR_TASK(Monitor, 0, 20, 0); // External Task: Debug Monitor
///AVRX_GCC_TASK(Monitor, 20, 0); // External Task: Debug Monitor
volatile uint16_t Myclock;
volatile uint8_t disp[4]={0x3f,0x3f,0x3f,0x3f};
TimerControlBlock timer1; // 声明定时器控制块
typedef struct//声明一个带内部变量的消息结构体MyMessage。
{
MessageControlBlock mcb;
uint8_t SwitchUp,switches; //变量在对应的任务中赋值
}
MyMessage;
MessageQueue MyQueue; // 消息队列
AVRX_SIGINT(SIG_OVERFLOW0)//用定时器0以1ms的中断速率来实现循环打鸣调度。
{
IntProlog(); //为实现任务间的无缝切换并与GCC编译器兼容,每个任务必须有35个字节以上的SRAM。
TCNT0 = TCNT0_INIT;
AvrXTimerHandler();
Epilog();
}
SIGNAL(SIG_OUTPUT_COMPARE1A)
{ static MyMessage SW;//声明新变量
sei();
if((Myclock+=1)>=10000)Myclock=0;
SW.SwitchUp=1;
AvrXSendMessage(&MyQueue, &SW.mcb);//
}
AVRX_GCC_TASKDEF(task1, 40, 3)
{ uint8_t Key=0;
//TCCR1B|=(_BV(WGM12)|_BV(CS12));//控制寄存器B,CTC模式256分频
MessageControlBlock *p;
while (1)
{
uint8_t i;
p = AvrXRecvMessage(&MyQueue);//AvrXWaitMessage
if((((MyMessage*)p)->switches==3)||(((MyMessage*)p)->switches==2))
{TCCR1B&=~(_BV(WGM12)|_BV(CS12));
Myclock=0;
disp[0]=disp[1]=disp[2]=disp[3]=0x3f;
TIMSK&=~_BV(OCIE1A);
TCNT1=0;
Key=0;//Myclock=0;
}
else if(Key^=((MyMessage*)p)->switches)
{TCCR1B|=(_BV(WGM12)|_BV(CS12));
TIMSK|=_BV(OCIE1A);
}
else {TCCR1B&=~(_BV(WGM12)|_BV(CS12));
TIMSK&=~_BV(OCIE1A);
}
if(((MyMessage*)p)->SwitchUp)
{uint8_t dd=0;uint16_t div=1000;uint16_t t;
t=Myclock;
for(i=0;i<4;i++)
{
while(t>=div){dd+=1;t-=div;};
disp[i]=pgm_read_byte(&LEDD[dd]);div/=10;dd=0;}
}
}
}
AVRX_GCC_TASKDEF(task2, 15, 1)
{uint8_t Tcount=0;
static MyMessage SwitchMessage;//声明新变量
while (1)
{ uint8_t current;
AvrXDelay(&timer1, 4);
PORTC=0xff;
PINC|=0xF0;
if((Tcount+=1)==4)
Tcount=0;
switch(Tcount&0x3)
{case 0x0:PORTC&=0x7f;break;
case 0x1:PORTC&=0xbf;;break;
case 0x2:PORTC&=0xdf;break;
case 0x3:PORTC&=0xef;break;
}
if(((Tcount&0x3)==0)&&(disp[Tcount]==0x3f))
LED=0xff;
else if((Tcount&0x3)==1)
LED = disp[Tcount]|0x80;
else LED = disp[Tcount];
// LED = disp[Tcount];
current=(~PINC)&03;
if(SwitchMessage.switches!=current)
{SwitchMessage.switches=current;
AvrXSendMessage(&MyQueue, &SwitchMessage.mcb);
}
}
}
int main(void) // Main runs under the AvrX Stack
{
AvrXSetKernelStack(0);
MCUCR = 1<<SE;
TCNT0 = TCNT0_INIT;
TCCR0 = TMC8_CK256;
TIMSK = 1<<TOIE0;
LEDDDR = 0xFF;
LED = 0xFF;
PORTC=0xff;
DDRC=0xf0;
PINC=0xFF;
OCR1A=F_CPU*10/256/1000;//10ms
TCNT1=0;
AvrXRunTask(TCB(task1));
AvrXRunTask(TCB(task2));
/// AvrXRunTask(TCB(Monitor));
InitSerialIO(UBRR_INIT); // Initialize USART baud rate generator
Epilog(); // Switch from AvrX Stack to first task
while(1);
}
结论
AVRX是一个不错的RTOS,最显著的特点就是内核小,速度快,编译后大概只需700~1000字(words),且基本的调度功能一个也不少。由于其代码公开,结合不同型号AVR单片机的特性,可以在此基础上进行系统的裁减和扩展,使之能达到更好的效果。本文为AVR嵌入式系统的应用提供了借鉴。
AVRX系统的应用 (3)
AVRX_GCC_TASKDEF(task1, 40, 3)
{
uint8_t Key=0;
//TCCR1B|=(_BV(WGM12)|_BV(CS12));//控制寄存器B,CTC模式256分频 MessageControlBlock *p;
while (1)
{
uint8_t i;
p = AvrXRecvMessage(&MyQueue);//AvrXWaitMessage
if((((MyMessage*)p)->switches==3)||(((MyMessage*)p)->switches==2))
{
TCCR1B&=~(_BV(WGM12)|_BV(CS12));
Myclock=0;
disp[0]=disp[1]=disp[2]=disp[3]=0x3f;
TIMSK&=~_BV(OCIE1A);
TCNT1=0;
Key=0;//Myclock=0;
}
else if(Key^=((MyMessage*)p)->switches)
{
TCCR1B|=(_BV(WGM12)|_BV(CS12));
TIMSK|=_BV(OCIE1A);
}
else
{
TCCR1B&=~(_BV(WGM12)|_BV(CS12));
TIMSK&=~_BV(OCIE1A);
}
if(((MyMessage*)p)->SwitchUp)
{
uint8_t dd=0;
uint16_t div=1000;
uint16_t t;
t=Myclock;
for(i=0;i<4;i++)
{
while(t>=div){dd+=1;t-=div;};
disp=pgm_read_byte(&LEDD[dd]);
div/=10;
}
}
AVRX_GCC_TASKDEF(task2, 15, 1)
{uint8_t Tcount=0;
static MyMessage SwitchMessage;//声明新变量
while (1)
{ uint8_t current;
AvrXDelay(&timer1, 4);
PORTC=0xff;
PINC|=0xF0;
if((Tcount+=1)==4)
Tcount=0;
switch(Tcount&0x3)
{case 0x0 ORTC&=0x7f;break;
case 0x1 ORTC&=0xbf;;break;
case 0x2 ORTC&=0xdf;break;
case 0x3:PORTC&=0xef;break;
}
if(((Tcount&0x3)==0)&&(disp[Tcount]==0x3f))
LED=0xff;
else if((Tcount&0x3)==1)
LED = disp[Tcount]|0x80;
else LED = disp[Tcount];
// LED = disp[Tcount];
current=(~PINC)&03;
if(SwitchMessage.switches!=current)
{SwitchMessage.switches=current;
AvrXSendMessage(&MyQueue, &SwitchMessage.mcb);
}
}
}
int main(void) // Main runs under the AvrX Stack
{
AvrXSetKernelStack(0);
MCUCR = 1<<SE;
TCNT0 = TCNT0_INIT;
TCCR0 = TMC8_CK256;
TIMSK = 1<<TOIE0;
LEDDDR = 0xFF;
LED = 0xFF;
PORTC=0xff;
DDRC=0xf0;
PINC=0xFF;
OCR1A=F_CPU*10/256/1000;//10ms
TCNT1=0;
AvrXRunTask(TCB(task1));
AvrXRunTask(TCB(task2));
/// AvrXRunTask(TCB(Monitor));
InitSerialIO(UBRR_INIT); // Initialize USART baud rate generator
Epilog(); // Switch from AvrX Stack to first task
while(1);
}
结论
AVRX是一个不错的RTOS,最显著的特点就是内核小,速度快,编译后大概只需700~1000字(words),且基本的调度功能一个也不少。由于其代码公开,结合不同型号AVR单片机的特性,可以在此基础上进行系统的裁减和扩展,使之能达到更好的效果。本文为AVR嵌入式系统的应用提供了借鉴。
笔者注:个人曾使用AVRX+mega64开发了可视对讲部分,感觉AVRX真的是一个非常非常好用的小型系统,实时性很强,效率也比较高。