前言
本系列学习笔记是本人跟随米醋电子工作室学习嵌入式的学习笔记,自用为主,不是教学或经验分享,若有误,大佬轻喷,同时欢迎交流学习,侵权即删。
一、概述
什么是任务调度器?
嵌入式系统中的"课程时间表",按预定时间间隔安排不同任务执行
二、基本概念
任务调度器的基本功能
定时执行任务:按精确时间间隔运行特定函数
周期性操作:实现循环执行功能
非阻塞式设计:避免延时函数造成系统阻塞
模块化结构:将不同功能分割为不同模块,提高代码移植性与可维护性
三、C语言结构体
本部分详见
四、调度器实现
调度器的实现主要包含三个部分:任务数组、初始化函数和运行函数。
任务数组
cpp
/ 全局变量,用于存储任务数量
uint8_t task_num;
// 静态任务数组,每个任务包含任务函数、执行周期(毫秒)和上次运行时间(毫秒)
static scheduler_task_t scheduler_task[] =
{
{Led_Proc, 1, 0}, // LED控制任务:周期1ms
{Key_Proc, 10, 0}, // 按键扫描任务:周期10ms
{Sensor_Proc, 100, 0}, // 传感器读取任务:周期100ms
{Comm_Proc, 50, 0} // 通信处理任务:周期50ms
};
初始化函数
cpp
**
* @brief 调度器初始化函数
* 计算任务数组的元素个数,并将结果存储在 task_num 中
*/
void scheduler_init(void)
{
// 计算任务数组的元素个数,并将结果存储在 task_num 中
task_num = sizeof(scheduler_task) / sizeof(scheduler_task_t);
}
运行函数
cpp
/**
* @brief 调度器运行函数
* 遍历任务数组,检查是否有任务需要执行。如果当前时间已经超过任务的执行周期,则执行该任务并更新上次运行时间
*/
void scheduler_run(void)
{
// 遍历任务数组中的所有任务
for (uint8_t i = 0; i < task_num; i++)
{
// 获取当前的系统时间(毫秒)
uint32_t now_time = HAL_GetTick();
// 检查当前时间是否达到任务的执行时间
if (now_time >= scheduler_task[i].rate_ms + scheduler_task[i].last_run)
{
// 更新任务的上次运行时间为当前时间
scheduler_task[i].last_run = now_time;
// 执行任务函数
scheduler_task[i].task_func();
}
}
}
实际运行流程图
主循环(无限循环)
↓
调用 scheduler_run() // 检查所有任务
↓
检查任务1:LED任务到时间了吗?
↓
检查任务2:按键任务到时间了吗?
↓
检查任务3:传感器任务到时间了吗?
↓
检查任务4:通信任务到时间了吗?
↓
回到主循环开始
五、完整示例(配详细注释)
5.1 头文件 scheduler.h
cpp
#ifndef __SCHEDULER_H
#define __SCHEDULER_H
#include "stm32f1xx_hal.h" // HAL库头文件
// 任务结构体定义
typedef struct {
void (*task_func)(void); // 函数指针:指向任务函数
uint32_t rate_ms; // 执行周期(毫秒)
uint32_t last_run; // 上次执行时间(毫秒)
} scheduler_task_t;
// 函数声明
void scheduler_init(void); // 调度器初始化
void scheduler_run(void); // 调度器运行
#endif
5.2 源文件 scheduler.c
cpp
#include "scheduler.h"
// 全局变量:任务数量
uint8_t task_num;
// 任务数组:所有要调度的任务都在这里
static scheduler_task_t scheduler_task[] =
{
// 格式说明:
// {任务函数名, 执行周期(ms), 上次执行时间(初始0)}
// LED任务:1ms执行一次(快速闪烁)
{Led_Proc, 1, 0},
// 按键任务:10ms执行一次(防抖检测)
{Key_Proc, 10, 0},
// 传感器任务:100ms执行一次(不需要太快)
{Sensor_Proc, 100, 0},
// 通信任务:50ms执行一次(发送数据)
{Comm_Proc, 50, 0}
};
// 调度器初始化
void scheduler_init(void)
{
// 计算任务数量
// sizeof(数组):整个数组占多少字节
// sizeof(结构体):一个任务结构体占多少字节
// 相除得到:数组中有几个元素
task_num = sizeof(scheduler_task) / sizeof(scheduler_task_t);
// 举例:假设每个任务结构体占12字节,4个任务就是48字节
// 48 ÷ 12 = 4个任务
}
// 调度器运行函数(在main循环中不断调用)
void scheduler_run(void)
{
uint32_t now_time; // 当前时间
uint8_t i; // 循环计数器
// 遍历所有任务(4个任务都要检查)
for (i = 0; i < task_num; i++)
{
// 获取当前系统时间(单位:毫秒)
now_time = HAL_GetTick();
// 判断任务是否需要执行
// 条件:当前时间 ≥ 上次执行时间 + 执行周期
if (now_time >= scheduler_task[i].last_run + scheduler_task[i].rate_ms)
{
// 更新"上次执行时间"为现在
// 这样下次就会在 now_time + rate_ms 时再执行
scheduler_task[i].last_run = now_time;
// 执行任务函数
// 这会调用相应的函数,比如 Led_Proc()
scheduler_task[i].task_func();
}
}
}
5.3 任务函数示例
cpp
// LED任务函数:1ms执行一次
void Led_Proc(void)
{
static uint32_t counter = 0;
counter++;
// 每500ms翻转一次LED(500次 × 1ms = 500ms)
if (counter >= 500)
{
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
counter = 0;
}
}
// 按键任务函数:10ms执行一次
void Key_Proc(void)
{
static uint8_t key_state = 0;
// 检测按键是否按下
if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_RESET)
{
key_state++;
if (key_state >= 5) // 连续检测到5次(50ms)才算有效
{
// 执行按键动作
key_state = 0;
}
}
else
{
key_state = 0;
}
}
// 传感器任务函数:100ms执行一次
void Sensor_Proc(void)
{
// 读取温度传感器
float temperature = read_temperature();
// 可以在这里进行数据处理
// ...
}
// 通信任务函数:50ms执行一次
void Comm_Proc(void)
{
// 发送数据到上位机
send_data_to_pc();
}
5.4 主函数 main.c
cpp
#include "main.h"
#include "scheduler.h"
int main(void)
{
// HAL库初始化
HAL_Init();
// 系统时钟配置
SystemClock_Config();
// 外设初始化(GPIO、串口等)
MX_GPIO_Init();
MX_USART1_UART_Init();
// 调度器初始化
scheduler_init();
// 无限循环(嵌入式程序特点)
while (1)
{
// 运行调度器:检查并执行任务
scheduler_run();
// 注意:这里没有delay!
// CPU会全速运行,不断检查任务
// 如果CPU太快,可以加短暂延时避免过度占用
// HAL_Delay(1); // 可选:延时1ms
}
}