2010年2月20日 星期六

8051簡單多工7:堆疊(stack)使用狀況

發現忘了說使用這個架構是為了什麼。當然是省記憶體,但到底是省到那一種。答案是我想省下使用8051 Register file。

有玩8051的應該會知道8051產生副程式呼叫所使用的堆疊在那裏,就是Registers file。問題是它的大小只有256byte。

也就是說,最大只能呼叫128層的副程式,還要扣除使用暫存器、中斷程式的使用,實際上連100層都到不了。

當堆疊玩爆了,什麼怪現象都會冒出來,沒有對處理器了解的人,是永遠也不知問題出在那裡。

可是有人會問,是誰有辦法寫出40層以上的呼叫,玩到堆疊爆掉。其實也不用那麼多層也可以爆掉的。

在函式內的區域變數也是在用堆疊的,也就是如果函式內皆使用30byte的區域變數,呼叫個十層,就會爆了。

可是有人會問,他寫8051程式已經很大了,並沒有遇到我說的現象。這就是Compiler的工作了,像Keil C在每次函式呼叫,會去計算使用多少堆疊。所以可以預測是否出現堆疊爆掉的狀況。

不過中斷程式也是使用堆疊,卻不在計算範圍內,所以有時中斷程式寫太大仍會發生怪現象。


好了,談了這麼多,和多工有何關係?

當然有,因為多工,所以函式的數量一定大增,以RTOS來寫,每一個Task都會要求獨立的堆疊,8051的堆疊根本不夠用。

不過8051上仍有RTOS,像uCOS就有8051版本,只不過它在切換Task時,是將整個Stack都搬到外部記憶體去,然後又從外部記憶體搬另一個Task的回來。這樣做其實效率並不好,但也只能接受。因為8051原設計就沒想到裝作業系統這樣東西進去。

而Bee在設計上,有想到保留堆疊(stack),所以採用非強制多工的方法,如此一來就可以確定函式執行完成退出後,才會輪到另一個task的函式執行。這就是一開始設計上的考量。

不過有所犧牲,程式只能以狀態機的方式寫,會使得別人不好閱讀程式。

不過Task之間因為要回到main()中才能切換,回到main()即表示全部堆疊都已釋放,也就是Task之間不會有堆疊互相堆積的問題。也就可以確保每個Task都幾乎有完整的8051堆疊可用。

這是這個架構的好處,在實際使用上,就有許多變數可以放進Register file可以增加8051的運作速度了。或者可以使用比較多層的中斷程式也比較不用擔心。

但這個架構仍有一個問題,那就是Keil C因為會去預測堆疊的使用。當安排一個Task是由一群循環的狀態函式所組成時,Keil C會提出使用到遞迴結構的警告。這個我還不知要如何去除。看看那天能有人給點建議。

不過只是警告,還不至於影響執行。


Bee實作上,有遇到一次的是,單一的Task掛了,產生資料存取錯誤,另三個Task仍很正常。大概是沒有影響到全域變數,所以還好。可見穩定性還可以。

至於是那一個事件,可以去另一篇LCM當機解法看看。


2010年2月18日 星期四

8051簡單多工6:核心編譯之組語

這篇是給慣用組合語言的人做為比較的,並且看看8051是如何去做函式指標呼叫。

我使用SDCC的組譯程式來看,因為Keil c的比較複雜。以下為main()中程式轉成組合語言段落:

C語言部分:前面數字為行號,可以在組合語言中找到對應段落。TASK_NUM設為4
77:void main(void)
78:{
79:    PCA0MD &= ~0x40;                    // Disable Watchdog timer
80:    Init_Device();
81:    while (1)
82:    {
83:        register void (*Current_Func)(void);
84:        EA = 0;
85:        Current_Func = TaskFunc[FuncID]; // may break with interrupt
86:        EA = 1;
87:        Current_Func();
88:        FuncID = (++FuncID) % TASK_NUM;
89:    }
90:}

ASM組合語言結果:
;------------------------------------------------------------
;Allocation info for local variables in function 'main'
;------------------------------------------------------------
;Current_Func              Allocated to registers r2 r3
;    main.c 77
;    -----------------------------------------
;     function main
;    -----------------------------------------
_main:
;    main.c 79
    anl    _PCA0MD,#0xBF
;    main.c 80
    lcall    _Init_Device
;    main.c 81
00102$:
;    main.c 84
    clr    _EA
;    main.c 85
    mov    r0,#_FuncID
    mov    a,@r0
    add    a,acc
; Peephole 105   removed redundant mov
    mov  r2,a
    add    a,#_TaskFunc
    mov    r0,a
    mov    ar2,@r0
    inc    r0
    mov    ar3,@r0
    dec    r0
;    main.c 86
    setb    _EA
;    main.c 87
    mov    a,#00107$
    push    acc
    mov    a,#(00107$ >> 8)
    push    acc
    push    ar2
    push    ar3
    ret
00107$:
;    main.c 88
    mov    r0,#_FuncID
    mov    a,#0x01
    add    a,@r0
    mov    r2,a
    mov    r0,#_FuncID
    mov    b,#0x04
    mov    a,r2
    div    ab
    mov    @r0,b
; Peephole 132   changed ljmp to sjmp
    sjmp 00102$
00104$:
    ret

可以看到Current_Func是使用r2及r3暫存器。
取函式指標執行是第87行的動作,使用四個push及一個ret來做,這不是一般人可以理解的吧。Keil C呼叫更多東西。
唯一可以確定的是push ar2及puah ar3中的ar2及ar3就是暫存器r2及r3。

從這裏可以看到除餘(%)真的是拿去除,所以上一篇才會提到加速及展開的問題。不過Keil C就聰明多了,會用and 3去做。
要玩MCU還是要看一下組合語言在幹什麼,常常和想的不同。



8051簡單多工5:使用上的調整及範例

編譯環境差異:
目前在Keil c及SDCC下編譯皆有過。但Bee沒有使用SDCC實際去執行過。不過SDCC編出來的組合語言看來沒有問題,應是可以用的。

核心加速:
在看編出來的組合語言,可以發現Bee使用除餘(%)來做FuncID調整會動用到除法,使執行效率下降。
比較好的寫法是將FuncID整個展開,這樣也不用去計算它。
以使用三個Task為例,將while改寫為:
    while (1)
    {
        TaskID = 0;
        EA = 0;
        Current_Func = TaskFunc[TaskID];
        EA = 1;
        Current_Func();
        TaskID++;
        EA = 0;
        Current_Func = TaskFunc[TaskID];
        EA = 1;
        Current_Func();
        TaskID++;
        EA = 0;
        Current_Func = TaskFunc[TaskID];
        EA = 1;
        Current_Func();
    }
這樣執行起來更有效率。

另一個例子:
這是Key Scan範例
// Key Scan function
#define KEY_SCAN_DELAY        1  // 1ms
void key_scan2(void);
void key_scan3(void);
void key_scan4(void);
void key_Encode(void);
unsigned char KeyScan[5]; // unknow why KeyScan[0] can't be used? just skip

void key_scan1(void)
{
    KeyScan[4] = P1;
    P1 = ~(1<<0)<<4 | 0x0F;
    Task_Delay_Next(key_scan2);
    Task_Delay_Ms(KEY_SCAN_DELAY);
}

void key_scan2(void)
{
    KeyScan[1] = P1;
    P1 = ~(1<<1)<<4 | 0x0F;
    Task_Delay_Next(key_scan3);
    Task_Delay_Ms(KEY_SCAN_DELAY);
}

void key_scan3(void)
{
    KeyScan[2] = P1;
    P1 = ~(1<<2)<<4 | 0x0F;
    Task_Delay_Next(key_scan4);
    Task_Delay_Ms(KEY_SCAN_DELAY);
}

void key_scan4(void)
{
    KeyScan[3] = P1;
    P1 = ~(1<<3)<<4 | 0x0F;
    Task_Set_Next(key_Encode);
}

// Encode Key code
void key_Encode(void)
{
    // do some thing here
    Task_Delay_Next(key_scan1);
    Task_Delay_Ms(KEY_SCAN_DELAY);
}
// Key Scan function end

比較一下使用uCOS的寫法
void key_scan(void)
{
    while(1)
    {
        KeyScan[4] = P1;
        P1 = ~(1<<0)<<4 | 0x0F;
        OSTimeDly(10);
        KeyScan[1] = P1;
        P1 = ~(1<<1)<<4 | 0x0F;
        OSTimeDly(10);
        KeyScan[2] = P1;
        P1 = ~(1<<2)<<4 | 0x0F;
        OSTimeDly(10);
        KeyScan[3] = P1;
        P1 = ~(1<<3)<<4 | 0x0F;
        // do something here
        OSTimeDly(10);
    }
}

可以發現這個簡單多工是以狀態機的方式下去寫,就是以一個狀態的函式再跳到另一個狀態的函式的寫法。
而uCOS在執行OSTimeDly()之後可以再從它的下一行執行下去。這個是比較容易理解的寫法。
不過8051的資源不多,Bee才會自創多工核心。


8051簡單多工4:中斷延時

這是基本延時功能,但要各組Task都可以使用。LED閃爍最常用了。
新加程式碼如下:
idata unsigned char Task_Delay_Count[TASK_NUM]={0,0,0,0};
void Dummy(void)
{
    return;
}

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 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
        }
    }
}

各函式功能說明:
Dummy()使用中斷延時,原本的函式就會用這個取代。
Task_Delay_Next()設定延時完畢後要執行的函式。
Task_Delay_Ms()設定延時時間。
Timer0_ISR()中斷程式。
大家可以比較一下和上篇的差異,其實主要的功能是一樣的,只是改由中斷做。

只有一個問題可能在使用時會遇到,那就是設定延時的變數Bee是用unsigned char,所以只能延時255ms。
因為Bee剛好就用這麼多,要加長就改一下變數型態就可以了。


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都會換新卡了吧!