Skip to content

CoTask, A simple single-file, pseudo-coroutine, implicit state machine code framework. CoTask, 一个简单,单文件实现,伪协程,的隐式状态机代码框架。

License

Notifications You must be signed in to change notification settings

Sheep118/CoTask

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

excalidraw-animate

CoTask, 一个简单,单文件实现,伪协程,的隐式状态机代码框架。

CoTask的主要思想

基于嵌入式中经典的前后台轮询,和ptlw_coroutine 两位大佬开源的纯C语言实现协程的编程思想,有了CoTask这个开源的代码框架。

CoTask主要实现了以下功能:

类似协程的非阻塞延时

在CoTask回调函数的代码中,可以通过COTASK_AWAIT_TICK() 这个宏进行非阻塞延时固定的TICK,且只要延时到期后,没有其他CoTask任务占用MCU运行的话,MCU会从延时的这个地方往后运行,直至回调函数结束。

回调函数运行结束后会重新运行该回调函数,直至这个CoTask的回调函数主动调用AWAIT 这���的接口函数才会主动让出MCU,运行其他CoTask的回调函数。

如果不想回调函数运行结束后重新运行的话,可以在回调函数中使用CoTask_Delete(p_cotask) 删除自己这个CoTask。这里的p_cotask 是回调函数的参数,传递进来的参数是CoTask自身的指针。

类似协程的任务间信号量通信

为了使CoTask更像协程,我还其中加入了信号量机制,用于不同协程间通信(需要在CoTask.h 中开启COTASK_SEMAPHORE_ENABLE 这个宏定义)。

当一个CoTask回调函数中调用 COTASK_AWAIT_SEM 或者 COTASK_AWAIT_SEM_UNTIL 这两个宏函数后,MCU会退出这个CoTask去执行其他CoTask,直到其他CoTask或者中断中有人调用 COTASK_RELEASE_SEM 释放这个信号量。当MCU空闲时,会逐个调用等待信号量的回调函数,和非阻塞延时一样,它从等待信号量处往后执行。

类似协程的执行利用率统计

同样的,为了使CoTask更像协程,我在其中加入了MCU执行利用率的统计,通过 CoTask_GetUtilization 这个函数,可以得到一个float类型的返回值,表示当前这一时刻,MCU执行的利用率,如果某个CoTask中十分耗时的操作的,这个数值会变大,最大为1,表示当前MCU一直被某个CoTask占用着。

CoTask移植与接口使用

由于整个CoTask的实现很简单,全部(包括空行和注释也就260行代码),所以这里效仿了著名的单文件开源库nothings/stb: stb single-file public domain libraries for C/C++ 的实现方式。整个CoTask实现只需要一个CoTask.h 文件。

CoTask移植

需要找一个.c文件作为CoTask的实现文件,这里建议直接在中断函数所在文件中实现,只需要在中断函数所在的.c文件中定义 COTASK_IMPLEMENT 这个宏,并包含CoTask.h 头文件,就算完成了 CoTask的实现。

接下来,需要在中断函数中定时调用 CoTask_Tick() 这个函数,为CoTask提供时基,并且需要根据提供时基的中断函数的中断频率,修改CoTask.h 中的FREQ_OF_TICK_COLCK 宏。

同时,需要在主循环中调用CoTask_Proc() 这个函数,用于处理信号量和非阻塞延时到期时的回调函数调用。

至此,移植完成,可以开始使用CoTask提供的接口函数了。下面是stm32 Hal库的移植例子截图:

CoTask移植案例

顺便一提,这个配置,是可以在keil中打开可视化查看和配置的。如下图:

keil可视化配置

CoTask接口使用

  • MS2TICK(ms):毫秒到TICK的换算。用于将延时的毫秒数转换为CoTask的tick单位。例如,MS2TICK(100) 表示延时100ms对应的tick数。

  • COTASK_ENTER:进入每个CoTask回调函数域时需要调用。建议在回调函数的最开始处调用,用于保存当前任务的状态。

  • COTASK_EXIT:退出每个CoTask回调函数域时调用。建议在回调函数的最后调用,用于重置任务状态并返回。

  • COTASK_AWIAT_TICK(tick_of_delay):在CoTask回调函数中调用,实现非阻塞延时。会立即退出当前任务,并在指定tick数后重新从当前位置继续执行。

  • CoTask_Create(CoTask *ptask, CoTaskFun task_callback):创建一个CoTask任务,将任务插入任务列表。ptask为任务结构体指针,task_callback为任务回调函数。

  • CoTask_Delete(CoTask *ptask):删除一个CoTask任务,从任务列表移除。

  • CoTask_Proc(void):遍历所有任务,执行满足条件的任务回调函数。通常在主循环中调用。

  • CoTask_Tick(void):定时调用,为CoTask提供时基。建议在定时器或SysTick中断中调用。

信号量相关接口(需开启 COTASK_SEMAPHORE_ENABLE 宏)

  • COTASK_RELEASE_SEM(sem):在任何地方调用,用于释放一个信号量。

  • COTASK_AWAIT_SEM(sem):在CoTask回调函数中调用,等待一个信号量被释放。任务会挂起,直到信号量可用。

  • COTASK_AWAIT_SEM_UNTIL(sem, tick_of_delay):等待信号量并设置超时时间。超时后任务会继续执行。

  • COTASK_WAIT_SEM_IS_TIMEOUT(sem):检查等待信号量是否超时。

利用率统计接口(需开启 COTASK_UTILIZATION_ENABLE 宏)

  • CoTask_GetUtilization(void):获取当前MCU执行CoTask的利用率,返回值为float类型,范围0~1。

使用建议:

  1. 每个CoTask任务建议使用静态变量保存状态,避免局部变量丢失。
  2. 不支持在CoTask回调函数中使用switch-case语法。
  3. 任务优先级无法指定,采用头插法,后创建的任务优先执行。

优缺点

优点 缺点
移植和实现简单 预编译后的文件可读性差(因为大部分接口都是宏)
伪协程的接口能简化一部分编程思路 很多时候需要使用静态变量来代替局部变量
(参考案例中的for循环)
整体代码量很小,占用小 不能在CoTask的回调函数中使用switch-case语法
CoTask执行的优先级无法指定(因为列表使用头插法,晚插入的CoTask先执行)

About

CoTask, A simple single-file, pseudo-coroutine, implicit state machine code framework. CoTask, 一个简单,单文件实现,伪协程,的隐式状态机代码框架。

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages