基于定时器中断的多任务轮询架构

架构及实现

调度器的主要构成

Architecture:调度架构

task.h:总任务队初始化 开启,各项任务的初始化,注销,执行函数定义;任务号定义;

task.c:总任务对初始化 开启(调用调度器),各项任务的初始化,注销,执行函数(调用相应组件)实现

scheduler.h:任务调度器的相关操作定义:任务注册,任务卸载,调度器初始化,调度器开关,调度器回调,调度器任务;任务结构体;

scheduler.c:任务调度器的相关操作实现,注册和卸载即对任务列表中的任务结构体赋值(调用定时器驱动);

BSP:硬件外设

BSP_INIT

bsp_init.h:硬件外设初始化定义

bsp_init.c:硬件外设初始化实现

BSP_TIMER

bsp_timer.h:定时器驱动器定义

bsp_timer.c:定时器驱动器实现

本架构的核心是基于定时器中断驱动任务执行,因此首先需要初始化一个定时器,并配置中断溢出时间,即最小时间单位。在定时器中断中执行调度器函数,即调度器函数中检查任务执行时间、标志位等参数并进行变更。

任务结构体如下

用于配置任务参数,包括任务号、任务函数、任务参数、任务执行标志和任务时间相关参数。


复制代码
typedef struct TASK_COMPONENTS
{
    volatile unsigned int iTaskTimerCnt;  // 任务执行倒计时
    volatile unsigned int iTaskTimerInval;  // 任务执行间隔
    volatile unsigned char ucIndex;  // 任务号
    volatile unsigned char isRunFlag;  // 任务执行标志
    void (*TaskHook)(void);  // 任务函数
    void *arg;  // 任务函数传参
} TASK_COMPONENTS_t;

调度器函数如下

在每次定时器中断中轮询任务列表,依次判断任务是否存在,若任务执行倒计时不为0,则接着检查任务运行标志,若任务执行标志为FLASE,即任务倒计时没有到并且任务未标记执行未FLASE的情况下,则对任务的倒计时进行减减。直到再次轮询到这个任务的执行倒计时为0的时候,将任务执行标志位置为1,并重装任务倒计时,任务标志位用于之后的任务调度函数执行各任务。

复制代码
void Scheduler_Timer_Hook(void)
{
    int i = 0;
    for(i = 0; i < MAX_TASK_NUMS; i++)
    {
        if(g_TASK_COMPONENTS[i].ucIndex != 0)
        {
            if(g_TASK_COMPONENTS[i].iTaskTimerCnt != 0)  // 检查任务执行倒计时
            {
                if(g_TASK_COMPONENTS[i].isRunFlag == FALSE)  // 检查任务执行标志位
                {
                    g_TASK_COMPONENTS[i].iTaskTimerCnt--;  // 任务执行倒计时减减
                    if(g_TASK_COMPONENTS[i].iTaskTimerCnt == 0)
                    {
                        g_TASK_COMPONENTS[i].isRunFlag = TRUE;  // 任务执行标志位置位
                        g_TASK_COMPONENTS[i].iTaskTimerCnt = g_TASK_COMPONENTS[i].iTaskTimerInval;  // 任务执行倒计时重装
                    }
                }
            }
        }
    }
}


定时器中断配置如下

每次定时器中断中调用执行调度器函数。

复制代码
void TIMx_IRQHandler(void)       
{   
    if(TIM_GetITStatus(TIMx, TIM_IT_Update) == SET)
    {
        Scheduler_Timer_Hook();  // 调度器函数
    }
    TIM_ClearITPendingBit(TIMx, TIM_IT_Update); 
}


定义完定时器中断驱动的调度之后,接着完善整个任务架构。实现任务调度函数不同的任务、任务注册、任务卸载以及调度器开关。

任务调度函数

任务调度函数中对任务列表进行轮询,查找任务标志位为1的函数并执行其任务,同时将此任务的运行标志位置为FLASE,等待下一个任务周期。

复制代码
void TaskPorcess(void)
{
    int i = 0;
    for(i = 0; i < MAX_TASK_NUMS; i++)
    {
        if(g_TASK_COMPONENTS[i].ucIndex != 0)
        {
            if(g_TASK_COMPONENTS[i].isRunFlag != FALSE)
            {
                g_TASK_COMPONENTS[i].TaskHook();
                g_TASK_COMPONENTS[i].isRunFlag = FALSE;
            }
        }
    }
}

任务注册函数

调用任务函数,实现任务到任务列表中。即配置任务的任务号、任务运行标志位、任务时间和任务执行函数。

复制代码
int Register_Task(unsigned char ucIndex, unsigned char isRunFlag, unsigned int iTaskTimerCnt,\
                  unsigned int iTaskTimerInval, void (*TaskHook)(void), void *arg)
{
    unsigned char ucIndexTemp         = ucIndex;
    unsigned char isRunFlagTemp       = isRunFlag;
    unsigned int  iTaskTimerInvalTemp = iTaskTimerInval;
    void *argTemp                     = arg;

    int iIndTemp = 0;

    if(ucIndex >= MAX_TASK_NUMS)
    {
        return -1;
    }

    // 任务号从1开始,任务列表从0开始
    iIndTemp = ucIndex -1;

    if(g_TASK_COMPONENTS[iIndTemp].ucIndex != 0)
    {
        return -2;
    }
    
    g_TASK_COMPONENTS[iIndTemp].ucIndex         = ucIndexTemp;
    g_TASK_COMPONENTS[iIndTemp].isRunFlag       = isRunFlagTemp;
    g_TASK_COMPONENTS[iIndTemp].iTaskTimerCnt   = iTaskTimerInvalTemp;
    g_TASK_COMPONENTS[iIndTemp].iTaskTimerInval = iTaskTimerInvalTemp;
    g_TASK_COMPONENTS[iIndTemp].TaskHook        = TaskHook;
    g_TASK_COMPONENTS[iIndTemp].arg             = argTemp;

    return 1;
}

任务卸载函数

调用任务卸载函数,对已实现的任务列表中的任务。清空任务的配置参数。

复制代码
int UnRegister_Task(unsigned char ucIndex)
{
    int iIndTemp = 0;

    if(ucIndex >= MAX_TASK_NUMS)
    {
        return -1;
    }

    iIndTemp = ucIndex -1;

    if(g_TASK_COMPONENTS[iIndTemp].ucIndex == 0)
    {
        return -2;
    }

    g_TASK_COMPONENTS[iIndTemp].ucIndex         = 0;
    g_TASK_COMPONENTS[iIndTemp].isRunFlag       = 0;
    g_TASK_COMPONENTS[iIndTemp].iTaskTimerCnt   = 0;
    g_TASK_COMPONENTS[iIndTemp].iTaskTimerInval = 0;

    return 1;
}


调度器开关

完成初始化调度器的最小时间和启停调度器

复制代码
void Task_Scheduler_Timer_Init(void)
{
    // 定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/f.
    TIM3_Int_Init(arr, psc);
}

int Task_Scheduler_Switch(unsigned char isOn)
{
    if(!(isOn == YES || isOn == NO))
    {
        return -1;
    }

    if(isOn == YES)
    {
        TIM_Cmd(TIM3, ENABLE);
    }
    if(isOn == NO)
    {
        TIM_Cmd(TIM3, DISABLE);
    }

    return 0;
}

至此基于定时器中断的多任务轮询架构实现完成

举个例子

通过一个例子,使用这个架构完成LED等闪烁任务

开启调度器

首先需要对选中的定时器初始化并开启调度器

复制代码
void APP_Task_Start(void)
{
    Task_Scheduler_Timer_Init();
    Task_Scheduler_Switch(YES);
}

完成任务注册、卸载和执行函数

传入任务号和任务相关配置参数完成任务注册和卸载函数实现,并在组件层完成相关任务实现细节,保持良好的分层架构设计。

复制代码
// 任务注册
void App_Led_Task_Init(void)
{  
    Register_Task(TASK_NUM_LED_FLASH, 0, 0, 1000, App_Led_Task_Fun, NULL);  // 任务执行时间间隔1000ms
}
// 任务卸载
void App_Led_Task_Exit(void)
{
    UnRegister_Task(TASK_NUM_LED_FLASH);
}
// 任务执行
void App_Led_Task_Fun(void)
{   
    LED1_Turn();
}

任务统一管理

建立任务初始化函数,便与后续扩展。

复制代码
void App_Task_Init(void)
{
    App_Led_Task_Init();
    // 添加后续任务
}

主函数

主函数进行任务从初始化,调度器启动并在主函数中执行任务调度函数。编译并烧录程序后,可以看到LED等以注册任务中1000ms参数间隔进行闪烁。

复制代码
int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    BSP_Init();
    App_Task_Init();
    APP_Task_Start();
    while(1)
    {
        TaskPorcess();
    }
}

分层架构设计

项目分层基于江科大的模板;

Architecture:调度架构

相关推荐
jllllyuz2 小时前
ESP32开发-迷你掌上平衡车miniBot完整开发指南
单片机·嵌入式硬件
不爱吃大饼2 小时前
WeMos开发板
单片机·嵌入式硬件
三佛科技-134163842122 小时前
LP3799FAC/LP3799FBC--非标60W(24V2.5A)电源芯片恒压恒流方案分析(电路图,PCB设计)
嵌入式硬件·物联网·智能家居·pcb工艺
PD我是你的真爱粉2 小时前
LangChain 与 LangGraph 完全指南:核心组件、架构原理、编排机制与 LlamaIndex 集成
算法·架构·langchain
雅斯驰2 小时前
BMS、电机控制、医疗设备:ISO1540DR的I²C隔离应用版图
单片机·嵌入式硬件
xiangw@GZ2 小时前
芯片失效分析:EM电迁移导致的BUCK输出失效
嵌入式硬件
yuan199972 小时前
温度传感器VC++串口通信程序(与51单片机通讯)
stm32·单片机·嵌入式硬件
飞翔的SA2 小时前
EmDash:WordPress 精神继承者,重构内容管理系统(CMS) 安全与现代架构
安全·重构·架构·cms
2501_948114242 小时前
Muse Spark 闭源转型背后的系统化演进:PAO 架构、KV Cache 压缩与聚合接入实践
大数据·架构·spark