找回密码
 立即注册
查看: 553|回复: 21

发现--【协程方式】实现【多任务调度 / 多线程】,好巧妙的方法!

[复制链接]
  • 打卡等级:常住居民III
  • 打卡总天数:143
  • 最近打卡:2026-03-08 08:36:28
已绑定手机

22

主题

2365

回帖

3529

积分

论坛元老

积分
3529
发表于 2025-12-27 19:29:22 | 显示全部楼层 |阅读模式
  https://www.stcaimcu.com/thread-16598-1-1.html

  协程方式实现多线程-仅需2byte的RAM,灵活多变,移植快捷
本方式区别于实时操作系统没有任何函数重入问题,本质还是代码
的顺序执行。

   关键是:通过软定时器实现了非堵塞的延时函数
并且实现了task_waittask_fortask_while 来实现代码状态的暂停
可控次数重复循环,实际使用相当灵活内部的实际占用为固定2Byte
RAM+每个任务6Byte RAM 体验接近于普通C语言代码编写。

感觉虽然不是操作系统,但常见的难题都可以解决,好聪明的做法!





回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:143
  • 最近打卡:2026-03-08 08:36:28
已绑定手机

22

主题

2365

回帖

3529

积分

论坛元老

积分
3529
发表于 2025-12-27 19:34:59 | 显示全部楼层
假如有这样的要求:
任务1:LED00每隔100ms闪烁一次,按下P32按键时,暂停LED00的闪烁,循环执行
任务2:LED01亮200ms,灭500ms,循环执行
任务3:LED02先灭1000ms,然后{快速闪烁(50ms)3次,常亮300ms}<-如此循环5次,循环执行

搞不掂了吧?

人家协程:


while(1)
        {
                task_start(0);//协程0开始
                P00 = ~P00;//每次执行取反P00端口电平
                task_delay(100);//设定延时100ms
                task_wait(!P32);//P32电平作为判断条件,为1则等待,为0则继续向下执行
                //因为按键按下为0,所以正常是一直闪烁,按下P32则LED00停止闪烁
                task_end(1);//为1则循环执行
               
                task_start(1);//协程1开始
                P01 = 0;//P01端口置0,LED亮
                task_delay(200);//设定延时200ms
                P01 = 1;//P01端口置1,LED灭
                task_delay(500);//设定延时500ms
                task_end(1);//为1则循环执行
               
                task_start(2);//协程2开始
                task_delay(1000);//先长延时一段
                task_for(cnt1=0, cnt1++)//初始化和运行表达式(中间要用逗号链接!)
                {
                        task_for(cnt2=0, cnt2++)//第二层for循环
                        {
                                P02 = ~P02;//取反P02端口
                                task_delay(50);//小延时,快速闪烁
                        }
                        task_break(cnt2<3);//第二层for的判断条件,为1则返回for开头,为0则继续执行
                        P02 = 0;//强制给P02端口置低
                        task_delay(300);//设定延时300ms
                }
                task_break(cnt1<5);//第一层for的判断条件,为1则返回for开头,为0则继续执行
                task_end(1);//为1则循环运行
        }


就OK!
回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:143
  • 最近打卡:2026-03-08 08:36:28
已绑定手机

22

主题

2365

回帖

3529

积分

论坛元老

积分
3529
发表于 2025-12-27 19:41:17 | 显示全部楼层
   任务1:LED00每隔100ms闪烁一次,按下P32按键时,暂停LED00的闪烁,循环执行            
                task_start(0);//协程0开始
                P00 = ~P00;//每次执行取反P00端口电平
                task_delay(100);//设定延时100ms
                task_wait(!P32);//P32电平作为判断条件,为1则等待,为0则继续向下执行
                //因为按键按下为0,所以正常是一直闪烁,按下P32则LED00停止闪烁
                task_end(1);//为1则循环执行
task_delay(100);//设定延时100ms 是非阻塞的延时
关键是
task_wait(!P32);//P32电平作为判断条件,为1则等待,为0则继续向下执行
和普通等待不同!
回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:143
  • 最近打卡:2026-03-08 08:36:28
已绑定手机

22

主题

2365

回帖

3529

积分

论坛元老

积分
3529
发表于 2025-12-27 19:46:06 | 显示全部楼层
携程的task_wait并不是真在这里等待,只是记录现场状态,然后继续往下走

task_wait(!P32);//P32电平作为判断条件,为1则等待,为0则继续向下执行

麻烦是第三个携程:
task_start(2);//协程2开始
                task_delay(1000);//先长延时一段
                task_for(cnt1=0, cnt1++)//初始化和运行表达式(中间要用逗号链接!)
                {
                        task_for(cnt2=0, cnt2++)//第二层for循环
                        {
                                P02 = ~P02;//取反P02端口
                                task_delay(50);//小延时,快速闪烁,是非阻塞的延时
                        }
                        task_break(cnt2<3);//第二层for的判断条件,为1则返回for开头,为0则继续执行
                        P02 = 0;//强制给P02端口置低
                        task_delay(300);//设定延时300ms,是非阻塞的延时
                }
                task_break(cnt1<5);//第一层for的判断条件,为1则返回for开头,为0则继续执行
                task_end(1);//为1则循环运行,为0就只执行1次
回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:143
  • 最近打卡:2026-03-08 08:36:28
已绑定手机

22

主题

2365

回帖

3529

积分

论坛元老

积分
3529
发表于 2025-12-27 19:50:56 | 显示全部楼层
任务3:LED02先灭1000ms,然后{快速闪烁(50ms)3次,常亮300ms}<-如此循环5次,循环执行

task_delay(1000);//先长延时一段,是非阻塞的延时,此时LED灭

task_for(cnt1=0, cnt1++)//初始化和运行表达式(中间要用逗号链接!)
                {
                        task_for(cnt2=0, cnt2++)//第二层for循环
                        {
                                P02 = ~P02;//取反P02端口
                                task_delay(50);//小延时,快速闪烁,是非阻塞的延时
                        }
                        task_break(cnt2<3);//第二层for的判断条件,为1则返回for开头,为0则继续执行
                        P02 = 0;//强制给P02端口置低
                        task_delay(300);//设定延时300ms,是非阻塞的延时
                }

这个task_for和普通for不同,注意主要是不阻塞的!
回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:143
  • 最近打卡:2026-03-08 08:36:28
已绑定手机

22

主题

2365

回帖

3529

积分

论坛元老

积分
3529
发表于 2025-12-27 19:55:41 | 显示全部楼层
看task_for(init, cnt)定义:

#define task_for(init, cnt) init;user_task[_this_task].state = __LINE__;\
case __LINE__: for(_task_for_cnt = 1;_task_for_cnt--;cnt,user_task[_this_task].state = __LINE__)
//传入两个表达式,init是初始化(仅调用一次),cnt是自增自减表达式(每次运行都调用一次(task_for_end返回来也算))

task_for(cnt1=0, cnt1++)

进来先执行初始化cnt1=0

接着执行这段内容;

               {
                        task_for(cnt2=0, cnt2++)//第二层for循环
                        {
                                P02 = ~P02;//取反P02端口
                                task_delay(50);//小延时,快速闪烁,是非阻塞的延时
                        }
                        task_break(cnt2<3);//第二层for的判断条件,为1则返回for开头,为0则继续执行
                        P02 = 0;//强制给P02端口置低
                        task_delay(300);//设定延时300ms,是非阻塞的延时
                }
回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:143
  • 最近打卡:2026-03-08 08:36:28
已绑定手机

22

主题

2365

回帖

3529

积分

论坛元老

积分
3529
发表于 2025-12-27 19:59:05 | 显示全部楼层
执行完内容后,再执行

task_for(cnt1=0, cnt1++)//初始化和运行表达式(中间要用逗号链接!)

中的cnt1++,再执行

task_break(cnt1<5);//第一层for的判断条件,为1则返回for开头,为0则继续执行


就是说,cnt1++后<5,就循环执行内容

直到cnt1++后<5不成立,才往下走

其实也就是循环了5次  (0,1,2,3,4)


回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:143
  • 最近打卡:2026-03-08 08:36:28
已绑定手机

22

主题

2365

回帖

3529

积分

论坛元老

积分
3529
发表于 2025-12-27 20:04:33 | 显示全部楼层
task_for语句支持嵌套:

                         task_for(cnt2=0, cnt2++)//第二层for循环
                        {
                                P02 = ~P02;//取反P02端口
                                task_delay(50);//小延时,快速闪烁,是非阻塞的延时
                        }
                        task_break(cnt2<3);//第二层for的判断条件,为1则返回for开头,为0则继续执行

其实就是快速闪烁3次(2026年01月31日复习时候,发现闪烁3次,应该的灭6次,亮6次。应该把cnt2<3改成cnt2<6才对)

接着:


                        P02 = 0;//强制给P02端口置低(讲人话就是点亮)
                        task_delay(300);//设定延时300ms,是非阻塞的延时(亮300ms)



回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:143
  • 最近打卡:2026-03-08 08:36:28
已绑定手机

22

主题

2365

回帖

3529

积分

论坛元老

积分
3529
发表于 2025-12-27 20:16:30 | 显示全部楼层
怎么移植?
首先定义1ms的定时器中断:
set_timer_mode(Timer0, "1ms", Timer_End);//设定定时器0定时时间为1ms

// 函数介绍:设置定时器模式,支持参数乱序输入和默认值配置功能
// 默认值为1s定时,打开中断,关闭时钟输出功能。即set_timer_mode(Timer0, Timer_End);
// 等效于:set_timer_mode(Timer0, "1s", Dis_OutClk, En_Int, Timer_End);
// 下面这个例子的功能是设置Timer0为1000hz的定时器。
// set_timer_mode(Timer0, "1000hz", Timer_End);
// 下面这个例子的功能是设置Timer0为10ms的定时器。
// set_timer_mode(Timer0, "10ms", Timer_End);
void set_timer_mode(timer_num num, ...)
{
    char *arg;
    char enable_interrupt = 0, enable_outclk = 0, _char = 0; // 中断使能和输出时钟使能
                int ct_mode = 0;                                                                                   // 定时器/计数器模式切换
    int _sw_dat = 0;                                         // 设置值缓存
    float _set_timer_value = 0, set_timer_value = 0;         // 缓存和定时器设置值
    va_list args;            // 可变参数列表
    va_start(args, num);     // 初始化可变参数列表
    get_main_fosc();         // 获取当前时钟主频
    enable_interrupt = 1;enable_outclk = 0;set_timer_value = 1.0f;// 默认为打开中断,关闭时钟输出,定时时间默认为1秒
    while (1)
    {
        arg = va_arg(args, char *);
        if (sscanf(arg, "en%c", &_char) == 1)break;//遇到哨兵值,结束
        enable_interrupt = sscanf(arg, "\x01int%d", &_sw_dat) == 1 ? _sw_dat : enable_interrupt;//设置是否中断使能
        enable_outclk = sscanf(arg, "\x01outclk%d", &_sw_dat) == 1 ? _sw_dat : enable_outclk;//设置是否时钟输出使能
                                ct_mode = sscanf(arg, "ct%d", &_sw_dat) == 1 ? _sw_dat : ct_mode;//设置是定时还是计数模式
        if(sscanf(arg, "%f%c", &_set_timer_value, &_char) == 2){// 单位处理
                                                if(_char == 'u')set_timer_value = _set_timer_value * 1e-6f;// 微秒
            if(_char == 'm')set_timer_value = _set_timer_value * 1e-3f;// 毫秒
            if(_char == 's')set_timer_value = _set_timer_value;// 秒
            if(_char == 'h')set_timer_value = 1.0f/_set_timer_value;}// 赫兹转换时间单位
    }
    timer_setting(num, enable_outclk, enable_interrupt);// 设置定时器模式
    timer_value_setting(num, set_timer_value, ct_mode); // 设置定时器值
    va_end(args); // 清理可变参数列表
}
回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:143
  • 最近打卡:2026-03-08 08:36:28
已绑定手机

22

主题

2365

回帖

3529

积分

论坛元老

积分
3529
发表于 2025-12-27 20:46:52 | 显示全部楼层
接着定义:
#define task_start(task_num) _this_task=task_num;if(task_num<Task_Max_Num)switch(user_task[_this_task].state){case 0:
//初始化线程,调用前必备

必须和task_end(1);//为1则循环运行,为0就只执行1次  配对用:

#define task_end(reload) if(reload)user_task[_this_task].state=0;else user_task[_this_task].state = __LINE__;break;}
//线程结束,内部填入是否重新开始线程,1为重新开始,0为结束等待

_this_task是当前运行的任务全局标志,表示当前是哪个任务在运行

把我们传入的数字task_num写进来,例如一开始就是0,表示是任务1

程序本身是顺序执行,一直辐射到下面最后一行:

                P00 = ~P00;//每次执行取反P00端口电平
                task_delay(100);//设定延时100ms,是非阻塞的延时
                task_wait(!P32);//P32电平作为判断条件,为1则等待,为0则继续向下执行
                //因为按键按下为0,所以正常是一直闪烁,按下P32则LED00停止闪烁
                task_end(1);//为1则循环执行,为0就只执行1次

直到 task_start(1);//协程1开始 这句,就被改写为1



回复

使用道具 举报 送花

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

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2026-3-14 22:43 , Processed in 0.117140 second(s), 80 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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