tiny macro os
简介
tiny macro os是借鉴了protothread和时间轮询的宏定义调度内核。
从各个论坛汲取代码汇总而成,
本版本是个人在开发中结合自身使用习惯优化后的版本。
下载链接GitHub
ProtoThread机制
Protothread是专为资源有限的系统设计的一种耗费资源特别少并且不使用堆栈的线程模型,其特点是:
- 以纯C语言实现,无硬件依赖性
- 极少的资源需求,每个Protothread最少仅需要2个额外的字节
- 可以用于有操作系统或无操作系统的场合
- 支持阻塞操作且没有栈的切换
使用Protothread实现多任务的最主要的好处在于它的轻量级。
每个Protothread不需要拥有自已的堆栈,所有的Protothread共享同一个堆栈空间,对于RAM资源有限的系统尤为有利。
相对于操作系统下的多任务而言,每个任务都有自已的堆栈空间,这将消耗大量的RAM资源,而每个Protothread仅使用一个变量保存当前函数状态。
时间轮询机制
时间轮询机制是一种比较简单易用的系统架构之一,它对于系统中的任务调度算法是分时处理。核心思路是把 CPU 的时间分时给各个任务使用。
需要注意的是,这种方法的要保证每个任务都是短小精悍的,要不然一个任务执行的时间过长,那其它任务就无法保证按它预设的时间来执行。
tiny macro os任务调度机制
tiny macro os是结合了ProtoThread机制和时间轮询机制的调度内核,每个任务都有单独的时间变量和函数状态变量。
时间变量为全局变量,在软硬件定时器中更新。
函数状态变量为全局变量,用于保存函数目前运行的位置。
目的只为极度精简,并且可为51所用。
任务分主任务和子任务,所以不采用链表管理任务,需要手动在主循环中添加运行主任务,子任务在主任务中调用。
移植
移植首先需要根据自己的需求在tiny-macro-os.h中修改下列宏定义:
#define COMPILER_SUPPORT_VA_ARGS 1 /* 编译器是否支持可变参数宏定义,C89(Keil C51)不支持,C99以上支持 */
#define TINY_MACRO_OS_TIME_t unsigned short /* 定义时间计数变量的类型,根据最长延迟修改 */
#define TINY_MACRO_OS_LINE_t unsigned short /* 定义任务切换记录变量的类型,根据最大函数占用行数修改 */
#define OS_SEM_t signed short /* 信号量类型声明,必须为signed类型 */
#define OS_SEC_TICKS 1000 /* 定时器时钟更新频率,每秒钟多少个ticks */
COMPILER_SUPPORT_VA_ARGS必须根据你的编译器设置,如果你的编译器支持可变参数宏定义,将该宏设置为1,如果不支持,设置为0。常见:Keil C51不支持;keil arm需要设置后才支持;IAR一般都支持;GCC一般都支持。
OS_SEM_t是信号量类型定义,信号量类型必须为signed,其类型的最大值决定了在使用信号量超时判断时的最大判断次数,和信号量检测时间一起决定了信号量超时时间,可以通过判断信号量是否为-1来判断是否超时
TINY_MACRO_OS_LINE_t根据系统中所有TASK的最大函数行数决定,如果最大行数都不超过255,那么可以设为unsigned char
TINY_MACRO_OS_TIME_t是系统中任务时间计算的变量类型,决定了任务可以延迟的最大TICKS数量,例如unsigned char最大为255,时钟更新频率为10ms,则最大延迟时间为255*10ms。同时,时间如果是在中断函数中更新,则时间类型最好是中断安全的,即8位机最大用8位,32位机最大32位。如果不是中断安全的,参考下方的非中断安全的时间处理代码
移植需要实现一个定时器更新时间变量,定时器周期需要是1s / OS_SEC_TICKS:
void SysTick_Handler(void)
{
OS_UPDATE_TIMERS();
}
如果任务延迟需求已经超出了单片机中断安全的类型,可以先在定时器中断中计数时间值,然后在主循环中使用OS_UPDATES_TIMERS(TICKS)更新任务时间
unsigned int time;
void SysTick_Handler(void)
{
time++; //在中断中更新时间计数
}
void tmos_test_main(void)
{
OS_INIT_TASKS();
unsigned char i = 0;
while (1)
{
if(time > 0)
{
unsigned int stime; //定义临时变量保存time的值
disable_interrupt(); //关中断,确保安全
stime = time; //保存时间到临时变量中
time = 0; //清零时间计数
enable_interrupt(); //重新打开中断,开始时间计数
OS_UPDATES_TIMERS(stime); //更新任务时间
}
OS_RUN_TASK(os_test1);
}
}
任务定义
所有任务task都需要在下面的enum枚举中增加自己的名字,TINY_MACRO_OS_TASKS_MAX_NUM不可修改或删除,必须保留,它是用来定义变量长度必要的常量,用户自己添加的task名在它前面写入即可。
/****TINY_MACRO_OS TASKS DECLARE**************************************************************************/
/* 将tiny macro os任务函数的识别名字放到这里,后续使用都是从这里使用。自己每次创建任务都需要在这里添加 */
enum
{
os_test1 = 0, /* 第一个任务枚举必须赋值为0,确保不会定义过长的数组无错。 */
os_test2, /* 添加自己的任务直接在TINY_MACRO_OS_TASKS_MAX_NUM上方依次将任务名称加入本枚举即可 */
TINY_MACRO_OS_TASKS_MAX_NUM, /* 该选项不可删除或修改,用于计算任务数量去定义时间数组和状态数组,最大255个任务 */
};
任务定义采用了可变参数宏定义,任务携带的参数类型随意,数量不限。
该定义方法巧妙运用了编译器的编译顺序:宏定义展开->枚举变量替换,从而实现了用一个枚举变量的名字当作OS调度的识别和操作任务参数的数组下标。
在任务中通过OS_TASK_START(NAME)定义,将数组下标变为局部枚举变量_task_name,实现函数内部对函数状态和时间的操作,所以函数内部不可以定义和使用变量_task_name。
无参数任务
void参数表示函数无参数,可以不写该void,但是定义不标准,所以写上void最好
OS_TASK(os_test1, void)
{
OS_TASK_START(os_test1);
/* 禁止在OS_TASK_START和OS_TASK_END之间使用switch */
while (1)
{
printf("os_test1\n");
OS_TASK_WAITX(OS_SEC_TICKS * 6 / 10);
}
OS_TASK_END(os_test1);
}
上述任务定义展开后为
unsigned short (os_test1_task)( void )
{
enum
{
_task_name = os_test1
};
switch( os_task_linenums[(os_test1)] )
{
case 0U:
;
while (1)
{
printf("os_test1\n");
do
{
os_task_linenums[(_task_name)]=(((unsigned short)(54)%((unsigned short)(0xffffffff)))+1U)

tinymacroos是一个结合ProtoThread和时间轮询机制的宏定义调度内核,适用于资源有限的系统。它支持阻塞操作,不使用堆栈,任务调度灵活。文章介绍了ProtoThread的原理、时间轮询机制以及tinymacroos的移植和任务定义方法,包括任务创建、信号量、子任务和有限状态机的使用,并提供了示例代码。
最低0.47元/天 解锁文章
687





