找回密码
 立即注册
查看: 1680|回复: 32

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

[复制链接]
  • 打卡等级:以坛为家II
  • 打卡总天数:458
  • 最近打卡:2025-05-01 07:48:22
已绑定手机
已实名认证

110

主题

2218

回帖

5450

积分

版主

积分
5450
发表于 2025-3-29 09:44:34 | 显示全部楼层 |阅读模式
协程方式】实现【多任务调度 / 多线程
协程方式实现多线程-仅需2byte的RAM,灵活多变,移植快捷
本方式区别于实时操作系统没有任何函数重入问题,本质还是代码的顺序执行。

但是通过软定时器实现了非堵塞的延时函数
并且实现了task_waittask_fortask_while

来实现代码状态的暂停可控次数重复循环,实际使用相当灵活
内部的实际占用为固定2Byte RAM+每个任务6Byte RAM
体验接近于普通C语言代码编写


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


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

以下是一个简单的例子和实际效果:
例程基于AI8051U实验箱,可以自行更改为其他的引脚(记得初始化对应引脚)
程序下载: 9使用协程完成多任务调度(占用极少).zip (221.85 KB, 下载次数: 12)

/*
使用说明:
使用协程前需要设定协程的时间基准base,推荐使用1ms的时间基准,并且使用定时器中断方式设置
本例子通过一个简单的多任务程序展现协程的使用效果
任务1:LED00每隔100ms闪烁一次,按下P32按键时,暂停LED00的闪烁,循环执行
任务2:LED01亮200ms,灭500ms,循环执行
任务3:LED02先灭1000ms,然后{快速闪烁(50ms)3次,常亮300ms}<-如此循环5次,循环执行
*/



实际效果视频:


实现这部分的代码:
  1. while(1)
  2. {
  3.         task_start(0);//协程0开始
  4.         P00 = ~P00;//每次执行取反P00端口电平
  5.         task_delay(100);//设定延时100ms
  6.         task_wait(!P32);//P32电平作为判断条件,为1则等待,为0则继续向下执行
  7.         //因为按键按下为0,所以正常是一直闪烁,按下P32则LED00停止闪烁
  8.         task_end(1);//为1则循环执行
  9.         
  10.         task_start(1);//协程1开始
  11.         P01 = 0;//P01端口置0,LED亮
  12.         task_delay(200);//设定延时200ms
  13.         P01 = 1;//P01端口置1,LED灭
  14.         task_delay(500);//设定延时500ms
  15.         task_end(1);//为1则循环执行
  16.         
  17.         task_start(2);//协程2开始
  18.         task_delay(1000);//先长延时一段
  19.         task_for(cnt1=0, cnt1++)//初始化和运行表达式(中间要用逗号链接!)
  20.         {
  21.                 task_for(cnt2=0, cnt2++)//第二层for循环
  22.                 {
  23.                         P02 = ~P02;//取反P02端口
  24.                         task_delay(50);//小延时,快速闪烁
  25.                 }
  26.                 task_break(cnt2<3);//第二层for的判断条件,为1则返回for开头,为0则继续执行
  27.                 P02 = 0;//强制给P02端口置高
  28.                 task_delay(300);//设定延时300ms
  29.         }
  30.         task_break(cnt1<5);//第一层for的判断条件,为1则返回for开头,为0则继续执行
  31.         task_end(1);//为1则循环运行
  32. }
复制代码



协程部分实现的核心代码:
  1. void set_task_mode(void);
  2. //用于设置定时器的时间基准,需要每1ms调用一次(推荐),可以设置为其他基准
  3. //如果设置为1s调用一次,那么task_delay(1)就变成1s的延时了,以此类推
  4. #define task_start(task_num) _this_task=task_num;if(task_num<Task_Max_Num)switch(user_task[_this_task].state){case 0:
  5. //初始化线程,调用前必备
  6.         
  7. #define task_wait(ifx) user_task[_this_task].state=__LINE__;case __LINE__:if(ifx)break;
  8. //是否继续向下,内部填入表达式,为1则等待,为0则不等待
  9.         
  10. #define task_delay(delay_ms) user_task[_this_task].state=__LINE__;\
  11. user_task[_this_task].time=delay_ms;case __LINE__:if(user_task[_this_task].time!=0)break;
  12. //延时后继续向下,内部填入延时ms时间,等待时间后继续向下
  13.         
  14. #define task_for(init, cnt) init;user_task[_this_task].state = __LINE__;\
  15. case __LINE__: for(_task_for_cnt = 1;_task_for_cnt--;cnt,user_task[_this_task].state = __LINE__)
  16. //传入两个表达式,init是初始化(仅调用一次),cnt是自增自减表达式(每次运行都调用一次(task_for_end返回来也算))
  17. #define task_while() user_task[_this_task].state = __LINE__;\
  18. case __LINE__: for(_task_for_cnt = 1;_task_for_cnt--;user_task[_this_task].state = __LINE__)
  19. //while循环,内部不可以填,判断条件通过task_break进行判断
  20. #define task_break(ifx) if(ifx)break;
  21. //对ifx的条件进行判断,为1则回到task_for/task_while的开始,为0则往下继续执行
  22.         
  23. #define task_end(reload) if(reload)user_task[_this_task].state=0;break;}
  24. //线程结束,内部填入是否重新开始线程,1为重新开始,0为结束等待
复制代码

3 喜欢他/她就送朵鲜花吧,赠人玫瑰,手有余香!
回复

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:458
  • 最近打卡:2025-05-01 07:48:22
已绑定手机
已实名认证

110

主题

2218

回帖

5450

积分

版主

积分
5450
发表于 2025-4-8 12:44:57 | 显示全部楼层
新增三个协程应用例程
例程3矩阵按键非堵塞消抖实验
例程2蜂鸣器声音变调实验
例程1简易交通灯实验

10a 驱动数码管做简单交通信号灯.zip (251.02 KB, 下载次数: 23)
例程2蜂鸣器声音变调实验

10b 驱动蜂鸣器做变调歌曲.zip (242.52 KB, 下载次数: 28)
例程3矩阵按键非堵塞消抖实验

10c 使用多线程完成按键消抖和显示.zip (250.29 KB, 下载次数: 31)


以下是核心源代码;
例程1简易交通灯实验
  1. while(1)
  2. {
  3.         task_start(0);//线程0,用于刷新显示
  4.         task_for(seg_num = 0, seg_num++)
  5.         {
  6.                 _show_buff[0] = seg_data[show_buff[seg_num]];//段码刷新,1有效
  7.                 _show_buff[1] = ~(1<<seg_num);//位选,0有效
  8.                 task_delay(1);//延时一下,防止刷新过快太暗淡
  9.                 spi_printf(SPI0, Buff_Mode, _show_buff, 2);
  10.                 task_wait(!get_spi_state(SPI0));//等待SPI传输完成
  11.                 RCK = 1;RCK = 0;//触发刷新
  12.         }
  13.         task_break(seg_num < 8);
  14.         task_end(1);//线程0结束,循环执行
  15.         task_start(1);//用于控制红路灯1
  16.         //红绿灯1:10s红灯,7s绿灯,3s黄灯
  17.         YELLOW1 = 1;RED1 = 0;//关闭黄灯,打开红灯
  18.         task_for(traffic_lights1 = 10, traffic_lights1--)
  19.         {
  20.                 show_buff[0] = (traffic_lights1/10)==0?17:traffic_lights1/10;//遇0消隐
  21.                 show_buff[1] = traffic_lights1%10;
  22.                 task_delay(1000);
  23.         }
  24.         task_break(traffic_lights1>0);
  25.         RED1 = 1;GREEN1 = 0;//关闭红灯,打开绿灯
  26.         task_for(traffic_lights1 = 7, traffic_lights1--)
  27.         {
  28.                 show_buff[0] = (traffic_lights1/10)==0?17:traffic_lights1/10;//遇0消隐
  29.                 show_buff[1] = traffic_lights1%10;
  30.                 task_delay(1000);
  31.         }
  32.         task_break(traffic_lights1>0);
  33.         GREEN1 = 1;YELLOW1 = 0;//关闭绿灯,打开黄灯
  34.         task_for(traffic_lights1 = 3, traffic_lights1--)
  35.         {
  36.                 show_buff[0] = (traffic_lights1/10)==0?17:traffic_lights1/10;//遇0消隐
  37.                 show_buff[1] = traffic_lights1%10;
  38.                 task_delay(500);
  39.                 YELLOW1 = 1;
  40.                 task_delay(500);
  41.                 YELLOW1 = 0;//闪烁黄灯
  42.         }
  43.         task_break(traffic_lights1>0);
  44.         task_end(1);//线程1结束,循环执行
  45.         task_start(2);//用于控制红路灯2
  46.         //红绿灯2:7s绿灯,3s黄灯,10s红灯
  47.         RED2 = 1;GREEN2 = 0;// 关闭红灯,打开绿灯
  48.         task_for(traffic_lights2 = 7, traffic_lights2--)
  49.         {
  50.                 show_buff[6] = (traffic_lights2/10)==0?17:traffic_lights2/10;//遇0消隐
  51.                 show_buff[7] = traffic_lights2%10;
  52.                 task_delay(1000);
  53.         }
  54.         task_break(traffic_lights2>0);
  55.         GREEN2 = 1;YELLOW2 = 0;// 关闭绿灯,打开黄灯
  56.         task_for(traffic_lights2 = 3, traffic_lights2--)
  57.         {
  58.                 show_buff[6] = (traffic_lights2/10)==0?17:traffic_lights2/10;//遇0消隐
  59.                 show_buff[7] = traffic_lights2%10;
  60.                 task_delay(500);
  61.                 YELLOW2 = 1;
  62.                 task_delay(500);
  63.                 YELLOW2 = 0;//闪烁黄灯
  64.         }
  65.         task_break(traffic_lights2>0);
  66.         YELLOW2 = 1;RED2 = 0;// 关闭黄灯,打开红灯
  67.         task_for(traffic_lights2 = 10, traffic_lights2--)
  68.         {
  69.                 show_buff[6] = (traffic_lights2/10)==0?17:traffic_lights2/10;//遇0消隐
  70.                 show_buff[7] = traffic_lights2%10;
  71.                 task_delay(1000);
  72.         }
  73.         task_break(traffic_lights2>0);
  74.         task_end(1);//线程1结束,循环执行
  75. }
复制代码


例程2蜂鸣器声音变调实验
  1. while(1)
  2. {
  3.         task_start(0);//线程0开始,蜂鸣器鸣叫
  4.         Beep = ~Beep;
  5.         task_delay(x_delay);
  6.         task_end(1);//线程0结束,循环执行
  7.         task_start(1);//线程1开始,控制蜂鸣器变调
  8.         task_for(x_delay = 0, x_delay++)
  9.         {
  10.                 task_delay(1000);//缓慢上升100us*1000=100ms
  11.         }
  12.         task_break(x_delay < 20);
  13.         task_for(x_delay = 20, x_delay--)
  14.         {
  15.                 task_delay(300);//迅速下降100us*300=30ms
  16.         }
  17.         task_break(x_delay > 0);
  18.         task_end(1);//线程1结束,循环执行
  19. }
复制代码


例程3矩阵按键非堵塞消抖实验
  1. while(1)
  2. {
  3.         task_start(0);//线程0,用于刷新显示
  4.         task_for(seg_num = 0, seg_num++)
  5.         {
  6.                 _show_buff[0] = seg_data[show_buff[seg_num]];//段码刷新,1有效
  7.                 _show_buff[1] = ~(1<<seg_num);//位选,0有效
  8.                 task_delay(1);//延时一下,防止刷新过快太暗淡
  9.                 spi_printf(SPI0, Buff_Mode, _show_buff, 2);
  10.                 task_wait(!get_spi_state(SPI0));//等待SPI传输完成
  11.                 RCK = 1;RCK = 0;//触发刷新
  12.         }
  13.         task_break(seg_num < 8);
  14.         task_end(1);//线程0结束,循环执行
  15.         
  16.         task_start(1);//线程1,用于扫描按键
  17.         P06 = 0;P07 = 1;//先扫描0~3按键
  18.         task_delay(1);//等待电平稳定
  19.         if((P0&0x0f) != 0x0f)//判断有按键按下
  20.         {
  21.                 task_delay(2);//延时2ms,按键消抖
  22.                 if((P0&0x0f) != 0x0f)//判断有按键按下
  23.                 {
  24.                         key_scanf = P0&0x0f;//记录按键值
  25.                 }
  26.         }
  27.         P06 = 1;P07 = 0;//再扫描4~7按键
  28.         task_delay(1);//等待电平稳定
  29.         if((P0&0x0f) != 0x0f)//判断有按键按下
  30.         {
  31.                 task_delay(2);//延时2ms,按键消抖
  32.                 if((P0&0x0f) != 0x0f)//判断有按键按下
  33.                 {
  34.                         key_scanf = P0&0x0f|0x10;//记录按键值,添加标记
  35.                 }
  36.         }
  37.         task_end(1);//线程1结束,循环执行
  38.         task_start(2);//线程2,用于显示键值
  39.         show_buff[6] = key_scanf/16;//显示键值
  40.         show_buff[7] = key_scanf%16;//显示键值
  41.         task_delay(100);//延时100ms刷新一次
  42.         task_end(1);//线程2结束,循环执行
  43. }
复制代码



回复 支持 反对

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:5
  • 最近打卡:2025-04-30 07:46:22

33

主题

1079

回帖

2319

积分

荣誉版主

积分
2319
发表于 2025-4-2 09:21:54 | 显示全部楼层
Auto_Keil.EXE 具体是干了些什么

点评

和多线程操作无关,主要是帮忙设置Keil工程的各项设置,新建工程后就不用自己一点点去点了  详情 回复 发表于 2025-4-2 12:29
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:458
  • 最近打卡:2025-05-01 07:48:22
已绑定手机
已实名认证

110

主题

2218

回帖

5450

积分

版主

积分
5450
发表于 2025-4-2 12:29:20 | 显示全部楼层
tzz1*** 发表于 2025-4-2 09:21
Auto_Keil.EXE 具体是干了些什么

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

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:5
  • 最近打卡:2025-04-30 07:46:22

33

主题

1079

回帖

2319

积分

荣誉版主

积分
2319
发表于 2025-4-2 12:31:28 | 显示全部楼层
会影响其它目录下的工程吗

点评

不会,仅操作操作当前目录下的。如果不想用直接删掉了也无所谓  详情 回复 发表于 2025-4-2 12:53
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:458
  • 最近打卡:2025-05-01 07:48:22
已绑定手机
已实名认证

110

主题

2218

回帖

5450

积分

版主

积分
5450
发表于 2025-4-2 12:53:24 | 显示全部楼层
tzz1*** 发表于 2025-4-2 12:31
会影响其它目录下的工程吗

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

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:116
  • 最近打卡:2025-04-29 09:23:46

6

主题

52

回帖

368

积分

中级会员

积分
368
发表于 2025-4-8 15:28:13 | 显示全部楼层
太强了, 收藏!~

点评

期待能帮助到大家  详情 回复 发表于 2025-4-8 15:40
STC32G配置包:https://gitee.com/main.c/SxPackages.STC32G
STC8配置包:https://gitee.com/main.c/SxPackages.STC8
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:54
  • 最近打卡:2025-05-01 09:07:55

717

主题

1万

回帖

1万

积分

管理员

积分
15609
发表于 2025-4-8 15:40:09 | 显示全部楼层

期待能帮助到大家
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:319
  • 最近打卡:2025-04-30 08:16:39
已绑定手机

3

主题

35

回帖

987

积分

高级会员

积分
987
发表于 2025-4-9 09:53:09 | 显示全部楼层
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看I
  • 打卡总天数:13
  • 最近打卡:2025-04-15 09:17:07

0

主题

5

回帖

142

积分

注册会员

积分
142
发表于 2025-4-10 11:12:07 | 显示全部楼层
__LINE__   当前程序行的行号,这个才是重点 !
回复 支持 反对

使用道具 举报 送花

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

本版积分规则

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

GMT+8, 2025-5-1 19:40 , Processed in 0.132841 second(s), 120 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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