2013年12月27日 星期五

keil c linker scatter

原由:

不知為什麼,總是有人來問我這個問題。

Q:怎樣將一個global varible(RW/RO)指定到其一特定位置上?

 

所以,雖然我目前沒有用到這個功能。

這一項好像大多用到有外部記憶体裝置(SDRAM)的系統上,而SOC本身並沒有RAM/或不夠時

才需要這項功能。

但也有人是將特定資訊(版本控制、加密)等....,而使用的。總之,我也試著實作了。

 

1.what is linker (scatter)

請參照:

https://sourceware.org/binutils/docs-2.18/ld/Scripts.html#Scripts

 

2.為什麼要用這個

我們編輯出的程式在ROM/Flash長怎樣(先不管格式);

而在執行又長怎樣?

C語言中的記憶体佈局

 

3.how to do this on keil C

  在keil C中Project中的Options for …Linker Tab可以找到(Scatter.ld)

image

p.s: 你也必須注意到,下面的Misc control 及Liker control string(不可改寫, 但你可以由Misc control中去改)

      在某些project不知為何scatter file不能改

4.

在這個linker scatter中內容(注意的是它的格式,函空格/Tab 對齊等)

 

A.目前的(標準的,大致都長的像這樣)

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
 
LR_IROM1 0x00014000 0x0002B000  {    ; load region size_region
  ER_IROM1 0x00014000 0x0002B000  {  ; load address = execution address
   *.o (RESET, +First)  
   *(InRoot$$Sections)
   .ANY (+RO)
  }
  
RW_IRAM1 0x20002000 0x00002000  {  ; RW data      
   .ANY (+RW +ZI)
  }
}
 
 


可以很簡單的看其它的section及配置


 


5.


A.在某個 *.c /*.s中宣告你要的變數,但一定要是全域(非static)才可以讓在object中可見symbol


(一般可以nm去看,但keil C好像沒看到)


在這裡只簡單地以 *.s實作.


可以參考,原本的寫法(注意它的格式及format)



AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
SPACE   Heap_Size
__heap_limit

B.加入我們自己的



AREA    STEVEN_DATA, DATA, READWRITE
EXPORT  __TEST_STEVEN
 DCD      0;

C.修改原本的,加入



TEST_IRAM1 0x2002000 0x00000004 {
   *.o (STEVEN_DATA, +First)  
  }


D.指定其linker Scatter(若在scatter中不能指定時,直接在misc controls 覆寫設定。


image


E. 去看它的map file(同在project 中listing Tab打開它)


image


F.compiler後,檢查


我目標是將它(TEST_STEVEN)放在第一個位置上。



Execution Region TEST_IRAM1 (Base: 0x02002000, Size: 0x00000004, Max: 0x00000004, ABSOLUTE)
 
Base Addr    Size         Type   Attr      Idx    E Section Name        Object
 
0x02002000   0x00000004   Data   RW            4    STEVEN_DATA         arm_startup_nrf51.o

I.


那要如何使用它,這裡我舉出一個方法。理應有好幾種方式處理


macro

#define STEVEN (*((volatile unsigned int*) 0x20002000))



這樣子就可以把它當作一個register來使用了,同mcu中去存取control register一樣


STEVEN=11;

 


----------------------------------------------------------------------------------------------------------------------


上個範例是指定RW(即可變的)


而這次是另外一個案子(不同系統的),但改為RO(read only)


其前面的步驟,是類似的。


檢查其


在keil C中Project中的Options for …Linker Tab可以找到(Scatter.ld)


及其相關設定Misc control 及Liker control string


 


打開這個project的linker scripter



#!armcc -E --device=DARMCM1
#define ROM_SIZE            0x23400
#define SRAM_START            0x20000000
#define SRAM_SIZE            0x3000
 
 
_ROM                    0x00000     ROM_SIZE
{
    _RESET         +0
    {
        boot_ISD9xx.o
    }
    
    _CODE        +0
    {
        * (+RO)
    }
    
    _SRAM                SRAM_START ALIGN 4
    {
        * (+RW, +ZI)
    }
    
    _Overlap_1               +0 ALIGN 4       OVERLAY
    {
        featurex.o(+RW,+ZI)
        audioadccontrol.o(+RW,+ZI)
        VR.o(+RW,+ZI)
        Recording.o(+RW,+ZI)
    }
    
    _Overlap_2               +0 ALIGN 4        OVERLAY
    { 
        ShareRam.o(+RW,+ZI)
    }
}

和前面的不同了,若要修改還是應詢問相關人員(看起來還滿像是gnu的寫法,可能由那改過的)。這裡我們先不去管它。


其misc control



--map --first='boot.o(vectors)' --datacompressor=off --info=inline --entry Reset_Handler


相同地,都是必須要將VIC(interrupt vector)放在第一位(0x0000)。


所以我們可將其固定RO data放在它的後面。


 


1.這次我們改以 *.C 來作。


所以create a c file (test.c)



const unsigned int TEST=0xA5A5A5;

2.因為keil c ,會幫你最佳化動作。


所以,你必須先把這個拿去用。(即被其它人參照)


在這裡我將它的compiler設定成:(最少動作)


image


image


ps .在mian.c(或其它file中,把它參照到)


     目前其optimzation 必須設O0


 


3.改寫linker scripter


加入


_RESET         +0
{           
    boot_ISD9xx.o
    boot.o(vectors)
}

_ABC        +0
{
    test.o(+RO)
}

 

最後變成



#!armcc -E --device=DARMCM1
#define ROM_SIZE            0x23400
#define SRAM_START            0x20000000
#define SRAM_SIZE            0x3000
 
 
_ROM                    0x00000     ROM_SIZE
{
    _RESET         +0
    {            
        boot_ISD9xx.o
        boot.o(vectors)
    }
    _ABC        +0
    {
        test.o(+RO)
    }
    
    _CODE        +0
    {        
        * (+RO)
    }
    
    _SRAM                SRAM_START ALIGN 4
    {
        * (+RW, +ZI)
    }
    
    _Overlap_1               +0 ALIGN 4       OVERLAY
    {
        featurex.o(+RW,+ZI)
        audioadccontrol.o(+RW,+ZI)
        VR.o(+RW,+ZI)
        Recording.o(+RW,+ZI)
    }
    
    _Overlap_2               +0 ALIGN 4        OVERLAY
    { 
        ShareRam.o(+RW,+ZI)
    }
}



先把boot.o(vectors)載入(VIC內容),再把我們要的固定值加入


 


4.檢查:


打開*.map


A.變數有存在?


在Image Symbol Table中可見


test.c                                   0x00000000   Number         0  test.o ABSOLUTE

.constdata                               0x000000c0   Section        4  test.o(.constdata)

 

Global Symbols

TEST                                     0x000000c0   Data           4  test.o(.constdata)

 


B.其memory map


最後產生的hex file內容:(可能非執行時內容)


Memory Map of the image

 

Image Entry point : 0x0000cfd9

Load Region _ROM (Base: 0x00000000, Size: 0x0001245c, Max: 0x00023400, ABSOLUTE)

  Execution Region _RESET (Base: 0x00000000, Size: 0x000000c0, Max: 0xffffffff, ABSOLUTE)

  Base Addr    Size         Type   Attr      Idx    E Section Name        Object

  0x00000000   0x000000c0   Data   RO           96    vectors             boot.o

  Execution Region _ABC (Base: 0x000000c0, Size: 0x00000004, Max: 0xffffffff, ABSOLUTE)

  Base Addr    Size         Type   Attr      Idx    E Section Name        Object

  0x000000c0   0x00000004   Data   RO         2335    .constdata          test.o

 


C.看其*hex內容:


image


p.s:


最後,我想說的。這二種RW/RO,都是必須去修改linker scripter(新增一個section放)


若沒必要還是不要去動比較好。


2.可用來使其不被Optimail


--keep='boot.o(SysInfo)

Optimal program and debug trace

 
程式的執行速度和所佔用的記憶体空間這兩個是決定系統中的效率。
 
其中ROM/RAM 及處理器的頻率有必要的關連性。
 
在嵌入式系統中,程式的執行速度比程式所佔用的記憶体空間顯得更重要。
 
1. 這是因為嵌入式系統是針對某一特定應用所開發的,而且大多平台可定址的範圍也同於PC;但在處理器速度上卻有很大的差距。
 
2. 而且,程式的memory size是比較好控制的因素,而程式執行中佔用多少處理器時間,則比較難量測的。
 
所以,我們可以將程式中犠牲某一特定的儲存容量來換取程式執行速度。
 
其中以查表法為例,(這個不止在一般的嵌入式系統中,這也用於在晶片設計上,FPGA),用於數學函數的處理(正弦、餘強表及對數等)
 
A.查表法
如何在4-bits的數值求有幾個位元為 ' 1 '.
int getnumber(unsigned int a)
{
    int i, num = 0;
    unsigned int temp = a & (0x0f);
 
    for(i = 0; i < 4; i++)
        if((temp >> 1) & 0x1)
        {
            num++;
        }
 
    return num;
}

 


const int table[16] ={0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4};
int getnumber(unsigned int a)
{
    return table[a];
}

 

B.迴圈

迴圈的執行的次數,是C中最常遇到的也同樣是可以來提高效率的地方。

 

像這個範例,

1.條件變數最佳化:


void change_list_value()
{
    int i;
    POSITION pos;
    CPtrList* plist;
    plist = get_start(pos);
 
    for(i = 0; i < get_count() ; i++)
    {
        plist = get_next(pos);
        set_val(plist);
    }
 
    return 0;
}

這個中,每次get_count()並在裡面得到下一個位址並再去做設定的動作,所以呼叫了三次的函式這對於嵌入式的呼叫時會去對暫存器做暫存。

 


void change_list_value()
{
    int i,count;
    POSITION pos;
    CPtrList* plist;
    plist = get_start(pos);
    count = get_count();
    for(i = 0; i < count ; i++)
    {
        plist = get_next(pos);
        set_val(plist);
    }
 
    return 0;
}

若改變函數中的呼叫流程,這樣可以減少呼叫次數。

"在迴圈運算中,對於迴圈條件,可以使用臨時變數"

 

2.迴圈展開最佳化:


void change_list_value()
{
    int i, count, total;
    POSITION pos;
    CPtrList* plist;
    plist = get_start(pos);
    count = get_count();
 
    for(i = 0; i < count ; i++)
    {
        plist = get_next(pos);
        total += set_val(plist);
    }
 
    return total;
}

而若每次迴圈內容的各種運算所佔的百分比,若要減少次數則要增加迴圈中運算則可能造成程式設計問題。

因此可以修正為,


void change_list_value()
{
    int i, count, total;
    POSITION pos;
    CPtrList* plist;
    plist = get_start(pos);
    count = get_count();
 
    for(i = 0; i < count+2 ; i++)
    {
        plist = get_next(pos);
        total += set_val(plist);
 
        plist = get_next(pos);
        total += set_val(plist);
    }
    //處理最後的
    if(i < count )
    {
        plist = get_next(pos);
        total += set_val(plist);
    }
    return total;
}

 

C. 小數點運算:

    在應用程式中,不僅需要整數及邏輯運算,也要浮點運算。像是float、double(實數)等來表示小數點。

若在不支援浮點運算的平台中使用了double,在compiler會哹叫一些滇算法來處理來代取。

 

"C語言中,int、long、short表示整數(整數),而float、double而浮點數(實數)。

 

而其中,並不是整數及小數的差別,而是' 小數點是否根據需要移動"

 

EX:

      對於一個2位數的十進位數字,可以表示0~99個數數值,也可表示為0.0 ~ 9.9這帶有小點的100個實數以及 0.01 ~0.99。這完全取決程式者認為小數點在什麼位置。 如果直接使用浮點數,對系統浪費很大的。

 

     所以可在某些場合中可以使用 " 定點數" 來代表 "浮點數"。

 

利用定點數表示浮點數的本質是使用整數型資料代替浮點數,進行定點的小數點運算。 從理論上,定點小數點和整型數的記憶體格式是一樣的。

 

整型數與浮點數的區別在於整型數表示資料範圍比較小,實際上,整數型的資料只需要加上知道小數點位置就可以表示小數點。

 

Q16格式的資料,小數點在16bits上。 例如 :一個32 bits,若以hex 表示成0x52C00。

而以Q16格式則為

0x 0005 | 2C00。

十進位== 0x 5.2C == 5 x2^0 + 2x 2^-4+12 x 2^-16 =5.171875

 

所以,可以用Q格式來表示浮點數。來節省資源浪費,只是用定點數表示浮點數的缺點表示數的範圍變小。(但需要記往小數點位置)。

 

但到底是否需要使用定點數表示或是直接用浮點數,需要看運算內容 ,如下:

 

EX:

求 3個浮點數並以 它所佔的權重求其總數。

 

a           90.24      32.6%

b           75.45      41.3%

c           80.36      21.1%

 


float  getvalue(float a, float b, float c)
{
    float sum = 0.326 * a + 0.413 * b + 0.261 * c;
    return sum;
}

 

但這樣的運算是不需要用到浮點運算,可將其權重轉成整數(乘上1000倍)

 

p.s : 不/可重入函數;


void swap1(int *i,int *j)
{
    int temp;
    temp= *i;
    *i=*j;
    *j=temp;
}
 
int temp;
void swap2(int *i,int *j)
{
    temp= *i;
    *i=*j;
    *j=temp;
}

swap 1為可重入,即可被中斷,因為其temp是被存在stack中。

而swap 2則為global所以可能被其的改變。

 

 

D.程式的除錯和巨集使用技巧

 

1. 列印檔、函數和程式列:

    它所使用的是各編輯器的巨集。

如下:


#define APP_ERROR_HANDLER(ERR_CODE)                         \
    do                                                      \
    {                                                       \
        app_error_handler((ERR_CODE), __LINE__, (uint8_t*) __FILE__);  \
    } while (0)

其中__FILE__、__LINE__。利用這些來得到除錯資訊。而且這些是compiler自己產生而不是變數定義。


2.# : 字串化運算子.

在gcc 的preprocesss中,可以使用"#"將當前內容轉換成字串。

 


#define dprint (expr) printf("<main>%s=%d \n",#expr,expr);
 
char a = "1";
 
dprint(3 / 4);
dprint(a);
dprint(123);
//------------------------
 
<main>3 / 4 = 0;
<main>a     = 321300;
<main>123   = 123;
 

它的優點是可以用統一的方法列印運算式的內容,所以程式中可以方便直觀地看到轉成成字串之後的運算式。


#define dprint (expr) printf("<main>%s=%d \n",#expr,expr);
#define dprintc (expr) printf("<main>%s=%c \n",#expr,expr);
#define dprintd (expr) printf("<main>%s=%f \n",#expr,expr);

3. ## :連結運算子.

在gcc 的preprocessor中,它將實作字元串連的運算。


#define test(x) test## x
 
void test1(int a)
{
    printf("test 1 integer:%d\n",a);
}
 
void test2(char* a)
{
    printf("test 2 string:%s\n",a);
}
 
void main(void)
{
    test(1)(100);
    test(2)("abc");
}
//--------------
test 1 integer:100
test 2 string:abc

因為是preprocess所以是在做代取字串而已,

test(1)(100):先test(1) 對應macro

 

#define test(x) test## x , 變成test1。

另一個使用範例為:

#define DPRINT(fmt,args...) printf(fmt,##args)

使用DPRINT代替printf使用。而且preprocess中將二個參數以##連結。

而其中##表示連結可變的參數列表;fmt則為其格式。

即因為在macro中想要其參數(可變參數)傳給代取的字串。

所以不能明確地指定其那個參數對映,故以##做連結。

--------------------------

最後,我們要將其上面的#及## 整合在一起。做出除錯巨集:

如下:



#define DEBUG_OUT1(fmt,args...) \
{ \
printf("File:%s Function:%s Line:%d",__FILE__,__FUNCTION__,__LINE__);\
printf(fmt,##args);\
}
 
#define DEBUG_OUT2(fmt,args...) \
{ \
printf("File:%s Function:%s Line:%d"fmt,__FILE__,__FUNCTION__,__LINE__,##args);\
}
 
 

















而其使用的方式:



int a=100;
int b=200;
char *s= "string";
DEBUG_OUT("a= %d;b =%d \n",a,b);
DEBUG_OUT("%s",s);
//DEBUG_OUT(s);error
//------------------
File:main.c Function:main Line:155 a=100;b=200
File:main.c Function:main Line:156 s= string


===================


對除錯程式碼進行分級審查


在定義除錯的巨集,在工程大時,可能會導致其輸出訊息過多。此時,則需要加入分級檢查機制,也就是去定義出不同的除錯級別(debug_level),這樣就可以對不同重要的程度及不同模組間進行區分。


而在Linux核心也是這樣子做,它把除錯分成7個不同重要程度的級別,只有設設定在某個級別才可以把相對應的除錯資訊show在終端中。



void show_debug()
{
    int level;
    if(level == XXX_MODULE)
    {
#define DEBUG_OUT2(fmt,args...) \
            printf("File:%s Function:%s Line:%d"fmt,__FILE__,__FUNCTION__,__LINE__,##args)
 
    }
    else
        ....
}
 
#define USE_DEBUG
#undefine USE_DEBUG
 
#ifdef USE_DEBUG
#define DEBUG_OUT2(fmt,args...) \
            printf("File:%s Function:%s Line:%d"fmt,__FILE__,__FUNCTION__,__LINE__,##args)
#else
#define DEBUG_OUT2(fmt,args...)
#endif
 
#define USE_DEBUG 1
#if USE_DEBUG ==1
#define DEBUG_OUT2(fmt,args...) \
            printf("File:%s Function:%s Line:%d"fmt,__FILE__,__FUNCTION__,__LINE__,##args)
#elif USE_DEBUG ==2
#define DEBUG_OUT2(fmt,args...)
#endif