STM32固件庫的CMSIS簡析(摘自網絡2011-02)
CMSIS軟件層次CMSIS可以分為多個軟件層次,分別由ARM公司、芯片供應商提供。
1、ARM Cortex™ 微控制器軟件接口標准(CMSIS:Cortex Microcontroller Software Interface Standard)
是 Cortex-M 處理器系列的與供應商無關的硬件抽象層。 使用CMSIS,可以為處理器和外設實現一致且簡單的軟件接口,從而簡化軟件的重用、縮短微控制器新開發人員的學習過程,並縮短新設備的上市時間。軟件的創建被嵌入式行業公認為主要成本系數。通過在所有Cortex-M 芯片供應商產品中標准化軟件接口,這一成本會明顯降低,尤其是在創建新項目或將現有軟件遷移到新設備時。最新版本的CMSIS[2]為3.5。
2結構
CMSIS是ARM公司與多家不同的芯片和軟件供應商一起緊密合作定義的,提供了內核與外設、實時操作系統和中間設備之間的通用接口。
CMSIS軟件層次
CMSIS可以分為多個軟件層次,分別由ARM公司、芯片供應商提供。
其中ARM提供了下列部分,可用於多種編譯器:
● 內核設備訪問層:包含了用來訪問內核的寄存器設備的名稱定義,地址定義和助手函數。同時也為RTOS(實時操作系統)定義了獨立於微控制器的接口,該接口包括調試通道定義。
● 中間設備訪問層:為軟件提供了訪問外設的通用方法。芯片供應商應當修改中間設備訪問層,以適應中間設備組件用到的微控制器上的外設。目前中間設備訪問層仍處於開發過程中,本文不做詳述。 芯片供應商擴展下列軟件層:
● 微控制器外設訪問層:提供片上所有外設的定義。
● 外設的訪問函數(可選):為外設提供額外的助手函數。CMSIS為Cortex-Mx微控制器系統定義了:
● 訪問外設寄存器的通用方法和定義異常向量的通用方法。
● 內核設備的寄存器名稱和內核異常向量的名稱。
● 獨立於微控制器的RTOS接口,帶調試通道。
● 中間設備組件接口(TCP/IP協議棧,閃存文件系統)。
CMSIS包含的組件
1、外圍寄存器和中斷定義: 適用於設備寄存器和中斷的一致接口
2、內核外設函數:特定處理器功能和內核外設的訪問函數
3、DSP 庫:優化的信號處理算法,並為 SIMD 指令提供Cortex-M4 支持
4、系統視圖說明(SVD):描述設備外設和中斷的XML 文件。
該標准完全可擴展,可確保其適合於所有 Cortex-M處理器系列微控制器,從最小的8 KB 設備到具有復雜通信外設(如以太網或USB)的設備。(內核外設函數的內存要求少於1 KB 代碼,少於10 字節RAM)。
基於CMSIS應用程序的基本結構CMSIS-外設訪問層的文件
基於CMSIS應用程序的基本結構[3]
獨立於編譯器的文件:
● Cortex-M3內核及其設備文件(core_cm3.h + core_cm3.c)
─ 訪問Cortex-M3內核及其設備:NVIC,SysTick等
─ 訪問Cortex-M3的CPU寄存器和內核外設的函數
● 微控制器專用頭文件(device.h)
─ 指定中斷號碼(與啟動文件一致)
─ 外設寄存器定義(寄存器的基地址和布局)
─ 控制微控制器其他特有的功能的函數(可選)
● 微控制器專用系統文件(system_device.c)
─ 函數SystemInit,用來初始化微控制器
─ 函數Sysem_ExtMemCtl,用來配置外部存儲器控制器。它位於文件
startup_stm32f10x_xx.s /.c,在跳轉到main前調用
─SystemFrequncy,該值代表系統時鍾頻率
─ 微控制器的其他功能(可選)
編譯器供應商+微控制器專用啟動文件
● 編譯器啟動代碼(匯編或者C)(startup_device.s)
─ 微控制器專用的中斷處理程序列表(與頭文件一致)
─ 弱定義(Weak)的中斷處理程序默認函數(可以被用戶代碼覆蓋)
CMSIS軟件包詳細目錄
下載的CMSIS軟件包含有Cortex M3核的支持文件以及基於Cortex M3核處理器的啟動代碼和庫引導文件。
使用步驟以STM32F10XX序列處理為例,介紹CMSIS的使用步驟。
1、從ST官方網站下載3.0以上版本的標准外設庫。
2、基於開發軟件新建工程,建立相應的文件目錄,注意詢問是否加入啟動文件時,選擇“否”。
3、根據所選芯片,把Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm中的啟動代碼加到工程中。
4、根據處理器FLASH容量的大小,選擇啟動代碼。stm32f10x.h 的66-73 行表明了啟動代碼的類型。例如STM32F10X_HD 行表示大容量的,如果不需要直接注釋掉即可。
5、選擇是否啟用標准外設庫。stm32f10x.h的105行的USE_STDPERIPH_DRIVER用於確定是否啟用,如果不啟用直接注釋掉。
6、確定處理器的頻率。system_stm32f10x.c的110-115行用於確定處理器的頻率,注釋掉不需要的即可。例如處理器的頻率為是36MHz,注釋SYSCLK_FREQ_72MHz,去掉SYSCLK_FREQ_36MHz注釋。
STM32的V3.5.0庫,內有CMSIS的文件夾為arm Cortex微控制器軟件接口標准,現將實際工作中應用作一個簡要分析:
1.選擇啟動文件:
根據自己所用的芯片的型號,選擇正確的啟動文件。這個根據數據手冊上的划分選擇STM32F107系列的處理器,我們就選擇startup_stm32f10x_hd.s。選擇STM32F103VBT6,就選擇startup_stm32f10x_md.s,在這個文件里,首選要定義自已的堆和棧的大小,這個根據自已的需要確定。文件中已經定義好了中斷向量的位置及堆和棧的初始化操作。
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
END
下面對上面的一段匯編進行解釋,相同的就省略解釋。
Reset_Handler PROC //0x0復位入口
EXPORT Reset_Handler [WEAK] // EXPORT或者GLOBAL:制定某標號代表的程序可為其它文件調用,即是聲明一個全局標號,[WEAK]選項聲明其他的同名標號優先於該標號被引用。聲明一個可全局引用的標號Reset_Handler
IMPORT __main //通知編譯器當前文件要引用標號__main,但__main在其他源文件中定義
IMPORT SystemInit //通知編譯器當前文件要引用標號SystemInit ,但SystemInit 在其他源文件中定義
LDR R0, =SystemInit //
BLX R0 //BLX指令進行ARM/Thumb狀態切換並且保存返回地址的轉移指令,放回地址(BLX指令指向下一條指令地址)保存鏈接寄存器R14中
LDR R0, =__main //向通用寄存器傳輸任意不大於232的立即數,即__main,立即數用=32位立即數,等同於MOV R0,#_main
BX R0 //BX指令進行ARM/Thumb狀態切換但不保存返回地址的轉移指令,R0為包含目標指令絕對地址的寄存器
END //結束
從上面這段文字中,可以看到,在系統復位后,先執行SystemInit(),再進入main()函數。SystemInit()在文件system_stm32f10x.c中定義,稍后再說。
2.stm32f10x.h:這個頭文件包含了STM32的大部份定義:
a.定義芯片的類型,如#define STM32F10x_MD
b.定義是否包含標准庫,#define USE_STDPERIPH_DRIVER
c.定義外部振盪器頻率,#define HSE_VALUE
上面三個定義,建議在main.c文件中剛開始就定義好,或者是在編譯器選項中定義好,這樣就可以不修改這個文件了。
d.采用枚舉的方式定義中斷號。定義中斷號在stm32f10x_it.c stm32f10x_it.h
e.包含 core_cm3.h,system_stm32f10x.h (#include "core_cm3.h" #include "system_stm32f10x.h" #include <stdint.h> )
f.定義數據類型,例如:typedef uint32_t u32此外還定義了FlagStatus,ITStatus及ErrorStatus等。u8,s8等為了兼容以前的庫所定義的數據,建議程序中用標准的uint8_t這樣的類型。此外還定義了bool,FlagStatus,alStatus及ErrorStatus
g.定義外設結構體,地址及用到的數據常量。
h.包含stm32f10x_conf.h來配置外設。#ifdef USE_STDPERIPH_DRIVER #include "stm32f10x_conf.h" #endif
i.定義位操作的宏
3.system_stm32f10x.h和system_stm32f10x.c,這兩個文件中:
a.定義一個全局變量 extern uint32_t SystemCoreClock: 系統時鍾頻率與你選擇有關
b.SystemInit():這個函數就是啟動文件中調用的函數
(1) 在system_stm32f10x.c的開始部份,選擇相關的系統時鍾頻率,
如#define SYSCLK_FREQ_24MHz 24000000
(2) 通過SystemInit()函數,就將SYSCLK = HCLK = PCLK1=PCLK2=PLL輸出24MHz。注意:這個頻率為HSE為8MHz時為條件,如果HSE不為8MHz或用HSI時,就會有問題。
c.SystemCoreClockUpdate():更新SystemCoreClock的值,與系統頻率一致。
可能看到,這個文件中的RCC設置很有局限性,所以在程序中,可以不用它,而用標准庫存中的stm32f10x_rcc中的函數進行設置。
4.stm32f10x_conf.h
a.配置需要的標准外設庫,需要用到的外設,把相應頭文件包含進去就可以。
b.定義assert_Param的模式,選擇#define USE_FULL_ASSERT時,斷言輸出問題所在的位置,在調試時很有用,在正式版本時,把它注釋掉即可。
5.core_cm3:與CM3內核相關的操作,重點如下:
a. 在MDK中,開總中斷:__enable_irq(); 關總中斷:__disable_irq();
b. 中斷處理程序:
(1) NVIC_SetPriorityGrouping(uint32_t PriorityGroup);
設置中斷組,這里的值只能是0~7,在STM32中,只能用8位中的前4位來設置組,可以分為兩部份:搶占優先級和亞優先級。這個數值就是亞優先級開始的位。它前面的位是搶占式優先級的位。例如:NVIC_SetPriorityGrouping(5),那么D7,D6表示搶占式優先級(0~3),D5,D4表示亞優先級(0~3)。優先級數值越小,優先級越高。搶占式優先級高的中斷可以打斷搶占式優先級低的中斷。搶占式優先級相同,亞優先級不同的兩個中斷,如果同時到來,先執行亞優先級高的中斷,再執行亞優先級低的中斷,但不能打斷。
可以理解為搶占式多任務。
(2) NVIC_EnableIRQ(IRQn_Type IRQn); 使能一個中斷EnableIRQ
(3) NVIC_DisableIRQ(IRQn_Type IRQn); 禁止一個中斷DisableIRQ
(4) NVIC_SetPriority(IRQn_Type IRQn,uint32 priority); 設置一個中斷的優先級
(5)NVIC_EncodePriority(uint32_t PriorityGroup,uint32_t PreemptPriority,uint32_t SubPriority);
(4)和(5)通常一起使用,這樣設置更直觀,例如要將外部中斷0設為搶占式優先級為0,亞優先級為2,則:
NVIC_SetPriority(EXTI0_IRQn,NVIC_EncodePriority(5,0,2));
注意PriorityGroup的參數應與(1)中設置的一致。
除了這種方式設置中斷外,也可以使用標准庫中的misc中的中斷設置函數來操作。
c. SysTick_Config(uint32_t ticks):設置系統嘀嗒時鍾並使能中斷
在STM32中與CM3內核描述不太一樣,這個時鍾源有兩個選擇:AHB/8和AHB,在該函數中是選擇了HCLK(SysTick_CTRL_CLKSOURCE_Msk),所以定時時間=ticks / HCLK,當要定時10ms,而HCLK為24MHz時,ticks = 10000 * 24 = 240000。
如果需要選擇HCLK/8,可以直接修改這個函數,或在這個函數后跟隨misc中的SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)來設置。
d.NVIC_SystemReset():復位芯片。
6. stm32f10x_it中斷實現,在這里編寫相應的中斷服務函數。
7.還需要注意的一點是:進入main函數后,除了設置嘀嗒時鍾和中斷外,在操作各外設之前,需要調用:
RCC_AHBPeriphClockCmd(),RCC_APB1PeriphClockCmd(),RCC_APB2PeriphClockCmd(),啟動相應的時鍾,否則外設就不能正常工作。
在這里簡單介紹一下NVIC
NVIC嵌套向量中斷控制器 Nested Vectored Interrupt Controller
系統中斷管理。——管理系統內部的中斷,負責打開和關閉中斷。
基礎應用1,中斷的初始化函數,包括設置中斷向量表位置,和開啟所需的中斷兩部分。所有程序中必須的。
用法:void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;//中斷管理恢復默認參數
#ifdef VECT_TAB_RAM
//如果C/C++ Compiler\Preprocessor\Defined symbols中的定義了VECT_TAB_RAM(見程序庫更改內容的表格)
NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0); //則在RAM調試
#else //如果沒有定義VECT_TAB_RAM
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);//則在Flash里調試
#endif //結束判斷語句
//以下為中斷的開啟過程,不是所有程序必須的。
//NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//設置NVIC優先級分組,方式。
//注:一共16個優先級,分為搶占式和響應式。兩種優先級所占的數量由此代碼確定,NVIC_PriorityGroup_x可以是0、1、 2、3、4,分別代表搶占優先級有1、2、4、8、16個和響應優先級有16、8、4、2、1個。規定兩種優先級的數量后,所有的中斷級別必須在其中選擇,搶占級別高的會打斷其他中斷優先執行,而響應級別高的會在其他中斷執行完優先執行。
//NVIC_InitStructure.NVIC_IRQChannel = 中斷通道名;
//開中斷,中斷名稱見函數庫
//NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
//搶占優先級
//NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
//響應優先級
//NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//啟動此通道的中斷
//NVIC_Init(&NVIC_InitStructure); //中斷初始化
}