基于单片机的简单的任务调度器
近來工作之余,研究了一下APM的源碼。
APM源碼連接https://pan.baidu.com/s/17Dg1oEJT_fj12DM1BmZWxA
發現源碼中有一個簡單的任務調度器,不太重要的任務都在Scheduler中完成。為什么需要Scheduler呢??
飛行器姿態信息需要角速度積分,位置信息也需要光流的積分,也就是需要獲取精確的時間間隔,對時間敏感。
Scheduler的主要作用是保證主函數運行的時間相同,什么原理呢?
假設我們主循環中有6個任務, 每個任務執行時間為大概20us - 1ms之間,而我們的控制周期要求是5ms,任務1、2、3是必要任務,每次循環都要執行的,比如飛行器的姿態解算,任務4、5是周期任務,任務4每隔4個周期執行一次,任務5每隔10個周期執行一次,任務6是非必要任務比如LED閃爍任務,一般情況下10個周期執行一次。我們可以寫成如下偽代碼,但是這樣十分繁瑣,并且修改移植起來十分麻煩,任務調度器就是代替這段代碼的,方便移植用的。
int main() {uint32_t count = 0;uint32_t time = 0;while(1){if(get_now_time_ms() - time > 5) //判斷5ms是否過去{count ++;time = get_now_time_ms(); //記錄當前時間Task1(); //任務1 1msTask2(); //任務2 1msTask3(); //任務3 1msif(get_now_time_ms() - time > 1 && count % 4 == 0){Task4(); //任務4 1ms}if(get_now_time_ms() - time > 1 && count % 10 == 0){Task5(); //任務5 1msif(get_now_time_ms() - time > 1 ){Task6(); //任務6 1ms}}}} }Scheduler也可以使代碼達到上述代碼的效果,換句話來說就是保證控制周期穩定。控制周期穩定有什么用呢? PID! 我們運用PID控制電機時,一般都會默認控制周期不變,從而將PID參數簡化為Kp、Ki、Kd,注意使用Ki、Kd的前提是控制周期不變,而控制周期不變就可以Scheduler來解決了。
下面就是一個簡單的任務調度器代碼, 注意任務調度器需要一個時計器來提供時基。
scheduler.h
#ifndef __SCHEDULER_H #define __SCHEDULER_H#include "stdint.h"typedef struct {void (*task_func)(void); //函數指針uint16_t interval_ticks; //每隔多少周期執行一次uint16_t max_time_micros; //該函數運行一次的大概時間usuint32_t last_tick_counter;//上一次函數運行時tick }sched_task_t;typedef struct {void (* init )(uint8_t num);void (* tick )(void);void (* run )(uint32_t time_available);}sched_t;extern sched_t sched;#endif //__SCHEDULER_Hscheduler.c
#include "Scheduler.h" #include "stdio.h"/* debug 調試宏定義 */ #define _MY_DEBUG_ 1#ifdef _MY_DEBUG_ #define MY_DEBUG_PRINT_INFO(...) printf(__VA_ARGS__)#define MY_DEBUG_PRINT_VAR(X, ...) printf("file: "__FILE__", Line: %d:"X"\r\n",__LINE__,##__VA_ARGS__) #else#define MY_DEBUG_PRINT_INFO(...)#define MY_DEBUG_PRINT_VAR(X, ...) #endif/* 獲取當前時間 us 注意這里需要換成自己的獲取當前時間函數 */ #define GET_NOW_TIME_US systime.get_time_us()/* 聲明時分調度器任務表 */ extern sched_task_t sched_task[];/* 任務數量 */ static uint8_t num_task; /*** @brief 時分任務調度器初始化** @param ** @return ** @note 獲取任務數量** @example ** @date 2019/6/29 星期六*/ void shceduler_init(uint8_t task_num) {num_task = task_num; }/* shceduler_run執行次數 */ static uint32_t tick_counter = 0;/*** @brief 記錄執行周期數** @param ** @return ** @note ** @example ** @date 2019/6/29 星期六*/ void shceduler_tick() {tick_counter++; }/*** @brief 調度** @param ** @return ** @note ** @example ** @date 2019/6/29 星期六*/ void shceduler_run(uint32_t time_available) {uint64_t now;uint16_t dt;uint16_t interval_ticks;for (uint8_t i = 0; i < num_task; i++){now = GET_NOW_TIME_US;//獲取當前時間dt = tick_counter - sched_task[i].last_tick_counter;//計算任務上次運行到現在相隔多少周期interval_ticks = sched_task[i].interval_ticks; //任務相隔多少周期運行一次if( dt >= interval_ticks){//調試時使用,任務長時間未運行,說明任務的運行周期太長或者shceduler_run的可運行時間太短if (dt >= interval_ticks*2){MY_DEBUG_PRINT_VAR("Scheduler slip task[%u] (%u/%u/%u)\n",(unsigned)i,(unsigned)dt,(unsigned)interval_ticks,(unsigned)sched_task[i].max_time_micros);}if (sched_task[i].max_time_micros <= time_available){//運行任務sched_task[i].task_func();//更新last_tick_countersched_task[i].last_tick_counter = tick_counter;#ifdef _MY_DEBUG_uint32_t end_time = GET_NOW_TIME_US - now; //如果任務運行過后發現時間超出shceduler_run的可運行時間time_available,說明任務的max_time_micros設置小了if(end_time > time_available){MY_DEBUG_PRINT_VAR("Scheduler slip task[%u] (%u/%u)\n",(unsigned)i,(unsigned)end_time,(unsigned)sched_task[i].max_time_micros);return;}#endif //debug打印}}//更新time_availabletime_available -= (GET_NOW_TIME_US - now);}//更新tick_countersched.tick(); // 該函數作用為tick_counter++ }sched_t sched = {shceduler_init,shceduler_tick,shceduler_run, };在使用時,調度器需要使用定時器,因此需要先行初始化定時器,確保 GET_NOW_TIME_US 可以使用,在對調度器進行初始化,確定任務數量
// 任務調度結構體,注意重要任務放在前面 sched_task_t sched_task[] = {// 函數指針 每隔多少周期執行一次 該函數運行一次的大概時間us 上一次函數運行時tick { loop_200hz , 1, 500, 0 }, { loop_100hz , 2, 4000, 0 }, { loop_50hz , 4, 100, 0 },{ loop_20hz , 10, 4000, 0 },};/* 初始化時分任務調度器 */ sched.init(sizeof (sched_task) / sizeof (sched_task[0]));最后在循環中調用調度器即可,調度器會自動對任務函數進行周期分配
/* 記錄當前時間差 */ uint32_t current_time; /* 記錄上一次時間 */ uint32_t last_time;while(1){current_time = systime.get_time_us() - last_time;if(current_time >= 5000) { last_time = systime.get_time_us();sched.run(5000); }}總結
以上是生活随笔為你收集整理的基于单片机的简单的任务调度器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 嵌入式C语言代码的调试技巧
- 下一篇: cortex M内核优先级设置