linux 下編譯STM32

今天,嘗試了在我使用的Gentoo系統上位Cortex-m3構建GNU工具鏈,沒想到如此簡單。 
以超級用戶權限運行如下命令: 
crossdev --g 4.3.1-r1 -t arm-elf 
因爲官方的gcc在4.3版本下加入了對cortex-m3的支持,所以上面的命令用 --g 4.3.1-r1參數,指定了4.3.1-r1版的GCC。整個編譯過程非常順利,編譯 

成功後得到了:arm-elf-gcc,arm-elf-ld,arm-elf-objcopy等命令,這些就是所需要的工具。 

參考 
1、 
大俠 bozai 章其波 在 
[原創] 支持cortex-M3 的GNU ARM編譯器 CodeSourcery 上的第一個STM32F10x例子 
http://www.ouravr.com/bbs/bbs_list.jsp?bbs_id=3011&bbs_page_no=2 
一帖中給出的工程(makefile和ldscripts) 

2、大俠bluelucky翻譯的《Cortex-M3權威指南》中有關用gcc進行開發的章節。 

寫了一個簡單的程序,經測試成功的點亮了LED。 

所有心得不敢獨享,在這裏與大家分享一下,一併謝謝bluelucky和章其波的辛勤勞作。 

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

一、安裝GNU工具鏈 
因爲在Gentoo Linux下有crossdev這個非常強大的構建交叉編譯工具鏈的工具,安裝Cortex-m3的交叉工具鏈非常簡單,方法前以述及,這裏不贅述。 

二、STM32F10x(Cortex-m3)基於GNU工具鏈的開發流程 

《Cortex-M3權威指南》一書中有如下這個開發流程圖: 
 
gnu工具鏈開發流程 (原文件名:GNU Toolchain development flow.JPG) 



由圖可知,用C語言進行stm32的程序開發,仍然是:寫代碼--->編譯、連接--->下載到flash這樣一個過程。只不過除此以外,我認爲比較重要的還需要 

知道這樣幾點: 
1、如何訪問此種單片機的外圍設備寄存器; 
2、如何書寫此種單片機的中斷服務程序; 
3、此種單片機復位後,從什麼地址處開始執行代碼;然後我們如何告訴編譯工具把代碼按照這個入口地址開始安排我們的代碼。 
4、需不需要爲構建C語言的運行環境作一些工作,也就是啓動代碼。 
5、通過命令行選項通知編譯器爲特定的單片機生成代碼。 

三、編寫一個最精簡的代碼 

1、一個main函數就足夠了嗎? 

先讓我們簡單回顧一下在PC機,一個程序的執行過程大概是怎樣的。因爲程序是在操作系統的管理下運行的,過程大概爲: 

操作系統----------> 啓動代碼(編譯器自動加入,做一些堆棧、全局變量的初始化工作)-----------> main 

然而在裸奔的單片機上,操作系統沒有了,所以原來由操作系統和編譯器作的事情,現在需要我們手工DIY了(如果交叉編譯工具沒有爲我們做好這些事 
情的話,因爲我也不知道gcc現在有沒有爲stm32做好這一切,所以我暫時假定什麼都得靠自己)。 

2、C程序的典型內存佈局 

            +-------------------------------+ 
            |                               | 
            |            堆棧               | 
            |                               | 
            + - - - - - - - - - - - - - - - + 
            |                               | 
            |                               | 
            |                               | 
            |                               | 
            |                               | 
            |                               | 
            |                               | 
            |                               | 
            |                               | 
            + - - - - - - - - - - - - - - - + 
            |                               | 
            |             堆                | 
            |                               | 
            +-------------------------------+ 
            |                               | 
            |        未初始化的數據         | 
            |           .bss段              | 
            |                               | 
            +-------------------------------+ 
            |                               | 
            |         初始化的數據          | 
            |           .data段             | 
            |                               | 
            +-------------------------------+ 
            |                               | 
            |            正文               |            
            |           .text段             | 
            |          .rodata段            | 
            |                               | 
            +-------------------------------+ 

上圖中,正文對應的是可執行代碼.text和常量表格數據等.rodata,.data對應初始化了的全局變量,編譯後將位於可執行文件中,由啓動代碼負責加載 
到數據區中(在單片機中這部分數據會存於flash中,需要有啓動代碼把這部分內容拷貝到sram中),.bss段是沒有初始值的全局變量,由啓動代碼把這 
部分內容全初始化爲0;爲了保證C程序的執行,還需要設置好程序運行時的堆棧區。 

在有了這些基礎知識後,除了main以外,我們還需要做些什麼就比較清楚了:設置堆棧區,把編譯好的內容放到單片機中正確的地方中去。 


3、設置堆棧區和啓動代碼 

Cortex-m3內核在地址0x0000 0000處存放一個向量表,向量表的第0個單元,也即地址0x0000 0000處存放的是堆棧頂的地址,Cortex-m3復位後即從該處 
取出數據用以初始化MSP寄存器。向量表中的內容是32位的地址,這些地址是中斷異常服務程序的入口地址,其中向量表的第一個單元, 
即地址0x0000 0004處存放的是復位向量,也就是說Cortex-m3復位後,執行該向量(可理解爲函數指針)指向的復位代碼。看看代碼吧: 

__attribute__ ((section(".stackarea")))  
static unsigned long pulStack[STACK_SIZE];  
這一句定義了一個pulStack的數組,程序把這個數組作爲了堆棧區。這條語句使用了__attribute__ ((section(".stackarea"))) 把數組定位在 

了.stackarea這個段中。 

typedef void (* pfnISR)(void); 

__attribute__ ((section(".isr_vector")))  
pfnISR        VectorTable[] =   
{  

        (pfnISR)((unsigned long)pulStack + sizeof(pulStack)),        // The initial stack pointer  
        ResetISR,                                               // The reset handler  
        NMIException,  
        HardFaultException  
};  

定義了一個數組VectorTable,作爲向量表,定位於.isr_vector段中。通過鏈接腳本的控制這個表將放在正文區的最開始,正文區又將從flash的最開始 
存放,這樣這個向量表就會起到相當於存放在0x0000 0000開始的地址空間的效果。 
向量表的第0個單元是((unsigned long)pulStack + sizeof(pulStack)),這是數組的最後一個元素,因爲Cortex-m3的堆棧是向下增長的。 
向量表的第1個單元是ResetISR,它指向復位處理的代碼,也是整個程序的入口。本程序用它來實現啓動代碼的功能。 

extern unsigned long _etext;  
extern unsigned long _data;  
extern unsigned long _edata;  
extern unsigned long _bss;  
extern unsigned long _ebss;  

void ResetISR(void)  
{  
        unsigned long *pulSrc, *pulDest;  

        //  
        // Copy the data segment initializers from flash to SRAM.  
        //  
        pulSrc = &_etext;  
        for(pulDest = &_data; pulDest < &_edata; )  
        {  
                *pulDest++ = *pulSrc++;  
        }  

        //  
        // Zero fill the bss segment.  
        //  
        for(pulDest = &_bss; pulDest < &_ebss; )  
        {  
                *pulDest++ = 0;  
        }  

        //  
        // Call the application's entry point.  
        //  
        main();  
}  
這段代碼用到了通過連接器賦值的幾個變量值。_etext的值爲正文段結尾處的地址,這之後的flash空間是初始化的數據值,應該複製到sram中去, 
_data、_edata的值分別爲數據段的開始和結尾處的地址,這部分應該是sram的地址。 

        pulSrc = &_etext;  
        for(pulDest = &_data; pulDest < &_edata; )  
        {  
                *pulDest++ = *pulSrc++;  
        }  
這部分代碼就是將保存於flash中的初始化數據複製到sram中。 
上面代碼中的第二個循環是將.bss段清零。最後調用main進入到我們的主程序。 

4、訪問外圍設備寄存器 
Cortex-m3的外圍設備寄存器位於線性的4GB地址空間中,所以定義指向該外圍設備所處地址的指針即可訪問了。 

#define GPIOC_CRL         (*((volatile unsigned int*)(0x40011000)))  
#define GPIOC_BSRR        (*((volatile unsigned int*)(0x40011010)))  
#define GPIOC_BRR         (*((volatile unsigned int*)(0x40011014)))  
#define RCC_APB2ENR       (*((volatile unsigned int*)(0x40021018))) 

使用宏GPIOC_CRL等即可訪問相應的寄存器。 

5、鏈接 

gcc編譯C源程序文件後,得到目標文件,目標文件需要連接得到最後的可執行文件,程序才能執行。一般來說,目標文件包含 

.text段:  可執行代碼 
.rodata段: 只讀的數據,對應程序中的常量 
.data段:   初始化的全局變量 
.bss段:    未初始化的全局變量 

連接器所作的工作簡單的講就是,把所有目標文件相應的段連接到一起,並把目標文件中的“變量地址”“函數地址”重定位至正確的地址空間; 
比如,對於stm32來說向量表,.text和.rodata就應該放到從0x0800 0000開始的flash,.data,.bss和堆棧就應該定位至從0x2000 0000開始的sram中。 
這些定位都可以通過鏈接腳本進行控制。 

MEMORY  
{  
        FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 0x20000  
        SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x5000  
}  
這些語句說明了flash和sram開始的地址以及大小。 


.text :  
        {  
                KEEP(*(.isr_vector .isr_vector.*))  
                *(.text .text.*)  
                *(.rodata .rodata*)  
                _etext = .;  
        } > FLASH  

按.isr_vector, .text, .rodata的順序排列正文段的內容;回憶前述VectorTable[]數組就被定爲與.isr_vector段中,所以這段腳本就保證了向量表爲 

與正文區的最開端,將存放於0x0800 0000開始的位置了。但向量表不是應該從0x0000 0000開始嗎?原來stm32可以通過boot0、boot1引腳的配置將 

flash映射到0x0000 0000處。具體可參考stm32的數據手冊。 
_etext = .; 這條語句把計數器“.”的值賦給了變量_etext;“.”現在的值就爲.text的尾部。 

另,後面的.data、.bss、.stackarea部分可自行分析,原理一樣。 


四、編譯程序 

step1:  arm-elf-gcc -mcpu=cortex-m3 -mthumb gpio_test.c -nostartfiles -T stm32f103VBT6.ld -o gpio_test.o 

注意參數 -nostartfiles指示不要包含編譯器自帶的啓動代碼,-T stm32f103VBT6.ld表示使用stm32f103VBT6.ld這個鏈接腳本。 

step2:  arm-elf-ld -T stm32f103VBT6.ld -o gpio_test.out gpio_test.o   
同樣使用stm32f103VBT6.ld這個鏈接腳本。 

step3:  arm-elf-objcopy -Obinary gpio_test.out gpio_test.bin  
從elf的文件格式中得到最終需要的gpio_test.bin二進制目標文件。 

step4:  使用官方的flash下載demo程序將得到的gpio_test.bin通過usart1燒錄至芯片。 
/* filename: gpio_test.c */ 
#define GPIOC_CRL         (*((volatile unsigned int*)(0x40011000))) 
#define GPIOC_BSRR        (*((volatile unsigned int*)(0x40011010))) 
#define GPIOC_BRR        (*((volatile unsigned int*)(0x40011014))) 
#define RCC_APB2ENR        (*((volatile unsigned int*)(0x40021018))) 

#ifndef STACK_SIZE 
#define STACK_SIZE                              64 
#endif 

void ResetISR(void); 
void NMIException(void); 
void HardFaultException(void); 
void delay(void); 

typedef void (* pfnISR)(void);                        // Pointer to exception handle function 

// mthomas: added section -> alignment thru linker-script 

__attribute__ ((section(".stackarea"))) 
static unsigned long pulStack[STACK_SIZE]; 


__attribute__ ((section(".isr_vector"))) 
pfnISR        VectorTable[] =  


        (pfnISR)((unsigned long)pulStack + sizeof(pulStack)),        // The initial stack pointer 
        ResetISR,                                               // The reset handler 
        NMIException, 
        HardFaultException 
}; 

void delay(void) 

        unsigned int i; 
        for( i = 0; i < 0x3ffff; ++i) 
                asm("nop"); 


int main(void) 


        RCC_APB2ENR |= (1<<4); 
        GPIOC_CRL &= 0x0000FFFF; 
        GPIOC_CRL |= 0x33330000; 

        while(1){ 
                GPIOC_BRR = (1<<4); 
                GPIOC_BSRR = (1<<7); 
                delay(); 

                GPIOC_BRR = (1<<7); 
                GPIOC_BSRR = (1<<6); 
                delay(); 

                GPIOC_BRR = (1<<6); 
                GPIOC_BSRR = (1<<5); 
                delay(); 

                GPIOC_BRR = (1<<5); 
                GPIOC_BSRR = (1<<4); 
                delay(); 
        } 


//***************************************************************************** 
// 
// The following are constructs created by the linker, indicating where the 
// the "data" and "bss" segments reside in memory.  The initializers for the 
// for the "data" segment resides immediately following the "text" segment. 
// 
//***************************************************************************** 
extern unsigned long _etext; 
extern unsigned long _data; 
extern unsigned long _edata; 
extern unsigned long _bss; 
extern unsigned long _ebss; 

void ResetISR(void) 

        unsigned long *pulSrc, *pulDest; 

        // 
        // Copy the data segment initializers from flash to SRAM. 
        // 
        pulSrc = &_etext; 
        for(pulDest = &_data; pulDest < &_edata; ) 
        { 
                *pulDest++ = *pulSrc++; 
        } 

        // 
        // Zero fill the bss segment. 
        // 
        for(pulDest = &_bss; pulDest < &_ebss; ) 
        { 
                *pulDest++ = 0; 
        } 

        // 
        // Call the application's entry point. 
        // 
        main(); 


void NMIException(void) 

        return; 


void HardFaultException(void) 

        return; 



/*************************************************/ 
/* filename: stm32f103VBT6.ld                    */ 
/* linkscript for STM32F103VBT6  microcontroller */ 
/*                                               */ 
/* modified by XiaoFeng Li (ifree64) China       */ 
/*                                               */ 
/*                                               */ 
/* E-Mail: [email protected]                 */ 
/* 2008-9-6                                      */ 
/*************************************************/  

MEMORY 

        FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 0x20000 
                     SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x5000 


/* Section Definitions */ 
SECTIONS 

        .text : 
        { 
                KEEP(*(.isr_vector .isr_vector.*)) 
                *(.text .text.*) 
                *(.rodata .rodata*) 
                _etext = .; 
        } > FLASH 

        .data : AT (_etext) 
        { 
                _data = .; 
                *(.data .data.*) 
                . = ALIGN(4); 
                _edata = . ; 
        } > SRAM 

        /* .bss section which is used for uninitialized data */ 
        .bss (NOLOAD) : 
        { 
                _bss = . ; 
                *(.bss .bss.*) 
                *(COMMON) 
                . = ALIGN(4); 
                _ebss = . ; 
        } > SRAM 

        .stackarea (NOLOAD) : 
        { 
                . = ALIGN(8); 
                *(.stackarea .stackarea.*) 
                . = ALIGN(8); 
        } > SRAM 

        . = ALIGN(4); 
        _end = . ; 
}

step1:  arm-elf-gcc -mcpu=cortex-m3 -mthumb gpio_test.c -nostartfiles -T stm32f103VBT6.ld -o gpio_test.o 

step2:  arm-elf-ld -T stm32f103VBT6.ld -o gpio_test.out gpio_test.o  

step3:  arm-elf-objcopy -Obinary gpio_test.out gpio_test.bin 

step4:  download gpio_test.bin to stm32, ok! 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章