# CoTask **Repository Path**: samsparks/CoTask ## Basic Information - **Project Name**: CoTask - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-10-29 - **Last Updated**: 2025-10-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ![excalidraw-animate](./pic/excalidraw-animate.svg) # CoTask, 一个简单,单文件实现,伪协程,的隐式状态机代码框架。 ## CoTask的主要思想 基于嵌入式中经典的前后台轮询,和[pt](https://github.com/gburd/pt/tree/master),[lw_coroutine](https://github.com/xiaoliang314/lw_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++](https://github.com/nothings/stb/tree/master) 的实现方式。整个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移植案例](./pic/CoTask移植案例.png) 顺便一提,这个配置,是可以在keil中打开可视化查看和配置的。如下图: ![keil可视化配置](./pic/keil可视化配置.png) ### 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先执行) |