大家好,我是豹哥,獵豹的豹,犀利哥的哥。今天豹哥給大家講的是 嵌入式開發里的source文件 。 眾所周知,嵌入式開發屬於偏底層的開發,主要編程語言是C和彙編。所以本文要講的source文件主要指的就是c文件和彙編文件。 儘管在平常開發中,我們都只會關註自己創建的.c/.h/.s源文件,但實際上我們 ...
大家好,我是豹哥,獵豹的豹,犀利哥的哥。今天豹哥給大家講的是嵌入式開發里的source文件。
眾所周知,嵌入式開發屬於偏底層的開發,主要編程語言是C和彙編。所以本文要講的source文件主要指的就是c文件和彙編文件。
儘管在平常開發中,我們都只會關註自己創建的.c/.h/.s源文件,但實際上我們不知不覺中也跟很多不是我們創建的源文件在打交道,那麼問題來了,一個完整的嵌入式工程(以基於ARM Cortex-M控制器的工程為例)到底會包含哪些source文件呢?
現在就到了豹哥的show time了,豹哥將這些文件按來源分為五類十種,下麵豹哥按類別逐一分析這些文件:
第一類:Provided by Committee
第一類文件由C標準委員會提供,該類文件伴隨著標準的發佈而逐漸壯大。該類文件主要就是一種,即C標準庫。
1. C standard Library
大家都知道C語言是有標準的,常見的C標準有ANSI C(C89)、C99、C11,而C標準函數庫(C Standard library)就是所有符合C標準的頭文件的集合,以及常用的函數庫實現程式。C標準庫由Committee制訂發佈,通常會被包含在IDE里。列舉一些常見文件和函數如下,是不是覺得似曾相識?
/* 常用文件 */ assert.h,stdio.h,stddef.h,stdint.h,string.h ...
/* 常用定義 */ bool,NULL,uint8_t,uint16_t,uint32_t...
/* 常用函數 */ assert(),printf(),memset(),memcpy()...
第二類:Provided by IDE(Compiler)
第二類文件由IDE提供,C語言是編譯型語言,需要編譯器將C程式彙編成機器碼,所有便有了一些跟編譯器特性相關的函數庫。
2. Compiler Library
我們在開發嵌入式應用時需要藉助集成開發環境(IDE),常見的IDE有GCC(GNUC),Keil MDK(ARMCC),IAR EWARM(ICCARM),這些IDE都有配套的C編譯器,這些編譯器是各有特色的,為了充分展示各編譯器特色,配套的函數庫便應運而生。
編譯器函數庫是因IDE而異的,此處僅講一個例子以供參考,需要瞭解更多需查看各IDE手冊。
以IAR EWARM里的DLib_Product_string.h文件為例,該文件中重定義了memcpy的實現:
#define _DLIB_STRING_SKIP_INLINE_MEMCPY
#pragma inline=forced_no_body
__EFF_NENR1NW2R1 __ATTRIBUTES void * memcpy(void * _D, const void * _S, size_t _N)
{
__aeabi_memcpy(_D, _S, _N);
return _D;
}
第三類:Provided by ARM
第三類文件由ARM提供,嵌入式程式的執行靠的是控制器內核(此處指的內核便是ARM內核),ARM公司在設計內核時,提供了一些內核模塊的介面,開發者可以通過這些介面訪問內核資源,CMSIS header里就是這些內核模塊資源的介面。
3. CMSIS header
完整的CMSIS header目錄應該是下麵這個樣子,而必須要關註的只有\CMSIS\Include下麵的core_cmx.h文件
\CMSIS
\Core
\DAP /* ARM debugger實現 */
\Driver /* ARM統一的常用外設driver API */
\DSP_Lib /* ARM優化實現的DSP Lib */
\Include /* ARM內核資源介面 */
\arm_xx.h
\cmsis_xx.h
\core_cmx.h
\Lib /* ARM優化實現的標準Lib */
\Pack
\RTOS /* ARM推出的RTOS- RTX */
\RTOS2
\SVD
\Utilities
core_cmx.h文件里定義了內核資源介面,裡面最常用的三大模塊是SCB,SysTick,NVIC,一個嵌入式開發的老手看到這些模塊應該要向豹哥揮手示意,來,讓豹哥看見你們的雙手~~~
第四類:Provided by Chip Producer
第四類文件是由ARM晶元生產商提供,我們在選型一個ARM晶元時,除了看ARM內核類型外,還得看晶元內部外設資源,是這些外設導致了ARM晶元差異,於是便有了各大ARM廠商爭奇鬥艷,比如NXP(Freescale), ST, Microchip(Atmel),ARM廠商賦予了ARM晶元各種外設資源,同時也會提供這些外設資源的介面。
該類別下文件有四種:
4. device.h:晶元頭文件,主要包含中斷號定義(xx_IRQn)、外設模塊類型定義(xx_Type) 、外設基地址定義(xx_BASE)。
/////////////////////////////////////////////////////
// 中斷號定義
typedef enum IRQn {
NotAvail_IRQn = -128,
/* Core interrupts */
NonMaskableInt_IRQn = -14,
HardFault_IRQn = -13,
...
SysTick_IRQn = -1,
/* Device specific interrupts */
WDT0_IRQn = 0,
...
} IRQn_Type;
////////////////////////////////////////////////////
// 外設寄存器定義
typedef struct {
__IO uint32_t MOD;
...
__IO uint32_t WINDOW;
} WWDT_Type;
#define WWDT_WINDOW_WINDOW_MASK (0xFFFFFFU)
#define WWDT_WINDOW_WINDOW_SHIFT (0U)
#define WWDT_WINDOW_WINDOW(x) (((uint32_t)(((uint32_t)(x)) << WWDT_WINDOW_WINDOW_SHIFT)) & WWDT_WINDOW_WINDOW_MASK)
////////////////////////////////////////////////////
// 外設基地址定義
#define WWDT0_BASE (0x5000E000u)
5. startup_device.s:晶元中斷向量表文件,主要包含中斷向量表定義(DCD xx_Handler) ,以及各中斷服務程式的弱定義(PUBWEAK)。 Note:該文件因編譯器而異。
;;基於IAR的startup_device.s文件
MODULE ?cstartup
;; Forward declaration of sections.
SECTION CSTACK:DATA:NOROOT(3)
SECTION .intvec:CODE:NOROOT(2)
PUBLIC __vector_table
PUBLIC __Vectors_End
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 中斷向量表定義
DATA
__vector_table
DCD sfe(CSTACK)
DCD Reset_Handler
DCD NMI_Handler
DCD HardFault_Handler
...
DCD SysTick_Handler
; External Interrupts
DCD WDT0_IRQHandler
...
__Vectors_End
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 中斷服務程式弱定義
THUMB
PUBWEAK WDT0_IRQHandler
PUBWEAK WDT0_DriverIRQHandler
SECTION .text:CODE:REORDER:NOROOT(2)
WDT0_IRQHandler
LDR R0, =WDT0_DriverIRQHandler
BX R0
WDT0_DriverIRQHandler
B .
END
6. system_device.c/h:晶元系統初始化文件,主要包含全局變數SystemCoreClock定義(提供晶元內核預設工作頻率)、SystemInit()函數定義(完成最基本的系統初始化,比如WDOG初始化,RAM使能等,這部分因晶元設計而異)。
7. device SDK Library:官方提供的晶元外設SDK driver包文件,有了這個SDK包可以直接使用片內外設設計自己的應用,而不需要查看晶元手冊里的外設模塊寄存器去重寫外設驅動。當然並不是每個廠商都有完善的SDK包,這取決於各廠商對軟體服務的重視程度。
// 來自於NXP SDK的WWDT driver API
void WWDT_GetDefaultConfig(wwdt_config_t *config);
void WWDT_Init(WWDT_Type *base, const wwdt_config_t *config);
void WWDT_Deinit(WWDT_Type *base);
void WWDT_ClearStatusFlags(WWDT_Type *base, uint32_t mask);
void WWDT_Refresh(WWDT_Type *base);
第五類:Created by Developer
第五類文件是開發者自己創建,用於實現開發者自己的嵌入式應用,分為應用系統啟動文件,應用系統初始化文件,應用文件。其中應用系統啟動和初始化文件屬於main函數之前的文件,一般可以通用,大部分開發者並不關心其具體內容,但是瞭解其過程可以加深對嵌入式系統結構的理解。
8. reset.s: 應用系統複位啟動文件,瞭解ARM原理的都知道,image前8個位元組數據分別是晶元上電的初始SP, PC,其中PC指向的便是本文件里的Reset_Handler,這是晶元執行的第一個函數入口,該函數主要用於完成應用系統初始化工作,包含應用中斷向量表重定向、調用晶元系統初始化、ARM系統寄存器rx清零、初始化應用程式各數據段、初始化ARM系統中斷、跳轉main函數。
// 一段經典的startup code
SECTION .noinit : CODE
THUMB
import SystemInit
import init_data_bss
import main
import CSTACK$$Limit
import init_interrupts
EXTERN __vector_table
REQUIRE __vector_table
#define SCB_BASE (0xE000ED00)
#define SCB_VTOR_OFFSET (0x00000008)
PUBLIC Reset_Handler
EXPORT Reset_Handler
Reset_Handler
// Mask interrupts
cpsid i
// Set VTOR register in SCB first thing we do.
ldr r0,=__vector_table
ldr r1,=SCB_BASE
str r0,[r1, #SCB_VTOR_OFFSET]
// Init the rest of the registers
ldr r2,=0
ldr r3,=0
ldr r4,=0
ldr r5,=0
ldr r6,=0
ldr r7,=0
mov r8,r7
mov r9,r7
mov r10,r7
mov r11,r7
mov r12,r7
// Initialize the stack pointer
ldr r0,=CSTACK$$Limit
mov r13,r0
// Call the CMSIS system init routine
ldr r0,=SystemInit
blx r0
// Init .data and .bss sections
ldr r0,=init_data_bss
blx r0
// Init interrupts
ldr r0,=init_interrupts
blx r0
// Unmask interrupts
cpsie i
// Set argc and argv to NULL before calling main().
ldr r0,=0
ldr r1,=0
ldr r2,=main
blx r2
__done
B __done
END
9. startup.c:應用系統初始化文件,該文件里主要包含兩個初始化函數,init_data_bss()、 init_interrupts(),data, bss段數據的初始化是為了保證嵌入式系統中所有全局變數能有一個開發者指定的初值。由於data,bss段的位置是在鏈接階段確定的,所以此處需要配合linker文件才能找到正確的data,bss位置,linker文件是因IDE而異的,所有本文件要想做到通用,必須增加各IDE條件編譯,此處僅以IAR下的實現為例:
//基於IAR的startup.c文件
#if (defined(__ICCARM__))
#pragma section = ".intvec"
#pragma section = ".data"
#pragma section = ".data_init"
#pragma section = ".bss"
#pragma section = "CodeRelocate"
#pragma section = "CodeRelocateRam"
#endif
void init_data_bss(void)
{
#if defined(__ICCARM__)
uint8_t *data_ram, *data_rom, *data_rom_end;
uint8_t *bss_start, *bss_end;
uint8_t *code_relocate_ram, *code_relocate, *code_relocate_end;
uint32_t n;
// 初始化data段 .data section (initialized data section)
data_ram = __section_begin(".data");
data_rom = __section_begin(".data_init");
data_rom_end = __section_end(".data_init");
n = data_rom_end - data_rom;
if (data_ram != data_rom)
{
while (n--)
{
*data_ram++ = *data_rom++;
}
}
// 初始化bss段 .bss section (zero-initialized data)
bss_start = __section_begin(".bss");
bss_end = __section_end(".bss");
n = bss_end - bss_start;
while (n--)
{
*bss_start++ = 0;
}
// 初始化CodeRelocate段 (執行在RAM中的函數(由IAR指定的__ramfunc修飾的函數)).
code_relocate_ram = __section_begin("CodeRelocateRam");
code_relocate = __section_begin("CodeRelocate");
code_relocate_end = __section_end("CodeRelocate");
n = code_relocate_end - code_relocate;
while (n--)
{
*code_relocate_ram++ = *code_relocate++;
}
#endif
}
void init_interrupts(void)
{
NVIC_ClearEnabledIRQs();
NVIC_ClearAllPendingIRQs();
}
10. application.c/h: 應用文件,此處便是主函數以及各功能函數的集合了,嵌入式老司機們,請開始你的表演~~~
void taskn(void)
{
...
}
int main(void)
{
printf("hello world\r\n");
taskn();
...
return 0;
}
至此,嵌入式開發里的各種來源的source文件豹哥便介紹完畢了,掌聲在哪裡~~~