2010年2月18日 星期四

8051簡單多工3:簡易延時

這個簡易延時,就是跳過數次執行後再回復所要執行的函式。可以做為微量延時用,也是Bee用來控制LCM的主要方法。
但它並不精準,只能保證大於某一個時間。
它新加了以下程式:
unsigned char Task_Skip_Count[TASK_NUM]={0,0,0,0};
void (*TaskResumeFunc[TASK_NUM])(void) = {Dummy,Dummy,TestSkip0,LED_Light};

void Task_Skip(void)
{
    if ( Task_Skip_Count[TaskID] ){
        Task_Skip_Count[TaskID]--;
        if (! Task_Skip_Count[TaskID]) TaskFunc[TaskID]=TaskResumeFunc[TaskID];
    }
}

void Task_Skip_Next(void (*p_func)(void))
{
    TaskResumeFunc[TaskID]=p_func;
}

void Task_Set_Skip(unsigned char n)
{
    if (! n ) n = 1;
    TaskFunc[TaskID] = Task_Skip; // to sleep
    Task_Skip_Count[TaskID] = n;
}

各函式功能說明:
TaskSkip()就是實際執行計數用函式,在各Task未回復到使用者的程式前,就是先用它頂進去做計數用。
Task_Skip_Next()設定延時結束時,要回復的函式。先存於TaskResumeFunc[TASK_NUM]變數中。
Task_Set_Skip()設定跳過執行的次數。
因為後二個太常用一起用,所以可以用一個巨集取代。
#define TASK_SKIP_N_NEXT(n,p_func)        Task_Skip_Next(p_func);Task_Set_Skip(n);

在使用上因為涉及時間控制,但各處理器速度不同。所以只能實際量測。
Bee是先用一次簡單延時再觸發8051的腳位,量出時間,才確定要給多少值。
要是那次有別的工作用了比較長的時間,那就不保證,所以只能確定至少有多長。
像Bee用的LCM其命令之間需要73us的狀況,差不多設定跳過9次就足夠了。好在多的時間不太會影響。

另外程式開發完成後最好再量一次,因為工作多了,會和開發中的延時狀況不同,Bee的LCM程式在開發完成後發現延時多了,結果又降了一次。

有人會問,使用這種延時有什麼好處?
我只能說,可以讓多組這樣的程式一起跑,不會因一組延時就卡死。這就是一種多工的方法。


8051簡單多工2:基本設計規格及原理

在8051上想做多工,安裝RTOS又太大,大的不是程式本身,而是使用於Task registers記憶體太多了。
為了省下使用記憶體及加速切換,改用函式指標的方式做為多工切換的方法。
在多組程式運行的系統上,必須有FuncID做為各別task區分用。
所以核心的程式樣貌是這樣:
idata unsigned char FuncID = 0;
idata void (*TaskFunc[TASK_NUM])(void) = {Dummy,Dummy,TestSkip0,LED_Light};
void main(void)
{
    Init_Device();
    while (1)
    {
        register void (*Current_Func)(void);
        EA = 0;
        Current_Func = TaskFunc[FuncID]; // may break with interrupt
        EA = 1;
        Current_Func();
        FuncID = (++FuncID) % TASK_NUM;
    }
}
其實這段程式原是想要這樣執行:
while (1)
{
    TaskFunc[0]();
    TaskFunc[1]();
    TaskFunc[2]();
    TaskFunc[3]();
}
而TaskFunc[]內容是可以由程式改變,而原先初值設定為Dummy(),就會執行Dummy()。
如果要做程式移轉,只要做TaskFunc[0]=Dummy2;就會改為執行Dummy2()。
兩個程式若是要對跳,可以這樣寫:
void Test1(void)
{
    // do 1
    TaskFunc[0] = Test2;
}

void Test2(void)
{
    // do 2
    TaskFunc[0] = Test1;
}
然後執行時就可以看到TaskFunc[0]()執行動作為,一次執行Test1(),一次執行Test2()不停循環下去。
為了省去寫程式去記目前Task使用的FuncID,所以加入一組函式來做。
void Task_Set_Next(void (*p_func)(void))
{
    TaskFunc[FuncID]=p_func;
}
程式就可以簡單寫為
void Test1(void)
{
    // do 1
    Task_Set_Next(Test2);
}

void Test2(void)
{
    // do 2
    Task_Set_Next(Test1);
}

但各程式塊不想看到其他程式,也就是要有管理程式來分隔。
所要加入的管理功能,首要是時間控制。有二種時間控制是Bee需要的。
1.短暫時間控制
    定義為不使用中斷方式,以程式跳過執行權方式做延時。跳過次數可以設定。
2.ms級時間控制
    定義為使用中斷方式,在執行多少ms後回復執行權。
其實並不是真正的放棄執行權,而是函式指標暫時以Dummy填入,待延時結束後才填回使用者的程式。
這時FuncID的用處出來了,因為它可以知道是那一組可以做回復動作。

最後,主程式要去開關中斷又是如何?就是EA=0後取得TaskFunc[FuncID]內容後,然後才執行。因為就結果來看來多此一舉。
如果您認為多此一舉,那代表您在中斷上使用還不夠熟。
因為若是直接寫
TaskFunc[FuncID]();
會發生死當,但機率不高。以組合語言追蹤當機的指令時,會發現一個很少見的狀況。
因為要取得TaskFunc[FuncID]的內容,8051要取用二次資料,這是因為8051是8位元,但指標是16位元。
就在取出第一個byte內容時,剛好中斷發生,跑去執行中斷程式,若又剛好是回復執行的動作,會回填新的函式指標值,然後退出中斷。
接下來取出第二個byte,但這個是回復的指標值,和原先的不同,接下來去執行就跑去怪地方死當了。
所以在取出函式指標時要加入關閉中斷,取完後回復中斷。

取出後,為了不浪費記憶體及加速,所以使用暫存器宣告register告知編譯器放在暫存器即可。
所以
register void (*Current_Func)(void);
這行是宣告一個暫存器變數,型態為函式指標。這可不是函式原型宣告哦!


8051簡單多工1:整體程式

以前在雷兒電子網發表的文章,重貼在這裏。目前有使用在商品上,穩定性還不錯。分成幾篇來貼。

這是為了對付8051這種沒有多大堆疊的MCU發開出來的。

我懶的用組合語言寫,實際上是說我對8051組合語言不太熟,所以用純粹C寫。因為簡單,所以拿來教學應是不錯。

整個系統可以說是為了對付LCM這個對時間有點敏感的裝置設計出來的。
LCM的規格上每次下指令或資料的時間間隔為73us。說大不大,又說小不小,拿來做單純delay不做事也很浪費。所以就想出這種多工方法。
因有對時間有點敏感的Task存在,故採用固定輪詢法(Polling)來保障單一Task微量延時的時間。
基本程式如下:

#include "c8051f340.h"                       // SFR declarations
#include < stdio.h >

#define LCM_DB          P2      /* LCM Data Bus */
#include "F340Init.h"
sbit LED1    = 0xB4;
sbit LED2    = 0xB5;
sbit LED3    = 0xB6;
sbit LED4    = 0xB7;

// task start function
void LED_Light(void);
void LED_Black(void);

// measure skip time
void TestSkip0(void);
void TestSkip1(void);

// O.S function declaim
#define TASK_NUM     4
void Dummy(void);
idata unsigned char FuncID = 0;
idata unsigned char Task_Delay_Count[TASK_NUM]={0,0,0,0};
unsigned char Task_Skip_Count[TASK_NUM]={0,0,0,0};
idata void (*TaskFunc[TASK_NUM])(void) = {Dummy,Dummy,TestSkip0,LED_Light};
void (*TaskResumeFunc[TASK_NUM])(void) = {Dummy,Dummy,TestSkip0,LED_Light};

// O.S functions
void Dummy(void)
{
    return;
}

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

void Task_Delay_Next(void (*p_func)(void))
{
    TaskResumeFunc[FuncID]=p_func;
}

void Task_Delay_Ms(unsigned char n)
{
    if (! n ) n = 1;
    TaskFunc[FuncID] = Dummy; // to sleep
    Task_Delay_Count[FuncID] = n;
}

void Task_Skip_Next(void (*p_func)(void))
{
    TaskResumeFunc[FuncID]=p_func;
}

void Task_Skip(void)
{
    if ( Task_Skip_Count[FuncID] ) {
        Task_Skip_Count[FuncID]--;
        if (! Task_Skip_Count[FuncID]) TaskFunc[FuncID]=TaskResumeFunc[FuncID];
    }
}

void Task_Set_Skip(unsigned char n)
{
    if (! n ) n = 1;
    TaskFunc[FuncID] = Task_Skip; // to sleep
    Task_Skip_Count[FuncID] = n;
}

#define TASK_DELAY_MS_NEXT(n,s)     Task_Delay_Next(s);Task_Delay_Ms(n);
#define TASK_SKIP_N_NEXT(n,s)        Task_Skip_Next(s);Task_Set_Skip(n);

// O.S functions end

// main loop
void main(void)
{
    PCA0MD &= ~0x40;                    // Disable Watchdog timer
    Init_Device();
    while (1)
    {
        register void (*Current_Func)(void);
        EA = 0;
        Current_Func = TaskFunc[FuncID]; // may break with interrupt
        EA = 1;
        Current_Func();
        FuncID = (++FuncID) % TASK_NUM;
    }
}

void Timer0_ISR (void) interrupt INTERRUPT_TIMER0
{
    unsigned char i;

    TL0  = 0x80;
    TH0  = 0x44; // set delay for 1 ms
    for ( i=0; i < TASK_NUM ; i++ )
    {
        if ( Task_Delay_Count[i] )
       {

            Task_Delay_Count[i]--;
            if (! Task_Delay_Count[i]) TaskFunc[i]=TaskResumeFunc[i]; // wake up function
        }
    }
}

// Begin User Tasks
// Task 0

// Task 1

// Task 2 : measure skip time
void TestSkip0(void)
{
    LED4 = 0;
    TASK_SKIP_N_NEXT(10,TestSkip1);
}

void TestSkip1(void)
{
    LED4 = 1;
    TASK_SKIP_N_NEXT(20,TestSkip0);
}

// Task 3 : function for LED
void LED_Light(void)
{
    LED1 = 0;
    TASK_DELAY_MS_NEXT(250,LED_Black);
}

void LED_Black(void)
{
    LED1 = 1;
    TASK_DELAY_MS_NEXT(250,LED_Light);
}


2010年2月9日 星期二

如何讀書:速讀

這是老掉牙的話題了。
不過這樣說,還是很多人把書拿起來看就可以和周公報到了。

我現在其實也沒好到那裡去,很少撐得過半小時。上班後更是如此,不可能有完整的一小時可以好好看。

既然可以看書的時間有限,所以後來改用壓縮吸收力的方式來補強,於是去學速讀。

速讀對我最大的改善是對資訊的抽取訓練,也就是一眼就要取得關鍵訊息。這個能力可以快速過濾大量訊息,並取得有用的。

另一個是對於記憶力的訓練,從聲音記憶改以影像記憶。多一種記憶方式,就可以記更多,同時反應也加快。

就是因為速讀有效,使得我開始有興趣對於頭腦的使用。

也就是用更有效率的方式來使用頭腦,我之前都沒想過腦的效率是可以訓練來改善。

這也許是我想追上別人的唯一方法吧。


2010年2月3日 星期三

C語言寫法及效能差異

這是Bee的舊文章,貼出來讓大家參考。

這是Bee見過最好的文章,來解釋指標效能及寫法的影響。
取自"Pointer on C"由Kenneth A. Reek所著。
簡體書為"C和指針",人民郵電出版社出版。
內容的一段我簡單說明。

範例是使用68000處理器
程式碼共用此段宣告
#define SIZE 50
int x[SIZE];
int y[SIZE];
int i;
int *p1, *p2;


第一個程式
void try1()
{
        for( i =0; i< SIZE; i++ )
                x[i] = y[i];
}
很直覺的寫法。
組合語言碼為
_try1:  clrl    _i
        jra     L20
L20001: movl    _i,d0
        asll    #2,d0
        movl    #_y,a0
        movl    _i,d1
        asll    #2,d1
        movl    #_x,a1
        movl    a0@(0,d0:L),a1@(0,d1:L)
        addql   #1,_i
L20:    moveq   #50,d0
        cmpl    _i,d0
        jgt     L20001


第二個程式
void try2()
{
        for( p1 = x, p2 = y; p1 - x < SIZE; )
                *p1++ = *p2++;
}              
改使用指標。
組合語言碼為
_try2:  movl    #_x,_p1
        movl    #_y,_p2
        jra     L25
L20003: movl    _p2,a0
        movl    _p1,a1
        movl    a0@,a1@
        addql   #4,_p2
        addql   #4,_p1
L25:    moveq   #4,d0
        movl    d0,sp@-
        movl    _p1,d0
        subl    #_x,d0
        movl    d0,sp@-
        jbsr    ldiv
        addql   #8,sp
        moveq   #50,d1
        cmpl    d0,d1
        jgt     L20003
組合語言碼變長了,而且因為指標相減調整(除4)而去呼叫ldiv函式。增加許多執行時間。


第三個程式
void try3()
{
        for( i = 0, p1 = x, p2 = y; i < SIZE; i++ )
                *p1++ = *p2++;
}                      
計數改用變數,不讓指標調整。
組合語言碼為
_try3:  clrl    _i
        movl    #_x,_p1
        movl    #_y,_p2
        jra     L30
L20005: movl    _p2,a0
        movl    _p1,a1
        movl    a0@,a1@
        addql   #4,_p2
        addql   #4,_p1
        addql   #1,_i
L30:    moveq   #50,d0
        cmpl    _i,d0
        jgt     L20005
似乎不錯。但還有更好。 


第四個程式
void try4()
{
        register int *p1, *p2;
        register int i;
       
        for( i = 0, p1 = x, p 2= y; i < SIZE; i++ )
                *p1++ = *p2++;
}              
使用暫存器變數宣告,將區域變數使用暫存器。
組合語言碼為
_try4:  moveq   #0,d7
        movl    #_x,a5
        movl    #_y,a4
        jra     L35
L20007: movl    a4@+,a5@+
        addql   #1,d7
L35:    moveq   #50,d0
        cmpl    d7,d0
        jgt     L20007
如果夠了解編譯器及語言特性,就會找到更好的調整。       


第五個程式
void try5()
{
        register int *p1, *p2;
       
        for( p1 = x, p2 = y; p1 < &x[SIZE]; )
                *p1++ = *p2++;
}
移除計數。                             
組合語言碼為
_try5:  movl    #_x,a5
        movl    #_y,a4
        jra     L40
L20009: movl    a4@+,a5@+
L40:    cmpl    #_x+200,a5
        jcs     L20009 
最精簡。但C原始碼可讀性極差。


這個例子可以看出效能及可讀性之間的移轉。
也可以看出如何依組合語言碼而調整C程式碼。
到底是要好讀,還是效能,端看使用場合而定。
   

C語言:浮點數通信傳輸

這是Bee的舊文章,只是每隔一兩年就會有人提。那就改貼在這裡吧!

這是Bee在做控制系統時發生的一種狀況:必須將浮點數經通信傳輸到遠端控制器上,如使用DSP做為控制器等裝置,或是網路應用等。
但通信裝置資料多半是使用串列通信的方式,如一次傳輸一個Btye的方式做傳輸,而浮點數至少是32位元的資料。
Bee用C語言資料結構解決了此問題,可是我在網路上DSP討論區也發現有人也有相同問題,只不過是用指標來解決。
我整理以此兩種方法的解決方式:
1.指標方法:
void put_float(float a)
{
    char *cp=(char*)&a;
    Output(*cp++);
    Output(*cp++);
    Output(*cp++);
    Output(*cp);
}
float get_float()
{
    float f;
    char *c = (char*)&f;
    c[0] = Input();
    c[1] = Input();
    c[2] = Input();
    c[3] = Input();
    return f;
}
使用指標鑄型將要用的float資料或char資料取出及存入。
2.資料結構方法:   
void put_float(float a)
{
    union {
        float f;
        char  c[4];
    }var;

    var.f = a;
    Output(var.c[0]);
    Output(var.c[1]);
    Output(var.c[2]);
    Output(var.c[3]);
}
float get_float(void)
{
    union {
        float f;
        char  c[4];
    }var;

    var.c[0] = Input();
    var.c[1] = Input();
    var.c[2] = Input();
    var.c[3] = Input();
    return (var.f);
}
使用共用資料結構union,做資料交換的中間資料存放。
結論:
要注意浮點數存入記憶體的結構,在不同CPU上可能使用不同順序,一端為Big Endian資料格式的CPU,而另一端為Little Endian的CPU,則必須注意存放及取用順序的不同。
就結果論,兩種方法沒有好壞。但以維護性來說使用資料結構方式因可讀性高,會比較好。而我也建議使用此方法。   


2010年2月2日 星期二

GTX275基本測試資料

發現沒有放GTX275的測試資料,補一下。

PC配備:
CPU
AMD Phenom(tm) 9350e
Quad-Core Processor 2.00 GHz

O.S.
Windows XP SP3

RAM
DDR2-800 2G*4(但O.S.只有顯示使用3.25G)

測試圖:


不過GTX275現在買不太到了,可能也要絶版了。現在GTX260以上的卡都不好買。
也許是缺貨加上世代交替的影響。再來玩的CUDA都會換新卡了吧!