基于单片机的多任务轮询系统
@
时间片轮询系统
通过时间片的划分,可以利用一个定时器或者系统滴答定时器,通过多任务轮询方法,实现一个多任务的基于时间轮询调度的系统——Schedule.
版权声明
本文展示的源码为网络上所获取的资源,如有侵权,请告知删除。
此处仅为交流学习使用。
文件结构
有三个文件构成,两个h文件一个c文件。
sch_chg.h 类型定义文件
schedule.c 源码实现
shcedule.h 宏定义和配置文件
源码
sch_chg.h
#ifndef __SCH_CFG_H_
#define __SCH_CFG_H_
//定义可裁剪部分
#define SCH_CFG_Q_EN 1u /* 任务内建消息使能 */
#define SCH_MAX_TASKS 4 /* 最大任务数量可以增加最大255个*/
#define SCH_MBOX_EN 1 /* Enable (1) or Disable (0) 邮箱*/
#define SCH_TICKS_PER_SEC 1000 /* 1秒钟多少个系统时钟数 */
/*------------------------------------------------------------------------------
* 与编译器相关数据类型定义
*-----------------------------------------------------------------------------*/
typedef unsigned char BOOLEAN;
typedef unsigned char INT8U; /* Unsigned 8 bit quantity */
typedef signed char INT8S; /* Signed 8 bit quantity */
typedef unsigned short INT16U; /* Unsigned 16 bit quantity */
typedef signed short INT16S; /* Signed 16 bit quantity */
typedef unsigned int INT32U; /* Unsigned 32 bit quantity */
typedef signed int INT32S; /* Signed 32 bit quantity */
typedef float FP32; /* Single precision floating point */
typedef double FP64; /* Double precision floating point */
typedef unsigned int SCH_TICKS; //定义延时时钟数据类型
//定义数据类型
typedef unsigned char SCH_UINT8;
typedef unsigned int SCH_UINT16;
#endif
schedule.h
#ifndef __SCHEDULE_H
#define __SCHEDULE_H
#ifdef __cplusplus
extern "C" {
#endif
/*------------------------------------------------------------------------------
schedule 版本号
------------------------------------------------------------------------------*/
#define SCH_VERSION 02200u
/*------------------------------------------------------------------------------
* 包含头文件
------------------------------------------------------------------------------*/
#include "sch_cfg.h"
#ifndef SCH_GLOBALS
#define SCH_EXT extern
#else
#define SCH_EXT
#endif
/*------------------------------------------------------------------------------
* 系统状态定义
------------------------------------------------------------------------------*/
#define SCH_DLY_TYPE SCH_UINT16
#define SCH_ERR_NONE 0u
#define SCH_ERR_TIMEOUT 10u
#define SCH_TASK_RUN 0
#define SCH_TASK_PEND (SCH_DLY_TYPE)0xffff
#if SCH_MAX_TASKS <= 255
#define SCH_MAX_TASK_TYPE SCH_UINT8 //最大任务数<=255时定义为u8
#else
#define SCH_MAX_TASK_TYPE SCH_UINT16 //最大任务为>255则定义为u16
#endif
/*------------------------------------------------------------------------------
* 数据结构定义
------------------------------------------------------------------------------*/
typedef struct
{
SCH_TICKS TaskDly;
/*延时数据*/
INT8U SubExitFlag;
/*这个是什么?*/
#if SCH_CFG_Q_EN > 0u
void *pData;
/*队列指针*/
SCH_UINT8 Size;
/*消息的长度*/
#endif
}SCH_TCB;
/*----------------------------------------------------------------------------*/
/*----------------------------邮箱数据类型定义--------------------------------*/
typedef struct
{
SCH_TICKS Dly;
/*延时量*/
void *pMbox;
/*邮箱的指针*/
} MBOX;
typedef SCH_TICKS SEM; //信号量数据类型定义
/*------------------------------------------------------------------------------
* 全局变量定义
------------------------------------------------------------------------------*/
SCH_EXT SCH_TCB TaskTcb[SCH_MAX_TASKS];
//任务状态数据结构
SCH_EXT INT8U CurRunTaskId;
//当前运行任务的ID号
/*----------------------------------------------------------------------------*/
/*------------------------------------------------------------------------------
任务相关宏
【1】任务创建按实例创建:
void Task_xxx(void)
{
SCH_TaskBegin();
while(1) {
//用户代码
SCH_TimeDly(SCH_TICKS_PER_SEC/100); //根据任务实际周期进行延时
}
SCH_TaskEnd();
}
【注意】
1,任务必须是无限循环,不能返回;
2,任务中必须有延时函数或等待信号量,便于任务出让CPU使用权;
3,任务函数中使用的非静态局部变量会在任务出让CPU后,
变量消失,因此要想某个变量不消失,则要使用静态变量
【2】任务调度,按以下实例
SCH_TaskRun(Task_xxx,0);
【注意】
1,各任务的ID不能相同
------------------------------------------------------------------------------*/
//@任务开始宏在任务函数的变量声明后引用
#define SCH_TaskBegin() static INT16U _lc=0u; switch(_lc){case 0:
//@任务结束宏在任务尾引用
#define SCH_TaskEnd() }
//@任务调用宏在主循环中调用
//@ TaskName:任务名
//@ TaskID:任务ID,最大值不能超过"SCH_MAX_TASKS"值,且每个任务的ID不能相同
#define SCH_TaskRun(TaskName,TaskID) \
do { \
CurRunTaskId = TaskID; \
if(TaskTcb[CurRunTaskId].TaskDly==0u) { \
TaskName(); \
} \
} while(0);
/*----------------------------------------------------------------------------*/
/*------------------------------------------------------------------------------
子任务相关宏
--------------------------------------------------------------------------------
【1】子任务创建按实例创建:
void SubTask_xxx(void)
{
SCH_SubTaskBegin();
//用户代码
SCH_TimeDly(SCH_TICKS_PER_SEC/100); //根据任务实际周期进行延时
SCH_SubTaskEnd();
}
【注意】
1,子任务不可以是无限循环,否则子任务后面程序无法执行;
2,子任务可以带参数,但是不能返回;
3,子任务中必须有延时函数或等待信号量,便于任务出让CPU使用权;
4,子任务函数中使用的非静态局部变量会在任务出让CPU后,变量消失,
因此要想某个变量不消失,则要使用静态变量
【2】子任务调度,按以下实例
SCH_CallSubTask(SubTask_xxx());
------------------------------------------------------------------------------*/
//@子任务开始宏,在子任务函数的变量声明后引用
#define SCH_SubTaskBegin() static INT16U _lc=0u;switch(_lc){case 0:
//@子任务结束宏,在子任务函数尾引用
#define SCH_SubTaskEnd() }_lc=0u;TaskTcb[CurRunTaskId].SubExitFlag=0u;return
//@子任务调用宏,在任务中调用,允许多层嵌套
//@ SubTaskName:子任务名
//@ p_arg:子线程函数输入参数
#define SCH_CallSubTask(SubTaskFun) \
TaskTcb[CurRunTaskId].SubExitFlag=1u; \
while(1) { \
_lc=(__LINE__%65535);return;case(__LINE__%65535): { \
SubTaskFun; \
} \
if(TaskTcb[CurRunTaskId].SubExitFlag==0u) { \
break; \
} \
}
/*------------------------------------------------------------------------------
* 延时宏定义
------------------------------------------------------------------------------*/
//@延时宏,在任务开始宏和结束宏之间的任意位置引用;
//@【注】由于此调度器运用了C语言的行号的宏代码,
//@ 因此此宏定义最好放在一行代码中,不能拆分此宏定义,防止行号返回出错
//@ ticks:延时时间
#define SCH_TimeDly(ticks) do{_lc=(__LINE__%65535);TaskTcb[CurRunTaskId].TaskDly=ticks;TaskTcb[CurRunTaskId].SubExitFlag=1u;return;}while(0);case (__LINE__%65535):
/*------------------------------------------------------------------------------
信号量相关调用宏定义
------------------------------------------------------------------------------*/
//@信号量创建
#define SCH_SemCreate(Sem) Sem = 1u
//@w信号量等待
//@ sem--等待信号量;
//@ timeout--等待超时时间,0--直到成功等待信号量;
//@ err--等待信号状态,SCH_ERR_NONE-等待成功,SCH_ERR_TIMEOUT-等待超时
#define SCH_SemPend(sem,timeout,err) \
do { \
if(sem==0u) { \
sem = 1u; \
err = SCH_ERR_NONE; \
} else { \
sem = timeout + 1u; \
SCH_TimeDly(0u); \
if(timeout==0u) { \
if(sem>0u) { \
TaskTcb[CurRunTaskId].TaskDly = 0u; \
return; \
} \
sem = 1u; \
err = SCH_ERR_NONE; \
} else { \
if(sem>1u) { \
sem --; \
TaskTcb[CurRunTaskId].TaskDly = 1u; \
return; \
} else if(sem==1u) { \
err = SCH_ERR_TIMEOUT; \
} else { \
sem = 1u; \
err = SCH_ERR_NONE; \
} \
} \
} \
} while(0)
//@ 发出一个信号量
#define SCH_SemPost(Sem) \
do { \
Sem = 0u; \
} while(0)
/*------------------------------------------------------------------------------
邮箱相关调用宏定义
------------------------------------------------------------------------------*/
#if SCH_MBOX_EN > 0u
//@创建邮箱
#define SCH_MboxCreate(mbox) \
do { \
mbox.Dly = 1u; \
mbox.pMbox = (void*)0; \
} while(0)
//@ 邮箱等待
//@ mbox--等待邮箱;
//@ pmsg--等待成功后接收邮箱数据指针;
//@ timeout--等待邮箱超时时间,0--直到成功等待邮箱;
//@ err--等待邮箱状态,SCH_ERR_NONE-等待成功,SCH_ERR_TIMEOUT-等待超时
#define SCH_MboxPend(mbox,pmsg,timeout,err) \
do { \
if(mbox.pMbox!=(void*)0) { \
pmsg = mbox.pMbox; \
mbox.pMbox = (void*)0; \
err = SCH_ERR_NONE; \
} else { \
mbox.Dly = timeout + 1u; \
SCH_TimeDly(0); \
if(timeout==0u) { \
if(mbox.pMbox==(void*)0) { \
TaskTcb[CurRunTaskId].TaskDly = 0u; \
return; \
} \
pmsg = mbox.pMbox; \
mbox.pMbox = (void*)0; \
err = SCH_ERR_NONE; \
} else { \
if(mbox.pMbox!=(void*)0) { \
pmsg = mbox.pMbox; \
mbox.pMbox = (void*)0; \
err = SCH_ERR_NONE; \
} else { \
if(mbox.Dly>1u) { \
mbox.Dly --; \
TaskTcb[CurRunTaskId].TaskDly = 1u; \
return; \
} else if(mbox.Dly==1u) { \
pmsg = (void*)0; \
mbox.pMbox = (void*)0; \
err = SCH_ERR_TIMEOUT; \
} \
} \
} \
} \
} while(0)
//@ 发出一个邮箱
#define SCH_MboxPost(mbox,pmsg) \
do { \
mbox.Dly = 0u; \
mbox.pMbox = (void*)pmsg; \
} while(0)
#endif
/*------------------------------------------------------------------------------
操作指定任务,不常用
------------------------------------------------------------------------------*/
//挂起(暂停)指定任务
#define SCHTaskPend(TaskPendTCB) TaskPendTCB.TimeCounter = SCH_TASK_PEND
//恢复指定任务(运行)
#define SCHTaskResume(TaskResumeTCB) TaskResumeTCB.TimeCounter = SCH_TASK_RUN
//指定任务延时X个时间节拍后恢复
#define SCHTaskDly(TaskDlyTCB, Ticks) TaskDlyTCB.TimeCounter = Ticks
/*------------------------------------------------------------------------------
消息队列功能实现
------------------------------------------------------------------------------*/
#if SCH_CFG_Q_EN > 0u
#define SCH_Q_FREE 1
#define SCH_Q_BUSY 0
//等待消息
#define SCHTaskQpend() {_lc=(__LINE__%65535);pCurTCB->TaskDly = SCH_TASK_PEND;pCurTCB->pData=(void *)0;pCurTCB->Size=0;}return;case (__LINE__%65535):
//释放消息
#define SCHTaskQpost(PostTCB, pDat, Len) PostTCB.pData = pDat; PostTCB.Size = Len; PostTCB.TaskDly = SCH_TASK_RUN
//查询消息列队状态,是否是自由(可用)或忙(不可用),
//调用SCHTaskQpend()时会将其设置为自由状态
#define SCHTaskGetQFree(TaskTCB, RetStatus) RetStatus = SCH_Q_BUSY; if (TaskTCB.TaskDly == SCH_TASK_PEND){RetStatus = SCH_Q_FREE;}
#endif
//------------------------------------------------------------------------------
/*------------------------------------------------------------------------------
外部变量声明
------------------------------------------------------------------------------*/
extern SCH_TCB *pCurTCB;
/*------------------------------------------------------------------------------
函数申明
------------------------------------------------------------------------------*/
void SCH_Init(void);
void SCH_TimeTick(void);
#ifdef __cplusplus
}
#endif
#endif
shcedule.c
#define SCH_GLOBALS
#include "schedule.h"
SCH_TCB *pCurTCB;
/*------------------------------------------------------------------------------
** 函数名: SCH_Init
** 输 入: 无
** 输 出: 无
** 功能说明:调度器初始化函数,任务运行前调用
**----------------------------------------------------------------------------*/
void SCH_Init(void)
{
INT8U i;
CurRunTaskId = 0;
for(i=SCH_MAX_TASKS;i>0;i--)
{
TaskTcb[i-1].TaskDly = 0;
TaskTcb[i-1].SubExitFlag = 0;
}
}
/*------------------------------------------------------------------------------
** 函数名: SCH_TimeTick
** 输 入: 无
** 输 出: 无
** 功能说明:调度器时钟节拍函数,在定时中断中调用
**----------------------------------------------------------------------------*/
void SCH_TimeTick(void)
{
INT8U i;
for(i=SCH_MAX_TASKS;i>0;i--)
{
if(TaskTcb[i-1].TaskDly>0)
{
//任务延时时间处理
TaskTcb[i-1].TaskDly --;
}
}
}
/*-----------------------------End Of File------------------------------------*/
具体代码就不再解释,可以结合源码注释自行研究理解。
应用
基于STM32F103系列单片机,展示实际应用。
包含头文件
schedule.h
定时器调用
定时器调用任务轮询函数
/*
定时器可设置为10ms周期,或其他
*/
void TIM2_IRQHandler(void)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
SCH_TimeTick();
}
创建任务
/*******************************************************************************
* 函数名称:Thread_InitMain
* 函数功能:主线程
* 入口参数:无
* 返 回 值:无
* 其他说明:无
*******************************************************************************/
void Thread_InitMain(void)
{
SCH_TaskBegin(); //注意这里
while(1) //注意这是死循环
{
SCH_TimeDly(150); //这是这个任务的演示函数,单位为定时器定时周期,如果
//如果定时器定时周期为10ms,则此处延时为150*10
}
SCH_TaskEnd(); //注意这里
}
/*******************************************************************************
* 函数名称:Thread_Menu
* 函数功能:菜单线程
* 入口参数:无
* 返 回 值:无
* 其他说明:无
*******************************************************************************/
void Thread_Menu(void)
{
SCH_TaskBegin();
while(1)
{
KeyValue = Scan_key();
if(KeyValue.key != 0)
{
QueuePushIn(&KeyValue.key, 1);
}
Menu();
SCH_TimeDly(100);
}
SCH_TaskEnd();
}
/*******************************************************************************
任务初始化
SCH_Init(); //注意这里哦,先初始化
Time2_Init(); //注意这里哦,再初始化
//SEGGER_RTT_Init();
Init_V9203(); //初始化V9203
UserData_Init();
InitQueue(&stQueue);
ClearScreen();
DisplayFrequency = DISPLAY_FREQUENCY;
while (1)
{
SCH_TaskRun(Thread_InitMain, 0);
SCH_TaskRun(Thread_Menu, 1);
}
任务调用
int main(void)
{
SCH_Init();
Time2_Init();
while (1)
{
SCH_TaskRun(Thread_InitMain, 0); //这里入口参数的第二个参数为任务id,
//id不能重复
SCH_TaskRun(Thread_Menu, 1);
}
}