# coro **Repository Path**: Ethan-ZYS/coro ## Basic Information - **Project Name**: coro - **Description**: 基于pigweed async2和zephyr RTOS的使用C++20编写嵌入式系统协程库, 无堆内存分配, 无RTTI和异常. - **Primary Language**: C++ - **License**: MIT - **Default Branch**: dev - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-05-30 - **Last Updated**: 2025-09-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 协程库简介 ## 0. 引言 - 为什么使用协程 1. 更好地编写异步代码 在嵌入式编程中,大部分异步代码都是使用回调函数完成,比如在处理GPIO中断时,使用C语言常见的处理方法为, ```c bool key_pressed = false; // 1. GPIO中断回调:按键触发后,需延时防抖(依赖定时器异步回调) void timer_callback(void) { // 2. 定时器防抖回调:读取按键状态 key_pressed = gpio_read_key(); if (key_pressed) { // 3. 若按键有效,控制LED(若LED控制需异步,会继续嵌套) gpio_toggle_led(); } } void gpio_key_isr(void) { // 启动10ms防抖定时器,定时器完成后执行防抖回调 timer_start(10, timer_callback); } // 初始化:注册GPIO中断回调 void init(void) { gpio_register_isr(GPIO_KEY_PIN, gpio_key_isr); // 中断触发时执行gpio_key_isr(); } ``` 中断触发时,CPU 跳转到回调函数执行。若回调逻辑中还需依赖其他异步操作(如延时防抖、读取传感器状态),会被迫在回调内嵌套新的回调或状态机,导致代码碎片化。 所产生的问题可以总结为: 1. **逻辑嵌套**:中断回调依赖定时器回调,代码呈 “中断回调→定时器回调” 的嵌套结构,若后续需加 “LED 亮后读取传感器”,会继续嵌套新回调; 2. **状态管理复杂**:需用全局变量(key_pressed)传递状态,多中断场景下易出现竞态; 3. **可读性差**:异步逻辑分散在多个回调函数中,流程需 “跳转追踪”。 使用状态机可以打打简化逻辑嵌套的问题, ```c // 状态变量:0=等待中断,1=消抖中,2=读取键值,3=执行动作 uint8_t key_state = 0; uint8_t key_val; // 存储键值 // 1. GPIO中断服务函数:触发后切换状态 void GPIO_IRQHandler() { key_state = 1; // 中断触发→进入“消抖中”状态 timer_start(20); // 启动20ms消抖定时器 } // 2. 定时器回调:消抖结束→切换状态 void timer_callback() { key_state = 2; // 消抖结束→进入“读取键值”状态 } // 3. 主循环:轮询状态变量,分支处理 void main_loop() { while(1) { switch(key_state) { case 0: // 等待中断:无操作 break; case 1: // 消抖中:等待定时器回调(无逻辑) break; case 2: // 读取键值:切换到“执行动作”状态 key_val = GPIO_ReadPin(); key_state = 3; break; case 3: // 执行动作:完成后回到“等待中断” if(key_val == KEY1_PRESSED) LED_Toggle(); else if(key_val == KEY2_PRESSED) LOG_Print("KEY2 pressed"); key_state = 0; break; } } } ``` 状态机与普通嵌套中断在不同方面的优劣性对比: | 对比维度 | 状态机 | 普通嵌套中断 | | :--------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | | 复杂性与可维护性 | 优点:逻辑相对清晰,代码围绕状态变量以分支判断形式组织,便于梳理执行顺序与功能模块划分。
缺点:状态或转移逻辑复杂时,代码可读性差,维护易出错,需仔细梳理状态转移条件。 | 优点:能及时响应不同优先级外部事件,保障实时性要求高的任务及时处理。
缺点:程序执行流程复杂,难以直观理解,中断嵌套易出现死锁、中断丢失等问题,维护困难。 | | 实时性与响应速度 | 优点:若有高效触发机制,可按既定逻辑处理状态转换。
缺点:实时性依赖轮询或触发机制,可能存在延迟,响应速度通常不如中断快。 | 优点:天然具有实时性优势,能快速响应外部突发事件,响应速度极快(几个机器周期内)。
缺点:依赖中断优先级配置等正确设置,否则可能出现响应异常情况。 | | 资源占用与开销 | 优点:资源消耗较稳定,主要占用代码及少量运行内存存储状态相关信息。
缺点:状态判断和转移逻辑若不合理,会产生不必要开销。 | 优点:可按需利用中断机制处理紧急任务,发挥高优先级中断优势。
缺点:占用额外硬件资源用于中断触发等,且每次中断需保存和恢复上下文,占用栈空间,频繁中断影响效率。 | | 调试难度 | 优点:可通过查看状态变量判断所处状态,按状态转移逻辑排查问题,思路较清晰。
缺点:状态多、逻辑复杂时,梳理不同状态执行情况及异常转移较耗时。 | 优点:可快速定位到中断相关问题,对硬件触发相关故障排查有针对性。
缺点:调试难度极大,中断触发不可预测,嵌套中断执行流程难追踪,需借助专业工具及详细记录排查。 | | 应用场景侧重 | 适用于系统行为可清晰划分为不同阶段、状态变化规律且实时性要求不高的场景,如设备工作模式切换、游戏角色状态管理等。 | 适用于对实时性要求极高、需快速响应外部硬件事件且按优先级处理并发事件的场景,如航空航天飞行控制、汽车ECU中传感器信号处理等。 | 而使用C++20协程 ```c++ CO_EVT_DECLARE(button_pressed, bool) auto coro_toggle_led() -> coro::Status { using std::chrono_literal; coro::Timer timer; const auto ret = co_await button_pressed.Get(); if (ret) { co_await timer.WaitFor(10ms); if (gpio_read_pin(GPIO_KEY_PIN) == 1) { led_toggle_pin(); co_return coro::OkStatus(); } } co_return coro::Unknown(); } auto gpio_isr() -> void { button_pressed.Set(true); } auto init() { gpio_register_isr(GPIO_KEY_PIN, gpio_key_isr); auto coro_task = coro::Task(coro_toggle_led); coro::Dispatcher d; d.Post(d); d.RunUntilCompleted(); } ``` 协程解决回调问题的核心逻辑 1. **中断触发→协程唤醒**:GPIO 中断不再直接执行业务逻辑,而是通过等待器的回调唤醒暂停的协程,将 “中断回调” 转化为 “协程恢复信号”; 2. **线性流程无嵌套**:原本 “中断回调→定时器回调” 的嵌套逻辑,通过co_await转化为 “等待中断→等待定时器→读按键→控 LED” 的线性代码,流程直观,无需跳转追踪; 3. **状态内聚**:协程内部可维护局部状态(如key_pressed),无需全局变量,避免多中断竞态; 非阻塞暂停:协程等待co_await时会释放 CPU,不阻塞其他任务(如嵌入式中的 RTOS 任务),兼顾异步响应与 CPU 利用率。 对[pigweed async2](https://github.com/google/pigweed)的 ## 1. 使用方法 - 克隆dev分支 ``` git clone -b dev https://gitee.com/Ethan-ZYS/coro.git ``` - 链接该库 ``` target_link_libraries(app PRIVATE coro.lib) ``` ## 2. 示例代码 ```c++ #include "coro/coro.hpp" #include "pw_allocator/first_fit.h" coro::Event evt; coro::Event evt2; coro::Awaitable task1(coro::Context &) { using namespace std::chrono_literals; puts("task1 wating for an event\n"); auto data = co_await *evt; printf("data: %d\n", data); puts("task1 waiting for timeout\n"); coro::Timer timer; co_await timer.WaitFor(3500ms); puts("task2 timeout\n"); puts("stask1 handled\n"); evt2(666, 777); co_return pw::OkStatus(); } coro::Awaitable task2(coro::Context &cx) { using namespace std::chrono_literals; coro::Timer timer; puts("task2 wating for an event\n"); const auto [d1, d2] = co_await *evt2; printf("task 2 data1 = %d; data2 = %d\n", d1, d2); co_await timer.WaitFor(4s); puts("task2 handled\n"); co_return pw::OkStatus(); } coro::Event<> e1, e2; coro::Awaitable task3(coro::Context &cx) { puts("task3 wating for an event\n"); co_await WhenAll(*e1, *e2); puts("task3 wait done\n"); co_return pw::OkStatus(); } coro::Awaitable task4(coro::Context &cx) { CO_EVT(e3); CO_EVT(e4); CO_EVT(e5); puts("task4 wating for any event\n"); co_await coro::WhenAny(*e1, *e2, *e3); puts("task4 wait done\n"); co_return pw::OkStatus(); } std::array buff; int main() { puts("=========================begin test====================\n"); pw::allocator::FirstFitAllocator alloc{buff}; coro::Context coro_cx{alloc}; CO_EVT(e3); auto t1 = coro::Task(task1(coro_cx)); auto t2 = coro::Task(task2(coro_cx)); auto t3 = coro::Task(task3(coro_cx)); auto t4 = coro::Task(task4(coro_cx)); coro::Runnable::Instance().Start(t1).Start(t2).Start(t3, t4); printf("%s\n", coro::Runnable::Instance().UntilStalled().IsReady() ? "ready" : "not ready"); evt(789); e1(); e2(); e3(); printf("%s\n", coro::Runnable::Instance().UntilStalled().IsReady() ? "ready" : "not ready"); puts("==========================end test======================\n"); coro::Runnable::Instance().Join(); return 0; } ``` ## 3. 生产者和消费者示例 ```c++ #include "coro/coro.hpp" #include "pw_allocator/first_fit.h" #include class Consumer { public: Consumer() : m_task(coro::Task([](coro::Context &cx) -> coro::Awaitable { CO_EVT(producer, int, int); CO_EVT(consumer, pw::Status); int i = 10; while (i--) { const auto [d1, d2] = co_await *producer; printf("get value: %d and %d\n", d1, d2); if (d1 % 3 == 0) { consumer(pw::Status::DataLoss()); } } co_return pw::OkStatus(); }(m_cx))) { coro::Runnable::Instance().Start(m_task); } private: pw::async2::CoroOrElseTask m_task; inline static std::byte m_buff[512]; inline static pw::allocator::FirstFitAllocator m_alloc{m_buff}; inline static coro::Context m_cx{m_alloc}; }; class Producer { public: Producer() : m_task(coro::Task(CoTask(m_cx))) { coro::Runnable::Instance().Start(m_task); } coro::Awaitable CoTask(coro::Context &cx) { using namespace std::chrono_literals; CO_EVT(producer, int, int); CO_EVT(consumer, pw::Status); auto count = 10; auto i = 0; int j = i + 1; coro::Timer timer; while (count--) { co_await timer.WaitFor(1s); producer(++i, ++j); } co_return pw::OkStatus(); } private: pw::async2::CoroOrElseTask m_task; inline static std::byte m_buff[512]; inline static pw::allocator::FirstFitAllocator m_alloc{m_buff}; inline static coro::Context m_cx{m_alloc}; }; int main() { puts("=========================begin test====================\n"); Consumer c{}; Producer s{}; printf("number %d\n", coro::Runnable::Instance().NumberOfCompleted()); coro::Runnable::Instance().Join(); printf("number %d\n", coro::Runnable::Instance().NumberOfCompleted()); puts("==========================end test======================\n"); return 0; } ``` ## 4. 具体使用方法 ### 1. 事件 - 事件声明 - 数据传递 ### 2. WhenAll ### 3. WhanAny ## 5. TODO - 增加executor模块, 方便dispathcher在不同的线程下进行调度 - 增加裸机环境下的dispatcher