2012年12月29日 星期六

STM8 Low Power Run引發的問題

STM8 Low Power Run是最省電的執行模式,省電省到連Flash ROM的電都關閉,只使用SRAM執行程式。
但引發一個有趣的問題:如何將程式載入到RAM中。

有些編譯器支援inram這個函式修飾字。
但bee個人是覺得1KB的RAM中若是有幾個函式都是inram,是會有問題的。
因為inram也是有分不同狀況,不會全部狀況的都是要用到inram。
也就是不同狀況inram的函式不同,為了節省RAM,都是要用時才使用Flash複製到RAM。

這樣就會產生程式是存於Flash中,但執行時在RAM中。
這樣子並不符合一般編譯器運作原理,且會引發程式重定位問題。
比較簡單的方式是,在RAM中執行的程式用另一個專案去編。
再將Machine Code以data的方式存於Flash ROM中,執行inram前再將程式複到到RAM中。


MCU多功能IO應用

確定STM8是6502進化版,沒想到是在這種狀況下學到6502。
不管其核心,ST的周邊系統是不錯的。

在使用開發板的觸控應用,發覺效果不錯。
可是其上的STM8使用的是一般GPIO腳。
原理是將GPIO設定高電位、低電位及高阻抗之間變換做RC充放電。
並應用輸入位準變換來測RC充放電時間。
真是將GPIO用到極致。

要不是從信號上來看,光看程式應是無法看懂是如何做觸控檢測。
所以MCU程式,並不是純軟體,而是信號產生及處理的程式。

2012年12月24日 星期一

STM8使用評估

STM8是一個8位元MCU。
所以,它和8051及Cortex M0+的關係造成Bee做為評估的原因。
其實又和MSP430也有一點關係。

就價格來說,Cortex M0+已將8位元及16位元MCU價格給封頂了。
所以只能在Cortex M0+無法達到的地方才有考量其他微控器的需求。
Bee就遇到,主是功耗考量。

在具有功耗考量的環境,其實就是使用電池電力的環境。
長期以來一直是MSP430的天下。
不過MSP430和Cortex M0+的價格已經開始重疊。
再低價下去,就要往8051做為考量。不過8051的功耗似乎是低不下來。
此時Bee想到FAE曾經告知,現行新的8位元MCU其功耗不比MSP430來得差。
這是有可能的,因為不考量效能的狀況下,8位元MCU其邏輯閘數是較低,功耗也可較低。
所以就去評估STM8L051F3這個MCU。

STM8除核心外,其週邊架構和STM32是同一套結構。
這個架構會使得習慣使用STM32的C語言使用者容易去習慣STM8。
週邊從STM32來的特性沒有降太多。
有12位元ADC、USART、SPI、I2C及RTC,放了不少。
還有DMA,可以節省通信上的控制。這點Bee還蠻喜歡的。
另一個可以利用UART做軟體更新,只需利用原先UART通信,不像MSP430是另外一型通信格式。

只是8位元MCU的價格及功能,Bee看了只是覺得做MCU硬體的錢還真難賺。


2012年11月18日 星期日

如何在VS2010下編譯64位元組合語言

查了一些資料,自己試了一下。簡單記錄以免又忘記。
1. 在VS2010下面開一個新Project,選用Empty Project,這點和開純C的程式一樣。
2. Platform改為x64
3. 點專案名右鍵,找到Build Customization,將其masm選項打勾
4. 在Source File下加入 *.asm。若為新檔,可以先選用C++,再將副檔名改為asm。
5. 修改Proprety內的設定:
   a. 在Advanced下的Entry Point寫上啟始函式名
   b. 在System下的SubSystem選一個型別

看起來容易,問題是不好查。
除錯時只要將Disassembly打開就可以了。
有了除錯器,再來就簡單了。


2012年11月11日 星期日

64位元組合語言讀書心得

除了介紹X64組合語言外,也介紹了Win64 ABI。
看了Win64 ABI終於了解為何Win32的程式在Win64下是無法使用的。
因為改了基本的C函式呼叫的動作。
為了增加C函式呼叫的速度,將原先參數堆放在Stack的動作,部分改到暫存器中。
這確實會增加執行效率,而Bee在ARM處理器也看到是用這樣的動作。
Win64 ABI是使用四個暫存器 RCX, RDX, R8, R9。
一時好奇去查了一下Linux ABI用了六個RDI, RSI, RDX, RCX, R8, R9。
看來大家都是用同一招做C函式呼叫加速。

其他的,就和一般處理器差不多。
再下來就是我自己的作業了:寫一個程式。


2012年10月24日 星期三

程式寫好,非但沒有讚美,反而被罵!

這是以前公司真實事件。


公司機器從我進公司以來,一直有生產調精準度問題。


機器程式到我手上已有四年,產品必須改善功能才能追上市場腳步。


在新加功能中,多了一個檢知器,可以看到工件上標記,並進行加工。


開發過程中,發現系統使用3*3矩陣做映射對位。這塊程式古老到超過十年未有人去動。


為了符合新觀察到的現象,我使用4*4矩陣來做映射對位。


效果出奇的好,精度提高4倍。且生技工程師也稱讚比以前好調很多。


這次修改只動用到軟體,沒有增加任何硬體,等於未加入任何成本。


我認為這是件不錯的事。


 


主管列入績效,卻沒有下文。


去追結果,此項似乎被老闆刪除了。


 


半年後,主管提出我升職申請,再次列入績效內。


很怪的是,我升不了。要不是主管拼了命一再申請,我根本升不了。


 


既然升了,總要報告我的工作績效。


一提到此項功能,我總算了解我升不了的原因。


在研發會議上,老闆指著我這個項目,對著四十幾位工程師面前說:


"這明顯就是一個軟體錯誤,只是修正錯誤,根本不是功能。你根本不夠格談這件事。"


好吧!這件事老闆如此說,我也不回嘴。


接下來,我便報告目前我手上維護機器數目及名稱。大約有五十多機種。


 


三個月後,我離職了。還好接手工程師程度好,沒有問我太多問題。


公司機種大約一百多種,在我手上快一半,銷售數量佔產出機器70%


 


真不知老闆到底在想什麼?



2012年8月12日 星期日

新工作一年感想

一句話:突飛猛進。

工作量大,新技術不斷出現。部門人口暴增三倍。
所以就沒有多少時間寫文章了。

一邊工作,一邊找人。只是,人難找。
找嵌入式系統的人,都是做手機,不想來碰硬體。
找單晶片的人,不懂RTOS。
找馬達控制的人,幾乎沒有工作經驗。
找影像處理的人,不幸的目前無空缺。
有合的真的不多。



簡單多工3.1版


Bee用第3版發展到程式大了,使用的Task數目一多,整個反應時間就慢了。
但有不少Task其實作用時間很短,且工作量很低。
所以就加入了暫時性的工作。
其實考量了可以使用暫時性工作可以用,大部分都是暫時性工作的使用機會高。
這樣就不會一下子增加太多的工作數目,可以保持掃描率,反應不會太慢。

只是暫時性工作要除錯完成再改為暫時性管理方式。因為TaskID一直變的話是不好追蹤的。

新增程式功能說明:

#define MIN_FREE_NUM    3
設定從那一個ID數往後找

unsigned char Get_Temp_Task(void (*p_func)(void));
設定暫時性工作,傳入啟動函式,回傳可用的TaskID。
若回傳為0xFF,則是無空的Task可用。

void Task_Retire(void);
暫時性工作結束

unsigned char Check_Task_Free(unsigned char TaskID);
看工作是否為空閒,主要是用來檢查暫時性工作是否結束。


整體程式:
#include "mt.h"
#define TASK_NUM        4
#define MIN_FREE_NUM    3
extern void SimInit(void);
extern void OS_ENTER_CRITICAL(void);
extern void OS_EXIT_CRITICAL(void);

#define INIT_DEVICE()        SimInit()
#define DIS_INTR()            OS_ENTER_CRITICAL()
#define ENA_INTR()            OS_EXIT_CRITICAL()
// O.S function declaim
void Dummy(void);
unsigned char FuncID;
volatile unsigned char Task_State[TASK_NUM];
unsigned short Task_Delay_Count[TASK_NUM];
void (*TaskFunc[TASK_NUM])(void);

// O.S functions
void InitSystem(void)
{
    extern void Task1(void);
    extern void Task2(void);
    extern void Task3(void);
    unsigned char i;

    FuncID = 0;
    for ( i= 0; i< TASK_NUM ; i++)
    {
        Task_State[i] = TASK_RUNNING;
        Task_Delay_Count[i] = 0;
        TaskFunc[i] = Dummy;
    }
    TaskFunc[0] = Task1;
    TaskFunc[1] = Task2;
    TaskFunc[2] = Task3;
}

void Sleep_Next(void (*p_func)(void))
{
    TaskFunc[FuncID] = p_func;
    Task_State[FuncID] = TASK_WAIT;
}

void WakeUp(unsigned char n)
{
    Task_State[n] = TASK_RUNNING;
}

void Task_Set_Next(void (*p_func)(void))
{
    TaskFunc[FuncID]=p_func;
}

void Dummy(void)
{
    Sleep_Next(Dummy);
}

void Task_Delay_Ms_Next(unsigned int n, void (*p_func)(void))
{
    if (! n ) n = 1;
    TaskFunc[FuncID] = p_func;
    Task_Delay_Count[FuncID] = n;
    Task_State[FuncID] = TASK_DELAY;
}

void Task_Set_Skip_Next(unsigned int n, void (*p_func)(void))
{
    if (! n ) n = 1;
    TaskFunc[FuncID] = p_func;
    Task_Delay_Count[FuncID] = n;
    Task_State[FuncID] = TASK_SKIP;
}

unsigned char Get_Temp_Task(void (*p_func)(void))
{
    int i = MIN_FREE_NUM;
    while(i < TASK_NUM )
    {
        if( TaskFunc[i] == Dummy )
        {
            TaskFunc[i] = p_func;
            return i;
        }
        i++;
    }
    return 0xff;
}

void Task_Retire(void)
{
    Sleep_Next(Dummy);
}

unsigned char Check_Task_Free(unsigned char TaskID)
{
    return ( TaskFunc[TaskID] == Dummy )? 1: 0;
}

// O.S functions end

// main loop
void main(void)
{
    INIT_DEVICE();
    InitSystem();
    while (1)
    {
        switch (Task_State[FuncID])
        {
        case TASK_RUNNING :
            TaskFunc[FuncID]();
            break;
        case TASK_SKIP :
            if ( Task_Delay_Count[FuncID] )
            {
                Task_Delay_Count[FuncID]--;
                if (! Task_Delay_Count[FuncID]) WakeUp(FuncID);
            }
            break;
        default:
            break;
        }
        if ( ++FuncID >= TASK_NUM ) FuncID = 0;
    }
}

void TimeTick(void)  // This is timer interrupt
{
    unsigned char i;

    for ( i=0; i< TASK_NUM; i++)
    {
        if ( Task_State[i] == TASK_DELAY )
        {
            if ( Task_Delay_Count[i] )
            {
                Task_Delay_Count[i]--;
                if (! Task_Delay_Count[i]) WakeUp(i);
            }
        }
    }
}



開頭檔內容:
void Sleep_Next(void (*p_func)(void));
void WakeUp(unsigned char n);
void Task_Set_Next(void (*p_func)(void));
void Task_Delay_Ms_Next(unsigned int n, void (*p_func)(void));
void Task_Set_Skip_Next(unsigned int n, void (*p_func)(void));
unsigned char Get_Temp_Task(void (*p_func)(void));
void Task_Retire(void);
unsigned char Check_Task_Free(unsigned char TaskID);

//  Task State
#define        TASK_RUNNING  0
#define        TASK_SKIP     1
#define        TASK_DELAY    2
#define        TASK_WAIT     3

// O.S. Macro
#define     SET_TASK(id,p_func)     {TaskFunc[id]=p_func;}
#define     SET_STATE(id,state)     {Task_State[id]=state;}
#define     SET_DELAY(id,n)         {Task_Delay_Count[id]=n;}

// Time Constant
#define     SKIP_CYCLE_TIME_US      (0.083)
#define     SKIP_US(x)              ((int)((x)*(1.0/SKIP_CYCLE_TIME_US)))


測試程式:
#include < stdio.h >
#include "mt.h"

unsigned char temp_count;
void TempTask(void)
{
    printf("T");
    temp_count++;
    if(temp_count < 10)
    {
        Task_Set_Skip_Next(SKIP_US(500),TempTask);
    }
    else
    {
        Task_Retire();
    }
}

void Task1(void)
{
    unsigned char task_handle;
  
    printf("G");
    temp_count = 0;
    task_handle = Get_Temp_Task(TempTask);
    WakeUp(task_handle);
    Task_Delay_Ms_Next(10,Task1);
}

void Task2(void)
{
    printf("z");
    Task_Delay_Ms_Next(1,Task2);
}

void Task3(void)
{
    printf(".");
    Task_Set_Skip_Next(SKIP_US(1000),Task3);
}


測試結果:



2012年8月5日 星期日

Cortex-M3 : 遇到HardFault該如何處理

寫Config週邊時最常遇到的是,週邊不動,不然就是HardFault。

若是發展中的程式,還知道現在正在加入的是那一個功能。

但拿到的是舊程式,出現HardFault就糟了,因為從那裏產生的都不知。

其實沒有如此糟,只要去看堆疊內容就行了。

SP+24的位置,就是產生HardFault的位址,一般會是標準函式庫。
就可以查出是那一個呼叫弄錯了。

另外SP+20則是Link Register的內容,可以進一步告知前一個呼叫函式位置。
往前追就可以看到是那一個應用程式弄錯了。

不過大概要會組合語言的人才會知道怎麼回事,原理不多說了。


2012年7月29日 星期日

機器視覺的計算硬體

目前可以使用的計算硬體有:

1. CPU
  這是最傳統的,但計算效能較低。就算是影像用的DSP,效能仍輸以下介紹的硬體。
  但它有一個好處是其他硬體沒有的:良好的除錯及程式修改環境。
 
2. FPGA
  Bee有一段時間是想要做這個,才開始要做就出現了另一個硬體。
  使用FPGA有最快的執行效能,但是要包含記憶體控制才能做得好。
  所以寫程式的難度算是最高的。相對的價格也是高。
  開發慢且修改不易是FPGA影像處理發展上的門檻。
 
3. GPU
  不錯的執行效能及相對便宜的價格。程式開發也還可以,也不需擔心記憶體管理上的瑣事。
  只是起步較晚資料相對少。
 
之所以提這個問題,是看到有人想往機器視覺發展,選擇的路徑有些奇怪。
可能是限制於知識背景,所以使用單一方法去做。

Bee則是因為三種方法都會並無此問題。
問題反而是:正確可以計算的方法為何?
Bee採用的方法是:
先以CPU的架構先去取得資料,採用背景分析的方式去找運算方法。
然後採用GPU的方式去實現,因為程式相近,所以轉換較快速。
再不行的話再將前處理以FPGA去處理,不過還沒走到這一步過。

對於FPGA還不是很熟的人要去做機器視覺,Bee只能說:請您自求多福。    


2012年5月6日 星期日

STM32和RTOS的完美配合

STM32後期,Timer越來越多,和週邊控制相關的信號,Bee皆以Timer做為產生或是觸發源。
另有一些和軟體相關,或是慢速度IO則以RTOS中的時間延時函式來做。
這樣的組合,使STM32無需高度依賴高即時性的RTOS,使用上更加方便。

不過也引入一點問題:系統要如何分割?
在這點上面,Bee目前所處的團隊完全看不出這個問題。因為分工得很好,各思其職。
在名目上有二組人,一組是做系統,一組是做人工智慧。

Bee在系統組內,做IO設定的軟體,也就是利用Timer及RTOS實現各式週邊。
因為工作都是一個人規劃,所以無分割上的衝突。

之所以工作分到Bee這裏來,是因為STM32手冊有1300頁之大。所以有人會說STM32並不好入門。

功能最大的Timer,具有多種能力,可以是Input也可以是Output,連畫電路的工程師都搞不清楚。

最後都是由Bee來建議使用腳位。

基本上要操作STM32,需要Cortex-M3使用手冊,這個不是ST給的。一份stm32 reference manual。
還有一個型號的manual。除了Cortex-M3使用手冊之外,其餘皆為原文。

這種軟硬體間的苦工,就由Bee包了。另外RTOS也是。
RTOS將硬體隔開,人工智慧的程式在測試及移植上較為容易得多。

只需提供好進入函式,不用擔心干擾其他程序,可以盡全力執行。其餘必要工作,不是RTOS負擔,就是Timer和DMA做了。

這樣工程就由數人同時開發。另有一人全力最統合,就是將開發完成的程式組合起來並測試。
系統中還有一位工程師是做介面的,將資料送到PC的介面程式。基本上就是人機界面。

軟體工程師的職務如下
系統工程師1:硬體及驅動,這就是Bee目前的職務
系統工程師2:整合及系統建造
系統工程師3:人機界面
演算法工程師:機器學習及人工智能開發
測試工程師:測試整機成果

若是沒有RTOS,要做到並行開發,那就真的需要很大的規劃了。


2012年4月28日 星期六

簡單多工3版補強

主要是補充巨集。因為結構簡化,使用巨集就可以了。
巨集中的SKIP_CYCLE_TIME_US,請自己填入量測值,現在的4.14是Bee目前用的值,在不同應用值皆不同。
Time Constant是用在 Task_Set_Skip_Next中,可以用    Task_Set_Skip_Next(SKIP_US(25), NextFunction);    使工作延時約25us,但受限於掃描率的時間單位。只是不用自己去算次數,閱讀上也方便些。


void Sleep_Next(void (*p_func)(void));
void WakeUp(unsigned char n);
void Task_Set_Next(void (*p_func)(void));
void Task_Delay_Ms_Next(unsigned int n, void (*p_func)(void));
void Task_Set_Skip_Next(unsigned int n, void (*p_func)(void));


//  Task State
#define  TASK_RUNNING 0
#define  TASK_SKIP  1
#define  TASK_DELAY  2
#define  TASK_WAIT  3


// O.S. Macro
#define     SET_TASK(id,p_func)     {TaskFunc[id]=p_func;}
#define     SET_STATE(id,state)     {Task_State[id]=state;}
#define     SET_DELAY(id,n)         {Task_Delay_Count[id]=n;}


// Time Constant
#define     SKIP_CYCLE_TIME_US      (4.14)
#define     SKIP_TIME_CONSTANT      (1.0/SKIP_CYCLE_TIME_US)
#define     SKIP_US(x)              ((int)((x)*SKIP_TIME_CONSTANT))


32位元MCU新時代

Bee換工作後,參與了一個不錯的團隊。Bee主要工作是架設RTOS及連接硬體裝置。
或是依硬體設定產生所要的信號。

其中有一組人員是搞機器學習。Bee所設計的程式在機器上會收取資料。
機器學習就是將這些機器上的IO及Sensor資料在PC上學習。
有時PC要跑上數小時,再將學習結果轉化為簡單矩陣回存到MCU。
MCU依據現在IO及Sensor的數值,再乘上學習矩陣,很快就得到決策結果。

Bee在未接觸機器學習前,對於人工智慧是停留在遊戲等級,是以規則庫為主的系統。
但機器學習的反應速度,實在是出奇的快,規則庫絶非對手。
不過原先的機器學習是在PC上發展出來的,一但MCU進入32位元。
浮點運算力足夠應付,就開始使用這些新的演算法產生新產品。

32位元MCU直接將MCU帶入新時代。一個以數位信號處理及機器學習的新時代。


2012年4月14日 星期六

STM32 DMA應用

在Cortex-M3上所有的週邊皆有DMA,第一次接觸還覺得很怪。因為以往的單晶片並無此類裝置。

最奇怪的,是ADC不用DMA還不行。主因是ADC支援multi-channel,但卻只有一個data register。

若是啟動Scan功能,會一次多個channel掃入,就好像是有多個ADC,只是時間上是分開的。
但結果全部只在一個data register上。
所以問題來了,一但Scan一個channel結束,沒有取走資料,下一個channel會蓋過。

DMA的功能來了,它就是將資料依各channel分開將結果傳到RAM的位置上。
這個功能很方便,可以將RAM當成是ADC的結果register。還可以利用DMA的半滿旗標,做成double buffer。

其他週邊,如通信也有,若是已知的傳輸內容,可以設定DMA一次做完再通知。
然後就用在UART上看看,設定bund rate為1.5Mhz,就照計算上的速率在傳,傳輸利用率是100%。

Bee在一個24Mhz的單晶片上,利用DMA就實現了us級的信號時間控制。
在幾年前Bee遇到這樣的狀況會利用FPGA/CPLD去做。
但有DMA,FPGA就可以省下來了。
而且ADC也可以設定在訊號產生時做取樣,做到接近FPGA的時間控制,而且有12位元解析度。

重點是,單晶片本身的價格不高,已可以做到精確的時間上做取樣的工作。
所以單晶片在ARM Cortex-M系列的加持下,進入了新的應用方式。
接下來,只剩下工程師要如何發揮的問題了。


2012年3月23日 星期五

MCU使用變革:ADC,DAC皆免費時,數位訊號處理(DSP)將大量使用

最近使用STM32F207,發現到MCU使用習慣改變了。



因為ADC及DAC皆為內建,而且連接DMA的設定下,CPU可以去做DSP計算。



這個改變主因是有三項條件到位:
1. ADC及DAC精度提升
2. ADC取樣率提升
3. CPU運算力提升



結果使得DSP運算力到位。



DSP功能免費,可以將原先做信號處理的硬體省下來,變成軟體去做。



意指:外部的運算放大器使用率將下降。


原先的濾波器,可以使用FIR、IIR等以運算程式取代。


 


這對於原先就玩DSP的人也許沒有什麼。


但對於使用8051的工程師來說就不同了,因為大都沒有學過數位信號處理。


使用新的32位元微控器,將使電路大幅簡化。


而且8位元及32位元MCU差價不多,會使用DSP技術會使硬體成本下降。


"數位信號處理"技術會使得控制系統設計變得不同。



且從一開始的系統設計就呈現不一樣的思維。



 


也許還以為8位元MCU可以利用價格低,來取得產品優勢的這個狀況不再。


32位元MCU是較貴,但整體系統設計的元件少,會使總成本下降。


客戶買單是看總成本,而非MCU成本。


所以Cortex-M系列的低價壓制8051這件事將會成真。



 


單晶片工程師想要在這場MCU變革中存活下來,數位訊號處理會變成新的必備技術。


2012年2月11日 星期六

使用簡單多工3版遇到的問題

主要是在使用WakeUp()在中斷程式所遇到的問題。
因為新加的功能會方便很多,所以在中斷程式中Bee用了不少,結果還是發生程序卡死的狀況。
程式碼如下:

void ADC_Start(void)
{
    void ADC_GetValue(void);

    Start_ADC();
    Sleep_Next(ADC_GetValue);
}

中斷程式中為
    WakeUp(ADC_TASK_ID);

結果Run一段時間會卡死。
去看Task_State[ADC_TASK_ID]內容為3,就是TASK_WAIT
但ADC暫存器卻顯示已產生過中斷。

後來將Start_ADC();放在最後一行,問題就沒有了。

發生這樣的問題主要是下指令到產生中斷時間太短,在進入Sleep之前就產生中斷。
然後Sleep_Next才將TASK_WAIT寫入Task_State[ADC_TASK_ID],從此變成長睡不起。

以後要將會產生中斷的指令寫在函式最尾,以免再發生這樣的問題。


2012年2月5日 星期日

使用Cortex-M3後的事

在前公司,一直摸不到ARM。在技術上這是一個趨勢,早晚會碰到,不如早點用,但苦無機會。
現在用Cortex-M3,不只用,還要很熟。

在設計上有次算錯了效能,結果放了一個FPGA上去。FPGA廠商很高興,因為公司以往最多用到CPLD,遇到會寫的人,自然全力支援。
就有二家全力支援產品開發,都希望有機會採用。
後來Bee就發現MCU也可以做,加上開始熟STM32,結果用中斷程式不用十行就解決了。
對公司是好的,又少了一個零件。但FPGA廠商就沒這樣高興了。

去年底出了Cortex-M4,廠商送來Demo Board,趁空擋試了一下,將FreeRTOS給裝上去,看來還不錯。
不過因為案子皆已開跑,就先放著。
然後,其他也出M4的廠商也來訪,也送來一套。過完農曆年,又一家廠商來也想推。
看來Cortex-M系列真的很拼,MCU市場還真難賺。

2012年1月26日 星期四

簡單多工3版

在STM32上運行時發現,可以不使用DIS_INTR()這個巨集。
因為這個在原子操作下已無需要。
後來就將Task管理,從function pointer改為原子操作下的資料。
結果發現,還可以進一步省下記憶體。

另外Skip方式也改了,改到主程式去做。現在不會跳去Dummy(),檢查完畢就移到下一個工作去。

新增功能有
WakeUp(i),可以從中斷程式中呼叫,所以可以設定只由中斷叫醒的Task,免去使用全域變數做溝通。
Sleep_Next(p_func),Task沉睡,設定醒來時執行函式,只能由WakeUp(i)叫醒。

新版本程式如下:
#include "mt.h"
#define TASK_NUM    4
extern void SimInit(void);
extern void Task1(void);
extern void Task2(void);
extern void Task3(void);
extern void OS_ENTER_CRITICAL(void);
extern void OS_EXIT_CRITICAL(void);

#define INIT_DEVICE()        SimInit()
#define DIS_INTR()            OS_ENTER_CRITICAL()
#define ENA_INTR()            OS_EXIT_CRITICAL()
// O.S function declaim
void Dummy(void);
unsigned char FuncID;
volatile unsigned char Task_State[TASK_NUM];
unsigned short Task_Delay_Count[TASK_NUM];
void (*TaskFunc[TASK_NUM])(void);

// O.S functions
void InitSystem(void)
{
    unsigned char i;

    FuncID = 0;
    for ( i= 0; i< TASK_NUM ; i++)
    {
        Task_State[i] = TASK_RUNNING;
        Task_Delay_Count[i] = 0;
        TaskFunc[i] = Dummy;
    }
    TaskFunc[1] = Task1;
    TaskFunc[2] = Task2;
    TaskFunc[3] = Task3;
}

void Sleep_Next(void (*p_func)(void))
{
    TaskFunc[FuncID] = p_func;
    Task_State[FuncID] = TASK_WAIT;
}

void WakeUp(unsigned char n)
{
    Task_State[n] = TASK_RUNNING;
}

void Task_Set_Next(void (*p_func)(void))
{
    TaskFunc[FuncID]=p_func;
}

void Dummy(void)
{
    Sleep_Next(Dummy);
}

void Task_Delay_Ms_Next(unsigned int n, void (*p_func)(void))
{
    if (! n ) n = 1;
    TaskFunc[FuncID] = p_func;
    Task_Delay_Count[FuncID] = n;
    Task_State[FuncID] = TASK_DELAY;
}

void Task_Set_Skip_Next(unsigned int n, void (*p_func)(void))
{
    if (! n ) n = 1;
    TaskFunc[FuncID] = p_func;
    Task_Delay_Count[FuncID] = n;
    Task_State[FuncID] = TASK_SKIP;
}

// O.S functions end

// main loop
void main(void)
{
    INIT_DEVICE();
    InitSystem();
    while (1)
    {
        switch (Task_State[FuncID])
        {
        case TASK_RUNNING :
            TaskFunc[FuncID]();
            break;
        case TASK_SKIP :
            if ( Task_Delay_Count[FuncID] )
            {
                Task_Delay_Count[FuncID]--;
                if (! Task_Delay_Count[FuncID]) WakeUp(FuncID);
            }
            break;
        default:
            break;
        }
        if ( ++FuncID >= TASK_NUM ) FuncID = 0;
    }
}

void TimeTick(void)  // This is timer interrupt
{
    unsigned char i;

    for ( i=0; i< TASK_NUM; i++)
    {
        if ( Task_State[i] == TASK_DELAY )
        {
            if ( Task_Delay_Count[i] )
            {
                Task_Delay_Count[i]--;
                if (! Task_Delay_Count[i]) WakeUp(i);
            }
        }
    }
}

// Begin User Tasks

mt.h內容為
void Sleep_Next(void (*p_func)(void));
void WakeUp(unsigned char n);
void Task_Set_Next(void (*p_func)(void));
void Task_Delay_Ms_Next(unsigned int n, void (*p_func)(void));
void Task_Set_Skip_Next(unsigned int n, void (*p_func)(void));

//  Task State
#define        TASK_RUNNING    0
#define        TASK_SKIP          1
#define        TASK_DELAY        2
#define        TASK_WAIT          3

實驗程式如下:
#include < stdio.h >
#include "mt.h"
void Task1(void)
{
    printf("G");
    Task_Delay_Ms_Next(10,Task1);
}

void Task2(void)
{
    printf("z");
    Task_Delay_Ms_Next(1,Task2);
}

void Task3(void)
{
    printf(".");
    Task_Set_Skip_Next(60000,Task3);
}

Windows模擬結果