STM32 / 单片机 · 2025年5月2日

从零手搓简易自动鱼缸套件(一)- 定时任务

本系列文章主要围绕实现带有恒温、恒光照、换水、喂食及联网操控的鱼缸控制套件。
主控采用成本较低的 STM32F103 系列MCU,因此内存资源较为匮乏,无法直接搭载RTOS,但好在对于实现所述套件来讲,RTOS并不是刚需。
定时任务往往是一个必不可少的需求,例如每隔若干秒检测一次水温,每隔若干天喂食一次,因此,首先要实现一个简易的定时任务模块。中心思想是利用MCU的系统滴答时钟或定时器产生一个固定的心跳,例如使用定时器创建1ms的中断,系统以此作为计时基数。

在实现定时任务之前,首先要考虑会有怎样的实际需求。例如定时任务仅需运行一次后就删除、每隔固定时间间隔运行一次、运行N次后删除。

/**
 * @file  timed_task.h
 * @brief All the interfaces related to the timer task are in this file
 */

#ifndef __TIMED_TASK_H
#define __TIMED_TASK_H

#include "stddef.h"
#include "stdint.h"
#include "string.h"

// Maximum number of tasks. The number of tasks you create cannot exceed this value
#define TIMER_TASK_MAX_SIZE             8
// Maximum length of task name. The length of this task cannot exceed this value
#define TIMER_TASK_NAME_MAX_LENGTH      32

typedef struct timer_task_worker TimerTask_Ctx_TypeDef;
typedef void(*timer_task_handler)(void *usr_var);

enum TimerTask_Event {
    TimerTaskEvent_None = 0x0000,
    TimerTaskEvent_Reached,
    TimerTaskEvent_NotReach
};

/**
 * @defgroup 任务句柄
 */
typedef struct timer_task {
    char task_name[TIMER_TASK_NAME_MAX_LENGTH];     // 任务名称
    timer_task_handler handler;                     // 任务句柄
    void *data;                                     // 任务传参指针
} Task_Ctx_TypeDef;

/**
 * @defgroup 定时任务
 */
struct timer_task_worker {
    uint32_t tick_cnt;              // 记录时间
    uint8_t reach_flag;             // 定时到达,运行一次
    uint32_t repeat;                // 重复次数; 0, 每隔设定市场执行一次;1-65535,执行N次后删除该任务
    uint32_t tick_span;             // 任务触发的时间间隔
    uint32_t trigger_times;         // 任务已触发的次数

    Task_Ctx_TypeDef *task_ctx;     // 需要运行的任务句柄
    // 任务链为环形链,last_node 实际指向的是任务链的末尾
    struct timer_task_worker* last_node;    // 上一个任务节点
    struct timer_task_worker* next_node;    // 下一个任务节点
};

typedef struct timer_task_global {
    uint32_t tick;
    int8_t spinlock;
    TimerTask_Ctx_TypeDef task_head;
    TimerTask_Ctx_TypeDef task_arr[TIMER_TASK_MAX_SIZE];
} TimerTaskWorker_TypeDef;

/**
 * @defgroup
 */

extern TimerTaskWorker_TypeDef timer_task_global;

/**
 * @brief Run in the interrupt function. Such as TIM1 which generate interruption base on 1ms
 */
void timer_task_tick_inc();

/**
 * @brief Initialize the global context of timed-task before the mainloop
 * */
void timer_task_worker_init();

/**
 * @brief Run in the mainloop.
 */
void timer_task_main();

/**
 * @brief Delete an timed task
 */
void timer_task_del(TimerTask_Ctx_TypeDef *ctx);

/**
 * @brief Create an timed task
 * @param[in] tick_cnt How many milliseconds will this task run. unit: ms
 * @param[in] repeat How many times will his task run? 0-Infinite number of times; others: Only xx
 * */
int timer_task_add(Task_Ctx_TypeDef *ctx, uint32_t tick_cnt, uint32_t repeat);

/**
 * Convert the number of days into milliseconds
 * */
uint32_t timer_task_day_to_ms(uint32_t day);

/**
 * Convert the number of hours into milliseconds
 * */
uint32_t timer_task_hour_to_ms(uint32_t hour);

/**
 * Convert the number of minutes into milliseconds
 * */
uint32_t timer_task_minute_to_ms(uint32_t minute);

/**
 * Convert the number of seconds into milliseconds
 * */
uint32_t timer_task_second_to_ms(uint32_t second);

/**
 * @defgroup public user API
 */
void task_init(Task_Ctx_TypeDef *ctx, char *task_name, timer_task_handler func, void *data);

#ifdef __cplusplus
}
#endif

#endif

源代码实现

#include "TimedTask/timed_task.h"

TimerTaskWorker_TypeDef timer_task_worker;

/**
 * @brief 初始化定时任务上下文
 */
static void timer_task_ctx_init(TimerTask_Ctx_TypeDef* ctx)
{
    ctx->tick_cnt = 0x0000;
    ctx->reach_flag = TimerTaskEvent_NotReach;
    ctx->repeat = 0x0001;
    ctx->tick_span = 0x0000;
    ctx->trigger_times = 0x0000;
    ctx->task_ctx = NULL;
    ctx->last_node = NULL;
    ctx->next_node = NULL;
}

void timer_task_worker_init()
{
    timer_task_worker.tick = 0x00000000;
    timer_task_worker.spinlock = 0;
    for (int i = 0; i < TIMER_TASK_MAX_SIZE; i++)
    {
        timer_task_ctx_init(&timer_task_worker.task_arr[i]);
    }
    timer_task_ctx_init(&timer_task_worker.task_head);
}

void timer_task_main()
{
    TimerTask_Ctx_TypeDef *ctx;
    ctx = &timer_task_worker.task_head;

    // 从 timer_task_head 开始查找任务
    // 若当前任务链中没有包含任务,则直接退出
    if (ctx->next_node == NULL)
    {
        return;
    }

    // 判断并执行任务链中的每个任务
    for ( ;; )
    {
        // 任务链中的最后一个任务,退出
        if (ctx->next_node == NULL)
        { break; }

        ctx = ctx->next_node;
        if (ctx->reach_flag == TimerTaskEvent_NotReach)
        { continue; }

        ctx->task_ctx->handler(ctx->task_ctx->data);
        ctx->trigger_times++;
        ctx->reach_flag = TimerTaskEvent_NotReach;
        ctx->tick_cnt += ctx->tick_span;
        // 判断是无限次定时任务,还是有限次定时任务
        // 若 repeat != 0 则表示此为有限次任务,故判断是否达到运行次数
        if (ctx->repeat != 0)
        {
            // 达到运行次数,从任务链中去除当前任务
            if ((--ctx->repeat) == 0)
            {
                timer_task_del(ctx);
            }
        }
    }
}

void task_init(Task_Ctx_TypeDef* ctx, char* task_name, timer_task_handler func, void* data)
{
    uint16_t name_len = strlen(task_name);
    name_len = (name_len < TIMER_TASK_NAME_MAX_LENGTH) ? name_len : TIMER_TASK_NAME_MAX_LENGTH;
    memset(ctx->task_name, 0, TIMER_TASK_NAME_MAX_LENGTH);
    strncpy(ctx->task_name, task_name, name_len);
    ctx->handler = func;
    ctx->data = data;
}

void timer_task_tick_inc()
{
    TimerTask_Ctx_TypeDef *ctx;
    timer_task_worker.tick++;
    // 判断任务链表是否为空
    if (timer_task_worker.task_head.next_node == NULL)
    {
        return;
    }

    ctx = &timer_task_worker.task_head;
    for ( ;; )
    {
        if (ctx->next_node == NULL)
        { break; }

        ctx = ctx->next_node;

        if (ctx->tick_cnt == 0 || ctx->tick_cnt > timer_task_worker.tick)
        { continue; }

        ctx->reach_flag = TimerTaskEvent_Reached;
    }
}

int timer_task_add(Task_Ctx_TypeDef* ctx, uint32_t tick_cnt, uint32_t repeat)
{
    int idx = -1;

    // Search a free task-worker from memory
    for (int i = 0; i < TIMER_TASK_MAX_SIZE; i++)
    {
        if (timer_task_worker.task_arr[i].task_ctx == NULL)
        {
            idx = i;
            break;
        }
    }
    if (idx == -1)
    { return -1; }

    timer_task_worker.task_arr[idx].task_ctx = ctx;
    timer_task_worker.task_arr[idx].tick_cnt = timer_task_worker.tick + tick_cnt;
    timer_task_worker.task_arr[idx].tick_span = tick_cnt;
    timer_task_worker.task_arr[idx].repeat = repeat;

    // 任务链表为空
    if (timer_task_worker.task_head.next_node == NULL )
    {
        timer_task_worker.task_head.next_node = &timer_task_worker.task_arr[idx];
        timer_task_worker.task_head.last_node = &timer_task_worker.task_arr[idx];
    }
    else
    {
        timer_task_worker.task_arr[idx].last_node = timer_task_worker.task_head.last_node;
        timer_task_worker.task_head.last_node->next_node = &timer_task_worker.task_arr[idx];
        timer_task_worker.task_head.last_node = &timer_task_worker.task_arr[idx];
    }

    return 0;
}

void timer_task_del(TimerTask_Ctx_TypeDef* ctx)
{
    // 判断是否为任务链中唯一的任务
    if (timer_task_worker.task_head.next_node == ctx && timer_task_worker.task_head.last_node == ctx)
    {
        timer_task_worker.task_head.next_node = NULL;
        timer_task_worker.task_head.last_node = NULL;
        timer_task_ctx_init(ctx);
        return;
    }

    // 判断是否为第一个任务,即上一个任务节点指向 task_head
    if (ctx == timer_task_worker.task_head.next_node)
    {
        timer_task_worker.task_head.next_node = ctx->next_node;
        timer_task_ctx_init(ctx);
        return;
    }

    // 判断是否为最后一个任务
    if (ctx == timer_task_worker.task_head.last_node)
    {
        ctx->last_node->next_node = NULL;
        timer_task_worker.task_head.last_node = ctx->last_node;
        timer_task_ctx_init(ctx);
        return;
    }

    ctx->last_node->next_node = ctx->next_node;
    timer_task_ctx_init(ctx);

}

uint32_t timer_task_day_to_ms(uint32_t day)
{
    return (day * 24 * 60 * 60 * 1000);
}

uint32_t timer_task_hour_to_ms(uint32_t hour)
{
    return (hour * 60 * 60 * 1000);
}

uint32_t timer_task_minute_to_ms(uint32_t minute)
{
    return (minute * 60 * 1000);
}

uint32_t timer_task_second_to_ms(uint32_t second)
{
    return (second * 1000);
}