王昱顺 发表于 2025-3-29 09:44:34

【协程方式】实现【多任务调度 / 多线程】 仅需2byte的RAM 并带有详细视频讲解

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

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

=================================================================
底层原理讲解,细分到每一步的原理,同时讲解了移植和使用注意事项:


=================================================================

以下是一个简单的例子和实际效果:
例程基于AI8051U实验箱,可以自行更改为其他的引脚(记得初始化对应引脚)
程序下载:

/*
使用说明:
使用协程前需要设定协程的时间基准base,推荐使用1ms的时间基准,并且使用定时器中断方式设置
本例子通过一个简单的多任务程序展现协程的使用效果
任务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则循环运行
}


协程部分实现的核心代码:
void set_task_mode(void);
//用于设置定时器的时间基准,需要每1ms调用一次(推荐),可以设置为其他基准
//如果设置为1s调用一次,那么task_delay(1)就变成1s的延时了,以此类推

#define task_start(task_num) _this_task=task_num;if(task_num<Task_Max_Num)switch(user_task.state){case 0:
//初始化线程,调用前必备
      
#define task_wait(ifx) user_task.state=__LINE__;case __LINE__:if(ifx)break;
//是否继续向下,内部填入表达式,为1则等待,为0则不等待
      
#define task_delay(delay_ms) user_task.state=__LINE__;\
user_task.time=delay_ms;case __LINE__:if(user_task.time!=0)break;
//延时后继续向下,内部填入延时ms时间,等待时间后继续向下
      
#define task_for(init, cnt) init;user_task.state = __LINE__;\
case __LINE__: for(_task_for_cnt = 1;_task_for_cnt--;cnt,user_task.state = __LINE__)
//传入两个表达式,init是初始化(仅调用一次),cnt是自增自减表达式(每次运行都调用一次(task_for_end返回来也算))

#define task_while() user_task.state = __LINE__;\
case __LINE__: for(_task_for_cnt = 1;_task_for_cnt--;user_task.state = __LINE__)
//while循环,内部不可以填,判断条件通过task_break进行判断

#define task_break(ifx) if(ifx)break;
//对ifx的条件进行判断,为1则回到task_for/task_while的开始,为0则往下继续执行
      
#define task_end(reload) if(reload)user_task.state=0;break;}
//线程结束,内部填入是否重新开始线程,1为重新开始,0为结束等待

王昱顺 发表于 2025-4-8 12:44:57

新增三个协程应用例程!
例程3:矩阵按键非堵塞消抖实验
例程2:蜂鸣器声音变调实验
例程1:简易交通灯实验


例程2:蜂鸣器声音变调实验


例程3:矩阵按键非堵塞消抖实验




以下是核心源代码;
例程1:简易交通灯实验
while(1)
{
      task_start(0);//线程0,用于刷新显示
      task_for(seg_num = 0, seg_num++)
      {
                _show_buff = seg_data];//段码刷新,1有效
                _show_buff = ~(1<<seg_num);//位选,0有效
                task_delay(1);//延时一下,防止刷新过快太暗淡
                spi_printf(SPI0, Buff_Mode, _show_buff, 2);
                task_wait(!get_spi_state(SPI0));//等待SPI传输完成
                RCK = 1;RCK = 0;//触发刷新
      }
      task_break(seg_num < 8);
      task_end(1);//线程0结束,循环执行

      task_start(1);//用于控制红路灯1
      //红绿灯1:10s红灯,7s绿灯,3s黄灯
      YELLOW1 = 1;RED1 = 0;//关闭黄灯,打开红灯
      task_for(traffic_lights1 = 10, traffic_lights1--)
      {
                show_buff = (traffic_lights1/10)==0?17:traffic_lights1/10;//遇0消隐
                show_buff = traffic_lights1%10;
                task_delay(1000);
      }
      task_break(traffic_lights1>0);
      RED1 = 1;GREEN1 = 0;//关闭红灯,打开绿灯
      task_for(traffic_lights1 = 7, traffic_lights1--)
      {
                show_buff = (traffic_lights1/10)==0?17:traffic_lights1/10;//遇0消隐
                show_buff = traffic_lights1%10;
                task_delay(1000);
      }
      task_break(traffic_lights1>0);
      GREEN1 = 1;YELLOW1 = 0;//关闭绿灯,打开黄灯
      task_for(traffic_lights1 = 3, traffic_lights1--)
      {
                show_buff = (traffic_lights1/10)==0?17:traffic_lights1/10;//遇0消隐
                show_buff = traffic_lights1%10;
                task_delay(500);
                YELLOW1 = 1;
                task_delay(500);
                YELLOW1 = 0;//闪烁黄灯
      }
      task_break(traffic_lights1>0);
      task_end(1);//线程1结束,循环执行

      task_start(2);//用于控制红路灯2
      //红绿灯2:7s绿灯,3s黄灯,10s红灯
      RED2 = 1;GREEN2 = 0;// 关闭红灯,打开绿灯
      task_for(traffic_lights2 = 7, traffic_lights2--)
      {
                show_buff = (traffic_lights2/10)==0?17:traffic_lights2/10;//遇0消隐
                show_buff = traffic_lights2%10;
                task_delay(1000);
      }
      task_break(traffic_lights2>0);
      GREEN2 = 1;YELLOW2 = 0;// 关闭绿灯,打开黄灯
      task_for(traffic_lights2 = 3, traffic_lights2--)
      {
                show_buff = (traffic_lights2/10)==0?17:traffic_lights2/10;//遇0消隐
                show_buff = traffic_lights2%10;
                task_delay(500);
                YELLOW2 = 1;
                task_delay(500);
                YELLOW2 = 0;//闪烁黄灯
      }
      task_break(traffic_lights2>0);
      YELLOW2 = 1;RED2 = 0;// 关闭黄灯,打开红灯
      task_for(traffic_lights2 = 10, traffic_lights2--)
      {
                show_buff = (traffic_lights2/10)==0?17:traffic_lights2/10;//遇0消隐
                show_buff = traffic_lights2%10;
                task_delay(1000);
      }
      task_break(traffic_lights2>0);
      task_end(1);//线程1结束,循环执行
}

例程2:蜂鸣器声音变调实验
while(1)
{
      task_start(0);//线程0开始,蜂鸣器鸣叫
      Beep = ~Beep;
      task_delay(x_delay);
      task_end(1);//线程0结束,循环执行

      task_start(1);//线程1开始,控制蜂鸣器变调
      task_for(x_delay = 0, x_delay++)
      {
                task_delay(1000);//缓慢上升100us*1000=100ms
      }
      task_break(x_delay < 20);
      task_for(x_delay = 20, x_delay--)
      {
                task_delay(300);//迅速下降100us*300=30ms
      }
      task_break(x_delay > 0);
      task_end(1);//线程1结束,循环执行
}

例程3:矩阵按键非堵塞消抖实验
while(1)
{
      task_start(0);//线程0,用于刷新显示
      task_for(seg_num = 0, seg_num++)
      {
                _show_buff = seg_data];//段码刷新,1有效
                _show_buff = ~(1<<seg_num);//位选,0有效
                task_delay(1);//延时一下,防止刷新过快太暗淡
                spi_printf(SPI0, Buff_Mode, _show_buff, 2);
                task_wait(!get_spi_state(SPI0));//等待SPI传输完成
                RCK = 1;RCK = 0;//触发刷新
      }
      task_break(seg_num < 8);
      task_end(1);//线程0结束,循环执行
      
      task_start(1);//线程1,用于扫描按键
      P06 = 0;P07 = 1;//先扫描0~3按键
      task_delay(1);//等待电平稳定
      if((P0&0x0f) != 0x0f)//判断有按键按下
      {
                task_delay(2);//延时2ms,按键消抖
                if((P0&0x0f) != 0x0f)//判断有按键按下
                {
                        key_scanf = P0&0x0f;//记录按键值
                }
      }
      P06 = 1;P07 = 0;//再扫描4~7按键
      task_delay(1);//等待电平稳定
      if((P0&0x0f) != 0x0f)//判断有按键按下
      {
                task_delay(2);//延时2ms,按键消抖
                if((P0&0x0f) != 0x0f)//判断有按键按下
                {
                        key_scanf = P0&0x0f|0x10;//记录按键值,添加标记
                }
      }
      task_end(1);//线程1结束,循环执行

      task_start(2);//线程2,用于显示键值
      show_buff = key_scanf/16;//显示键值
      show_buff = key_scanf%16;//显示键值
      task_delay(100);//延时100ms刷新一次
      task_end(1);//线程2结束,循环执行
}


tzz1983 发表于 2025-4-2 09:21:54

Auto_Keil.EXE 具体是干了些什么

王昱顺 发表于 2025-4-2 12:29:20

tzz1983 发表于 2025-4-2 09:21
Auto_Keil.EXE 具体是干了些什么

和多线程操作无关,主要是帮忙设置Keil工程的各项设置,新建工程后就不用自己一点点去点了

tzz1983 发表于 2025-4-2 12:31:28

会影响其它目录下的工程吗

王昱顺 发表于 2025-4-2 12:53:24

tzz1983 发表于 2025-4-2 12:31
会影响其它目录下的工程吗

不会,仅操作操作当前目录下的。如果不想用直接删掉了也无所谓

xinxinsky 发表于 2025-4-8 15:28:13

太强了, 收藏!~

神农鼎 发表于 2025-4-8 15:40:09

xinxinsky 发表于 2025-4-8 15:28
太强了, 收藏!~

期待能帮助到大家

proktv 发表于 2025-4-9 09:53:09

{:shengli:}

mod3h 发表于 2025-4-10 11:12:07

__LINE__   当前程序行的行号,这个才是重点 ! {:4_165:}
页: [1] 2 3 4
查看完整版本: 【协程方式】实现【多任务调度 / 多线程】 仅需2byte的RAM 并带有详细视频讲解