我相信昨天的文章你一定大飽眼福了,沒關(guān)系,接下來的更精彩,也會(huì)對(duì)C語言有個(gè)全新的理解。
今天這個(gè)文件屬于CM3核心定義:有CMSIS核心的所有結(jié)構(gòu)和符號(hào)
Cortex-M核心寄存器和位域,Cortex-M核心外設(shè)基址。
今天的對(duì)象在這里
先看h文件的前面幾行
根據(jù)前面三行宏定義,最終計(jì)算出的CMSIS HAL庫完整版本號(hào)為:__CM3_CMSIS_VERSION = (0x01 << 16) | 0x30 = 0x010030所以,完整的版本號(hào)為0x010030。
其中:__CM3_CMSIS_VERSION_MAIN = 0x01,主版本號(hào)為0x01__CM3_CMSIS_VERSION_SUB = 0x30,子版本號(hào)為0x30通過左移16位實(shí)現(xiàn)主版本號(hào) occupies 的高16位,子版本號(hào)占低16位,然后按位或生成完整32位的版本號(hào)0x010030。
這個(gè)32位版本號(hào)包含了CMSIS HAL庫的主版本號(hào)與子版本號(hào)信息,通過該版本號(hào),根據(jù)這三行宏定義,可以知道當(dāng)前使用的CMSIS HAL庫的版本號(hào)為0x010030。其中高16位0x01表示主版本號(hào),低16位0x30表示子版本號(hào)。
0x010030
= 0x01 * (2^16) + 0x30 * (2^0)
= 1 * 65536 + 48 * 1
= 65536 + 48
= 65680
這是轉(zhuǎn)成10進(jìn)制的數(shù)字。
其中,主版本號(hào)0x01對(duì)應(yīng)的10進(jìn)制數(shù)為1,子版本號(hào)0x30對(duì)應(yīng)的10進(jìn)制數(shù)為48。
通過將主版本號(hào)的值×2^16,子版本號(hào)的值×2^0相加,我們可以得出CMSIS HAL庫完整版本號(hào)對(duì)應(yīng)的10進(jìn)制數(shù)65680。
這個(gè)10進(jìn)制數(shù)同樣包含了主版本號(hào)1和子版本號(hào)48的信息,我們可以清楚知道當(dāng)前使用的CMSIS HAL庫版本號(hào)為1.48。
1代表主版本號(hào),48代表子版本號(hào),兩者組合即為完整版本號(hào)1.48。
所以,總結(jié)來說,CMSIS-HAL庫的版本號(hào)0x010030
可以表示為:
Hex: 0x010030
Decimal: 65680
Version: 1.48
那不免有疑問,明明可以直接10進(jìn)制文件的,為啥這么復(fù)雜呢?讓我來斗膽的分析一下。
主要有以下幾個(gè)考慮因素:
1. 兼容性:使用位運(yùn)算生成的版本號(hào)格式0x010030與CMSIS HAL庫一致,這樣可以最大限度保證與庫的兼容性。
2. 擴(kuò)展性:使用位運(yùn)算,主版本號(hào)占高16位,子版本號(hào)占低16位,這樣主版本號(hào)可以擴(kuò)展到65536,子版本號(hào)也有很高擴(kuò)展空間,更利于版本的長期維護(hù)與擴(kuò)展。
3. 信息包含:32位的版本號(hào)可以同時(shí)包含主版本號(hào)與子版本號(hào),一目了然,這個(gè)信息直接清楚可見。如果只使用1.48形式,無法同時(shí)看到主子版本號(hào)的值,信息表達(dá)不夠直接。
4. 處理方便:位運(yùn)算生成的版本號(hào)可以通過簡單的移位與位運(yùn)算提取主版本號(hào)與子版本號(hào)的值,這在編程處理時(shí)比較方便。
5. 標(biāo)準(zhǔn)形式:0x開頭的十六進(jìn)制數(shù)是MCU編程中常用的標(biāo)準(zhǔn)表達(dá)形式,使用起來比較習(xí)慣。
所以,總體來說,雖然直接使用1.48的形式更簡單直觀,但使用位運(yùn)算生成0x010030格式的版本號(hào),可以在兼容性、擴(kuò)展性、信息包含以及處理方便性等方面獲得優(yōu)勢,也符合編程習(xí)慣的標(biāo)準(zhǔn)表達(dá)形式??紤]到CMSIS HAL庫作為MCU的底層支撐庫,需要長期維護(hù)與迭代,所以選擇使用位運(yùn)算生成版本號(hào)格式可以獲得更多優(yōu)點(diǎn),這可能也是CMSIS HAL庫設(shè)計(jì)者選擇這種版本號(hào)格式的主要考量因素。
接下來看這個(gè)
這段代碼主要完成了MCU內(nèi)核類型與數(shù)據(jù)類型的定義。
__CORTEX_M (0x03) 此宏定義指定了內(nèi)核類型為Cortex-M3,其十六進(jìn)制值為0x03。
#include 此行包含stdint.h頭文件,用于定義標(biāo)準(zhǔn)數(shù)據(jù)類型,如Uint8_t、int32_t等。
#if defined(__ICCARM__) 此行條件判斷是否使用IAR編譯器,如果使用則包含intrinsics.h頭文件。
#include 此行包含intrinsics.h頭文件,該頭文件定義了IAR C/C++編譯器的內(nèi)嵌匯編指令。
所以,這段代碼主要完成了兩方面的工作:
1. 通過__CORTEX_M宏定義指定了MCU使用的內(nèi)核類型為Cortex-M3。2. 包含stdint.h頭文件,定義了標(biāo)準(zhǔn)的數(shù)據(jù)類型,用于MCU開發(fā)時(shí)使用。
3. 如果使用IAR編譯器,則額外包含intrinsics.h頭文件,可以使用IAR編譯器提供的內(nèi)嵌匯編指令。
這段簡短的代碼定義和包含了MCU開發(fā)最基礎(chǔ)的信息:
1. 內(nèi)核類型:我們知道目標(biāo)MCU使用的內(nèi)核是Cortex-M3。
2. 數(shù)據(jù)類型:通過stdint.h我們可以使用標(biāo)準(zhǔn)的數(shù)據(jù)類型,如Uint32_t等。
3. 如果使用IAR編譯器,可以使用內(nèi)嵌匯編指令來優(yōu)化程序。
這幾個(gè)macro是頻繁出現(xiàn)的,細(xì)說一下
這段代碼主要定義了中斷優(yōu)先級(jí)位數(shù)和IO操作權(quán)限。
#ifndef __NVIC_PRIO_BITS
#define __NVIC_PRIO_BITS 4
#endif
這兩行定義了中斷優(yōu)先級(jí)位數(shù)為4,如果__NVIC_PRIO_BITS未定義,則進(jìn)行定義,否則忽略。
#ifdef __cplusplus
#define __I volatile
#else
#define __I volatile const
#endif
這幾行判斷是否使用C++編譯器,如果使用C++編譯器,__I定義為volatile,否則定義為volatile const,表示只讀屬性。
#define __O volatile
此行定義__O為volatile,表示只寫屬性。
#define __IO volatile
此行定義__IO為volatile,表示讀寫屬性。
所以,這段代碼主要完成了:
1. 如果__NVIC_PRIO_BITS未定義,則定義中斷優(yōu)先級(jí)位數(shù)為4。否則忽略。
2. 根據(jù)編譯器選擇定義只讀屬性__I為volatile或volatile const。
3. 定義只寫屬性__O為volatile。
4. 定義讀寫屬性__IO為volatile。
5. 這四個(gè)屬性主要用于定義外設(shè)寄存器的訪問權(quán)限,以確保編譯器不會(huì)對(duì)訪問的代碼作優(yōu)化,影響讀取的準(zhǔn)確性。
也就是說,這段代碼為寄存器的訪問屏蔽了編譯器的優(yōu)化,在編譯過程中讓編譯器明確區(qū)分:
這是一個(gè)只讀寄存器,值可能會(huì)被其他因素改變,讀取時(shí)總是獲取最新值。
這是一個(gè)只寫寄存器,每次寫入的值必須被外設(shè)接收。
這個(gè)寄存器是可讀寫的,讀取與寫入都必須準(zhǔn)確地映射到外設(shè)。這樣可以最大限度地確保我們的程序以預(yù)期的方式使用這些寄存器,也不會(huì)因?yàn)榫幾g器的優(yōu)化而導(dǎo)致意外的結(jié)果。
接下來我們看這個(gè),知識(shí)點(diǎn)有點(diǎn)密集
中斷到中斷向量的映射
顯示了中斷(或IRQ號(hào))如何映射到中斷寄存器和相應(yīng)的CMSIS變量(每個(gè)中斷有一位)。
這段代碼定義了NVIC_Type結(jié)構(gòu)體,用于表示NVIC(嵌套向量中斷控制器)的控制與狀態(tài)寄存器。
NVIC_Type結(jié)構(gòu)體包含以下成員:
__IO uint32_t ISER[8]; 中斷使能置位寄存器,用于使能中斷,數(shù)組8個(gè)成員對(duì)應(yīng)NVIC的8個(gè)中斷組。
__IO uint32_t ICER[8]; 中斷清除使能寄存器,用于禁止中斷,數(shù)組8個(gè)成員對(duì)應(yīng)NVIC的8個(gè)中斷組。
__IO uint32_t ISPR[8]; 中斷待處理置位寄存器,用于置位某中斷的待處理標(biāo)志,數(shù)組8個(gè)成員對(duì)應(yīng)NVIC的8個(gè)中斷組。
__IO uint32_t ICPR[8]; 中斷待處理清除寄存器,用于清除某中斷的待處理標(biāo)志,數(shù)組8個(gè)成員對(duì)應(yīng)NVIC的8個(gè)中斷組。
__IO uint32_t IABR[8]; 中斷激活寄存器,指示哪些中斷被激活,數(shù)組8個(gè)成員對(duì)應(yīng)NVIC的8個(gè)中斷組。
__IO uint8_t IP[240]; 中斷優(yōu)先級(jí)寄存器,設(shè)置優(yōu)先級(jí),共240個(gè)成員,每個(gè)成員1字節(jié),對(duì)應(yīng)MCU中的240個(gè)中斷優(yōu)先級(jí)設(shè)置。
__O uint32_t STIR; 軟件觸發(fā)中斷寄存器,用于軟件觸發(fā)指定的中斷。
所以,這個(gè)結(jié)構(gòu)體包含了NVIC所有的控制與狀態(tài)寄存器,通過這些寄存器,我們可以完成:
1. 中斷使能與失能設(shè)置。
2. 中斷待處理標(biāo)志的置位與清除。
3. 檢測哪些中斷被激活。
4. 設(shè)置各個(gè)中斷的優(yōu)先級(jí)。
5. 軟件觸發(fā)某個(gè)指定的中斷。
簡單來說,這個(gè)結(jié)構(gòu)體高度抽象和集成地代表了NVIC及其所有的功能與控制寄存器,使用時(shí)直接通過相應(yīng)的成員來操作寄存器。
這個(gè)NVIC_Type結(jié)構(gòu)體代表的不是某個(gè)具體的存儲(chǔ)區(qū)域,而是概念性地將NVIC所有的寄存器集成在一個(gè)結(jié)構(gòu)體中,以方便我們管理和訪問這些寄存器。
結(jié)構(gòu)體中的每個(gè)成員,如__IO uint32_t ISER[8]都代表NVIC中的一個(gè)實(shí)際的32位寄存器。
這些寄存器的地址在MCU的外設(shè)地址映射中已經(jīng)固定,結(jié)構(gòu)體將它們邏輯上集成在一起,方便我們按功能管理和訪問。
所以,當(dāng)我們需要操作NVIC使能某個(gè)中斷時(shí),只需要像下面這樣使用ISER成員:
NVIC->ISER[2]|=(1<5);?//?使能中斷組2中的第6個(gè)中 這行代碼通過NVIC結(jié)構(gòu)體操作寄存器,而ISER[2]則映射到NVIC使能寄存器組2實(shí)際的物理地址。
在MDK或IAR等IDE中,這些寄存器的具體地址將在外設(shè)地址映射Memap窗口中顯示。
例如,ISER[2]可能映射到0xE000E200這樣的實(shí)際地址,這個(gè)地址上的32位寄存器包含對(duì)應(yīng)的位用于使能第6個(gè)中斷。
所以,總結(jié)來說:
1. NVIC_Type 結(jié)構(gòu)體邏輯上將NVIC的所有寄存器集成在一起,方便管理和訪問,但本身不代表實(shí)際的存儲(chǔ)區(qū)域。
2. 結(jié)構(gòu)體中的每個(gè)成員代表NVIC中的一個(gè)實(shí)際物理寄存器,映射到固定的地址。
3. 我們通過結(jié)構(gòu)體操作這些寄存器,然后編譯器會(huì)將其映射到實(shí)際的物理地址上。
4. 這些寄存器的具體地址將在MCU的外設(shè)地址映射中指定,我們可以在IDE的Memap窗口中查看。
5. 所以結(jié)構(gòu)體更像是一個(gè)邏輯上的抽象,將NVIC的寄存器方便地集成在一起,在程序中按功能管理和訪問。
更多詳細(xì)的內(nèi)容得看這個(gè)
嵌套中斷矢量控制器
Cortex-M3 NVIC寄存器的CMSIS映射為了提高軟件效率,CMSIS簡化了NVIC寄存器的表示。
在CMSIS中:Set-enable, Clear-enable, Set-pending, Clear-pending和Active Bit寄存器映射到32位整數(shù)數(shù)組,因此:
看這個(gè)IS,IC,下面就不放了
數(shù)組ISER[O] ~ ISER[2]對(duì)應(yīng)寄存器ISERO-ISER2,
數(shù)組ICER[O] ~ ICER[2]對(duì)應(yīng)寄存器ICERO-ICER2,
數(shù)組ISPR[0] ~ ISPR[2]對(duì)應(yīng)寄存器ISPRO-ISPR2,
數(shù)組ICPR[O] ~ ICPR[2]對(duì)應(yīng)寄存器ICPRO-ICPR2,
數(shù)組IABR[O] ~ IABR[2]對(duì)應(yīng)寄存器IABRO-IABR2。
中斷優(yōu)先級(jí)寄存器的8位字段映射到一個(gè)8位整數(shù)數(shù)組,因此數(shù)組IP[O]到IP[67]對(duì)應(yīng)于寄存器IPRO-IPR67,數(shù)組]條目IP[n]保持中斷n的中斷優(yōu)先級(jí)。
CMSIS提供線程安全的代碼,提供對(duì)中斷優(yōu)先級(jí)寄存器的原子訪問。
我繼續(xù)說更多的細(xì)節(jié),__IO uint32_t ISER[8];比如這種寫法前面的__IO 是干嘛用的?我來解釋一下,看不懂的應(yīng)該是沒學(xué)過C。
__IO的定義如下:
#define __IO volatile
它被定義為volatile,意味著ISER[8]成員所代表的寄存器是一個(gè)讀寫寄存器。
所以,__IO的作用是:
1.通知編譯器ISER[8]成員所對(duì)應(yīng)寄存器的讀寫屬性,是可讀可寫的。
2.阻止編譯器對(duì)讀寫這些寄存器的代碼做優(yōu)化。因?yàn)檫@些寄存器的值可能會(huì)被其他因素改變,每次讀寫的值必須準(zhǔn)確對(duì)應(yīng)于寄存器的當(dāng)前值。如果不使用__IO對(duì)其進(jìn)行修飾,編譯器在編譯過程中可能會(huì)對(duì)訪問這些寄存器的代碼作優(yōu)化,這會(huì)導(dǎo)致我們讀到的值不是寄存器的真實(shí)值,產(chǎn)生意外的后果。
所以,__IO關(guān)鍵字通過定義為volatile,告訴編譯器:
1. ISER[8]成員代表的寄存器是可讀可寫的。
2. 每次讀取該寄存器必須從外設(shè)獲取最新值,寫入時(shí)必須將新值準(zhǔn)確寫入外設(shè)。
3. 編譯器在編譯過程中不得對(duì)其進(jìn)行任何優(yōu)化。
簡單來說,__IO關(guān)鍵字修飾uint32_t ISER[8]成員,目的是通知編譯器其對(duì)應(yīng)的寄存器屬性和訪問要求,進(jìn)而阻止編譯器的優(yōu)化,確保我們的程序以預(yù)期的方式正確訪問這些寄存器。這有利于我們編寫的代碼正常工作,不會(huì)因?yàn)榫幾g器的優(yōu)化引入意外的副作用,訪問寄存器時(shí)總是獲取最新的準(zhǔn)確值。
說完了嗎?還沒有,我還想bibi幾句:
在C語言中,__IO這樣在標(biāo)識(shí)符(如結(jié)構(gòu)體成員名)前面加上的關(guān)鍵字被稱為修飾符(qualifier)。修飾符的作用是為標(biāo)識(shí)符添加某種屬性或額外的語義。__IO 就是一個(gè)典型的修飾符例子,它被用來表示標(biāo)識(shí)符代表的是一個(gè)讀寫寄存器,并禁止編譯器對(duì)其優(yōu)化。
所以,__IO 在這里相當(dāng)于一個(gè)寄存器的修飾符,為其添加讀寫以及volatile 的屬性。
在C語言中,常見的修飾符還有:
1.const:常量修飾符,用于表示標(biāo)識(shí)符是一個(gè)常量。
2.volatile:指示值可能會(huì)被其他因素改變的修飾符,告訴編譯器每次讀取該值必須重新從內(nèi)存中獲取。
3.restrict:表示某指針是唯一訪問某塊內(nèi)存的手段,可以用來提高效率。
4.inline:表示該函數(shù)是內(nèi)聯(lián)函數(shù),由編譯器直接將函數(shù)體插入調(diào)用處。
5. extern:表示該標(biāo)識(shí)符(如變量或函數(shù))的定義在其他地方,extern int a;
所以,總結(jié)來說:
1.修飾符是放在標(biāo)識(shí)符(如變量名、函數(shù)名、結(jié)構(gòu)體成員名)前面的關(guān)鍵字。2.修飾符的作用是為標(biāo)識(shí)符添加某種屬性或語義。
3.__IO 是作為寄存器修飾符使用的,表示寄存器是可讀寫的,并禁止編譯器對(duì)其優(yōu)化。
4.const、volatile、restrict、inline、extern都是常見的修飾符,用來表示常量性、值變化、函數(shù)內(nèi)聯(lián)等屬性。
5.使用修飾符可以為程序添加重要的額外信息,引導(dǎo)編譯器作出正確的處理。
再擴(kuò)展一些,這個(gè)東西可以在函數(shù)上面用嗎?我這里先噴,以前不懂這個(gè)群里面問半天,結(jié)果都雞兒半桶水,讓我寫什么程序自己實(shí)驗(yàn),真心累啊。
在C語言中修飾符也可以用于修飾函數(shù)。
常見的用于修飾函數(shù)的修飾符有:
1. inline:表示該函數(shù)是內(nèi)聯(lián)函數(shù),主要作用是鼓勵(lì)編譯器將函數(shù)體直接插入所有調(diào)用點(diǎn),以減少函數(shù)調(diào)用的開銷。
2. extern:表示該函數(shù)的定義在其他地方,用于函數(shù)前向聲明。例如: inline void func1() { ... } // 內(nèi)聯(lián)函數(shù)
extern void func2(); // 函數(shù)前向聲明
void func2() { ... } // 函數(shù)定義 在這個(gè)例子中:
func1被inline修飾,表示其是一個(gè)內(nèi)聯(lián)函數(shù),編譯器可以選擇將其函數(shù)體插入調(diào)用處。
func2首先被extern修飾,進(jìn)行前向聲明,然后給出函數(shù)定義。
另外,static、volatile 等修飾符也可以用于修飾函數(shù),區(qū)別如下:
static:static修飾的函數(shù)只在定義它的文件內(nèi)可見,表示私有函數(shù)。
volatile:volatile修飾的函數(shù)其地址可能改變,每次調(diào)用時(shí)必須從內(nèi)存中獲取最新地址。
主要用于嵌入式中斷函數(shù)等。所以,總結(jié)來說:
1.C語言中的修飾符不僅可以用于修飾變量,也可以用于修飾函數(shù)。
2.常見的用于修飾函數(shù)的修飾符有inline、extern、static、volatile等。3.inline表示內(nèi)聯(lián)函數(shù),extern表示函數(shù)前向聲明,static表示私有函數(shù),volatile表示地址可能改變的函數(shù)。
4.使用修飾符可以為函數(shù)添加額外的屬性和語義,引導(dǎo)編譯器生成我們期望的代碼,這在優(yōu)化程序性能方面具有很好的作用。
那我在這個(gè)函數(shù)前面就放一個(gè)用宏定義的修飾符,宏什么都不定義。這個(gè)會(huì)報(bào)錯(cuò)了,一定要記住程序是最確定的東西。
#define __MY_MODIFIER
__MY_MODIFIER void func() { ... }
這里的__MY_MODIFIER宏未進(jìn)行任何定義,所以編譯器并不知道它表示什么屬性或語義。在編譯這個(gè)代碼時(shí),編譯器會(huì)報(bào)類似下面的錯(cuò)誤:undefined identifier '__MY_MODIFIER'這是因?yàn)榫幾g器并不識(shí)別__MY_MODIFIER這個(gè)未定義標(biāo)識(shí)符,所以不明白它作為函數(shù)修飾符的作用,這會(huì)導(dǎo)致編譯錯(cuò)誤。要使用我們自己定義的修飾符,需要為其給出明確的定義,例如:
#define __MY_MODIFIER static
__MY_MODIFIER void func() { ... }
這里我們定義__MY_MODIFIER為static,這樣編譯器就能理解其作用,并將func函數(shù)定義為靜態(tài)的。所以,總結(jié)來說:1. 若要使用自定義的修飾符,必須為其給出明確的定義,否則編譯器無法理解其作用,會(huì)報(bào)錯(cuò)。2.自定義修飾符的定義可以通過#define來實(shí)現(xiàn),例如#define __MY_MODIFIER static。3. 定義后,修飾符可以用于修飾變量、函數(shù)等,編譯器會(huì)根據(jù)其定義來理解其修飾的作用。4. 未定義的修飾符會(huì)導(dǎo)致編譯錯(cuò)誤,因?yàn)榫幾g器不知道如何處理這個(gè)未知的標(biāo)識(shí)符。5. 自定義修飾符的一個(gè)重要應(yīng)用就是,當(dāng)標(biāo)準(zhǔn)修飾符無法滿足需要時(shí),我們可以定義自己的修飾符來擴(kuò)展語言和表達(dá)程序語義。
對(duì)于這個(gè)中斷的寄存器就是這些,不要陷入太深,繼續(xù)往下看
系統(tǒng)控制塊(SCB)系統(tǒng)控制塊(System control block, SCB)提供系統(tǒng)實(shí)現(xiàn)信息和系統(tǒng)控制。這包括系統(tǒng)異常的配置、控制和報(bào)告。
Cortex-M3 SCB寄存器的CMSIS映射為了提高軟件效率,CMSIS簡化了SCB寄存器的表示。
在CMSIS中,字節(jié)數(shù)組SHP[0]到SHP[12]對(duì)應(yīng)寄存器SHPR1-SHPR3。
接著就是這個(gè)了
這個(gè)結(jié)構(gòu)體定義了SCB(系統(tǒng)控制塊)的寄存器集。SCB模塊是Cortex-M內(nèi)核的一部分,用于系統(tǒng)控制與配置。
SCB_Type 結(jié)構(gòu)體包含以下主要成員:
__I uint32_t CPUID; CPUID寄存器,包含設(shè)備ID和修訂信息。
__IO uint32_t ICSR; 中斷控制狀態(tài)寄存器,用于中斷使能、優(yōu)先級(jí)設(shè)置和掛起狀態(tài)控制。
__IO uint32_t VTOR; 向量表偏移寄存器,配置中斷/異常向量表的位置和偏移。
__IO uint32_t AIRCR; 應(yīng)用中斷/復(fù)位控制寄存器,用于配置中斷優(yōu)先級(jí)組和系統(tǒng)復(fù)位。
__IO uint32_t SCR; 系統(tǒng)控制寄存器,用于配置中斷優(yōu)先級(jí)組、SLEEPDEEP位等。
__IO uint32_t CCR; 配置控制寄存器,用于配置存儲(chǔ)器mapped模式和無效指令報(bào)告位。
__IO uint8_t SHP[12]; 系統(tǒng)句柄程序優(yōu)先級(jí)寄存器,設(shè)置不同異常的優(yōu)先級(jí)。
__IO uint32_t SHCSR; 系統(tǒng)句柄控制和狀態(tài)寄存器,報(bào)告不同異常的掛起和激活狀態(tài)。
CFSR、HFSR、DFSR; 可配置故障狀態(tài)寄存器,用于報(bào)告各種故障和異常的狀態(tài)。
MMFAR、BFAR;內(nèi)存錯(cuò)誤和總線故障地址寄存器,報(bào)告相關(guān)故障的地址。PFR、DFR、ADR; 寄存器用于報(bào)告處理器特征、調(diào)試功和輔助功能。MMFR、ISAR; 寄存器用于報(bào)告內(nèi)存模型和指令集架構(gòu)的特征。
所以,SCB_Type結(jié)構(gòu)體包含SCB模塊所有的控制/狀態(tài)寄存器和ID寄存器,通過這些寄存器我們可以完成:
1. 中斷控制(使能/禁止)和優(yōu)先級(jí)設(shè)置。
2. 配置向量表位置和系統(tǒng)復(fù)位。
3. 設(shè)置不同異常的優(yōu)先級(jí)別。
4. 獲取設(shè)備ID、內(nèi)核修訂版本以及各種特征信息。
5. 獲取并處理不同類型的故障和異常。
6. 配置系統(tǒng)控制位,如SLEEPDEEP。
接下來定義的是這樣的東西,本來這種細(xì)節(jié)的東西就不寫了,但是為了精通這個(gè)小目標(biāo)是要寫的。
上面這個(gè)代碼可能看起來有點(diǎn)懵逼,這里其實(shí)都是底層的寄存器,不妨去看看這個(gè):
這個(gè)就是對(duì)應(yīng)的寄存器布局,我們只是想知道里面是啥而已
這些宏定義用于讀取SCB->CPUID寄存器中的設(shè)備ID和修訂信息。其中:SCB_CPUID_IMPLEMENTER_Pos和SCB_CPUID_IMPLEMENTER_Msk用于讀取IMPLEMENTER字段,該字段包含CPU的制造商ID。SCB_CPUID_VARIANT_Pos和SCB_CPUID_VARIANT_Msk用于讀取VARIANT字段,該字段包含CPU的變體編號(hào)。SCB_CPUID_PARTNO_Pos和SCB_CPUID_PARTNO_Msk用于讀取PARTNO字段,該字段包含CPU的具體型號(hào)。SCB_CPUID_REVISION_Pos和SCB_CPUID_REVISION_Msk用于讀取REVISION字段,該字段包含CPU的修訂版本。所以,通過這些宏,我們可以從SCB->CPUID寄存器中提取關(guān)鍵的設(shè)備信息:制造商ID:
uint32_t implementer = (SCB->CPUID & SCB_CPUID_IMPLEMENTER_Msk) >> SCB_CPUID_IMPLEMENTER_Pos;
變體編號(hào):
uint32_t variant = (SCB->CPUID & SCB_CPUID_VARIANT_Msk) >> SCB_CPUID_VARIANT_Pos;
CPU型號(hào):
uint32_t partNo = (SCB->CPUID & SCB_CPUID_PARTNO_Msk) >> SCB_CPUID_PARTNO_Pos;
CPU修訂版本:
uint32_t revision = (SCB->CPUID & SCB_CPUID_REVISION_Msk) >> SCB_CPUID_REVISION_Pos;
ARM Cortex-M內(nèi)核的CPUID寄存器包含這些關(guān)鍵字段,方便識(shí)別和區(qū)分不同的芯片,并獲取其精確的型號(hào)與修訂信息。宏定義極大地簡化提取這些信息的過程,只需要通過位域操作和移位就可以獲得所需要的ID參數(shù),這在很大程度上增強(qiáng)了代碼的可讀性。
不在乎含義,在乎寫法,接下來看寫法
SCB_CPUID_IMPLEMENTER_Pos代表:
IMPLEMENTER字段在CPUID寄存器中的起始位置(偏移),其值為24。
SCB_CPUID_IMPLEMENTER_Msk代表:
IMPLEMENTER字段的掩碼,通過將0xFF左移24位得到,其值為0xFF000000。所以,要讀取IMPLEMENTER字段,我們可以像下面這樣使用這兩個(gè)宏:
uint32_t implementer = (SCB->CPUID & SCB_CPUID_IMPLEMENTER_Msk) >> SCB_CPUID_IMPLEMENTER_Pos;
該語句通過與SCB_CPUID_IMPLEMENTER_Msk的位與操作獲取IMPLEMENTER字段,然后右移SCB_CPUID_IMPLEMENTER_Pos(24)位,將其移到最低8位,obtaining the implementer code.
例如,如果CPUID的值為0x410FC241,那么:SCB->CPUID = 0x410FC241
SCB_CPUID_IMPLEMENTER_Msk = 0xFF000000
通過與操作:0x410FC241 & 0xFF000000 = 0x41000000
右移24位:0x41000000 >> 24 = 0x41 = 65(十進(jìn)制)所以,IMPLEMENTER字段的值為65(十進(jìn)制),表示CPU的制造商是ARM。位域操作通過掩碼獲取目標(biāo)字段,位移則將其移到需要的位置。
位域和位移都是位運(yùn)算的概念,用于在二進(jìn)制位級(jí)別操作和訪問數(shù)據(jù)。位域操作用于在一個(gè)數(shù)據(jù)域(如寄存器)的不同位上訪問多個(gè)字段。它通過掩碼來選擇和操作目標(biāo)字段中的位。
常用的位域操作有:與(&):如果兩個(gè)操作數(shù)的對(duì)應(yīng)比特位都是1,則該位的結(jié)果為1,否則為0。用于選取目標(biāo)字段。或(|):只要兩個(gè)操作數(shù)的對(duì)應(yīng)比特位有一個(gè)為1,則該位的結(jié)果為1。用于修改或設(shè)置字段的值。非(~) :反轉(zhuǎn)操作數(shù)的每一位,0變1,1變0。用于對(duì)字段取反。
位移則用于將數(shù)據(jù)的位向左或向右移動(dòng)給定的位數(shù),實(shí)際上是在給定方向上對(duì)數(shù)據(jù)的表示形式進(jìn)行擴(kuò)展或截?cái)唷?/p>
按方向分為:左移(<<):向左移動(dòng),低位補(bǔ)0,用于擴(kuò)展數(shù)據(jù)類型或?qū)崿F(xiàn)乘法。右移(>>):向右移動(dòng),根據(jù)數(shù)據(jù)類型,高位或補(bǔ)0或補(bǔ)符號(hào)位,用于縮小數(shù)據(jù)類型或?qū)崿F(xiàn)除法。
所以,要訪問SCB->CPUID寄存器的不同字段,我們可以:1.使用與操作和掩碼獲取目標(biāo)字段,例如用SCB_CPUID_IMPLEMENTER_Msk獲取IMPLEMENTER字段。2.必要時(shí)使用位移將字段移到需要的位置,例如用SCB_CPUID_IMPLEMENTER_Pos右移24位將IMPLEMENTER移到低8位。3.組合位域操作讀取和修改字段。例如用或操作將某位設(shè)置為1,用與操作清0某位。
例如,要讀取CPUID的IMPLEMENTER字段:
uint32_t implementer = (SCB->CPUID & SCB_CPUID_IMPLEMENTER_Msk) >> SCB_CPUID_IMPLEMENTER_Pos;
要設(shè)置CPUID的REVISION字段的第3位:
SCB->CPUID |= (1 << 3); // 用或操作將第3位設(shè)置為1
要清CPUID的VARIANT字段的低4位:
SCB->CPUID &= ~((0xF) << SCB_CPUID_VARIANT_Pos); // 用與操作和反碼清除低4位
繼續(xù)放大鏡看這個(gè)代碼,我們看這個(gè)1ul的定義方式:
1ul
1ul是一個(gè)無符號(hào)長整型(unsigned long)常量,其值為1。在C語言中,整型常量的默認(rèn)類型為int,但可以通過后綴來指定不同的類型。常見的后綴有:無符號(hào)(unsigned):- u或U:無符號(hào)整型,如1u
- ul或UL:無符號(hào)長整型,如1ul長整型(long):- l或L:長整型,如1l無符號(hào)長整型(unsigned long):- ul或UL:無符號(hào)長整型,如1ul大小寫無關(guān),所以1u、1ul、1U和1UL都是等價(jià)的。
使用這些后綴的主要目的是為了在某些情況下指定常量的精確類型,避免由默認(rèn)類型帶來的怪異行為。例如,在32位系統(tǒng)上,int和unsigned int都是32位,所以1和1u的值相同。
但long可能是32位,而unsigned long是64位,所以1l和1ul的值會(huì)不同。所以,當(dāng)我們需要一個(gè)無符號(hào)的32/64位整型常量時(shí),就可以使用1u或1ul來指定其精確類型,這可以避免一些潛在的問題。
另外,這些后綴也常用于定義寄存器和位域的掩碼常量,例如:
#define UART_DATA_MASK 0xFFul // 8位無符號(hào)數(shù)據(jù)掩碼 #define UART_PARITY_MASK 0x01ul // 1位無符號(hào)奇偶校驗(yàn)位掩碼
這里使用ul是為了確保掩碼常量被定義為32位,與寄存器大小一致。所以,總結(jié)來說:1. 1ul是一個(gè)無符號(hào)長整型常量,其值為1。2. 后綴u、ul、U和UL用于定義無符號(hào)整型和無符號(hào)長整型常量。3. 使用這些后綴可以指定常量的精確類型,避免默認(rèn)類型帶來的問題。4. 這些后綴常用于定義寄存器和位域的掩碼常量,確保其大小與目標(biāo)寄存器一致。
這個(gè)結(jié)構(gòu)體定義了SysTick定時(shí)器的寄存器集。SysTick是Cortex-M內(nèi)核的一部分,用于生成定時(shí)中斷和延時(shí)。
SysTick_Type結(jié)構(gòu)體包含以下成員:
__IO uint32_t CTRL; SysTick控制和狀態(tài)寄存器,用于使能SysTick定時(shí)器,選擇時(shí)鐘源和計(jì)數(shù)模式。
__IO uint32_t LOAD; SysTick重載值寄存器,設(shè)置SysTick定時(shí)器的重載值,該值決定定時(shí)周期。
__IO uint32_t VAL; SysTick當(dāng)前值寄存器,在運(yùn)行過程中存儲(chǔ)SysTick定時(shí)器的當(dāng)前值。
__I uint32_t CALIB; SysTick校準(zhǔn)值寄存器,提供設(shè)備特定的時(shí)鐘頻率信息,用于計(jì)算延時(shí)。
所以,通過這個(gè)結(jié)構(gòu)體,我們可以訪問SysTick定時(shí)器的所有控制/狀態(tài)寄存器和校準(zhǔn)寄存器,并完成:
1. 使能或關(guān)閉SysTick定時(shí)器。
2. 選擇SysTick的時(shí)鐘源,如內(nèi)核時(shí)鐘AHB或外部參考時(shí)鐘。
3. 選擇遞減計(jì)數(shù)模式或遞增計(jì)數(shù)模式。
4.設(shè)置SysTick定時(shí)器的重載值,配置其定時(shí)周期。
5.讀取當(dāng)前的計(jì)數(shù)值VAL。
6.獲取設(shè)備的時(shí)鐘頻率信息CALIB,用于生成精確延時(shí)。
7.SysTick定時(shí)器溢出時(shí)產(chǎn)生中斷,所以也用作系統(tǒng)的節(jié)拍定時(shí)器。
SysTicktimer(STK)處理器有一個(gè)24位系統(tǒng)計(jì)時(shí)器SysTick,它從重新加載值開始計(jì)數(shù)到零,在下一個(gè)時(shí)鐘邊緣重新加載(封裝到)LOAD寄存器中的值,然后在隨后的時(shí)鐘上計(jì)數(shù)。當(dāng)處理器停止調(diào)試時(shí),計(jì)數(shù)器不會(huì)減少。
再詳細(xì)一些介紹這個(gè):
1.最大計(jì)數(shù)值為24位,所以最大延時(shí)為16777216個(gè)時(shí)鐘周期。
2.可以選擇內(nèi)核時(shí)鐘AHB或外部參考時(shí)鐘作為時(shí)鐘源。
3.可以選擇遞增計(jì)數(shù)模式或遞減計(jì)數(shù)模式。
4.重載值寄存器LOAD用于設(shè)置定時(shí)周期,每次定時(shí)器溢出時(shí)重新裝載該值。
5.當(dāng)前值寄存器VAL存儲(chǔ)定時(shí)器的實(shí)時(shí)計(jì)數(shù)值。
6.定時(shí)器溢出時(shí)觸發(fā)SysTick異常請(qǐng)求,可以配置為產(chǎn)生中斷。
7.時(shí)鐘頻率預(yù)分頻因子固定為8,不可配置。
8. 中斷優(yōu)先級(jí)固定為最低級(jí)別,僅可屏蔽但不可修改。
9.包含一設(shè)備特定的校準(zhǔn)寄存器,提供系統(tǒng)時(shí)鐘頻率信息,用于實(shí)現(xiàn)準(zhǔn)確延時(shí)?;?/p>
于以上特性,我們可以這樣配置和使用SysTick定時(shí)器:
1.使能SysTick定時(shí)器,選擇AHB時(shí)鐘源。
2.配置 SysTick_LOAD寄存器為重載值(如1000),每1000個(gè)時(shí)鐘周期產(chǎn)生一個(gè)中斷。
3.等待SysTick定時(shí)器中斷,并在中斷服務(wù)程序內(nèi)進(jìn)行任務(wù)調(diào)度或其他定時(shí)任務(wù)。
4.獲取SysTick_CALIB的值,例如0x0320,表明每個(gè)時(shí)鐘周期大約為30.5us。
5.要延時(shí)100ms,計(jì)算需要的時(shí)鐘周期數(shù):100ms / 30.5us = 3276。寫入SysTick_LOAD,使能定時(shí)器。
6.等待SysTick定時(shí)器中斷,表示延時(shí)完成。
所以,SysTick模塊為Cortex-M3內(nèi)核提供了一個(gè)簡單而高效的定時(shí)器,可用于RTOS的任務(wù)調(diào)度、軟件延時(shí)和其他定時(shí)事件。它的24位計(jì)數(shù)器和微秒級(jí)精度可以滿足大多數(shù)應(yīng)用的需要。
和其它的定時(shí)器外設(shè)比較有什么區(qū)別?
SysTick定時(shí)器有以下主要用途:
1.提供系統(tǒng)節(jié)拍定時(shí)器,用于RTOS的任務(wù)調(diào)度。RTOS可以配置SysTick產(chǎn)生中斷,并在中斷處理程序中進(jìn)行任務(wù)切換。
2.實(shí)現(xiàn)軟件延時(shí)。我們可以根據(jù)SysTick的定時(shí)周期計(jì)算需要的計(jì)數(shù)值來生成所需延時(shí)。
3.其他定時(shí)事件。SysTick定時(shí)器可以用于系統(tǒng)的各種定時(shí)任務(wù),如定時(shí)監(jiān)測、看門狗喂狗等。
與其他定時(shí)器外設(shè)相比,SysTick定時(shí)器有以下區(qū)別:
1.SysTick定時(shí)器是Cortex-M內(nèi)核的一部分,而其他定時(shí)器屬于MCU的外設(shè)。所以SysTick定時(shí)器更輕量,通用性更強(qiáng)。
2.SysTick定時(shí)器的時(shí)鐘源只能選擇內(nèi)核時(shí)鐘或外部參考時(shí)鐘,而其他定時(shí)器通常有更多時(shí)鐘源選擇。
3.SysTick定時(shí)器的計(jì)數(shù)器只有24位,范圍更小。其他定時(shí)器的計(jì)數(shù)器可以達(dá)32位或更高。
4.SysTick定時(shí)器的中斷優(yōu)先級(jí)固定為最低,無法配置。其他定時(shí)器的中斷通??梢耘渲脙?yōu)先級(jí)。
5.SysTick定時(shí)器只有比較基本的控制寄存器,更簡單。其他定時(shí)器通常具有更豐富的控制與配置選項(xiàng)。
6.SysTick定時(shí)器的溢出事件只能產(chǎn)生中斷,無法產(chǎn)生DMA請(qǐng)求等。部分定時(shí)器可以通過多種方式響應(yīng)溢出事件。
讓我來說下這個(gè)計(jì)數(shù)模式:
遞增計(jì)數(shù)模式和遞減計(jì)數(shù)模式是定時(shí)器的兩種不同的計(jì)數(shù)方式:
遞增計(jì)數(shù)模式:定時(shí)器的計(jì)數(shù)器從初始值(通常為0)開始遞增,當(dāng)計(jì)數(shù)器達(dá)到重載值時(shí),定時(shí)器溢出。然后計(jì)數(shù)器重載為初始值,重新開始遞增。如果配置為產(chǎn)生中斷,則在計(jì)數(shù)器達(dá)到重載值時(shí)觸發(fā)中斷。
例如,如果初始值為0,重載值為100,則計(jì)數(shù)序列為:
0, 1, 2, 3, ... 98, 99, 100 - 觸發(fā)中斷 - 0, 1, 2, 3 ...
遞減計(jì)數(shù)模式:
定時(shí)器的計(jì)數(shù)器從重載值開始遞減,當(dāng)計(jì)數(shù)器達(dá)到0時(shí),定時(shí)器溢出。然后計(jì)數(shù)器重載為重載值,重新開始遞減。如果配置為產(chǎn)生中斷,則在計(jì)數(shù)器達(dá)到0時(shí)觸發(fā)中斷。
例如,如果重載值為100,則計(jì)數(shù)序列為:
100, 99, 98, 97 ... 3, 2, 1, 0 - 觸發(fā)中斷 - 100, 99, 98 ...
所以,主要差異在于計(jì)數(shù)器的初始值和溢出條件不同:
遞增模式:
初始值:通常為0
溢出條件:計(jì)數(shù)器達(dá)到重載值
遞減模式:
初始值:等于重載值
溢出條件:計(jì)數(shù)器達(dá)到0
讓我們來探究一下這個(gè)設(shè)計(jì)意圖,就是定時(shí)器的設(shè)計(jì)意圖。
1.適應(yīng)開發(fā)者的習(xí)慣。有的開發(fā)者更習(xí)慣從0開始遞增計(jì)數(shù),有的更習(xí)慣從最大值開始遞減計(jì)數(shù),所以提供兩種模式以滿足不同習(xí)慣。
2.方便實(shí)現(xiàn)定時(shí)器的溢出中斷。無論是遞增模式從0溢出到重載值,還是遞減模式從重載值溢出到0,都可以很簡單地通過比較計(jì)數(shù)器與重載值/0來檢測溢出事件并產(chǎn)生中斷。
3.擴(kuò)展定時(shí)范圍。24位的定時(shí)器,遞增模式下最大延時(shí)為2^24個(gè)周期,若選擇遞減模式,最大延時(shí)可擴(kuò)展為2^24 + 重載值個(gè)周期,所以可獲得較大的定時(shí)范圍。
4. 不同模式下的溢出事件可用于不同用途。例如,可以選擇遞增模式用于周期性中斷,而選擇遞減模式用于超時(shí)檢測。兩種事件可以同時(shí)使用,擴(kuò)展定時(shí)器的應(yīng)用。
5. 簡化硬件設(shè)計(jì)。提供兩種模式而非只有一種,可以在軟件中通過配置來選擇模式,而不需要硬件支持兩套完全不同的定時(shí)與計(jì)數(shù)邏輯,簡化了定時(shí)器模塊的設(shè)計(jì)。
接下來說這個(gè)設(shè)計(jì)上面的簡便性,這個(gè)就比較深?yuàn)W了,之后我如果把玩FPGA我會(huì)寫詳細(xì)的。
如果SysTick定時(shí)器僅支持遞增模式或遞減模式中的一種,則其硬件結(jié)構(gòu)可以簡單設(shè)計(jì)為:
- 24位計(jì)數(shù)器寄存器
- 24位重載寄存器
- 比較邏輯,比較計(jì)數(shù)器與重載寄存器,產(chǎn)生溢出事件
但是,為了支持兩種模式,SysTick定時(shí)器的硬件結(jié)構(gòu)可以設(shè)計(jì)為:
- 24位計(jì)數(shù)器寄存器
- 24位重載寄存器
- 1位遞增/遞減模式選擇位
- 比較邏輯,當(dāng)模式選擇位選擇遞增模式時(shí),比較計(jì)數(shù)器與重載寄存,當(dāng)選擇遞減模式時(shí), 比較計(jì)數(shù)器與0,以產(chǎn)生溢出事件。
可以看到,僅添加一個(gè)1位的模式選擇邏輯,SysTick定時(shí)器就可以支持兩種模式,而不需要實(shí)現(xiàn)兩套完全獨(dú)立的計(jì)數(shù)/比較邏輯。在軟件層面,我們只需要設(shè)置MODE位為0選擇遞增模式,設(shè)置為1選擇遞減模式,然后處理溢出事件中斷即可。
硬件層面已經(jīng)為兩種模式實(shí)現(xiàn)了統(tǒng)一的定時(shí)邏輯。可以想象,如果定時(shí)器需要同時(shí)支持4種或更多種模式,僅靠硬件實(shí)現(xiàn)各自獨(dú)立的定時(shí)機(jī)制會(huì)變得非常復(fù)雜。
而采用類似SysTick的方式,通過軟件配置選擇定時(shí)模式,硬件只需實(shí)現(xiàn)一套相對(duì)通用的定時(shí)機(jī)制,這無疑可以大大簡化定時(shí)器模塊的設(shè)計(jì)。
-
寄存器
+關(guān)注
關(guān)注
31文章
5294瀏覽量
119816 -
Cortex
+關(guān)注
關(guān)注
2文章
202瀏覽量
46415 -
CM3
+關(guān)注
關(guān)注
0文章
5瀏覽量
1645
原文標(biāo)題:Cortex-M3精通之路-2(CMSIS核心結(jié)構(gòu))?
文章出處:【微信號(hào):TT1827652464,微信公眾號(hào):云深之無跡】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論