@[TOC]适合裸机编程的极简任务调度器

一、Tsk任务调度器的特点

  • 代码体积:核心代码不到 200 行 C 语言;
  • 内存占用:每个任务控制块占用内存极小,占用的大小与定义的任务数量有关;
  • 调度机制:采用非抢占式时间片轮询(Round-Robin),避免复杂的上下文切换开销,任务切换仅需修改当前任务指针;

二、裸机开发的模块化之痛

  在嵌入式系统开发中,裸机环境下的程序设计往往面临着独特的挑战。传统方案依赖状态机实现功能集成,随着业务复杂度提升,代码会逐渐演变为交织的 “面条式逻辑”:事件处理与业务逻辑混杂、模块间耦合紧密、调试定位困难,移植时更需推倒重来。尽管 FreeRTOS 等实时操作系统提供了完善的任务管理方案,但其复杂的内核机制(如抢占式调度、任务同步组件)在资源受限的微控制器(如 8 位 / 16 位 MCU 或低功耗 32 位芯片)上显得臃肿,且对于大多数实时性要求不高的中小项目而言,引入完整 RTOS 可能带来不必要的资源消耗。

  如何在 “轻量资源占用” 与 “模块化管理” 之间找到平衡?Tsk 轻量级任务调度器应运而生 —— 它专为裸机环境设计,以极小的内存开销实现任务级模块化管理,让开发者既能享受多任务编程的清晰架构,又无需背负完整 RTOS 的复杂度。

三、Tsk引入模块化架构

  传统状态机将功能拆解为枚举状态与条件判断,随着状态数增加,代码可读性呈指数级下降。大多数裸机应用对实时响应要求不是太严格,满足响应需求即可;而 对于高实时性(响应在us级别),可通过硬件中断直接处理。Tsk 引入任务(Task)概念,每个任务是独立的功能模块,降低系统设计的复杂度,实现功能的高内聚低耦合,方便开发维护以及功能移植。

// 任务函数模板:需包含非阻塞式逻辑
void Tsk1LedRun(void)
{
    Led0Toggle();
    Led1Toggle();
}

//  main函数实现 任务的注册,并在循环里实现调度
int main() {

  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  
  /* Initialize all configured peripherals */
  MX_GPIO_Init();

  TskCreate(500, Tsk1LedRun); // 创建任务1, 500 毫秒翻转一次指示灯
  
  while (1)
  {
 
     TskLooperHandle();  // 调度器实现任务的调度
     
  }
  
    return 0;
}

这种设计将复杂的状态转移转化为线性的任务函数,配合全局任务列表管理,使模块间依赖清晰可控,代码维护成本显著降低。

四、Tsk代码介绍

tsk.h 头文件

#ifndef _TSK_H_
#define _TSK_H_ 

// 设置开销的任务数
#define TSK_FUNC_NUM          ( 5 )

// 定义函数指针类型
typedef void (*TSK_FUNC_CALLBACK)(void);


void TimerSoftTick(void);
unsigned long long TimerGetTimeStampMs(void);
unsigned int TimerGetTimeStampSec(void);

unsigned char TskCreate(unsigned int cycleTime, TSK_FUNC_CALLBACK callBack);
void TskDelete(TSK_FUNC_CALLBACK callBack);
void TskLooperHandle(void);

Tsk 使用说明

 1. 根据时间需求定义 任务数量 TSK_FUNC_NUM  ,数量越多影响内存的占用;    
 2. TimerSoftTick() 在毫秒定时中断里进行调用;
 3. TskCreate() 注册任务调用的周期和回调函数;
 4. 若有必要,使用 TskDelete() 进行指定任务回调函数销毁;
 5. TskLooperHandle() 在循环里进行调用;

Tsk 应用示例

/* USER CODE BEGIN 0 */

void Tsk1LedRun(void)
{
    Led0Toggle();
    Led1Toggle();
}

void Tsk2AdcSample(void)
{
    
   // 任务2 数据采集

}

void Tsk3Communicat(void)
{
    // 通信处理
   
}

void Tsk4LogicHandle(void)
{
    // 逻辑处理
   
}

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  TskCreate(500, Tsk1LedRun); // 创建任务1, 500 毫秒翻转一次指示灯
  TskCreate(10, Tsk2AdcSample); // 创建任务2, 10 毫秒采集一次数据
  TskCreate(10, Tsk3Communicat); // 创建任务3, 10 毫秒处理一次通信数据
  TskCreate(20, Tsk4LogicHandle); // 创建任务4, 20 毫秒处理一次逻辑数据

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

     TskLooperHandle();

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

五、与 FreeRTOS 的定位对比

在这里插入图片描述
Tsk 并非 RTOS 的替代品,而是针对 “不需要抢占式调度与复杂同步” 场景的最优解。对于需要多任务协作、临界资源保护的项目,仍需引入完整 RTOS;但在中小规模裸机系统中,Tsk 提供了更轻量的模块化方案。

六、重新定义裸机开发范式

  Tsk 轻量级任务调度器的出现,为嵌入式开发者提供了介于 “纯裸机状态机” 与 “完整 RTOS” 之间的中间方案:

  • 对开发者:用简单的 API 实现功能模块化,降低状态机设计复杂度,提升代码可维护性;
  • 对系统:极小的资源占用适配全系列 MCU,中断机制保障实时性,为产品小型化、低功耗设计创造条件;
  • 对项目:标准化的任务接口加速功能移植,支持快速迭代与团队协作;

  在物联网终端、消费电子等领域,80% 的应用其实并不需要复杂的实时调度,Tsk 正是为这部分场景量身定制的解决方案。

Tsk代码地址

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐