微波EDA网,见证研发工程师的成长!
首页 > 硬件设计 > 嵌入式设计 > DIY:给单片机写个实时操作系统内核!

DIY:给单片机写个实时操作系统内核!

时间:11-29 来源:互联网 点击:

,就会用到CALL这条指令,它执行再从个动作,第一,先把当前的PC值保存起来,即现场保护,第二,把要调用的函数的入口地址送到PC,这样,在下一时刻到来的时候,CPU就自动跳转到特定的函数入口地址开始执行了。

第二条:RET/RETI。当一个函数执行完毕的时候,需要返回到原来执行的地方,这时候就要调用 RET指令(在中断函数中返回的时候调用RETI指令)。它把SP指向的数据,即上一次调用CALL时保存的那个地址原来到PC,这样,当下一时刻到来的时候,CPU就会跳回到原来的地方了。实际上函数调用过程就是这样的,所以有时候一些简单简短的函数宁愿用#define宏定义写出来,因为这样写出来就不用使用调用/返回过程,节省了时间。

第三/四条:PUSH/POP。这两个指令是两兄弟,即入栈及出栈。关于堆栈的特性说明一下:堆栈这种结构的特性就是后进先出,就像叠盘子一样,最后叠上去的盘子会被最先取出,这种原理非常好用,想象一下函数嵌套的时候发生的一切,就是利用到这种思路。PUSH指令用到把寄存器的值保存起来,它会把值到保存到SP指针所指的地方。POP指令则把数据从SP所指的地址恢复到原来的寄存器中。

 

用这几条指令,我们就可以写出一个任务切换函数了,不过写之前还要说明一下什么叫人工堆栈。其实上,一个程序在执行的时候,它会用到一块内存空间用于保存各种变量,比如调用函数的时候这块地方会用于保存地址以及寄存器,而在执行一些复杂算法的时候,如果CPU的寄存器已经用完了,这块地方也会作为临时中间变量的存放区,另外,在向一个函数传递参数的时候,比如:printf(a,b,c,d,e....),如果参数过多,多余的参数也会先存放到这块地方。所以说,这块地方就像是这个程序的仓库一样,存放着要用的东西。如果是在单道程序中,显然这样用没问题,但是如果是多道程序的话,问题就来了,因为如果所有任务共用那块区域,那旧任务保存的东西就会被新任务所冲掉,CPU一下子就疯掉了。。解决的办法就是:每个任务都给它提供一块专用的区域,这块专用区域就叫人工堆栈,每个任务都不一样,保证了不会相互冲突。

 

PS:因为51单片机的内存太小,基本无法实现多任务,实现了也不实用,所以硬件平台我选用了AVR单片机ATMEGA16,有1KB内存,应该够用了,花了两天时间把AVR的汇编指令看了一遍

首先,当需要切换任务的时候,要先把当前的所有寄存器全部入栈,在AVR单片机中有32个通用寄存器R0-R31,还有PC指针,PSW程序状态寄存器,这些都要入栈,所以需要的内存挺多的。现在的编译器都支持在线汇编,就是在C语言里面嵌入汇编语言,方便得多,下面我宏定义了一组入栈操作:PUSH_REG(),里面是用PUSH指令把32个寄存器全部入栈

#define PUSH_REG()

{_asm("PUSH R0" "PUSH R1" "PUSH R2" "PUSH R3"

"PUSH R4" "PUSH R5" "PUSH R6" "PUSH R7"

"PUSH R8" "PUSH R9" "PUSH R10" "PUSH R11"

"PUSH R12" "PUSH R13" "PUSH R14" "PUSH R15"

"PUSH R16" "PUSH R17" "PUSH R18" "PUSH R19"

"PUSH R20" "PUSH R21" "PUSH R22" "PUSH R23"

"PUSH R24" "PUSH R25" "PUSH R26" "PUSH R27"

"PUSH R28" "PUSH R29" "PUSH R30" "PUSH R31" ); }

入完栈完接下来要保护当前程序的SP指针,以便下次它要返回的时候能找到该人工堆栈的地址:

OS_LastThread->ThreadStackTop=(OS_DataType_ThreadStack *)SP;
 

这一句用C语言就可以实现了。

接下来关于当前这段程序的现场算是保护好了,然后找到要切换到的任务的人工堆栈地址,把它赋给SP指针,如下:

SP=(uint16_t)OS_CurrentThread->ThreadStackTop;
 

出栈跟入栈的语法差不多,只是出栈顺序要相反:

POP_REG();

接下来,要调用一条很重要的指令了!!!此令一出,CPU就乖乖地切换任务了!

_asm("RET");
 

调用返回指令,它就从SP里面取出函数地址放到PC,注意他取出的是刚刚放入SP指向地址的函数入口,所以它会返回到新任务执行。

就这样,一个操作系统里面最核心的”任务调度器“的模型就这样简单地实现了,操作系统里面所作的跟任务切换有关的事情到最后都要调用到这个任务调度器,现在我们实现调度器了,相当于成功了1/3,接下来的事情就是考虑在什么情况下调用这个调度器。

 

调度策略:实现了调度,还要继续考虑调度策略,就是什么情况下需要调度哪些任务。调度策略分很多种,有兴趣的可以去看那本《操作系统原理》,在我的源代码里面使用了”抢占式优先级调度+同一优先级下时间片轮询调度“的方法。

所谓抢占式优先级调度是一种实时调

上一篇:C++嵌入式开发
下一篇:单片机硬件心得

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top