硬汉嵌入式论坛

 找回密码
 立即注册
搜索
查看: 43|回复: 1
收起左侧

整理了一份 Protothreads协程(无栈操作系统) 资料分享给大家

[复制链接]

9

主题

30

回帖

57

积分

初级会员

积分
57
发表于 11 小时前 | 显示全部楼层 |阅读模式
Protothreads 是一种专为资源受限单片机(如 3KB RAM 的 PY32F002B 或 C51单片机)设计的无栈协程(Stackless Coroutine),让你以无阻塞的方式,写出看似阻塞的代码。

其本质是 [size=0.92em]switch-case,在阻塞的地方用__LINE__标识[size=0.92em]记录位置然后返回,第二次进入时,根据记录的位置跳转到中断的行。
[size=0.92em]

  • 内存开销极低:不分配独立任务堆栈,每个任务仅需 2~6 字节 RAM 记录断点行号与时间戳。
  • 解决痛点:将全屏刷屏、AT 指令握手等长耗时或包含多步延时的阻塞任务切片化,防止主循环卡死导致串口通信丢包。
  • 混合架构最佳实践:
    • 硬件中断 + RingBuffer:负责串口/SPI 字节接收,保证硬实时零丢包。
    • 普通函数:负责微秒级瞬时任务(如喂狗、读 ADC、帧解析),直接调用无额外开销。
    • PT 协程:负责需要分步执行或非阻塞延时的流程任务。




几个宏定义和1个小结构体,就实现了全部的功能


/**
* @brief 协程控制块结构体(每个任务仅占用 6 字节 RAM)
*/
typedef struct
{
    uint16_t lc;     // 记录上次让出时的代码行号 (Local Continuation)
    uint32_t timer;  // 记录非阻塞延时的起始时间戳
} pt_t;

// 外部全局 1ms 系统节拍(需在 SysTick_Handler 中每 1ms 自增 1)
extern volatile uint32_t sys_tick_ms;

// ==================== Protothreads 核心调度宏 ====================

// 1. 初始化协程控制块
#define PT_INIT(pt)          do { (pt)->lc = 0; (pt)->timer = 0; } while(0)

// 2. 协程函数体开始与结束标记
#define PT_BEGIN(pt)         switch((pt)->lc) { case 0:
#define PT_END(pt)           } (pt)->lc = 0; return 1

// 3. 主动让出 CPU(用于长耗时任务切片,下次调度从下一行继续)
#define PT_YIELD(pt)         do { (pt)->lc = __LINE__; return 0; case __LINE__:; } while(0)

// 4. 条件等待(条件不满足时让出 CPU,满足时继续往下执行)
#define PT_WAIT_UNTIL(pt, c) do { (pt)->lc = __LINE__; case __LINE__: if(!(c)) return 0; } while(0)

// 5. 非阻塞延时(单位:ms,基于 uint32_t 无符号减法,完美支持 49.7 天溢出回绕)
#define PT_DELAY(pt, ms)     do { \
                                 (pt)->timer = sys_tick_ms; \
                                 PT_WAIT_UNTIL((pt), (sys_tick_ms - (pt)->timer) >= (ms)); \
                             } while(0)


使用方法如下:

// ==================== 全局变量与协程句柄 ====================
volatile uint32_t sys_tick_ms = 0;             // 1ms 系统节拍
static volatile uint8_t need_refresh_flag = 0; // 刷屏触发标志

static pt_t pt_lcd;                            // 协程1:LCD 分步刷屏
static pt_t pt_led;                            // 协程2:LED 闪烁



// 协程任务A:LCD 全屏分步刷屏
uint8_t Task_LCD_Refresh(pt_t *pt)
{
        // 【关键规范】跨越 PT_YIELD 的循环变量必须加 static,禁止使用普通局部变量!
        static uint16_t current_row = 0;

        PT_BEGIN(pt);

        while (1)
        {
                // 等待刷屏触发信号
                PT_WAIT_UNTIL(pt, need_refresh_flag == 1);

                // 将 240 行刷屏拆分为 240 个微切片
                for (current_row = 0; current_row < 240; current_row++)
                {
                        // 局部瞬时计算不跨越让出点,可安全使用普通局部变量
                        {
                                uint16_t line_bytes = 320 * 2;
                                (void)line_bytes;
                                LCD_Draw_One_Line(current_row);
                        }

                        // 每刷完 1 行主动让出 CPU,确保通信与看门狗任务即时响应
                        PT_YIELD(pt);
                }

                need_refresh_flag = 0;
        }

        PT_END(pt);
}

// 协程任务B:LED 周期闪烁(亮 200ms,灭 800ms)
uint8_t Task_LED_Blink(pt_t *pt)
{
        PT_BEGIN(pt);

        while (1)
        {
                LED_ON();
                PT_DELAY(pt, 200); // 非阻塞延时 200ms

                LED_OFF();
                PT_DELAY(pt, 800); // 非阻塞延时 800ms
        }

        PT_END(pt);
}



int main(void)
{
        Hardware_Init();

        PT_INIT(&pt_lcd);
        PT_INIT(&pt_led);

        while (1)
        {
                // 混合调度:普通瞬时函数 + 无栈协程
                Normal_Task_Watchdog();
                Normal_Task_Comm();

                Task_LCD_Refresh(&pt_lcd);
                Task_LED_Blink(&pt_led);
        }
}

Protothreads协程.rar (4.62 KB, 下载次数: 0)

回复

使用道具 举报

4

主题

490

回帖

502

积分

金牌会员

积分
502
发表于 8 小时前 | 显示全部楼层
主循环中需要不断轮询每个协程状态机,所以不利于低功耗设计。在此基础上的contiki操作系统,它进一步设计了process概念,使得主循环不用轮询每个状态机,甚至当所有状态机都进入等待状态后会可进入睡眠模式,中断会唤醒对应的process来处理事件。
了解一下
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2026-11-12 21:13 , Processed in 0.224325 second(s), 26 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表