【协程方式】实现【多任务调度 / 多线程】 仅需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为结束等待
新增三个协程应用例程!
例程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结束,循环执行
}
Auto_Keil.EXE 具体是干了些什么 tzz1983 发表于 2025-4-2 09:21
Auto_Keil.EXE 具体是干了些什么
和多线程操作无关,主要是帮忙设置Keil工程的各项设置,新建工程后就不用自己一点点去点了 会影响其它目录下的工程吗 tzz1983 发表于 2025-4-2 12:31
会影响其它目录下的工程吗
不会,仅操作操作当前目录下的。如果不想用直接删掉了也无所谓 太强了, 收藏!~ xinxinsky 发表于 2025-4-8 15:28
太强了, 收藏!~
期待能帮助到大家 {:shengli:} __LINE__ 当前程序行的行号,这个才是重点 ! {:4_165:}