coroutine.h
#ifndef __COROUTINE_H__
#define __COROUTINE_H__
#include "pt.h"
#define PT_TIMER_USE_RTC 1
struct timer
{
#if PT_TIMER_USE_RTC
unsigned int current;
#endif
unsigned int interval;
unsigned int last;
};
extern unsigned int timer_expired(struct timer *t);
extern void timer_set(struct timer *t, unsigned int Interval);
extern void set_task(unsigned char id, PT_THREAD((*func)(struct pt *pt)));
extern void add_task(unsigned char id, PT_THREAD((*func)(struct pt *pt)));
extern int check_pt_main(int id);
extern int Idle_enable;
extern struct pt pt_temp[16];
extern void scheduler(void);
#endif
#include "coroutine.h"
#include "main.h"
#include "fifofast.h"
#define SLEEP_DEBUG 0
#define TASK_NUM (16)
PT_THREAD((*task[TASK_NUM])(struct pt *pt));
struct pt pt_main[TASK_NUM];
#define TASK_FIFO_SIZE (32)
_fff_declare(int8_t, tid_fifo, TASK_FIFO_SIZE);
_fff_init(tid_fifo);
#define DIS_INTR() __disable_irq()
#define ENA_INTR() __enable_irq()
int Idle_enable = 0;
void set_task(unsigned char id, PT_THREAD((*func)(struct pt *pt)))
{
task[id] = func;
}
void add_task(unsigned char id, PT_THREAD((*func)(struct pt *pt)))
{
if (!pt_main[id].lc)
{
_fff_write(tid_fifo, id);
}
PT_INIT(&pt_main[id]);
task[id] = func;
}
int check_pt_main(int id)
{
return (int)pt_main[id].lc;
}
unsigned int check_cur_task_num(void)
{
return (_fff_mem_level(tid_fifo));
}
#if PT_TIMER_USE_RTC
#define RTC_SS() (READ_REG(RTC->SSR))
// #define RTC_SS_MAX() ((unsigned int)1024)
#define RTC_SS_MAX() ((unsigned int)1000)
#define RTC_K() ((RTC_SS_MAX() << 8) / 1000)
#define RTC_KS8(n) (((n) * RTC_K()) >> 8)
unsigned int timer_expired(struct timer *t)
{
// RTC_SS is down count
unsigned int register pass_count;
unsigned int register now;
now = RTC_SS();
pass_count = (t->last >= now) ? t->last - now : (RTC_SS_MAX() - now) + t->last;
t->current += pass_count;
t->last = now;
return (t->current >= t->interval);
}
void timer_set(struct timer *t, unsigned int interval)
{
t->interval = RTC_KS8(interval);
t->last = RTC_SS();
t->current = 0;
}
#else
unsigned int timer_expired(struct timer *t)
{
return (unsigned int)(HAL_GetTick() - t->last) >= (unsigned int)t->interval;
}
void timer_set(struct timer *t, unsigned int Interval)
{
t->interval = Interval;
t->last = HAL_GetTick();
}
#endif
void scheduler(void)
{
while (_fff_mem_level(tid_fifo) != 0)
{
unsigned char cur_id;
cur_id = _fff_read(tid_fifo);
if (PT_SCHEDULE(task[cur_id](&pt_main[cur_id])))
{
DIS_INTR();
_fff_write(tid_fifo, cur_id);
ENA_INTR();
}
}
Idle_enable = 0;
}
# 協同排程器的運作原理
這個協同排程器(cooperative scheduler)是基於 **Protothreads** 函式庫實作的輕量級多工系統。讓我詳細說明它的運作機制:
## 1. 核心概念:合作式多工
與搶佔式(preemptive)作業系統不同,這個排程器採用**合作式**調度:
- 每個任務必須**主動讓出控制權**(透過 `PT_YIELD()` 或 `PT_WAIT_UNTIL()`)
- 不會強制中斷正在執行的任務
- 如果某個任務不讓出控制權,其他任務就會被「餓死」
## 2. 資料結構
```c
PT_THREAD((*task[TASK_NUM])(struct pt *pt)); // 任務函數指標陣列(最多16個)
struct pt pt_main[TASK_NUM]; // 每個任務的執行狀態
_fff_declare(int8_t, tid_fifo, TASK_FIFO_SIZE); // FIFO 佇列(最多32個任務ID)
```
- **`task[]`**:儲存每個任務的函數指標
- **`pt_main[]`**:儲存每個任務的 protothread 狀態,其中 `pt.lc`(line continuation)記錄任務上次中斷的位置
- **`tid_fifo`**:就緒佇列,存放準備執行的任務 ID
## 3. 排程器主迴圈
```c
void scheduler(void)
{
while (_fff_mem_level(tid_fifo) != 0) // 當佇列不為空
{
unsigned char cur_id;
cur_id = _fff_read(tid_fifo); // 從佇列取出任務ID
if (PT_SCHEDULE(task[cur_id](&pt_main[cur_id]))) // 執行任務
{
DIS_INTR(); // 關閉中斷
_fff_write(tid_fifo, cur_id); // 將任務ID放回佇列
ENA_INTR(); // 開啟中斷
}
}
Idle_enable = 0;
}
```
**運作流程:**
1. 檢查就緒佇列是否有任務
2. 從佇列頭部取出一個任務 ID
3. 執行該任務的 protothread 函數
4. 如果任務返回非零值(表示還有工作要做)→ 將任務 ID 放回佇列尾部
5. 如果任務返回零(表示任務結束)→ 不放回佇列
6. 重複直到佇列清空
## 4. Protothreads 的魔法:無堆疊協程
Protothreads 使用一個巧妙的技巧來實現「看起來會阻塞」但實際上不會消耗堆疊的程式碼:
```c
PT_THREAD(example_task(struct pt *pt))
{
PT_BEGIN(pt);
// 執行一些工作
while(1) {
PT_WAIT_UNTIL(pt, condition_is_true); // 在此「等待」
// 做某事
PT_YIELD(pt); // 讓出控制權
}
PT_END(pt);
}
```
**關鍵機制:**
- `pt->lc` 儲存一個整數,代表上次讓出控制權的「程式碼位置」
- 每次呼叫任務函數時,會用 `switch-case` 跳到上次的位置繼續執行
- 這樣就能在沒有堆疊的情況下「記住」執行進度
## 5. 任務管理
**新增任務:**
```c
void add_task(unsigned char id, PT_THREAD((*func)(struct pt *pt)))
{
if (!pt_main[id].lc) // 如果任務尚未初始化
{
_fff_write(tid_fifo, id); // 將任務ID加入就緒佇列
}
PT_INIT(&pt_main[id]); // 初始化 protothread 狀態
task[id] = func; // 設定任務函數
}
```
**重要特性:**
- 任務 ID 是陣列索引,必須保持穩定(0-15)
- 如果 `pt_main[id].lc` 非零,表示任務「還活著」
- 任務可以從主程式或中斷服務常式中喚醒
## 6. 計時器系統
本專案預設使用 **RTC(即時時鐘)** 的子秒暫存器來實現高精度計時:
```c
unsigned int timer_expired(struct timer *t)
{
unsigned int pass_count;
unsigned int now = RTC_SS(); // RTC 是遞減計數(999→0)
// 計算經過的時間(處理環繞情況)
pass_count = (t->last >= now) ?
t->last - now :
(RTC_SS_MAX() - now) + t->last;
t->current += pass_count;
t->last = now;
return (t->current >= t->interval);
}
```
**使用方式:**
```c
struct timer my_timer;
timer_set(&my_timer, 1000); // 設定 1000ms
PT_WAIT_UNTIL(pt, timer_expired(&my_timer)); // 等待計時器到期
```
## 7. 關鍵設計要點
**為什麼需要關閉中斷?**
```c
DIS_INTR();
_fff_write(tid_fifo, cur_id);
ENA_INTR();
```
因為中斷服務常式也可能呼叫 `add_task()` 來喚醒任務,如果不保護 FIFO 寫入操作,可能會發生競爭條件導致資料損壞。
**限制:**
- 最多 16 個並行任務(`TASK_NUM = 16`)
- 就緒佇列最多 32 個條目(`TASK_FIFO_SIZE = 32`)
- 如果 32 個插槽全滿,新任務會遺失
**效能考量:**
- 任務必須頻繁讓出控制權,否則會增加中斷延遲
- 關鍵區間(critical section)越短越好
- 適合處理 I/O、通訊、LED 動畫等需要「等待」的任務
## 總結
這個排程器實現了:
✅ 輕量級多工(每個任務只需少量記憶體)
✅ 易於閱讀的「線性」程式碼風格(看起來像會阻塞)
✅ 適合資源受限的 MCU(STM32F072 只有 16KB RAM)
✅ 可從主迴圈或中斷喚醒任務
但需要注意:
⚠️ 任務必須主動讓出控制權
⚠️ 一個失控的任務會拖累整個系統
⚠️ 無法利用多核心(單執行緒輪詢)
自己的程式,叫copilot解釋看看。寫得不錯,直接貼上。
回覆刪除只有Idle_enable = 0;沒有解譯,因為無中斷,那個位置放的是sleep程序,整個MCU可以直接進入完全停止的狀態,直到中斷叫醒。這裏我只留一個變數, AI自然無法判定要做什麼事。
回覆刪除