硬汉嵌入式论坛

 找回密码
 立即注册
查看: 2644|回复: 21
收起左侧

[技术讨论] 分享一个单片机裸机任务调度方案

  [复制链接]

5

主题

15

回帖

30

积分

新手上路

积分
30
发表于 2024-7-28 01:22:58 | 显示全部楼层 |阅读模式
本帖最后由 vuxtvg 于 2024-7-28 01:54 编辑

本文基于叶大鹏老师分享的任务调度方案,然后在工作使用的过程中发现有一些可以优化的,然后又添加了一些内容。

增加的内容
  -- 添加获取当前函数执行次数的接口 PS:例如test 100ms执行一次 可以调用接口获取运行次数 然后获取到的次数对2取余可以200ms执行一次,这样可以在单个函数中又多个时基的执行(主要是为了代码看起来比较简洁)
  -- 添加任务使能/失能
  -- 添加哈希算法节省查找任务的所需时间
以下是代码部分:


/*
* task_scheduler.h
*
*  Created on: Jul 26, 2024
*      Author: vuxtvg@163.com
*/

#ifndef TASK_SCHEDULER_H_
#define TASK_SCHEDULER_H_

#include <stdint.h>
#include <stdlib.h>

#define EnableTask(taskFunc) SetTaskEnabled((taskFunc), 1)
#define DisableTask(taskFunc) SetTaskEnabled((taskFunc), 0)

// 定义任务结构体
typedef struct
{
        void (*pTaskFuncCb)(void);  // 函数指针变量,用来保存业务功能模块函数地址
        uint16_t timCount;          // 时间片计数值
        uint16_t timRload;          // 时间片重载值
        uint32_t run :1;            // 调度标志,1:调度,0:挂起
        uint32_t enabled :1;        // 任务启用标志,1:启用,0:禁用
        uint32_t runCount :30;      // 任务运行次数计数器
} TaskComps_t;

extern TaskComps_t g_taskComps[];
extern uint8_t TASK_NUM_MAX;

// 函数声明
void TaskInit(void);
void TaskHandler(void);
void TaskScheduleCbReg(void (*pFunc)(void));
void SetTaskEnabled(void (*taskFunc)(void), uint8_t state);

uint32_t GetTaskRunCount(void (*taskFunc)(void));
void ExecuteOnMultiple(void (*taskFunc)(void), uint32_t multiple,
                void (*ActionFunc)(void));

void FreeHashTable(void);

#endif /* TASK_SCHEDULER_H_ */




/*
* task_scheduler.c
*
*  Created on: Jul 26, 2024
*      Author: vuxtvg@163.com
*/

#include "task_scheduler.h"

#define HASH_TABLE_SIZE 17

typedef struct HashNode
{
        void (*pTaskFuncCb)(void);
        TaskComps_t *taskComp;
        struct HashNode *next;
} HashNode_t;

HashNode_t *hashTable[HASH_TABLE_SIZE];

uint32_t HashFunc(void (*taskFunc)(void))
{
        return ((uintptr_t) taskFunc) % HASH_TABLE_SIZE;
}

void HashTableInsert(void (*taskFunc)(void), TaskComps_t *taskComp)
{
        uint32_t index = HashFunc(taskFunc);
        HashNode_t *newNode = (HashNode_t*) malloc(sizeof(HashNode_t));
        newNode->pTaskFuncCb = taskFunc;
        newNode->taskComp = taskComp;
        newNode->next = hashTable[index];
        hashTable[index] = newNode;
}

void InitHashTable(void)
{
        for (uint8_t i = 0; i < HASH_TABLE_SIZE; i++)
        {
                hashTable = NULL;
        }

        for (uint8_t i = 0; i < TASK_NUM_MAX; i++)
        {
                HashTableInsert(g_taskComps.pTaskFuncCb, &g_taskComps);
        }
}

TaskComps_t* HashTableSearch(void (*taskFunc)(void))
{
        uint32_t index = HashFunc(taskFunc);
        HashNode_t *node = hashTable[index];
        while (node)
        {
                if (node->pTaskFuncCb == taskFunc)
                {
                        return node->taskComp;
                }
                node = node->next;
        }
        return NULL;
}

void FreeHashTable(void)
{
        for (uint8_t i = 0; i < HASH_TABLE_SIZE; i++)
        {
                HashNode_t *node = hashTable;
                while (node)
                {
                        HashNode_t *temp = node;
                        node = node->next;
                        free(temp);
                }
                hashTable = NULL;
        }
}

void TaskHandler(void)
{
        for (uint8_t i = 0; i < TASK_NUM_MAX; i++)
        {
                if (g_taskComps.run && g_taskComps.enabled)
                {
                        g_taskComps.run = 0;
                        g_taskComps.runCount++;
                        g_taskComps.pTaskFuncCb();
                }
        }
}

void TaskScheduleCb(void)
{
        for (uint8_t i = 0; i < TASK_NUM_MAX; i++)
        {
                if (g_taskComps.timCount && g_taskComps.enabled)
                {
                        g_taskComps.timCount--;
                        if (g_taskComps.timCount == 0)
                        {
                                g_taskComps.run = 1;
                                g_taskComps.timCount = g_taskComps.timRload;
                        }
                }
        }
}

void SetTaskEnabled(void (*taskFunc)(void), uint8_t state)
{
        TaskComps_t *taskComp = HashTableSearch(taskFunc);
        if (taskComp)
        {
                taskComp->enabled = state;
        }
}

uint32_t GetTaskRunCount(void (*taskFunc)(void))
{
        TaskComps_t *taskComp = HashTableSearch(taskFunc);
        if (taskComp)
        {
                return taskComp->runCount;
        }
        return 0;  // 如果没有找到匹配的任务函数,返回0
}

void ExecuteOnMultiple(void (*taskFunc)(void), uint32_t multiple,
                void (*ActionFunc)(void))
{
        uint32_t runCount = GetTaskRunCount(taskFunc);
        if (runCount % multiple == 0)
        {
                ActionFunc();
        }
}

void TaskInit(void)
{
        TaskScheduleCbReg(TaskScheduleCb);
        InitHashTable();
}




/*
* task.c
*  Created on: Jul 26, 2024
*  Author: vuxtvg@163.com
*/

#include "main.h"
#include "task_scheduler.h"

typedef struct
{
        uint16_t p1cnt;
        uint16_t p2cnt;
        uint16_t p3cnt;
} CNT;

CNT cnt;

void uart_printf(void);
void led_toggle(void);

void PrintData1(void)
{
        cnt.p1cnt++;
        printf("cnt.p1cnt:%d\r\n", cnt.p1cnt);
}

void PrintData2(void)
{
        cnt.p2cnt++;
        printf("cnt.p2cnt:%d\r\n", cnt.p2cnt);
}

void PrintData3(void)
{
        cnt.p3cnt++;
        printf("cnt.p3cnt:%d\r\n", cnt.p3cnt);
}

void led_toggle(void)
{
        EnableTask(uart_printf);
        DisableTask(led_toggle);

        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}

uint16_t cc;
void uartf(void)
{
        cc++;
}

void uartf1(void)
{
        cc++;
}
void uartf2(void)
{
        cc++;
}
void uartf3(void)
{
        cc++;
}

void uartf4(void)
{
        cc++;
}
void uartf5(void)
{
        cc++;
}
void uart_printf(void)
{
        EnableTask(led_toggle);
        DisableTask(uart_printf);

        printf("uartTaskRunCount:%ld ledTaskRunCount:%ld \r\n",
                        GetTaskRunCount(uart_printf), GetTaskRunCount(led_toggle));

        ExecuteOnMultiple(uart_printf, 1, PrintData1);
        ExecuteOnMultiple(uart_printf, 2, PrintData2);
        ExecuteOnMultiple(uart_printf, 3, PrintData3);
}

TaskComps_t g_taskComps[] =
        {
                { led_toggle, 500, 500, 0, 1 },
                { uartf, 1000, 1000, 0, 1 },
                { uartf1, 1000, 1000, 0, 1 },
                { uartf2, 1000, 1000, 0, 1 },
                { uartf3, 1000, 1000, 0, 1 },
                { uartf4, 1000, 1000, 0, 1 },
                { uartf5, 1000, 1000, 0, 1 },
                { uart_printf, 500, 500, 0, 0 },
        // 添加其他任务
                };

uint8_t TASK_NUM_MAX = (sizeof(g_taskComps) / sizeof(g_taskComps[0]));





#include "task_scheduler.h"

int main(void)
{
        TaskInit();

    while (1)
    {
                TaskHandler();
    }
}



static void (*g_pTaskScheduleFunc)(void);  // 函数指针变量,保存任务调度的函数地址

void TaskScheduleCbReg(void (*pFunc)(void))
{
    g_pTaskScheduleFunc = pFunc;
}

void SysTick_Handler(void)
{
    if (g_pTaskScheduleFunc) g_pTaskScheduleFunc();
}


这个是本人优化后的代码,测试使用OK,如果缺点,还望指正

应该是可以支持所有的单片机的,硬件仅需用一个定时器即可,方便移植,task_scheduler.c/.h 不需要修改,task.c是使用示例,main.c是接口调用
Ps:如果您用到项目中,还望可以署名我(本人邮箱:vuxtvg@163.com)是这个模块的一个小小的作者,也希望您能在这个帖子下面发表一下评论,让我开心一下(*^▽^*)
如有转载,注明出处(本人邮箱:vuxtvg@163.com)
本文结束啦,谢谢大家,感谢平台



回复

使用道具 举报

33

主题

188

回帖

287

积分

高级会员

积分
287
发表于 2024-7-28 08:36:54 | 显示全部楼层
回复

使用道具 举报

5

主题

80

回帖

95

积分

初级会员

积分
95
发表于 2024-7-29 15:09:44 | 显示全部楼层
感谢大佬分享
回复

使用道具 举报

5

主题

80

回帖

95

积分

初级会员

积分
95
发表于 2024-7-29 15:11:02 | 显示全部楼层
要是多加一点注释就更好了
回复

使用道具 举报

4

主题

178

回帖

190

积分

初级会员

积分
190
发表于 2024-7-29 20:03:43 来自手机 | 显示全部楼层
代码中没看到,哈希表插入冲突解决办法呢
回复

使用道具 举报

5

主题

15

回帖

30

积分

新手上路

积分
30
 楼主| 发表于 2024-7-30 00:14:29 | 显示全部楼层
浴火重生 发表于 2024-7-29 20:03
代码中没看到,哈希表插入冲突解决办法呢

你提到的哈希表插入冲突解决办法其实已经在代码中实现了。具体来说,这个哈希表使用链地址法来解决冲突。主要是在插入的地方
回复

使用道具 举报

5

主题

15

回帖

30

积分

新手上路

积分
30
 楼主| 发表于 2024-7-30 00:16:49 | 显示全部楼层
lizhaoming 发表于 2024-7-29 15:11
要是多加一点注释就更好了

哈希表哪里有点难理解,其他应该还好,我下次注意
回复

使用道具 举报

7

主题

145

回帖

166

积分

初级会员

积分
166
发表于 2024-7-30 10:23:08 | 显示全部楼层
把这个再优化下,基于时间和事件,时间没到就基于事件调度。再加强下添加FIFO事件信号调度锁,
回复

使用道具 举报

4

主题

178

回帖

190

积分

初级会员

积分
190
发表于 2024-7-30 14:19:28 | 显示全部楼层
vuxtvg 发表于 2024-7-30 00:14
你提到的哈希表插入冲突解决办法其实已经在代码中实现了。具体来说,这个哈希表使用链地址法来解决冲突。 ...

是的看到了,但是这种封闭寻址法解决冲突有个弊端,因为函数指针都是4字字节或者8字节对齐的,那么取余中间一定会留下3个或者7个空,而且永远都填充不到,那么多了之后会在某些位置链接堆积中间又用不到
回复

使用道具 举报

5

主题

15

回帖

30

积分

新手上路

积分
30
 楼主| 发表于 2024-7-30 16:55:25 | 显示全部楼层
浴火重生 发表于 2024-7-30 14:19
是的看到了,但是这种封闭寻址法解决冲突有个弊端,因为函数指针都是4字字节或者8字节对齐的,那么取余中 ...

明白了,之前意识到这个问题了,不过当时用的stm32cubeide调试打印看了一下,当时看着没多少问题(不清楚原因),现在用keil就比较明显了。这个array有四分之三没有用到,我解决一下
回复

使用道具 举报

5

主题

15

回帖

30

积分

新手上路

积分
30
 楼主| 发表于 2024-7-30 16:57:39 | 显示全部楼层
mygod 发表于 2024-7-30 10:23
把这个再优化下,基于时间和事件,时间没到就基于事件调度。再加强下添加FIFO事件信号调度锁,

好的,谢谢指点,还有一点不明,想请教您一下,我现在用的是时间调度,在程序中好像用不到事件调度,我刚工作没多久,一直使用的是时间调度,请问能不能指点一下事件调度的应用场景
回复

使用道具 举报

5

主题

15

回帖

30

积分

新手上路

积分
30
 楼主| 发表于 2024-7-30 17:17:48 | 显示全部楼层
浴火重生 发表于 2024-7-30 14:19
是的看到了,但是这种封闭寻址法解决冲突有个弊端,因为函数指针都是4字字节或者8字节对齐的,那么取余中 ...

uint32_t HashFunc(void (*taskFunc)(void))
{
    uintptr_t addr = (uintptr_t)taskFunc;
    addr = (~addr) + (addr << 21); // mix the bits
    addr = addr ^ (addr >> 24);
    addr = (addr + (addr << 3)) + (addr << 8); //  addr * 265
    addr = addr ^ (addr >> 14);
    addr = (addr + (addr << 2)) + (addr << 4); //  addr * 21
    addr = addr ^ (addr >> 28);
    addr = addr + (addr << 31);
    return addr % HASH_TABLE_SIZE;
}
做了一些运算
回复

使用道具 举报

0

主题

1

回帖

1

积分

新手上路

积分
1
发表于 2025-1-7 09:07:00 | 显示全部楼层
想问一下 TaskScheduleCb 这个函数怎么被使用的,感觉它应该在中断函数中被调用
回复

使用道具 举报

0

主题

1

回帖

1

积分

新手上路

积分
1
发表于 2025-1-7 14:29:45 | 显示全部楼层
mark,mark
回复

使用道具 举报

5

主题

15

回帖

30

积分

新手上路

积分
30
 楼主| 发表于 2025-2-9 15:51:26 | 显示全部楼层
QCoCo 发表于 2025-1-7 09:07
想问一下 TaskScheduleCb 这个函数怎么被使用的,感觉它应该在中断函数中被调用

1ms的定时器调用
回复

使用道具 举报

1

主题

7

回帖

10

积分

新手上路

积分
10
发表于 2025-2-13 17:09:01 | 显示全部楼层
博主我也在写类似的调度器,我发现有个问题,就是任务的运行周期成倍数的时候,会在同一个tick同时达到就绪态,也就是调度器调度时,会有好多个任务撞到了一块,如果其中一个执行时间长,那其他任务的周期性没办法保证。所以我想的是在创建任务的时候,给任务计时器的初始值加一个偏移,这样任务之间错峰调度,cpu负载也更加均衡,但是这个偏移的算法一直没写出来,就是一个调度不均衡的问题。如下图,就是同一个时间片有多个任务同时到达就绪态的情形。
回复

使用道具 举报

5

主题

157

回帖

172

积分

初级会员

积分
172
发表于 2025-2-15 09:35:51 | 显示全部楼层
rongtopman 发表于 2025-2-13 17:09
博主我也在写类似的调度器,我发现有个问题,就是任务的运行周期成倍数的时候,会在同一个tick同时达到就绪 ...

给每个task添加优先级,有相同任务撞到一起的时候,优先执行优先级高的
回复

使用道具 举报

1

主题

7

回帖

10

积分

新手上路

积分
10
发表于 2025-2-15 11:36:30 | 显示全部楼层
2859932063 发表于 2025-2-15 09:35
给每个task添加优先级,有相同任务撞到一起的时候,优先执行优先级高的

噢噢,但是我的问题不是谁先执行的问题,您可以看图,图中明显CPU在某些时间片内非常闲,在某些时间片内非常忙,如果可以均衡开,会不会更好一些,谢谢
回复

使用道具 举报

5

主题

157

回帖

172

积分

初级会员

积分
172
发表于 2025-2-15 20:13:37 | 显示全部楼层
rongtopman 发表于 2025-2-15 11:36
噢噢,但是我的问题不是谁先执行的问题,您可以看图,图中明显CPU在某些时间片内非常闲,在某些时间片内 ...

那说明你的任务安排不对
回复

使用道具 举报

95

主题

528

回帖

828

积分

金牌会员

积分
828
发表于 2025-2-17 13:50:11 | 显示全部楼层
rongtopman 发表于 2025-2-15 11:36
噢噢,但是我的问题不是谁先执行的问题,您可以看图,图中明显CPU在某些时间片内非常闲,在某些时间片内 ...

时间片执行和安富莱的软件定时器有什么区别吗?
可以直接用安富莱提供的软件定时器的api能够实现你的需求吗?
共产主义一定胜利!
回复

使用道具 举报

1

主题

7

回帖

10

积分

新手上路

积分
10
发表于 2025-2-17 15:43:00 | 显示全部楼层
会飞的猪_2020 发表于 2025-2-17 13:50
时间片执行和安富莱的软件定时器有什么区别吗?
可以直接用安富莱提供的软件定时器的api能够实现你的需 ...

没有区别,就是最近在写类似于软件定时器的这个东西,发现了这个问题
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

积分
2
发表于 2025-4-14 10:09:12 | 显示全部楼层
rongtopman 发表于 2025-2-13 17:09
博主我也在写类似的调度器,我发现有个问题,就是任务的运行周期成倍数的时候,会在同一个tick同时达到就绪 ...

你的思路没问题,通过添加偏移值让任务执行时刻错开来,可以有效优化这个问题,具体实现你可以在初始化创建任务时,让任务的调度时刻=当前时刻+任务执行间隔时间+偏移值,后面任务调度重新计算调度时刻就只需要:任务的调度时刻=当前时刻+任务执行间隔时间,并且你任务调度时刻的重新计算可以放在任务执行前面,这样任务调度的间隔时间就不受任务执行时间长短的影响
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2025-4-26 02:06 , Processed in 0.472573 second(s), 24 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表