# 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