系統時鍾滴答實驗很不難,我就在面簡單說下,但其中涉及到了STM32最復雜也是以后用途最廣的外設-NVIC,如果說RCC是實時性所必須考慮的部分,那么NVIC就是stm32功能性實現的基礎,NVIC的難度並不高,但是理解起來還是比較復雜的,我會在本文中從實際應用出發去說明,當然最好去仔細研讀宋岩翻譯的<Cortex-M3權威指南>第八章,注意這不是一本教你如何編寫STM32代碼的工具書,而是闡述Cortex-M3內核原理的參考書,十分值得閱讀。
SysTick系統時鍾的核心有兩個,外設初始化和Systick_Handle()中斷處理函數。
Systick配置:
static void SysTick_UserConfig(void) { SysTick->CTRL &= 0xfffffffb; //采用內核外部時鍾,即SYSTICK
SysTick->LOAD = 0x8000; //重裝值寄存器,VAL內數值為0時重裝
SysTick->VAL = 0x00; //SysTick當前值寄存器 清零
SysTick->CTRL = 0x03; //SysTick定時器使能,中斷使能
}
NVIC配置:
void NVIC_UserConfig(void) { NVIC_SetVectorTable(NVIC_VectTab_FLASH,0x0); //將指針指向flash中的中斷向量表
}
中斷函數:
void SysTick_Handler(void) { static uint32_t LED_Flag = 0; if(LED_Flag < 50) { LED_1_ON(); } if(LED_Flag >= 50) { LED_1_OFF(); } LED_Flag++; if(LED_Flag == 100) { LED_Flag = 0; } }
如此,就完成了簡單的SysTick滴答實驗,代碼請參考:http://files.cnblogs.com/files/zc110747/4.SysTick.7z
看到這是不是就結束了,不過記得當時我寫完這個程序,疑問有以下幾點:
1.向量表是如何定義的,重定位的操作有什么作用
2.為什么中斷函數名一定要是void SysTick_Handler(void),怎么確定的
3.中斷打亂了正常的程序流程,cpu怎么知道回到之前運行的位置
4.中斷優先級如何配置和理解
如何解決這些問題,這都需要從原理方面來解決了,了解過IAP和uC/os-ii的對這些問題應該有一定認知,下面我就系統的講解下我的想法:
1~2問題. 當 CM3 內核響應了一個發生的異常后,對應的異常服務例程(ESR)就會執行。為了決定 ESR 的入口地址,這就是所謂的“向量表查表機制”。向量表其實是一個 WORD( 32 位整數)數組,每個下標對應一種異常,該下標元素的值則是該 ESR 的入口地址。如果細心查看startup_stm32f10x_cl.s,就會發現下面語句:
; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors EXPORT __Vectors_End EXPORT __Vectors_Size __Vectors DCD __initial_sp ; Top of Stack DCD Reset_Handler ; Reset Handler DCD NMI_Handler ; NMI Handler DCD HardFault_Handler ; Hard Fault Handler DCD MemManage_Handler ; MPU Fault Handler DCD BusFault_Handler ; Bus Fault Handler DCD UsageFault_Handler ; Usage Fault Handler DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD SVC_Handler ; SVCall Handler DCD DebugMon_Handler ; Debug Monitor Handler DCD 0 ; Reserved DCD PendSV_Handler ; PendSV Handler DCD SysTick_Handler ; SysTick Handler ; External Interrupts DCD WWDG_IRQHandler ; Window Watchdog ...... DCD OTG_FS_IRQHandler ; USB OTG FS __Vectors_End __Vectors_Size EQU __Vectors_End - __Vectors AREA |.text|, CODE, READONLY
其中_Vectors既是所提到的WORD數組,這就是定義的向量表,如果了解過指向函數的指針,那么就可以知道,DCD的每一項就是定義的中斷服務例程,這樣我們就知道為什么Systick中斷的中斷服務例程是SysTick_Handler,當然根據實際情況這個向量表只要和主函數代碼保持一致就可以實現中斷的查詢,例如uC/os-ii移植中修改PendSV_Handler。
看到這解決了第二個問題,那么第一個問題,從上面可以看出來,向量表一直位於代碼的最頂端,也就是偏移量0x0的位置,為什么有時還需要重定位呢?如果看過IAP那篇,了解了啟動機制后,應該明白上電后系統會默認跳轉0x00000000(flash地址0x08000000映射),然后讀取向量表偏移寄存器,查詢向量表,因為此時向量表的偏移量就是0x0,向量表就不需要重定義。而在IAP模式下,應用代碼的起始地址並不是flash首地址,而是由偏移量0x8000(假定值),從上面也可以簡單推出應用代碼的向量表偏移量也是0x8000,此時向量表偏移寄存器就需要重定義了。
下圖來自於list下生成的.map文件
再參考生成的bin文件和啟動文件:
可以很清晰證實上面的觀點。
3.了解M3芯片基礎的應該知道,M3擁有通用寄存器組R0~R15,這些寄存器在程序運行中保存着代碼流程的所有信息,包括當前地址,正在修改的變量參數。因此在中斷觸發時,只要將R0~R15依次壓棧,中斷結束后出棧,代碼就會回到運行之前的位置(uC/OS-ii正是模擬該過程實現任務切換的),當然這只是最簡單的一種情況,因為m3芯片本身支持中斷優先級和中斷嵌套,實際復雜度遠高於此.其實可以簡化為如下流程:
主程序暫停 -〉相關位置和狀態參數入棧-〉中斷服務例程執行-〉相關位置和狀態參數出棧-〉主程序恢復
4. Cortex-M3支持最多240個可配置中斷,中斷優先級的數目3~8位,也就是支持8~256個優先級,而事實上一般並沒有支持那么多,如8,16,32級,其中最多支持128個搶占級。arm中斷及復位控制寄存器中的3~8位設定優先級的部分,通過配置可將其分割為兩部分,前面為搶占級,后面為亞優先級,並且亞優先級至少為1位,分組是可以從保留的優先級組開始的。一個中斷的優先級可通過以下順序判斷,優先級依此降低。
優先級組:搶占級 > 亞優先級
優先級: 數值小 > 數值大
中斷號: 中斷號小 > 中斷號大
搶占級和亞優先級的區別:
搶占級是在發生在中斷嵌套的基礎,上面提到了中斷打斷了線程的流程,但是如果有搶占級的加入,中斷本身也會被優先級更高的中斷打斷,具體流程如下:
主程序暫停 -〉低優先級中斷 -〉高優先級中斷-〉低優先級中斷-〉主程序恢復
亞優先級表示沒有發生中斷的嵌套,一個中斷只有在它結束后另一個亞優先級的中斷才會響應,即不會發生中斷嵌套。
此外為了加快中斷執行的流程,Cortex-M3提供了基於優先級的四種動作:
1.占先: 主要發生在搶占級中,即高優先級中斷低優先級執行,發生中斷嵌套
2.末尾連鎖: 若占先發生上一個中斷的末尾出棧之前,則打斷出棧動作,直接執行高優先級中斷,結束后如果沒有發生搶占,才執行出棧
3.遲來 若占先發生在中斷的開始入棧階段,則繼續入棧,低優先級中斷掛起。
4.返回 出棧過程,如果收到高優先級中斷則停止並產生末尾連鎖
從上面可以看出,中斷優先級和中斷嵌套在加上ARM基於優先級機制的優化,可以讓中斷中高優先級任務更快執行,正是優先級設定的意義。