基于32位单片机的裸机开发:使用定时器进行任务调度 V2.0

  在嵌入式系统开发中,合理地管理和调度任务对于提高系统的实时性、可靠性和可维护性非常重要。本文将详细介绍一个基于定时器的任务管理系统的设计与实现,该系统无需使用系统滴答定时器(SysTick)来增加堆栈深度,而是采用基本的定时器来实现任务的分时执行。

V2.0对比V1.0优点

V2.0相比V1.0有几个显著的优点,这些改进使得任务管理系统更加健壮、灵活且易于维护。以下是V2.0的主要优点:

1. 增强的任务状态管理

  • 挂起状态的支持:V2.0增加了挂起状态 (TASK_STATE_SUSPEND),使得任务不仅可以在可执行 (TASK_STATE_RUN) 和等待 (TASK_STATE_STOP) 状态之间切换,还能被临时挂起。这使得系统在处理多任务时更加灵活,可以动态地暂停不需要立即执行的任务。
  • 2. 回调机制的引入

  • 挂起回调函数:当任务被挂起时,可以通过 callback_suspend 回调函数执行一些额外的逻辑,例如保存任务状态或者释放资源等。这提高了系统的可扩展性,允许在不同状态下执行特定的操作。
  • 3. 任务控制API的增加

  • 挂起和恢复任务的功能:提供了 Task_suspend 和 Task_resume 函数,允许应用程序根据需要动态地暂停和恢复任务。这使得在系统需要调整任务执行顺序或者暂时禁用某个任务时变得更加容易。
  • 4. 任务信息查询功能

  • 任务索引和状态查询:通过 Task_get_task_id 和 Task_get_task_state 函数,开发者可以轻松地查询任务的索引号和当前状态。这不仅便于调试,还提高了系统的透明度,使得开发者更容易监控任务的执行情况。
  • 5. 更高的灵活性和可扩展性

  • 动态管理任务:由于增加了挂起状态和支持回调函数,V2.0版本的任务管理系统能够更灵活地应对不同的应用场景。这种灵活性意味着系统可以更好地适应未来的变更和扩展需求。
  • 6. 增强的系统维护性

  • 易于维护和调试:通过提供更多的API来控制和查询任务状态,V2.0版本的任务管理系统使得维护和调试变得更加直观和简便。这减少了因系统复杂性带来的维护难度。
  • 二设计

    1. 任务管理器设计

    为了更好地管理多个任务,我们定义了一个任务结构体`Task_t`,以及一个任务列表`task_list`,用于存储所有需要执行的任务的信息。(task.c)

    // 使用结构体数组,封装所有的任务
    typedef struct Task{
    	
    	uint8_t 		state;		// 任务状态,1可执行,0等待中
    	uint16_t 		count;		// 任务计数,倒计数到0,修改任务状态为可执行
    	uint16_t   period;    // 任务周期,即多少ms执行一次任务(固定)
    	
    //	void (* callback)(void); // 任务执行回调
    	Task_callback		callback;	// 任务执行回调
    	Task_callback		callback_suspend;	// 任务执行回调
    	
    } Task_t;
    // 把所有要执行的任务封装为结构体的列表
    
    Task_t task_list[] = {
    	// state,		     count,  	period, 	 callback
    	{TASK_STATE_STOP,  0, 			10,  		App_Input_task},
    	{TASK_STATE_STOP,  0, 			50, 		App_Balance_task},
    	{TASK_STATE_STOP,  0, 			2000,		App_Protocol_task, NULL},
    	{TASK_STATE_STOP,  0, 			100, 		App_OLED_task, App_OLED_suspend},
    };

     2. Timer 任务执行状态的判定和切换

    定时器中断服务程序中,我们将不断地减少每个任务的计数器值。当计数器值减至0时,将任务状态设置为可执行,并重置计数器。(task.c)

    // 任务执行状态的判定和切换(在中断里1ms执行一轮)
    void Task_switch_handler(void){
    	// 1.不断给所有任务的count--
    	// 2.判断count值,如果减到0,设置可执行标记
    	// 3.把count重置为period
    	for(uint8_t i = 0; i < task_cnt; i++){
    		
    		// 如果任务是挂起状态, 跳过本次循环
    		if(task_list[i].state == TASK_STATE_SUSPEND){
    			continue;
    		}
    		
    		if(task_list[i].count > 0) task_list[i].count--;
    		
    		// 倒计数结束
    		if(task_list[i].count == 0){
    			// 任务状态切换
    			task_list[i].state = TASK_STATE_RUN;
    			// 重置count计数值
    			task_list[i].count = task_list[i].period;
    		}
    	}
    }

              任务的切换是在timer中进行的(timer.c)

    void TIMER6_IRQHandler(void){
      
      if(SET == timer_interrupt_flag_get(TIMER, TIMER_INT_FLAG_UP)){
        // 清除中断标记
        timer_interrupt_flag_clear(TIMER, TIMER_INT_FLAG_UP);
        
    	
    //		if(period_1ms_num > 0) period_1ms_num--;
    //		if(period_10ms_num > 0) period_10ms_num--;
    //		if(period_100ms_num > 0) period_100ms_num--;
    //		if(period_200ms_num > 0) period_200ms_num--;
    //		if(period_500ms_num > 0) period_500ms_num--;
    //		if(period_1000ms_num > 0) period_1000ms_num--;
    		task_tick++;
    		
    		Task_switch_handler();
      }
    }
    

    3. 在主循环中执行任务

    在主循环中,检查每个任务的状态,如果是可执行状态,则执行任务,并将其状态重置为等待状态。(main.c)

    // 任务执行句柄(在main函数循环执行execute所有任务)
    void Task_exec_handler(void){
    	// 将状态为 TASK_STATE_RUN 执行一次,将标记恢复成STOP
    	for(uint8_t i = 0; i < task_cnt; i++){
    		if(task_list[i].state  == TASK_STATE_RUN){
    			// 执行任务
    			task_list[i].callback();
    			// 任务状态切换
    			task_list[i].state = TASK_STATE_STOP;
    		}
    	}
    }

    4. 暂停与恢复任务

    允许用户暂停或恢复特定的任务。当任务被暂停时,可以执行特定的回调函数;当任务恢复时,它的计数器将被重置,状态将被恢复。(task.c)

    // 根据任务索引位置,挂起指定任务(暂停)
    void Task_suspend(uint8_t index){
    	if(index >= task_cnt) return;
    	
    	// 设置为挂起状态
    	task_list[index].state = TASK_STATE_SUSPEND;
    	
    	// 执行挂起回调函数(前提是此函数不是NULL)
    	if(task_list[index].callback_suspend != NULL){
    		task_list[index].callback_suspend();
    	}
    }
    
    // 根据任务索引位置,恢复指定任务
    void Task_resume(uint8_t index){
    	if(index >= task_cnt) return;
    
    	// 重置count计数值
    	task_list[index].count = task_list[index].period;
    	// 恢复状态
    	task_list[index].state = TASK_STATE_STOP;
    }

     5. 获取任务信息

    提供函数来获取任务的状态或索引,以便在应用程序中根据需要控制任务的行为。(task.c)

    // 根据任务函数名,查找对应位置
    int Task_get_task_id(Task_callback func_name){
    	
    	for(uint8_t i = 0; i < task_cnt; i++){
    		if(task_list[i].callback == func_name){
    			return i;
    		}
    	}
    	
    	return -1;
    }
    
    // 根据任务索引位置,获取任务状态
    uint8_t Task_get_task_state(uint8_t index){
    	if(index >= task_cnt) return TASK_STATE_STOP;
    	
    	return task_list[index].state;
    }

     示例应用

    可以根据任务的状态 (获取任务索引) 执行任务具体的逻辑

    下面是一个简单的按键示例,展示了如何根据按键输入来控制任务的暂停与恢复:

    void Keys_on_keydown(uint8_t index){
    	float cur_angle = Servo_get_angle();
    	
    	switch(index){
    		case 0:  
    			cur_angle -= 10;
    			break;
    		case 1: 
    			cur_angle += 10;
    			break;
    		case 2: 
    			printf("暂停任务\n");
    //			int index = Task_get_task_id(App_Balance_task);
    //			Task_suspend(index);
    			Task_suspend(3);
    			break;
    		case 3: 				
    			printf("恢复任务\n");
    //			Task_resume(Task_get_task_id(App_Balance_task));
    			Task_resume(3);
    			break;
    		default: break;
    	}

    通过上述方法,我们构建了一个简单但功能齐全的任务管理系统,它可以在没有操作系统支持的情况下有效地管理多个任务的执行。这对于资源受限的嵌入式系统来说尤其有用。

    总结

    V2.0版本的任务管理系统通过增加任务状态、回调机制、控制API以及信息查询功能,使得系统不仅更加灵活,而且更加易于扩展和维护。这些改进使得V2.0版本成为了一个更为成熟和强大的任务管理解决方案。

    希望这篇文章能帮助你在嵌入式开发中更好地理解和实现任务调度机制。

    作者:凉风yx

    物联沃分享整理
    物联沃-IOTWORD物联网 » 基于32位单片机的裸机开发:使用定时器进行任务调度 V2.0

    发表回复