網(wǎng)上有很多關(guān)于pos機(jī)方案開發(fā)板,探索者 STM32F407 開發(fā)板資料連載第四章 F4 開發(fā)基礎(chǔ)知識(shí)入門的知識(shí),也有很多人為大家解答關(guān)于pos機(jī)方案開發(fā)板的問題,今天pos機(jī)之家(www.shineka.com)為大家整理了關(guān)于這方面的知識(shí),讓我們一起來看下吧!
本文目錄一覽:
pos機(jī)方案開發(fā)板
1)實(shí)驗(yàn)平臺(tái):探索者 STM32F407 開發(fā)板
2)摘自《STM32F4 開發(fā)指南(HAL 庫版)》關(guān)注官方微信號(hào)公眾號(hào),獲取更多資料:正點(diǎn)原子
第四章 STM32F4 開發(fā)基礎(chǔ)知識(shí)入門
這一章,我們將著重 STM32 開發(fā)的一些基礎(chǔ)知識(shí),讓大家對(duì) STM32 開發(fā)有一個(gè)初步的了
解,為后面 STM32 的學(xué)習(xí)做一個(gè)鋪墊,方便后面的學(xué)習(xí)。這一章的內(nèi)容大家第一次看的時(shí)候
可以只了解一個(gè)大概,后面需要用到這方面的知識(shí)的時(shí)候再回過頭來仔細(xì)看看。這章我們分 7
個(gè)小結(jié),
·4.1 MDK 下 C 語言基礎(chǔ)復(fù)習(xí)
·4.2 STM32F4 系統(tǒng)架構(gòu)
·4.3 STM32F4 時(shí)鐘系統(tǒng)
·4.4 IO 引腳復(fù)用器和映射
·4.5 STM32F4 NVIC 中斷優(yōu)先級(jí)管理
·4.6 MDK 中寄存器地址名稱映射分析
·4.7 MDKHAL 庫快速開發(fā)技巧
4.1 MDK 下 C 語言基礎(chǔ)復(fù)習(xí)
這一節(jié)我們主要講解一下 C 語言基礎(chǔ)知識(shí)。C 語言知識(shí)博大精深,也不是我們?nèi)詢烧Z能
講解清楚,同時(shí)我們相信學(xué) STM32F4 這種級(jí)別 MCU 的用戶,C 語言基礎(chǔ)應(yīng)該都是沒問題的。我
們這里主要是簡單的復(fù)習(xí)一下幾個(gè) C 語言基礎(chǔ)知識(shí)點(diǎn),引導(dǎo)那些 C 語言基礎(chǔ)知識(shí)不是很扎實(shí)的
用戶能夠快速開發(fā) STM32 程序。同時(shí)希望這些用戶能夠多去復(fù)習(xí)一下 C 語言基礎(chǔ)知識(shí),C 語言
畢竟是單片機(jī)開發(fā)中的必備基礎(chǔ)知識(shí)。對(duì)于 C 語言基礎(chǔ)比較扎實(shí)的用戶,這部分知識(shí)可以忽略
不看。
4.1.1 位操作
C 語言位操作相信學(xué)過 C 語言的人都不陌生了,簡而言之,就是對(duì)基本類型變量可以在位級(jí)
別進(jìn)行操作。這節(jié)的內(nèi)容很多朋友都應(yīng)該很熟練了,我這里也就點(diǎn)到為止,不深入探討。下面
我們先講解幾種位操作符,然后講解位操作使用技巧。
C 語言支持如下 6 種位操作
表 4.1.1 16 種位操作
這些與或非,取反,異或,右移,左移這些到底怎么回事,這里我們就不多做詳細(xì),相信
大家學(xué) C 語言的時(shí)候都學(xué)習(xí)過了。如果不懂的話,可以百度一下,非常多的知識(shí)講解這些操作
符。下面我們想著重講解位操作在單片機(jī)開發(fā)中的一些實(shí)用技巧。
1) 不改變其他位的值的狀況下,對(duì)某幾個(gè)位進(jìn)行設(shè)值。
這個(gè)場景單片機(jī)開發(fā)中經(jīng)常使用,方法就是先對(duì)需要設(shè)置的位用&操作符進(jìn)行清零操作,
然后用|操作符設(shè)值。比如我要改變 GPIOA-> BSRRL 的狀態(tài),可以先對(duì)寄存器的值進(jìn)行&
清零操作
GPIOA-> BSRRL &=0XFF0F; //將第 4-7 位清 0
然后再與需要設(shè)置的值進(jìn)行|或運(yùn)算
GPIOA-> BSRRL |=0X0040;//設(shè)置相應(yīng)位的值,不改變其他位的值2) 移位操作提高代碼的可讀性。
移位操作在單片機(jī)開發(fā)中也非常重要,我們來看看下面一行代碼
GPIOx->ODR = (((uint32_t)0x01) << pinpos);
這個(gè)操作就是將 ODR 寄存器的第 pinpos 位設(shè)置為 1,為什么要通過左移而不是直接設(shè)
置一個(gè)固定的值呢?其實(shí),這是為了提高代碼的可讀性以及可重用性。這行代碼可以
很直觀明了的知道,是將第 pinpos 位設(shè)置為 1。如果你寫成
GPIOx->ODR =0x0030;
這樣的代碼就不好看也不好重用了。
3) ~取反操作使用技巧
SR 寄存器的每一位都代表一個(gè)狀態(tài),某個(gè)時(shí)刻我們希望去設(shè)置某一位的值為 0,同時(shí)
其他位都保留為 1,簡單的作法是直接給寄存器設(shè)置一個(gè)值:
TIMx->SR=0xFFF7;
這樣的作法設(shè)置第 3 位為 0,但是這樣的作法同樣不好看,并且可讀性很差??纯磶旌瘮?shù)
代碼中怎樣使用的:
TIMx->SR = (uint16_t)~TIM_FLAG;
而 TIM_FLAG 是通過宏定義定義的值:
#define TIM_FLAG_Update ((uint16_t)0x0001)
#define TIM_FLAG_CC1 ((uint16_t)0x0002)
看這個(gè)應(yīng)該很容易明白,可以直接從宏定義中看出 TIM_FLAG_Update 就是設(shè)置的第 0 位了,
可讀性非常強(qiáng)。
4.1.2 define 宏定義
define 是 C 語言中的預(yù)處理命令,它用于宏定義,可以提高源代碼的可讀性,為編程提供
方便。常見的格式:
#define 標(biāo)識(shí)符 字符串
“標(biāo)識(shí)符”為所定義的宏名。“字符串”可以是常數(shù)、表達(dá)式、格式串等。例如:
#define PLL_M 8
定義標(biāo)識(shí)符 PLL_M 的值為 8。
至于 define 宏定義的其他一些知識(shí),比如宏定義帶參數(shù)這里我們就不多講解。
4.1.3 ifdef 條件編譯
單片機(jī)程序開發(fā)過程中,經(jīng)常會(huì)遇到一種情況,當(dāng)滿足某條件時(shí)對(duì)一組語句進(jìn)行編譯,而
當(dāng)條件不滿足時(shí)則編譯另一組語句。條件編譯命令最常見的形式為:
#ifdef 標(biāo)識(shí)符
程序段 1
#else
程序段 2
#endif
它的作用是:當(dāng)標(biāo)識(shí)符已經(jīng)被定義過(一般是用#define 命令定義),則對(duì)程序段 1 進(jìn)行編譯,
否則編譯程序段 2。 其中#else 部分也可以沒有,即:
#ifdef
程序段 1
#endif
這個(gè)條件編譯在MDK里面是用得很多的,在stm32f4xx.h這個(gè)頭文件中經(jīng)常會(huì)看到這樣的語句:
#if defined (STM32F40_41xxx)
STM32F40x 系列和 STM32F41x 系列芯片需要的一些變量定義
#end
而(STM32F40_41xxx 則是我們通過#define 來定義的。條件編譯也是 c 語言的基礎(chǔ)知識(shí),這里
也就點(diǎn)到為止吧。
4.1.4 extern 變量申明
C 語言中 extern 可以置于變量或者函數(shù)前,以表示變量或者函數(shù)的定義在別的文件中,提示編
譯器遇到此變量和函數(shù)時(shí)在其他模塊中尋找其定義。這里面要注意,對(duì)于 extern 申明變量可以多
次,但定義只有一次。在我們的代碼中你會(huì)看到看到這樣的語句:
extern u16 USART_RX_STA;
這個(gè)語句是申明 USART_RX_STA 變量在其他文件中已經(jīng)定義了,在這里要使用到。所以,你肯定
可以找到在某個(gè)地方有變量定義的語句:
u16 USART_RX_STA;
的出現(xiàn)。下面通過一個(gè)例子說明一下使用方法。
在 Main.c 定義的全局變量 id,id 的初始化都是在 Main.c 里面進(jìn)行的。
Main.c 文件
u8 id;//定義只允許一次
main()
{
id=1;
printf("d%",id);//id=1
test();
printf("d%",id);//id=2
}
但是我們希望在main.c的 changeId(void)函數(shù)中使用變量id,這個(gè)時(shí)候我們就需要在main.c
里面去申明變量 id 是外部定義的了,因?yàn)槿绻簧昝?,變?id 的作用域是到不了 main.c 文件
中??聪旅?main.c 中的代碼:
extern u8 id;//申明變量 id 是在外部定義的,申明可以在很多個(gè)文件中進(jìn)行
void test(void){
id=2;
}
在 main.c 中申明變量 id 在外部定義,然后在 main.c 中就可以使用變量 id 了。
對(duì)于 extern 申明函數(shù)在外部定義的應(yīng)用,這里我們就不多講解了。
4.1.5 typedef 類型別名
typedef 用于為現(xiàn)有類型創(chuàng)建一個(gè)新的名字,或稱為類型別名,用來簡化變量的定義。
typedef 在 MDK 用得最多的就是定義結(jié)構(gòu)體的類型別名和枚舉類型了。
struct _GPIO
{
__IO uint32_t MODER;
__IO uint32_t OTYPER;
…
};
定義了一個(gè)結(jié)構(gòu)體 GPIO,這樣我們定義變量的方式為:
struct _GPIO GPIOA;//定義結(jié)構(gòu)體變量 GPIOA
但是這樣很繁瑣,MDK 中有很多這樣的結(jié)構(gòu)體變量需要定義。這里我們可以為結(jié)體定義一個(gè)別
名 GPIO_TypeDef,這樣我們就可以在其他地方通過別名 GPIO_TypeDef 來定義結(jié)構(gòu)體變量了。
方法如下:
typedef struct
{
__IO uint32_t MODER;
__IO uint32_t OTYPER;
…
} GPIO_TypeDef;
Typedef 為結(jié)構(gòu)體定義一個(gè)別名 GPIO_TypeDef,這樣我們可以通過 GPIO_TypeDef 來定義結(jié)構(gòu)體
變量:
GPIO_TypeDef _GPIOA,_GPIOB;
這里的 GPIO_TypeDef 就跟 struct _GPIO 是等同的作用了。 這樣是不是方便很多?
4.1.6 結(jié)構(gòu)體
經(jīng)常很多用戶提到,他們對(duì)結(jié)構(gòu)體使用不是很熟悉,但是 MDK 中太多地方使用結(jié)構(gòu)體以及
結(jié)構(gòu)體指針,這讓他們一下子摸不著頭腦,學(xué)習(xí) STM32 的積極性大大降低,其實(shí)結(jié)構(gòu)體并不是
那么復(fù)雜,這里我們稍微提一下結(jié)構(gòu)體的一些知識(shí),還有一些知識(shí)我們會(huì)在下一節(jié)的“寄存器
地址名稱映射分析”中講到一些。
聲明結(jié)構(gòu)體類型:
Struct 結(jié)構(gòu)體名{
成員列表;
}變量名列表;
例如:
Struct U_TYPE {
Int BaudRate
Int WordLength;
}usart1,usart2;
在結(jié)構(gòu)體申明的時(shí)候可以定義變量,也可以申明之后定義,方法是:
Struct 結(jié)構(gòu)體名字 結(jié)構(gòu)體變量列表 ;
例如:struct U_TYPE usart1,usart2;
結(jié)構(gòu)體成員變量的引用方法是:
結(jié)構(gòu)體變量名字.成員名
比如要引用 usart1 的成員 BaudRate,方法是:usart1.BaudRate;
結(jié)構(gòu)體指針變量定義也是一樣的,跟其他變量沒有啥區(qū)別。
例如:struct U_TYPE *usart3;//定義結(jié)構(gòu)體指針變量 usart1;
結(jié)構(gòu)體指針成員變量引用方法是通過“->”符號(hào)實(shí)現(xiàn),比如要訪問 usart3 結(jié)構(gòu)體指針指向的結(jié)
構(gòu)體的成員變量 BaudRate,方法是:
Usart3->BaudRate;
上面講解了結(jié)構(gòu)體和結(jié)構(gòu)體指針的一些知識(shí),其他的什么初始化這里就不多講解了。講到這里,
有人會(huì)問,結(jié)構(gòu)體到底有什么作用呢?為什么要使用結(jié)構(gòu)體呢?下面我們將簡單的通過一個(gè)實(shí)
例回答一下這個(gè)問題。
在我們單片機(jī)程序開發(fā)過程中,經(jīng)常會(huì)遇到要初始化一個(gè)外設(shè)比如串口,它的初始化狀態(tài)
是由幾個(gè)屬性來決定的,比如串口號(hào),波特率,極性,以及模式等。對(duì)于這種情況,在我們沒
有學(xué)習(xí)結(jié)構(gòu)體的時(shí)候,我們一般的方法是:
void USART_Init(u8 usartx,u32 u32 BaudRate,u8 parity,u8 mode);
這種方式是有效的同時(shí)在一定場合是可取的。但是試想,如果有一天,我們希望往這個(gè)函數(shù)里
面再傳入一個(gè)參數(shù),那么勢必我們需要修改這個(gè)函數(shù)的定義,重新加入字長這個(gè)入口參數(shù)。于
是我們的定義被修改為:
void USART_Init (u8 usartx,u32 BaudRate, u8 parity,u8 mode,u8 wordlength );
但是如果我們這個(gè)函數(shù)的入口參數(shù)是隨著開發(fā)不斷的增多,那么是不是我們就要不斷的修改函
數(shù)的定義呢?這是不是給我們開發(fā)帶來很多的麻煩?那又怎樣解決這種情況呢?
這樣如果我們使用到結(jié)構(gòu)體就能解決這個(gè)問題了。我們可以在不改變?nèi)肟趨?shù)的情況下,
只需要改變結(jié)構(gòu)體的成員變量,就可以達(dá)到上面改變?nèi)肟趨?shù)的目的。
結(jié)構(gòu)體就是將多個(gè)變量組合為一個(gè)有機(jī)的整體。上面的函數(shù),BaudRate,wordlength,
Parity,mode,wordlength 這些參數(shù),他們對(duì)于串口而言,是一個(gè)有機(jī)整體,都是來設(shè)置串口參
數(shù)的,所以我們可以將他們通過定義一個(gè)結(jié)構(gòu)體來組合在一個(gè)。MDK 中是這樣定義的:
typedef struct
{
uint32_t USART_BaudRate;
uint16_t USART_WordLength;
uint16_t USART_StopBits;
uint16_t USART_Parity;
uint16_t USART_Mode;
uint16_t USART_HardwareFlowControl;
} USART_InitTypeDef;
于是,我們?cè)诔跏蓟诘臅r(shí)候入口參數(shù)就可以是 USART_InitTypeDef 類型的變量或者指針變
量了,MDK 中是這樣做的:
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
這樣,任何時(shí)候,我們只需要修改結(jié)構(gòu)體成員變量,往結(jié)構(gòu)體中間加入新的成員變量,而不需
要修改函數(shù)定義就可以達(dá)到修改入口參數(shù)同樣的目的了。這樣的好處是不用修改任何函數(shù)定義
就可以達(dá)到增加變量的目的。
理解了結(jié)構(gòu)體在這個(gè)例子中間的作用嗎?在以后的開發(fā)過程中,如果你的變量定義過多,
如果某幾個(gè)變量是用來描述某一個(gè)對(duì)象,你可以考慮將這些變量定義在結(jié)構(gòu)體中,這樣也許可
以提高你的代碼的可讀性。
使用結(jié)構(gòu)體組合參數(shù),可以提高代碼的可讀性,不會(huì)覺得變量定義混亂。當(dāng)然結(jié)構(gòu)體的作
用就遠(yuǎn)遠(yuǎn)不止這個(gè)了,同時(shí),MDK 中用結(jié)構(gòu)體來定義外設(shè)也不僅僅只是這個(gè)作用,這里我們只
是舉一個(gè)例子,通過最常用的場景,讓大家理解結(jié)構(gòu)體的一個(gè)作用而已。后面一節(jié)我們還會(huì)講
解結(jié)構(gòu)體的一些其他知識(shí)。
4.2 STM32F4 總線架構(gòu)
STM32F4 的總線架構(gòu)比 51 單片機(jī)就要強(qiáng)大很多了。STM32F4 總線架構(gòu)的知識(shí)可以在
《STM32F4XX 中文參考手冊(cè)》第二章有講解,這里我們也把這一部分知識(shí)抽取出來講解,是
為了大家在學(xué)習(xí) STM32F4 之前對(duì)系統(tǒng)架構(gòu)有一個(gè)初步的了解。這里的內(nèi)容基本也是從中文參
考手冊(cè)中參考過來的,讓大家能通過我們手冊(cè)也了解到,免除了到處找資料的麻煩吧。如果需
要詳細(xì)深入的了解 STM32 的系統(tǒng)架構(gòu),還需要多看看《STM32F4XX 中文參考手冊(cè)》或者在網(wǎng)
上搜索其他相關(guān)資料學(xué)習(xí)。
我們這里所講的 STM32F4 系統(tǒng)架構(gòu)主要針對(duì)的 STM32F407 系列芯片。首先我們看看
STM32 的總線架構(gòu)圖:
圖 4.2.1 STM32F407 系統(tǒng)架構(gòu)圖
主系統(tǒng)由 32 位多層 AHB 總線矩陣構(gòu)成??偩€矩陣用于主控總線之間的訪問仲裁管理。仲裁采
取循環(huán)調(diào)度算法。總線矩陣可實(shí)現(xiàn)以下部分互聯(lián):
八條主控總線是:
Cortex-M4 內(nèi)核 I 總線, D 總線和 S 總線;
DMA1 存儲(chǔ)器總線, DMA2 存儲(chǔ)器總線;
DMA2 外設(shè)總線;
以太網(wǎng) DMA 總線;
USB OTG HS DMA 總線;
七條被控總線:
內(nèi)部 FLASH ICode 總線;
內(nèi)部 FLASH DCode 總線;
主要內(nèi)部 SRAM1(112KB)
輔助內(nèi)部 SRAM2(16KB);
輔助內(nèi)部 SRAM3(64KB) (僅適用 STM32F42xx 和 STM32F43xx 系列器件);
AHB1 外設(shè) 和 AHB2 外設(shè);
FSMC
下面我們具體講解一下圖中幾個(gè)總線的知識(shí)。
① I 總線(S0):此總線用于將 Cortex-M4 內(nèi)核的指令總線連接到總線矩陣。內(nèi)核通過此總
線獲取指令。此總線訪問的對(duì)象是包括代碼的存儲(chǔ)器。
② D 總線(S1):此總線用于將 Cortex-M4 數(shù)據(jù)總線和 64KB CCM 數(shù)據(jù) RAM 連接到總線矩
陣。內(nèi)核通過此總線進(jìn)行立即數(shù)加載和調(diào)試訪問。
③ S 總線(S2):此總線用于將 Cortex-M4 內(nèi)核的系統(tǒng)總線連接到總線矩陣。此總線用于訪
問位于外設(shè)或 SRAM 中的數(shù)據(jù)。
④ DMA 存儲(chǔ)器總線(S3,S4):此總線用于將 DMA 存儲(chǔ)器總線主接口連接到總線矩陣。
DMA 通過此總線來執(zhí)行存儲(chǔ)器數(shù)據(jù)的傳入和傳出。
⑤ DMA 外設(shè)總線:此總線用于將 DMA 外設(shè)主總線接口連接到總線矩陣。DMA 通過此
總線訪問 AHB 外設(shè)或執(zhí)行存儲(chǔ)器之間的數(shù)據(jù)傳輸。
⑥ 以太網(wǎng) DMA 總線:此總線用于將以太網(wǎng) DMA 主接口連接到總線矩陣。以太網(wǎng) DMA
通過此總線向存儲(chǔ)器存取數(shù)據(jù)。
⑦ USB OTG HS DMA 總線(S7):此總線用于將 USB OTG HS DMA 主接口連接到總線矩
陣。USB OTG HS DMA 通過此總線向存儲(chǔ)器加載/存儲(chǔ)數(shù)據(jù)。
對(duì)于系統(tǒng)架構(gòu)的知識(shí),在剛開始學(xué)習(xí) STM32 的時(shí)候只需要一個(gè)大概的了解,大致知道是個(gè)
什么情況即可。對(duì)于尋址之類的知識(shí),這里就不做深入的講解,中文參考手冊(cè)都有很詳細(xì)的講
解。
4.3 STM32F4 時(shí)鐘系統(tǒng)
STM32F4 時(shí)鐘系統(tǒng)的知識(shí)在《STM32F4 中文參考手冊(cè)》第六章復(fù)位和時(shí)鐘控制章節(jié)有非
常詳細(xì)的講解,網(wǎng)上關(guān)于時(shí)鐘系統(tǒng)的講解也基本都是參考的這里,講不出啥特色,不過作為一
個(gè)完整的參考手冊(cè),我們必然要提到時(shí)鐘系統(tǒng)的知識(shí)。這些知識(shí)也不是什么原創(chuàng),純粹根據(jù)官
方提供的中文參考手冊(cè)和自己的應(yīng)用心得來總結(jié)的,如有不合理之處望大家諒解。
這部分內(nèi)容我們分 3 個(gè)小節(jié)來講解:
·4.3.1 STM32F4 時(shí)鐘樹概述
·4.3.2 STM32F4 時(shí)鐘初始化配置
·4.3.3 STM32F4 時(shí)鐘使能和配置
4.3.1 STM32F4 時(shí)鐘樹概述
眾所周知,時(shí)鐘系統(tǒng)是 CPU 的脈搏,就像人的心跳一樣。所以時(shí)鐘系統(tǒng)的重要性就不言而
喻了。 STM32F4 的時(shí)鐘系統(tǒng)比較復(fù)雜,不像簡單的 51 單片機(jī)一個(gè)系統(tǒng)時(shí)鐘就可以解決一切。
于是有人要問,采用一個(gè)系統(tǒng)時(shí)鐘不是很簡單嗎?為什么 STM32 要有多個(gè)時(shí)鐘源呢? 因?yàn)槭?/p>
先 STM32 本身非常復(fù)雜,外設(shè)非常的多,但是并不是所有外設(shè)都需要系統(tǒng)時(shí)鐘這么高的頻率,
比如看門狗以及 RTC 只需要幾十 k 的時(shí)鐘即可。同一個(gè)電路,時(shí)鐘越快功耗越大,同時(shí)抗電磁
干擾能力也會(huì)越弱,所以對(duì)于較為復(fù)雜的 MCU 一般都是采取多時(shí)鐘源的方法來解決這些問題。
首先讓我們來看看 STM32F4 的時(shí)鐘系統(tǒng)圖:
圖 4.3.1.1STM32 時(shí)鐘系統(tǒng)圖
在 STM32F4 中,有 5 個(gè)最重要的時(shí)鐘源,為 HSI、HSE、LSI、LSE、PLL。其中 PLL 實(shí)
際是分為兩個(gè)時(shí)鐘源,分別為主 PLL 和專用 PLL。從時(shí)鐘頻率來分可以分為高速時(shí)鐘源和低速
時(shí)鐘源,在這 5 個(gè)中 HSI,HSE 以及 PLL 是高速時(shí)鐘,LSI 和 LSE 是低速時(shí)鐘。從來源可分為
外部時(shí)鐘源和內(nèi)部時(shí)鐘源,外部時(shí)鐘源就是從外部通過接晶振的方式獲取時(shí)鐘源,其中 HSE 和
LSE 是外部時(shí)鐘源,其他的是內(nèi)部時(shí)鐘源。下面我們看看 STM32F4 的這 5 個(gè)時(shí)鐘源,我們講
解順序是按圖中紅圈標(biāo)示的順序:①、LSI 是低速內(nèi)部時(shí)鐘,RC 振蕩器,頻率為 32kHz 左右。供獨(dú)立看門狗和自動(dòng)喚醒單元使用。
②、LSE 是低速外部時(shí)鐘,接頻率為 32.768kHz 的石英晶體。這個(gè)主要是 RTC 的時(shí)鐘源。
③、HSE 是高速外部時(shí)鐘,可接石英/陶瓷諧振器,或者接外部時(shí)鐘源,頻率范圍為 4MHz~26MHz。
我們的開發(fā)板接的是 8M 的晶振。HSE 也可以直接做為系統(tǒng)時(shí)鐘或者 PLL 輸入。
④、HSI 是高速內(nèi)部時(shí)鐘,RC 振蕩器,頻率為 16MHz。可以直接作為系統(tǒng)時(shí)鐘或者用作 PLL
輸入。
⑤、PLL 為鎖相環(huán)倍頻輸出。STM32F4 有兩個(gè) PLL:
1) 主 PLL(PLL)由 HSE 或者 HSI 提供時(shí)鐘信號(hào),并具有兩個(gè)不同的輸出時(shí)鐘。
第一個(gè)輸出 PLLP 用于生成高速的系統(tǒng)時(shí)鐘(最高 168MHz)
第二個(gè)輸出 PLLQ 用于生成 USB OTG FS 的時(shí)鐘(48MHz),隨機(jī)數(shù)發(fā)生器的時(shí)鐘和 SDIO
時(shí)鐘。
2)專用 PLL(PLLI2S)用于生成精確時(shí)鐘,從而在 I2S 接口實(shí)現(xiàn)高品質(zhì)音頻性能。
這里我們著重看看主 PLL 時(shí)鐘第一個(gè)高速時(shí)鐘輸出 PLLP 的計(jì)算方法。圖 4.3.1.2 是主 PLL 的
時(shí)鐘圖。
圖 4.3.1.2 STM32F4 主 PLL 時(shí)鐘圖
從圖 4.3.1.2 可以看出。主 PLL 時(shí)鐘的時(shí)鐘源要先經(jīng)過一個(gè)分頻系數(shù)為 M 的分頻器,然后經(jīng)過
倍頻系數(shù)為 N 的倍頻器出來之后的時(shí)候還需要經(jīng)過一個(gè)分頻系數(shù)為 P(第一個(gè)輸出 PLLP)或
者 Q(第二個(gè)輸出 PLLQ)的分頻器分頻之后,最后才生成最終的主 PLL 時(shí)鐘。
例如我們的外部晶振選擇 8MHz。同時(shí)我們?cè)O(shè)置相應(yīng)的分頻器 M=8,倍頻器倍頻系數(shù) N=336,
分頻器分頻系數(shù) P=2,那么主 PLL 生成的第一個(gè)輸出高速時(shí)鐘 PLLP 為:
PLL=8MHz * N/ (M*P)=8MHz* 336 /(8*2) = 168MHz
如果我們選擇HSE為PLL時(shí)鐘源,同時(shí)SYSCLK時(shí)鐘源為PLL,那么SYSCLK時(shí)鐘為 168MHz。
這對(duì)于我們后面的實(shí)驗(yàn)都是采用這樣的配置。
上面我們簡要概括了 STM32 的時(shí)鐘源,那么這 5 個(gè)時(shí)鐘源是怎么給各個(gè)外設(shè)以及系統(tǒng)提
供時(shí)鐘的呢?這里我們選擇一些比較常用的時(shí)鐘知識(shí)來講解。
圖 4.3.1.1 中我們用 A~G 標(biāo)示我們要講解的地方。
A.
這里是看門狗時(shí)鐘輸入。從圖中可以看出,看門狗時(shí)鐘源只能是低速的 LSI 時(shí)鐘。
B.
這里是 RTC 時(shí)鐘源,從圖上可以看出,RTC 的時(shí)鐘源可以選擇 LSI,LSE,以及
HSE 分頻后的時(shí)鐘,HSE 分頻系數(shù)為 2~31。
C.
這里是 STM32F4 輸出時(shí)鐘 MCO1 和 MCO2。MCO1 是向芯片的 PA8 引腳輸出時(shí)
鐘。它有四個(gè)時(shí)鐘來源分別為:HSI,LSE,HSE 和 PLL 時(shí)鐘。MCO2 是向芯片的PC9 輸出時(shí)鐘,它同樣有四個(gè)時(shí)鐘來源分別為:HSE,PLL,SYSCLK 以及 PLLI2S
時(shí)鐘。MCO 輸出時(shí)鐘頻率最大不超過 100MHz。
D.
這里是系統(tǒng)時(shí)鐘。從圖 4.3.1 可以看出,SYSCLK 系統(tǒng)時(shí)鐘來源有三個(gè)方面:
HSI,HSE 和 PLL。在我們實(shí)際應(yīng)用中,因?yàn)閷?duì)時(shí)鐘速度要求都比較高我們才會(huì)選
用 STM32F4 這種級(jí)別的處理器,所以一般情況下,都是采用 PLL 作為 SYSCLK
時(shí)鐘源。根據(jù)前面的計(jì)算公式,大家就可以算出你的系統(tǒng)的 SYSCLK 是多少。
E.
這里我們指的是以太網(wǎng) PTP 時(shí)鐘,AHB 時(shí)鐘,APB2 高速時(shí)鐘,APB1 低速時(shí)鐘。
這些時(shí)鐘都是來源于 SYSCLK 系統(tǒng)時(shí)鐘。其中以太網(wǎng) PTP 時(shí)鐘是使用系統(tǒng)時(shí)鐘。
AHB,APB2 和 APB1 時(shí)鐘是經(jīng)過 SYSCLK 時(shí)鐘分頻得來。這里大家記住,AHB
最大時(shí)鐘為168MHz, APB2高速時(shí)鐘最大頻率為84MHz,而APB1低速時(shí)鐘最大頻
率為 42MHz。
F.
這里是指 I2S 時(shí)鐘源。從圖 4.3.1 可以看出,I2S 的時(shí)鐘源來源于 PLLI2S 或者映
射到 I2S_CKIN 引腳的外部時(shí)鐘。I2S 出于音質(zhì)的考慮,對(duì)時(shí)鐘精度要求很高。探
索者 STM32F4 開發(fā)板使用的是內(nèi)部 PLLI2SCLK。
G.
這是 STM32F4 內(nèi)部以太網(wǎng) MAC 時(shí)鐘的來源。對(duì)于 MII 接口來說,必須向外部
PHY 芯片提供 25Mhz 的時(shí)鐘,這個(gè)時(shí)鐘,可以由 PHY 芯片外接晶振,或者使用
STM32F4 的 MCO 輸 出 來 提 供 。 然 后 , PHY 芯 片 再 給 STM32F4 提 供
ETH_MII_TX_CLK 和 ETH_MII_RX_CLK 時(shí)鐘。對(duì)于 RMII 接口來說,外部必須
提供 50Mhz 的時(shí)鐘驅(qū)動(dòng) PHY 和 STM32F4 的 ETH_RMII_REF_CLK,這個(gè) 50Mhz
時(shí)鐘可以來自 PHY、有源晶振或者 STM32F4 的 MCO。我們的開發(fā)板使用的是
RMII 接 口 , 使 用 PHY 芯 片 提 供 50Mhz 時(shí) 鐘 驅(qū) 動(dòng) STM32F4 的
ETH_RMII_REF_CLK。
H.
這里是指外部 PHY 提供的 USB OTG HS(60MHZ)時(shí)鐘。
這里還需要說明一下,Cortex 系統(tǒng)定時(shí)器 Systick 的時(shí)鐘源可以是 AHB 時(shí)鐘 HCLK 或
HCLK 的 8 分頻。具體配置請(qǐng)參考 Systick 定時(shí)器配置,我們后面會(huì)在 5.1 小節(jié)講解 delay 文件
夾代碼的時(shí)候講解。
在以上的時(shí)鐘輸出中,有很多是帶使能控制的,例如 AHB 總線時(shí)鐘、內(nèi)核時(shí)鐘、各種 APB1
外設(shè)、APB2 外設(shè)等等。當(dāng)需要使用某模塊時(shí),記得一定要先使能對(duì)應(yīng)的時(shí)鐘。后面我們講解
實(shí)例的時(shí)候會(huì)講解到時(shí)鐘使能的方法。
4.3.2 STM32F4 時(shí)鐘初始化配置
上一小節(jié)我們對(duì) STM32F407 時(shí)鐘樹進(jìn)行了詳細(xì)講解,接下來我們來講解通過 STM32F4 的
HAL 庫進(jìn)行 STM32F407 時(shí)鐘系統(tǒng)配置步驟。實(shí)際上,STM32F4 的時(shí)鐘系統(tǒng)配置也可以通過圖
形化配置工具 STM32CubeMX 來配置生成,這里我們講解初始化代碼,是為了讓大家對(duì) STM32
時(shí)鐘系統(tǒng)有更加清晰的理解。圖形化配置工具 STM32CubeMX 在另外的 Cube MX 教程,大家
可以對(duì)比參考學(xué)習(xí)。
前面我們講解過,在系統(tǒng)啟動(dòng)之后,程序會(huì)先執(zhí)行 HAL 庫定義的 SystemInit 函數(shù),進(jìn)行系
統(tǒng)一些初始化配置。那么我們先來看看 SystemInit 程序:
void SystemInit(void){ /* FPU settings ------------------------------------------------------------*/ #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)、 SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */ #endif /* Reset the RCC clock configuration to the default reset state ------------*/ /* Set HSION bit */ RCC->CR |= (uint32_t)0x00000001; /* Reset CFGR register */ RCC->CFGR = 0x00000000; /* Reset HSEON, CSSON and PLLON bits */ RCC->CR &= (uint32_t)0xFEF6FFFF; /* Reset PLLCFGR register */ RCC->PLLCFGR = 0x24003010; /* Reset HSEBYP bit */ RCC->CR &= (uint32_t)0xFFFBFFFF; /* Disable all interrupts */ RCC->CIR = 0x00000000;#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM) SystemInit_ExtMemCtl(); #endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */ /* Configure the Vector Table location add offset address ------------------*/#ifdef VECT_TAB_SRAM SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */#else SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */#endif}
從上面代碼可以看出,SystemInit 主要做了如下四個(gè)方面工作:
1) FPU 設(shè)置
2) 復(fù)位 RCC 時(shí)鐘配置為默認(rèn)復(fù)位值(默認(rèn)開始了 HIS)
3) 外部存儲(chǔ)器配置
4) 中斷向量表地址配置
HAL 庫的 SystemInit 函數(shù)并沒有像標(biāo)準(zhǔn)庫的 SystemInit 函數(shù)一樣進(jìn)行時(shí)鐘的初始化配置。HAL
庫的 SystemInit 函數(shù)除了打開 HSI 之外,沒有任何時(shí)鐘相關(guān)配置,所以使用 HAL 庫我們必須編
寫自己的時(shí)鐘配置函數(shù)。首先我們打開工程模板看看我們?cè)诠こ?SYSTEM 分組下面定義的 sys.c
文件中的時(shí)鐘初始化函數(shù) Stm32_Clock_Init 的內(nèi)容:
//時(shí)鐘系統(tǒng)配置函數(shù)//Fvco=Fs*(plln/pllm);//SYSCLK=Fvco/pllp=Fs*(plln/(pllm*pllp));//Fusb=Fvco/pllq=Fs*(plln/(pllm*pllq));//Fvco:VCO 頻率//SYSCLK:系統(tǒng)時(shí)鐘頻率//Fusb:USB,SDIO,RNG 等的時(shí)鐘頻率//Fs:PLL 輸入時(shí)鐘頻率,可以是 HSI,HSE 等. //plln:主 PLL 倍頻系數(shù)(PLL 倍頻),取值范圍:64~432.//pllm:主 PLL 和音頻 PLL 分頻系數(shù)(PLL 之前的分頻),取值范圍:2~63.//pllp:系統(tǒng)時(shí)鐘的主 PLL 分頻系數(shù)(PLL 之后的分頻),取值范圍:2,4,6,8.(僅限這 4 個(gè)值!)//pllq:USB/SDIO/隨機(jī)數(shù)產(chǎn)生器等的主 PLL 分頻系數(shù)(PLL 之后的分頻),取值范圍:2~15.//外部晶振為 8M 的時(shí)候,推薦值:plln=336,pllm=8,pllp=2,pllq=7.//得到:Fvco=8*(336/8)=336Mhz// SYSCLK=336/2=168Mhz// Fusb=336/7=48Mhz//返回值:0,成功;1,失敗void Stm32_Clock_Init(u32 plln,u32 pllm,u32 pllp,u32 pllq){ HAL_StatusTypeDef ret = HAL_OK; RCC_OscInitTypeDef RCC_OscInitStructure; RCC_ClkInitTypeDef RCC_ClkInitStructure; __HAL_RCC_PWR_CLK_ENABLE(); //使能 PWR 時(shí)鐘 //下面這個(gè)設(shè)置用來設(shè)置調(diào)壓器輸出電壓級(jí)別,以便在器件未以最大頻率工作 //時(shí)使性能與功耗實(shí)現(xiàn)平衡。 __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);//設(shè)置調(diào)壓器輸出電壓級(jí)別 1 RCC_OscInitStructure.OscillatorType=RCC_OSCILLATORTYPE_HSE; //時(shí)鐘源為 HSE RCC_OscInitStructure.HSEState=RCC_HSE_ON; //打開 HSE RCC_OscInitStructure.PLL.PLLState=RCC_PLL_ON;//打開 PLL RCC_OscInitStructure.PLL.PLLSource=RCC_PLLSOURCE_HSE;//PLL 時(shí)鐘源選擇 HSE RCC_OscInitStructure.PLL.PLLM=pllm; //主 PLL 和音頻 PLL 分頻系數(shù)(PLL 之前的分頻),取值范圍:2~63. RCC_OscInitStructure.PLL.PLLN=plln; //主 PLL 倍頻系數(shù)(PLL 倍頻),取值范圍:64~432. RCC_OscInitStructure.PLL.PLLP=pllp;//系統(tǒng)時(shí)鐘的主 PLL 分頻系數(shù)(PLL 之后的分頻),取值范圍:2,4,6,8.(僅限這 4 個(gè)值!) RCC_OscInitStructure.PLL.PLLQ=pllq; //USB/SDIO/隨機(jī)數(shù)產(chǎn)生器等的主 PLL 分頻系數(shù)(PLL 之后的分頻),取值范圍:2~15. ret=HAL_RCC_OscConfig(&RCC_OscInitStructure);//初始化 if(ret!=HAL_OK) while(1); //選中 PLL 作為系統(tǒng)時(shí)鐘源并且配置 HCLK,PCLK1 和 PCLK2RCC_ClkInitStructure.ClockType=(RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2); RCC_ClkInitStructure.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK;//設(shè)置系統(tǒng)時(shí)鐘時(shí)鐘源為 PLL RCC_ClkInitStructure.AHBCLKDivider=RCC_SYSCLK_DIV1;//AHB 分頻系數(shù)為 1 RCC_ClkInitStructure.APB1CLKDivider=RCC_HCLK_DIV4; //APB1 分頻系數(shù)為 4 RCC_ClkInitStructure.APB2CLKDivider=RCC_HCLK_DIV2; //APB2 分頻系數(shù)為 2 ret=HAL_RCC_ClockConfig(&RCC_ClkInitStructure,FLASH_LATENCY_5);//同時(shí)設(shè)置 FLASH 延時(shí)周期為 5WS,也就是 6 個(gè) CPU 周期。if(ret!=HAL_OK) while(1);//STM32F405x/407x/415x/417x Z 版本的器件支持預(yù)取功能if (HAL_GetREVID() == 0x1001){__HAL_FLASH_PREFETCH_BUFFER_ENABLE(); //使能 flash 預(yù)取}}
從函數(shù)注釋可知,函數(shù) Stm32_Clock_Init 的作用是進(jìn)行時(shí)鐘系統(tǒng)配置,除了配置 PLL 相關(guān)
參數(shù)確定 SYSCLK 值之外,還配置了 AHB,APB1 和 APB2 的分頻系數(shù),也就是確定了 HCLK,
PCLK1 和 PCLK2 的時(shí)鐘值。我們首先來看看使用 HAL 庫配置 STM32F407 時(shí)鐘系統(tǒng)的一般步
驟:
1) 使能 PWR 時(shí)鐘:調(diào)用函數(shù)__HAL_RCC_PWR_CLK_ENABLE()。
2) 設(shè)置調(diào)壓器輸出電壓級(jí)別:調(diào)用函數(shù)__HAL_PWR_VOLTAGESCALING_CONFIG()。
3) 選擇是否開啟 Over-Driver 功能:調(diào)用函數(shù) HAL_PWREx_EnableOverDrive()。
4) 配置時(shí)鐘源相關(guān)參數(shù):調(diào)用函數(shù) HAL_RCC_OscConfig()。
5) 配置系統(tǒng)時(shí)鐘源以及 AHB,APB1 和 APB2 的分頻系數(shù):調(diào)用函數(shù) HAL_RCC_ClockConfig()。
步驟 2 和 3,具有一定的關(guān)聯(lián)性,我們放在后面講解。對(duì)于步驟 1 之所以要使能 PWR 時(shí)鐘,是
因?yàn)楹竺娴牟襟E設(shè)置調(diào)節(jié)器輸出電壓級(jí)別以及開啟 Over-Driver 功能都是電源控制相關(guān)配置,所
以必須開啟 PWR 時(shí)鐘。接下來我們先著重講解步驟 4 和步驟 5 的內(nèi)容,這也是時(shí)鐘系統(tǒng)配置
的關(guān)鍵步驟。
對(duì)于步驟 4,使用 HAL 來配置時(shí)鐘源相關(guān)參數(shù),我們調(diào)用的函數(shù)為 HAL_RCC_OscConfig(),
該函數(shù)在 HAL 庫關(guān)鍵頭文件 stm32f4xx_hal_rcc.h 中聲明,在文件 stm32f4xx_hal_rcc.c 中定義。
首先我們來看看該函數(shù)聲明:
__weak HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct);
該函數(shù)只有一個(gè)入口參數(shù),就是結(jié)構(gòu)體 RCC_OscInitTypeDef 類型指針。接下來我們看看結(jié)構(gòu)體
RCC_OscInitTypeDef 的定義:
typedef struct{ uint32_t OscillatorType; //需要選擇配置的振蕩器類型 uint32_t HSEState; //HSE 狀態(tài) uint32_t LSEState; //LSE 狀態(tài) uint32_t HSIState; //HIS 狀態(tài) uint32_t HSICalibrationValue; //HIS 校準(zhǔn)值 uint32_t LSIState; //LSI 狀態(tài) RCC_PLLInitTypeDef PLL; //PLL 配置}RCC_OscInitTypeDef;
對(duì)于這個(gè)結(jié)構(gòu)體,前面幾個(gè)參數(shù)主要是用來選擇配置的振蕩器類型。比如我們要開啟 HSE,
那么我們會(huì)設(shè)置 OscillatorType 的值為 RCC_OSCILLATORTYPE_HSE,然后設(shè)置 HSEState 的值
為 RCC_HSE_ON 開啟 HSE。對(duì)于其他時(shí)鐘源 HSI,LSI 和 LSE,配置方法類似。這個(gè)結(jié)構(gòu)體還
有一個(gè)很重要的成員變量是 PLL,它是結(jié)構(gòu)體 RCC_PLLInitTypeDef 類型。它的作用是配置 PLL
相關(guān)參數(shù),我們來看看它的定義:
typedef struct{ uint32_t PLLState; //PLL 狀態(tài) uint32_t PLLSource; //PLL 時(shí)鐘源 uint32_t PLLM; //PLL 分頻系數(shù) M uint32_t PLLN; //PLL 倍頻系數(shù) N uint32_t PLLP; //PLL 分頻系數(shù) P uint32_t PLLQ; //PL
從 RCC_PLLInitTypeDef;結(jié)構(gòu)體的定義很容易看出該結(jié)構(gòu)體主要用來設(shè)置 PLL 時(shí)鐘源以及
相關(guān)分頻倍頻參數(shù)。
這個(gè)結(jié)構(gòu)體的定義我們就不做過多講解,接下來我們看看我們的時(shí)鐘初始化函數(shù)
Stm32_Clock_Init 中的配置內(nèi)容:
RCC_OscInitStructure.OscillatorType=RCC_OSCILLATORTYPE_HSE; //時(shí)鐘源為 HSERCC_OscInitStructure.HSEState=RCC_HSE_ON; //打開 HSERCC_OscInitStructure.PLL.PLLState=RCC_PLL_ON; //打開 PLLRCC_OscInitStructure.PLL.PLLSource=RCC_PLLSOURCE_HSE; //PLL 時(shí)鐘源為 HSERCC_OscInitStructure.PLL.PLLM=pllm;RCC_OscInitStructure.PLL.PLLN=plln;RCC_OscInitStructure.PLL.PLLP=pllp;RCC_OscInitStructure.PLL.PLLQ=pllq;ret=HAL_RCC_OscConfig(&RCC_OscInitStructure);
通過該段函數(shù),我們開啟了 HSE 時(shí)鐘源,同時(shí)選擇 PLL 時(shí)鐘源為 HSE,然后把
Stm32_Clock_Init 的 4 個(gè)入口參數(shù)直接設(shè)置作為 PLL 的參數(shù) M,N,P 和 Q 的值,這樣就達(dá)到了設(shè)
置 PLL 時(shí)鐘源相關(guān)參數(shù)的目的。設(shè)置好 PLL 時(shí)鐘源參數(shù)之后,也就是確定了 PLL 的時(shí)鐘頻率,
接下來我們就需要設(shè)置系統(tǒng)時(shí)鐘,以及 AHB,APB1 和 APB2 相關(guān)參數(shù),也就是我們前面提到
的步驟 5。
接下來我們來看看步驟 5 中提到的 HAL_RCC_ClockConfig()函數(shù),聲明如下:HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct,
uint32_t FLatency);
該函數(shù)有兩個(gè)入口參數(shù),第一個(gè)入口參數(shù) RCC_ClkInitStruct 是結(jié)構(gòu)體 RCC_ClkInitTypeDef
指針類型,用來設(shè)置 SYSCLK 時(shí)鐘源以及 AHB,APB1 和 APB2 的分頻系數(shù)。第二個(gè)入口參數(shù)
FLatency 用來設(shè)置 FLASH 延遲,這個(gè)參數(shù)我們放在后面跟步驟 2 和步驟 3 一起講解。
RCC_ClkInitTypeDef 結(jié)構(gòu)體類型定義非常簡單,這里我們就不列出來,我們來看看
Stm32_Clock_Init 函數(shù)中的配置內(nèi)容:
//選中 PLL 作為系統(tǒng)時(shí)鐘源并且配置 HCLK,PCLK1 和 PCLK2
RCC_ClkInitStructure.ClockType=(RCC_CLOCKTYPE_SYSCLK|\\
RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_PCLK1
|RCC_CLOCKTYPE_PCLK2);
RCC_ClkInitStructure.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK;//系統(tǒng)時(shí)鐘源 PLL
RCC_ClkInitStructure.AHBCLKDivider=RCC_SYSCLK_DIV1;//AHB 分頻系數(shù)為 1
RCC_ClkInitStructure.APB1CLKDivider=RCC_HCLK_DIV4; //APB1 分頻系數(shù)為 4
RCC_ClkInitStructure.APB2CLKDivider=RCC_HCLK_DIV2; //APB2 分頻系數(shù)為 2
ret=HAL_RCC_ClockConfig(&RCC_ClkInitStructure,FLASH_LATENCY_5);
第一個(gè)參數(shù) ClockType 配置說明我們要配置的是 SYSCLK,HCLK,PCLK1 和 PCLK2 四個(gè)時(shí)鐘。
第二個(gè)參數(shù) SYSCLKSource 配置選擇系統(tǒng)時(shí)鐘源為 PLL。
第三個(gè)參數(shù) AHBCLKDivider 配置 AHB 分頻系數(shù)為 1。
第四個(gè)參數(shù) APB1CLKDivider 配置 APB1 分頻系數(shù)為 4。
第五個(gè)參數(shù) APB2CLKDivider 配置 APB2 分頻系數(shù)為 2。
根據(jù)我們?cè)谥骱瘮?shù)中調(diào)用 Stm32_Clock_Init(336,8,2,7)時(shí)候設(shè)置的入口參數(shù)值,我們可以計(jì)
算出,PLL 時(shí)鐘為 PLLCLK=HSE*N/M*P=8MHz*336/(8*2)=168MHz,同時(shí)我們選擇系統(tǒng)時(shí)鐘
源 為 PLL , 所 以 系 統(tǒng) 時(shí) 鐘 SYSCLK=168MHz 。 AHB 分 頻 系 數(shù) 為 1 , 故 其 頻 率 為
HCLK=SYSCLK/1=168MHz。APB1 分頻系數(shù)為 4,故其頻率為 PCLK1=HCLK/4=42MHz。APB2
分頻系數(shù)為 2,故其頻率為 PCLK2=HCLK/2=168/2=84MHz。最后我們總結(jié)一下通過調(diào)用函數(shù)
Stm32_Clock_Init(336,8,2,7)之后的關(guān)鍵時(shí)鐘頻率值:
SYSCLK(系統(tǒng)時(shí)鐘)
=168MHz
PLL 主時(shí)鐘
=168MHz
AHB 總線時(shí)鐘(HCLK=SYSCLK/1)
=168MHz
APB1 總線時(shí)鐘(PCLK1=HCLK/4)
=42MHz
APB2 總線時(shí)鐘(PCLK2=HCLK/2)
=84MHz
時(shí)鐘系統(tǒng)配置相關(guān)知識(shí)就給大家講解到這里。
4.4 IO 引腳復(fù)用器和映射
STM32F4 有很多的內(nèi)置外設(shè),這些外設(shè)的外部引腳都是與 GPIO 復(fù)用的。也就是說,一個(gè) GPIO
如果可以復(fù)用為內(nèi)置外設(shè)的功能引腳,那么當(dāng)這個(gè) GPIO 作為內(nèi)置外設(shè)使用的時(shí)候,就叫做復(fù)用。
這部分知識(shí)在《STM32F4 中文參考手冊(cè)》第七章和芯片數(shù)據(jù)手冊(cè)有詳細(xì)的講解哪些 GPIO 管腳是
可以復(fù)用為哪些內(nèi)置外設(shè)。
對(duì)于本小節(jié)知識(shí),STM32F4 中文參考手冊(cè)講解比較詳細(xì),我們同樣會(huì)從中抽取重要的知識(shí)點(diǎn)
羅列出來。同時(shí),我們會(huì)以串口使用為例給大家講解具體的引腳復(fù)用的配置。
STM32F4 系列微控制器 IO 引腳通過一個(gè)復(fù)用器連接到內(nèi)置外設(shè)或模塊。該復(fù)用器一次只允
許一個(gè)外設(shè)的復(fù)用功能(AF)連接到對(duì)應(yīng)的 IO 口。這樣可以確保共用同一個(gè) IO 引腳的外設(shè)之
間不會(huì)發(fā)生沖突。
每個(gè) IO 引腳都有一個(gè)復(fù)用器,該復(fù)用器采用 16 路復(fù)用功能輸入(AF0 到 AF15),可通過
GPIOx_AFRL(針對(duì)引腳 0-7)和 GPIOx_AFRH(針對(duì)引腳 8-15)寄存器對(duì)這些輸入進(jìn)行配置,每四
位控制一路復(fù)用:
1)完成復(fù)位后,所有 IO 都會(huì)連接到系統(tǒng)的復(fù)用功能 0(AF0)。
2)外設(shè)的復(fù)用功能映射到 AF1 到 AF13。
3)Cortex-M4 EVENTOUT 映射到 AF15。
復(fù)用器示意圖如下圖 4.4.1:
圖 4.4.1 復(fù)用器示意圖
接下來,我們簡單說明一下這個(gè)圖要如何看,舉個(gè)例子,探索者 STM32F407 開發(fā)板的原
理圖上 PC11 的原理圖如圖 4.4.2 所示:
圖 4.4.2 探索者 STM32F407 開發(fā)板 PC11 原理圖
如上圖所示,PC11 可以作為 SPI3_MISO/U3_RX/U4_RX/SDIO_D3/DCMI_D4/I2S3ext_SD
等復(fù)用功能輸出,這么多復(fù)用功能,如果這些外設(shè)都開啟了,那么對(duì) STM32F1 來說,那就可
能亂套了,外設(shè)之間可互相干擾,但是 STM32F4,由于有復(fù)用功能選擇功能,可以讓 PC11 僅
連接到某個(gè)特定的外設(shè),因此不存在互相干擾的情況。
上圖 4.4.1 是針對(duì)引腳 0-7,對(duì)于引腳 8-15,控制寄存器為 GPIOx_AFRH。從圖中可以看出。
當(dāng)需要使用復(fù)用功能的時(shí)候,我們配置相應(yīng)的寄存器 GPIOx_AFRL 或者 GPIOx_AFRH,讓對(duì)應(yīng)引
腳通過復(fù)用器連接到對(duì)應(yīng)的復(fù)用功能外設(shè)。這里我們列出 GPIOx_AFRL 寄存器的描述,
GPIOx_AFRH 的作用跟 GPIOx_AFRL 類似,只不過 GPIOx_AFRH 控制的是一組 IO 口的高八位,
GPIOx_AFRL 控制的是一組 IO 口的低八位。
圖 4.4.3 GPIOx_AFRL 寄存器位描述
從表中可以看出,32 位寄存器 GPIOx_AFRL 每四個(gè)位控制一個(gè) IO 口,所以每個(gè)寄存器控制
32/4=8 個(gè) IO 口。寄存器對(duì)應(yīng)四位的值配置決定這個(gè) IO 映射到哪個(gè)復(fù)用功能 AF。
在微控制器完成復(fù)位后,所有 IO 口都會(huì)連接到系統(tǒng)復(fù)用功能 0(AF0)。這里大家需要注意,
對(duì)于系統(tǒng)復(fù)用功能 AF0,我們將 IO 口連接到 AF0 之后,還要根據(jù)所用功能進(jìn)行配置:
1) JTAG/SWD:在器件復(fù)位之后,會(huì)將這些功能引腳指定為專用引腳。也就是說,這些引腳
在復(fù)位后默認(rèn)就是 JTAG/SWD 功能。如果我們要作為 GPIO 來使用,就需要對(duì)對(duì)應(yīng)的 IO
口復(fù)用器進(jìn)行配置。
2) RTC_REFIN:此引腳在系統(tǒng)復(fù)位之后要使用的話要配置為浮空輸入模式。
3) MCO1 和 MCO2:這些引腳在系統(tǒng)復(fù)位之后要使用的話要配置為復(fù)用功能模式。
對(duì)于外設(shè)復(fù)用功能的配置,除了 ADC 和 DAC 要將 IO 配置為模擬通道之外其他外設(shè)功能一律
要配置為復(fù)用功能模式,這個(gè)配置是在 IO 口對(duì)應(yīng)的 GPIOx_MODER 寄存器中配置的。同時(shí)要配
置 GPIOx_AFRH 或者 GPIOx_AFRL 寄存器,將 IO 口通過復(fù)用器連接到所需要的復(fù)用功能對(duì)應(yīng)的
AFx。
不是每個(gè) IO 口都可以復(fù)用為任意復(fù)用功能外設(shè)。到底哪些 IO 可以復(fù)用為相關(guān)外設(shè)呢?這
在芯片對(duì)應(yīng)的數(shù)據(jù)手冊(cè)(請(qǐng)參考光盤目錄:)上面會(huì)有詳細(xì)的表格列出來。對(duì)于 STM32F407,數(shù)
據(jù)手冊(cè)里面的 Table 9.Alternate function mapping 表格列出了所有的端口 AF 映射表,因?yàn)?/p>
表格比較大,所以這里只列出 PORTA 的幾個(gè)端口為例方便大家理解:
表 4.4.4 PORTA 部分端口 AF 映射表
上一節(jié)我們講解了時(shí)鐘系統(tǒng)配置步驟。在配置好時(shí)鐘系統(tǒng)之后,如果我們要使用某些外設(shè),
例如 GPIO,ADC 等,我們還要使能這些外設(shè)時(shí)鐘。這里大家必須注意,如果在使用外設(shè)之前
沒有使能外設(shè)時(shí)鐘,這個(gè)外設(shè)是不可能正常運(yùn)行的。STM32 的外設(shè)時(shí)鐘使能是在 RCC 相關(guān)寄
存器中配置的。因?yàn)?RCC 相關(guān)寄存器非常多,有興趣的同學(xué)可以直接打開《STM32F4 中文參
考手冊(cè)》6.3 小節(jié)查看所有 RCC 相關(guān)寄存器的配置。接下來我們來講解通過 STM32F4 的 HAL
庫使能外設(shè)時(shí)鐘的方法。
在 STM32F4 的 HAL 庫中,外設(shè)時(shí)鐘使能操作都是在 RCC 相關(guān) HAL 庫文件頭文件
stm32f4xx_hal_rcc.h 定義的。大家打開 stm32f4xx_hal_rcc.h 頭文件可以看到文件中除了少數(shù)幾
個(gè)函數(shù)聲明之外大部分都是宏定義標(biāo)識(shí)符。外設(shè)時(shí)鐘使能在 HAL 庫中都是通過宏定義標(biāo)識(shí)符
來實(shí)現(xiàn)的。首先,我們來看看 GPIOA 的外設(shè)時(shí)鐘使能宏定義標(biāo)識(shí)符:
#define __HAL_RCC_GPIOA_CLK_ENABLE()
do { \\
__IO uint32_t tmpreg = 0x00; \\
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\\
tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\\
UNUSED(tmpreg); \\
} while(0)
這幾行代碼非常簡單,主要是定義了一個(gè)宏定義標(biāo)識(shí)符__HAL_RCC_GPIOA_CLK_ENABLE(),
它的核心操作是通過下面這行代碼實(shí)現(xiàn)的:
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);
這行代碼的作用是,設(shè)置寄存器 RCC->AHB1ENR 的相關(guān)位為 1,至于是哪個(gè)位,是由宏定義
標(biāo)識(shí)符 RCC_AHB1ENR_GPIOAEN 的值決定的,而它的值為:
#define RCC_AHB1ENR_GPIOAEN ((uint32_t)0x00000001)
所以,我們很容易理解上面代碼的作用是設(shè)置寄存器 RCC->AHB1ENR 寄存器的最低位為 1。
我們可以從 STM32F4 的中文參考手冊(cè)中搜索 AHB1ENR 寄存器定義,最低位的作用是用來使
用 GPIOA 時(shí)鐘。AHB1ENR 寄存器的位 0 描述如下:
位 0
GPIOAEN:IO 端口 A 時(shí)鐘使能
由軟件置 1 和清零
0:禁止 IO 端口 A 時(shí)鐘
1:使能 IO 端口 A 時(shí)鐘
那么我們只需要在我們的用戶程序中調(diào)用宏定義標(biāo)識(shí)符__HAL_RCC_GPIOA_CLK_ENABLE()
就可以實(shí)現(xiàn) GPIOA 時(shí)鐘使能。使用方法為:
__HAL_RCC_GPIOA_CLK_ENABLE();//使能 GPIOA 時(shí)鐘
對(duì)于其他外設(shè),同樣都是在 stm32f4xx_hal_rcc.h 頭文件中定義,大家只需要找到相關(guān)宏定義標(biāo)
識(shí)符即可,這里我們列出幾個(gè)常用使能外設(shè)時(shí)鐘的宏定義標(biāo)識(shí)符使用方法:
__HAL_RCC_DMA1_CLK_ENABLE();//使能 DMA1 時(shí)鐘
__HAL_RCC_USART2_CLK_ENABLE();//使能串口 2 時(shí)鐘
__HAL_RCC_TIM1_CLK_ENABLE();//使能 TIM1 時(shí)鐘
我們使用外設(shè)的時(shí)候需要使能外設(shè)時(shí)鐘,如果我們不需要使用某個(gè)外設(shè),同樣我們可以禁
止某個(gè)外設(shè)時(shí)鐘。禁止外設(shè)時(shí)鐘使用方法和使能外設(shè)時(shí)鐘非常類似,同樣是頭文件中定義的宏
定義標(biāo)識(shí)符。我們同樣以 GPIOA 為例,宏定義標(biāo)識(shí)符為:
#define __HAL_RCC_GPIOA_CLK_DISABLE() \\
(RCC->AHB1ENR &= ~(RCC_AHB1ENR_GPIOAEN))
同樣,宏定義標(biāo)識(shí)符__HAL_RCC_GPIOA_CLK_DISABLE()的作用是設(shè)置 RCC->AHB1ENR 寄
存器的最低位為 0,也就是禁止 GPIOA 時(shí)鐘。具體使用方法我們這里就不做過多講解,我們這
里同樣列出幾個(gè)常用的禁止外設(shè)時(shí)鐘的宏定義標(biāo)識(shí)符使用方法:
__HAL_RCC_DMA1_CLK_DISABLE();//禁止 DMA1 時(shí)鐘
__HAL_RCC_USART2_CLK_DISABLE();//禁止串口 2 時(shí)鐘
__HAL_RCC_TIM1_CLK_DISABLE();//禁止 TIM1 時(shí)鐘
關(guān)于 STM32F4 的外設(shè)時(shí)鐘使能和禁止方法我們就給大家講解到這里。
這類函數(shù)跟前面講解的外設(shè)時(shí)鐘函數(shù)使用方法基本一致,不同的是一個(gè)是用來使能外設(shè)時(shí)
鐘,一個(gè)是用來復(fù)位對(duì)應(yīng)的外設(shè)。這里大家在調(diào)用函數(shù)的時(shí)候一定不要混淆。
對(duì)于這些時(shí)鐘操作函數(shù),我們就不一一列舉出來,大家可以打開 RCC 對(duì)應(yīng)的文件仔細(xì)了解。
ST32F4 的端口復(fù)用和映射就給大家講解到這里,希望大家課余結(jié)合相關(guān)實(shí)驗(yàn)工程和手冊(cè)鞏
固本小節(jié)知識(shí)。
4.5 STM32 NVIC 中斷優(yōu)先級(jí)管理
CM4 內(nèi)核支持 256 個(gè)中斷,其中包含了 16 個(gè)內(nèi)核中斷和 240 個(gè)外部中斷,并且具有 256
級(jí)的可編程中斷設(shè)置。但 STM32F407 并沒有使用 CM4 內(nèi)核的全部東西,而是只用了它的一部
分。STM32F4xx 則總共有 101 個(gè)中斷,以下僅以 STM32F407xx 為例講解。
STM32F407xx 的 96 個(gè)中斷里面,包括 10 個(gè)內(nèi)核中斷和 91 個(gè)可屏蔽中斷,具有 16 級(jí)可編
程的中斷優(yōu)先級(jí),而我們常用的就是這 91 個(gè)可屏蔽中斷。在 MDK 內(nèi),與 NVIC 相關(guān)的寄存器,
MDK 為其定義了如下的結(jié)構(gòu)體:
typedef struct
{
__IO uint32_t ISER[8];
uint32_t RESERVED0[24];
__IO uint32_t ICER[8];
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8];
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8];
uint32_t RESERVED3[24];
__IO uint32_t IABR[8];
uint32_t RESERVED4[56];
__IO uint8_t IP[240];
uint32_t RESERVED5[644];
__O uint32_t STIR;
} NVIC_Type;
STM32F407 的中斷在這些寄存器的控制下有序的執(zhí)行的。只有了解這些中斷寄存器,才能
方便的使用 STM32F407 的中斷。下面重點(diǎn)介紹這幾個(gè)寄存器:
ISER[8]:ISER 全稱是:Interrupt Set-Enable Registers,這是一個(gè)中斷使能寄存器組。上面
說了 CM4 內(nèi)核支持 256 個(gè)中斷,這里用 8 個(gè) 32 位寄存器來控制,每個(gè)位控制一個(gè)中斷。但是
STM32F407 的可屏蔽中斷最多只有 91 個(gè),所以對(duì)我們來說,有用的就是三個(gè)(ISER[0~2]]),
總共可以表示 96 個(gè)中斷。而 STM32F407 只用了其中的前 91 個(gè)。ISER[0]的 bit0~31 分別對(duì)應(yīng)
中斷 0~31;ISER[1]的 bit0~32 對(duì)應(yīng)中斷 32~63;ISER[2]的 bit0~26 對(duì)應(yīng)中斷 64~90;這樣總共
91 個(gè)中斷就分別對(duì)應(yīng)上了。你要使能某個(gè)中斷,必須設(shè)置相應(yīng)的 ISER 位為 1,使該中斷被使
能(這里僅僅是使能,還要配合中斷分組、屏蔽、IO 口映射等設(shè)置才算是一個(gè)完整的中斷設(shè)置)。
具體每一位對(duì)應(yīng)哪個(gè)中斷,請(qǐng)參考 STM32F407xx.h 里面的第 84 行處。
ICER[8]:全稱是:Interrupt Clear-Enable Registers,是一個(gè)中斷除能寄存器組。該寄存器組
與 ISER 的作用恰好相反,是用來清除某個(gè)中斷的使能的。其對(duì)應(yīng)位的功能,也和 ICER 一樣。
這里要專門設(shè)置一個(gè) ICER 來清除中斷位,而不是向 ISER 寫 0 來清除,是因?yàn)?NVIC 的這些寄
存器都是寫 1 有效的,寫 0 是無效的。
ISPR[8]:全稱是:Interrupt Set-Pending Registers,是一個(gè)中斷掛起控制寄存器組。每個(gè)位
對(duì)應(yīng)的中斷和 ISER 是一樣的。通過置 1,可以將正在進(jìn)行的中斷掛起,而執(zhí)行同級(jí)或更高級(jí)別
的中斷。寫 0 是無效的。
ICPR[8]:全稱是:Interrupt Clear-Pending Registers,是一個(gè)中斷解掛控制寄存器組。其作
用與 ISPR 相反,對(duì)應(yīng)位也和 ISER 是一樣的。通過設(shè)置 1,可以將掛起的中斷接掛。寫 0 無效。
IABR[8]:全稱是:Interrupt Active Bit Registers,是一個(gè)中斷激活標(biāo)志位寄存器組。對(duì)應(yīng)位
所代表的中斷和 ISER 一樣,如果為 1,則表示該位所對(duì)應(yīng)的中斷正在被執(zhí)行。這是一個(gè)只讀寄
存器,通過它可以知道當(dāng)前在執(zhí)行的中斷是哪一個(gè)。在中斷執(zhí)行完了由硬件自動(dòng)清零。
IP[240]:全稱是:Interrupt Priority Registers,是一個(gè)中斷優(yōu)先級(jí)控制的寄存器組。這個(gè)寄
存器組相當(dāng)重要!STM32F407 的中斷分組與這個(gè)寄存器組密切相關(guān)。IP 寄存器組由 240 個(gè) 8bit
的寄存器組成,每個(gè)可屏蔽中斷占用 8bit,這樣總共可以表示 240 個(gè)可屏蔽中斷。而 STM32F407
只用到了其中的 91 個(gè)。IP[90]~IP[0]分別對(duì)應(yīng)中斷 90~0。而每個(gè)可屏蔽中斷占用的 8bit 并沒有
全部使用,而是 只用了高 4 位。這 4 位,又分為搶占優(yōu)先級(jí)和子優(yōu)先級(jí)。搶占優(yōu)先級(jí)在前,子
優(yōu)先級(jí)在后。而這兩個(gè)優(yōu)先級(jí)各占幾個(gè)位又要根據(jù) SCB->AIRCR 中的中斷分組設(shè)置來決定。
這里簡單介紹一下 STM32F407 的中斷分組:STM32F407 將中斷分為 5 個(gè)組,組 0~4。該
分組的設(shè)置是由 SCB->AIRCR 寄存器的 bit10~8 來定義的。具體的分配關(guān)系如表 4.5.1 所示:
表 4.5.1AIRCR 中斷優(yōu)先級(jí)分組設(shè)置表
通過這個(gè)表,我們就可以清楚的看到組 0~4 對(duì)應(yīng)的配置關(guān)系,例如組設(shè)置為 3,那么此時(shí)
所有的 91 個(gè)中斷,每個(gè)中斷的中斷優(yōu)先寄存器的高四位中的最高 3 位是搶占優(yōu)先級(jí),低 1 位是
響應(yīng)優(yōu)先級(jí)。每個(gè)中斷,你可以設(shè)置搶占優(yōu)先級(jí)為 0~7,響應(yīng)優(yōu)先級(jí)為 1 或 0。搶占優(yōu)先級(jí)的
級(jí)別高于響應(yīng)優(yōu)先級(jí)。而數(shù)值越小所代表的優(yōu)先級(jí)就越高。
這里需要注意兩點(diǎn):第一,如果兩個(gè)中斷的搶占優(yōu)先級(jí)和響應(yīng)優(yōu)先級(jí)都是一樣的話,則看
哪個(gè)中斷先發(fā)生就先執(zhí)行;第二,高優(yōu)先級(jí)的搶占優(yōu)先級(jí)是可以打斷正在進(jìn)行的低搶占優(yōu)先級(jí)
中斷的。而搶占優(yōu)先級(jí)相同的中斷,高優(yōu)先級(jí)的響應(yīng)優(yōu)先級(jí)不可以打斷低響應(yīng)優(yōu)先級(jí)的中斷。
結(jié)合實(shí)例說明一下:假定設(shè)置中斷優(yōu)先級(jí)組為 2,然后設(shè)置中斷 3(RTC_WKUP 中斷)的搶
占優(yōu)先級(jí)為 2,響應(yīng)優(yōu)先級(jí)為 1。中斷 6(外部中斷 0)的搶占優(yōu)先級(jí)為 3,響應(yīng)優(yōu)先級(jí)為 0。中
斷 7(外部中斷 1)的搶占優(yōu)先級(jí)為 2,響應(yīng)優(yōu)先級(jí)為 0。那么這 3 個(gè)中斷的優(yōu)先級(jí)順序?yàn)椋褐?/p>
斷 7>中斷 3>中斷 6。
上面例子中的中斷 3 和中斷 7 都可以打斷中斷 6 的中斷。而中斷 7 和中斷 3 卻不可以相互
打斷!
通過以上介紹,我們熟悉了 STM32F407 中斷設(shè)置的大致過程。接下來我們介紹如何使用
HAL 庫實(shí)現(xiàn)以上中斷分組設(shè)置以及中斷優(yōu)先級(jí)管理,使中斷配置簡單化。NVIC 中斷管理相關(guān)
函數(shù)主要在 HAL 庫關(guān)鍵文件 stm32f4xx_hal_cortex.c 中定義。
首先要講解的是中斷優(yōu)先級(jí)分組函數(shù) HAL_NVIC_SetPriorityGrouping,其函數(shù)申明如下:
void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup);
這個(gè)函數(shù)的作用是對(duì)中斷的優(yōu)先級(jí)進(jìn)行分組,這個(gè)函數(shù)在系統(tǒng)中只需要被調(diào)用一次,一旦
分組確定就最好不要更改,否則容易造成程序分組混亂。這個(gè)函數(shù)我們可以找到其函數(shù)體內(nèi)容
如下:
void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
{
/* Check the parameters */
assert_param(IS_NVIC_PRIORITY_GROUP(PriorityGroup));
/* Set the PRIGROUP[10:8] bits according to the PriorityGroup parameter value */
NVIC_SetPriorityGrouping(PriorityGroup);
}
從函數(shù)體以及注釋可以看出,這個(gè)函數(shù)是通過調(diào)用函數(shù) NVIC_SetPriorityGrouping 來進(jìn)行中斷
優(yōu)先級(jí)分組設(shè)置。通過查找(參考 3.5.3 小節(jié) MDK 中“Go to definition of”的使用方法),我們可
以知道函數(shù) NVIC_SetPriorityGrouping 是在文件 core_cm4.h 頭文件中定義的。接下來,我們來
分析一下函數(shù) NVIC_SetPriorityGrouping 函數(shù)定義。定義如下:
__STATIC_INLINE void NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
{
uint32_t reg_value;
uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07UL);
reg_value= SCB->AIRCR; /* read old register configuration */
reg_value&=~((uint32_t)(SCB_AIRCR_VECTKEY_Msk |SCB_AIRCR_PRIGROUP_Msk));
reg_value = (reg_value|((uint32_t)0x5FAUL << SCB_AIRCR_VECTKEY_Pos) |
(PriorityGroupTmp << 8U) );
SCB->AIRCR = reg_value;
}
從函數(shù)內(nèi)容可以看出,這個(gè)函數(shù)主要作用是通過設(shè)置 SCB->AIRCR 寄存器的值來設(shè)置中斷優(yōu)先
級(jí)分組,這在前面寄存器講解的過程中已經(jīng)講到。
關(guān)于函數(shù) HAL_NVIC_SetPriorityGrouping 的函數(shù)體內(nèi)容解讀我就給大家介紹到這里。接下
來我們來看看這個(gè)函數(shù)的入口參數(shù)。大家繼續(xù)回到函數(shù) HAL_NVIC_SetPriorityGrouping 的定義
可以看到,函數(shù)的最開頭有這樣一行函數(shù):
assert_param(IS_NVIC_PRIORITY_GROUP(PriorityGroup));
其中函數(shù) assert_param 是斷言函數(shù),它的作用主要是對(duì)入口參數(shù)的有效性進(jìn)行判斷。也就是說
我們可以通過這個(gè)函數(shù)知道入口參數(shù)在哪些范圍內(nèi)是有效的。而其入口參數(shù)通過在 MDK 中雙
擊選中 “IS_NVIC_PRIORITY_GROUP”,然后右鍵“Go to defition of …”可以查看到為:
#define IS_NVIC_PRIORITY_GROUP(GROUP)
(((GROUP) == NVIC_PriorityGroup_0) ||\\
((GROUP) == NVIC_PriorityGroup_1) || \\
((GROUP) == NVIC_PriorityGroup_2) || \\
((GROUP) == NVIC_PriorityGroup_3) || \\
((GROUP) == NVIC_PriorityGroup_4))
從這個(gè)內(nèi)容可以看出,當(dāng) GROUP 的值為 NVIC_PriorityGroup_0~ NVIC_PriorityGroup_4 的時(shí)候,
IS_NVIC_PRIORITY_GROUP 的值才為真。這也就是我們上面表 4.5.1 講解的,分組范圍為 0-4,
對(duì)應(yīng)的入口參數(shù)為宏定義值 NVIC_PriorityGroup_0~ NVIC_PriorityGroup_4。比如我們?cè)O(shè)置整個(gè)
系統(tǒng)的中斷優(yōu)先級(jí)分組值為 2,那么方法是:
HAL_NVIC_SetPriorityGrouping (NVIC_PriorityGroup_2);
這樣就確定了中斷優(yōu)先級(jí)分組為 2,也就是 2 位搶占優(yōu)先級(jí),2 位響應(yīng)優(yōu)先級(jí),搶占優(yōu)先級(jí)和響
應(yīng)優(yōu)先級(jí)的值的范圍均為 0-3。
講到這里,大家對(duì)怎么進(jìn)行系統(tǒng)的中斷優(yōu)先級(jí)分組設(shè)置,以及具體的中斷優(yōu)先級(jí)設(shè)置函數(shù)
HAL_NVIC_SetPriorityGrouping 的內(nèi)部函數(shù)實(shí)現(xiàn)都有了一個(gè)詳細(xì)的理解。接下來我們來看看在
HAL 庫里面,是怎樣調(diào)用 HAL_NVIC_SetPriorityGrouping 函數(shù)進(jìn)行分組設(shè)置的。
打開 stm32f4xx_hal.c 文件可以看到,文件內(nèi)部定義了 HAL 庫初始化函數(shù) HAL_Init,這個(gè)
函數(shù)非常重要,其作用主要是對(duì)中斷優(yōu)先級(jí)分組,F(xiàn)LASH 以及硬件層進(jìn)行初始化,我們?cè)?3.1
小節(jié)對(duì)其進(jìn)行了比較詳細(xì)的講解。這里我們只需要知道,在系統(tǒng)主函數(shù) main 開頭部分,我們都
會(huì)首先調(diào)用 HAL_Init 函數(shù)進(jìn)行一些初始化操作。在 HAL_Init 內(nèi)部,有如下一行代碼:
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
這行代碼的作用是把系統(tǒng)中斷優(yōu)先級(jí)分組設(shè)置為分組 4,這在我們前面已經(jīng)詳細(xì)講解。也
就是說,在主函數(shù)中調(diào)用 HAL_Init 函數(shù)之后,在 HAL_Init 函數(shù)內(nèi)部會(huì)通過調(diào)用我們前面講解
的 HAL_NVIC_SetPriorityGrouping 函數(shù)來進(jìn)行系統(tǒng)中斷優(yōu)先級(jí)分組設(shè)置。所以,我們要進(jìn)行中
斷優(yōu)先級(jí)分組設(shè)置,只需要修改 HAL_Init 函數(shù)內(nèi)部的這行代碼即可。中斷優(yōu)先級(jí)分組的內(nèi)容我
們就給大家講解到這里。
設(shè)置好了系統(tǒng)中斷分組,也就是確定了那么對(duì)于每個(gè)中斷我們又怎么確定他的搶占優(yōu)先級(jí)
和響應(yīng)優(yōu)先級(jí)呢?官方 HAL 庫文件 stm32f4xx_hal_cortex.c 中定義了三個(gè)單個(gè)中斷優(yōu)先級(jí)設(shè)置
函數(shù)。函數(shù)如下:
void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority);
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
void HAL_NVIC_DisableIRQ(IRQn_Type IRQn);
第一個(gè)函數(shù) HAL_NVIC_SetPriority 是用來設(shè)置單個(gè)優(yōu)先級(jí)的搶占優(yōu)先級(jí)和響應(yīng)優(yōu)先級(jí)的值。
第二個(gè)函數(shù) HAL_NVIC_EnableIRQ 是用來使能某個(gè)中斷通道。
第三個(gè)函數(shù) HAL_NVIC_DisableIRQ 是用來清除某個(gè)中斷使能的,也就是中斷失能。
這三個(gè)函數(shù)的使用都非常簡單,對(duì)于具體的調(diào)用方法,大家可以參考我們后面第九章外部中斷
實(shí)驗(yàn)講解。
這里大家還需要注意,中斷優(yōu)先級(jí)分組和中斷優(yōu)先級(jí)設(shè)置是兩個(gè)不同的概念。中斷優(yōu)先級(jí)
分組是用來設(shè)置整個(gè)系統(tǒng)對(duì)于中斷分組設(shè)置為哪個(gè)分組,分組號(hào)為 0-4,設(shè)置函數(shù)為
HAL_NVIC_SetPriorityGrouping,確定了中斷優(yōu)先級(jí)分組號(hào),也就確定了系統(tǒng)對(duì)于單個(gè)中斷的
搶占優(yōu)先級(jí)和響應(yīng)優(yōu)先級(jí)設(shè)置各占幾個(gè)位(對(duì)應(yīng)表 4.5.1)。設(shè)置好中斷優(yōu)先級(jí)分組,確定了分
組號(hào)之后,接下來我們就是要對(duì)單個(gè)優(yōu)先級(jí)進(jìn)行中斷優(yōu)先級(jí)設(shè)置。也就是這個(gè)中斷的搶占優(yōu)先
級(jí)和響應(yīng)優(yōu)先級(jí)的值,設(shè)置方法就是我們上面講解的三個(gè)函數(shù)。
最后我們總結(jié)一下中斷優(yōu)先級(jí)設(shè)置的步驟:
①系統(tǒng)運(yùn)行開始的時(shí)候設(shè)置中斷分組。確定組號(hào),也就是確定搶占優(yōu)先級(jí)和響應(yīng)優(yōu)先級(jí)的
分配位數(shù)。設(shè)置函數(shù)為 HAL_NVIC_PriorityGroupConfig。對(duì)于 HAL 庫,在文件 stm32f4xx_hal.c
內(nèi)部定義函數(shù) HAL_Init 中有調(diào)用 HAL_NVIC_PriorityGroupConfig 函數(shù)進(jìn)行相關(guān)設(shè)置,所以我
們只需要修改 HAL_Init 內(nèi)部對(duì)中斷優(yōu)先級(jí)分組設(shè)置即可。
① 設(shè)置單個(gè)中斷的中斷優(yōu)先級(jí)別和使能相應(yīng)中斷通道,使用到的函數(shù)函數(shù)主要為函數(shù)
HAL_NVIC_SetPriority 和函數(shù) HAL_NVIC_EnableIRQ。
4.6 MDK 中寄存器地址名稱映射分析
之所以要講解這部分知識(shí),是因?yàn)榻?jīng)常會(huì)遇到客戶提到不明白 HAL 庫中那些結(jié)構(gòu)體是怎么
與寄存器地址對(duì)應(yīng)起來的。這里我們就做一個(gè)簡要的分析吧。
首先我們看看 51 中是怎么做的。51 單片機(jī)開發(fā)中經(jīng)常會(huì)引用一個(gè) reg51.h 的頭文件,下
面我們看看他是怎么把名字和寄存器聯(lián)系起來的:
sfr P0 =0x80;
sfr 也是一種擴(kuò)充數(shù)據(jù)類型,點(diǎn)用一個(gè)內(nèi)存單元,值域?yàn)?0~255。利用它可以訪問 51 單片
機(jī)內(nèi)部的所有特殊功能寄存器。如用 sfr P1 = 0x90 這一句定義 P1 為 P1 端口在片內(nèi)的寄存
器。然后我們往地址為 0x80 的寄存器設(shè)值的方法是:P0=value;
那么在 STM32 中,是否也可以這樣做呢??答案是肯定的??隙ㄒ部梢酝ㄟ^同樣的方
式來做,但是 STM32 因?yàn)榧拇嫫魈嗵?,如果一一以這樣的方式列出來,那要好大的篇
幅,既不方便開發(fā),也顯得太雜亂無序的感覺。所以 MDK 采用的方式是通過結(jié)構(gòu)體來將
寄存器組織在一起。下面我們就講解 MDK 是怎么把結(jié)構(gòu)體和地址對(duì)應(yīng)起來的,為什么我
們修改結(jié)構(gòu)體成員變量的值就可以達(dá)到操作對(duì)應(yīng)寄存器的值。這些事情都是在 stm32f4xx.h
文件中完成的。我們通過 GPIOA 的幾個(gè)寄存器的地址來講解吧。
首先我們可以查看《STM32F4 中文參考手冊(cè)》中的寄存器地址映射表(P193)。這里我
們選用 GPIOA 為例來講解。GPIOA 寄存器地址映射如下表 4.6.1:
表 4.6.1 GIPOA 寄存器地址偏移表
從這個(gè)表我們可以看出,因?yàn)?GIPO 寄存器都是 32 位,所以每組 GPIO 的 10 個(gè)寄存器
中,每個(gè)寄存器占有 4 個(gè)地址,一共占用 40 個(gè)地址,地址偏移范圍為(0x00~0x24)。這個(gè)
地址偏移是相對(duì) GPIOA 的基地址而言的。GPIOA 的基地址是怎么算出來的呢?因?yàn)?GPIO
都是掛載在 AHB1 總線之上,所以它的基地址是由 AHB1 總線的基地址加上 GPIOA 在
AHB1 總線上的偏移地址決定的。同理依次類推,我們便可以算出 GPIOA 基地址了。下面
我們打開 stm32f429.h 定位到 GPIO_TypeDef 定義處:
typedef struct
{
__IO uint32_t MODER;
__IO uint32_t OTYPER;
__IO uint32_t OSPEEDR;
__IO uint32_t PUPDR;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t LCKR;
__IO uint32_t AFR[2];
} GPIO_TypeDef;
然后定位到:
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
可以看出,GPIOA 是將 GPIOA_BASE 強(qiáng)制轉(zhuǎn)換為 GPIO_TypeDef 結(jié)構(gòu)體指針,這句話的
意思是,GPIOA 指向地址 GPIOA_BASE,GPIOA_BASE 存放的數(shù)據(jù)類型為 GPIO_TypeDef。
然后在 MDK 中雙擊“GPIOA_BASE”選中之后右鍵選中“Go to definition of ”,便可以查
看 GPIOA_BASE 的宏定義:
#define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000)
依次類推,可以找到最頂層:
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000)
#define PERIPH_BASE ((uint32_t)0x40000000)
所以我們便可以算出 GPIOA 的基地址位:
GPIOA_BASE= 0x40000000+0x00020000+0x0000=0x40020000
下面我們?cè)俑禨TM32F 中文參考手冊(cè)》比較一下看看 GPIOA 的基地址是不是 0x40020000 。
截圖 P53 存儲(chǔ)器映射表我們可以看到,GPIOA 的起始地址也就是基地址確實(shí)是 0x40020000:
圖 4.6.2 GPIO 存儲(chǔ)器地址映射表
同樣的道理,我們可以推算出其他外設(shè)的基地址。
上面我們已經(jīng)知道 GPIOA 的基地址,那么那些 GPIOA 的 10 個(gè)寄存器的地址又是怎么
算出來的呢?在上面我們講過 GPIOA 的各個(gè)寄存器對(duì)于 GPIOA 基地址的偏移地址,所以
我們自然可以算出來每個(gè)寄存器的地址。
GPIOA 的寄存器的地址=GPIOA 基地址+寄存器相對(duì) GPIOA 基地址的偏移值
這個(gè)偏移值在上面的寄存器地址映像表中可以查到。
那么在結(jié)構(gòu)體里面這些寄存器又是怎么與地址一一對(duì)應(yīng)的呢?這里涉及到結(jié)構(gòu)體成員
變量地址對(duì)齊方式方面的知識(shí),這方面的知識(shí)大家可以在網(wǎng)上查看相關(guān)資料復(fù)習(xí)一下,這
里我們不做詳細(xì)講解。在我們定義好地址對(duì)齊方式之后,每個(gè)成員變量對(duì)應(yīng)的地址就可以
根據(jù)其基地址來計(jì)算。對(duì)于結(jié)構(gòu)體類型 GPIO_TypeDef,他的所有成員變量都是 32 位,成
員變量地址具有連續(xù)性。所以自然而然我們就可以算出 GPIOA 指向的結(jié)構(gòu)體成員變量對(duì)應(yīng)
地址了。
表 4.6.3 GPIOA 各寄存器實(shí)際地址表
我們可以把 GPIO_TypeDef 的定義中的成員變量的順序和 GPIOx 寄存器地址映像對(duì)比
可以發(fā)現(xiàn),他們的順序是一致的,如果不一致,就會(huì)導(dǎo)致地址混亂了。
這就是為什么 HAL 庫里面:GPIOA->BSRR=value;就是設(shè)置地址為 0x40020000
+0x18 (BSRR 偏移量)=0x40020018 的寄存器 BSRR 的值了。它和 51 里面 P0=value 是設(shè)置
地址為 0x80 的 P0 寄存器的值是一樣的道理。
看到這里你是否會(huì)學(xué)起來踏實(shí)一點(diǎn)呢?STM32 使用的方式雖然跟 51 單片機(jī)不一樣,
但是原理都是一致的。
4.7 MDK 代碼快速組織代碼技巧
這一節(jié)主要講解在 MDK 中使用 HAL 庫開發(fā)的一些小技巧,僅供初學(xué)者參考。這節(jié)的知識(shí)
大家可以在學(xué)習(xí)第一個(gè)跑馬燈實(shí)驗(yàn)的時(shí)候參考一下,對(duì)初學(xué)者應(yīng)該很有幫助。我們就用最簡單
的 GPIO 初始化函數(shù)為例。
現(xiàn) 在 我 們 要 初 始 化 某 個(gè) GPIO 端 口 , 我 們 要 怎 樣 快 速 操 作 呢 ? 在 頭 文 件
stm32f4xx_hal_gpio.h 頭文件中,聲明 GPIO 初始化函數(shù)為:
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
現(xiàn)在我們想寫初始化函數(shù),那么我們?cè)诓粎⒖计渌a的前提下,怎么快速組織代碼呢?
首先,我們可以看出,函數(shù)的入口參數(shù)是 GPIO_TypeDef 類型指針和 GPIO_InitTypeDef 類型指針,因?yàn)?GPIO_TypeDef 入口參數(shù)比較簡單,所以我們 就通過第二個(gè)入口參數(shù)
GPIO_InitTypeDef 類型指針來講解。雙擊 GPIO_InitTypeDef 后右鍵選擇“Go to definition of…”,
(前提是打開了“Browse Information”選項(xiàng),可以參考前面 3.3.2 章節(jié)說明,勾選上打開),如
下圖 4.7.1:
圖 4.7.1 查看類型定義方法
于是定位到 stm32f4xx_hal_gpio.h 中 GPIO_InitTypeDef 的定義處:
typedef struct
{
uint32_t Pin;
uint32_t Mode;
uint32_t Pull;
uint32_t Speed;
uint32_t Alternate;
}GPIO_InitTypeDef;
可以看到這個(gè)結(jié)構(gòu)體有 5 個(gè)成員變量,這也告訴我們一個(gè)信息,一個(gè) GPIO 口的狀態(tài)是由模式
(Mode),速度(Speed)以及上下拉(Pull)來決定的。我們首先要定義一個(gè)結(jié)構(gòu)體變量,下面
我們定義:
GPIO_InitTypeDef GPIO_InitStructure;
接著我們要初始化結(jié)構(gòu)體變量 GPIO_InitStructure。首先我們要初始化成員變量 Pin,這個(gè)時(shí)候我
們就有點(diǎn)迷糊了,這個(gè)變量到底可以設(shè)置哪些值呢?這些值的范圍有什么規(guī)定嗎?
這里我們就回到 HAL_GPIO_Init 聲明處,同樣雙擊 HAL_GPIO_Init,右鍵點(diǎn)擊“Go to
definition of …”,這樣光標(biāo)定位到 stm32f4xx_hal_gpio.c 文件中的 HAL_GPIO_Init 函數(shù)體開始處,
我們可以看到在函數(shù)中有如下幾行:
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)
{
…//此處省略部分代碼
assert_param(IS_GPIO_ALL_INSTANCE(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Init->Pin));
assert_param(IS_GPIO_MODE(GPIO_Init->Mode));
assert_param(IS_GPIO_PULL(GPIO_Init->Pull));
…//此處省略部分代碼
assert_param(IS_GPIO_AF(GPIO_Init->Alternate));
…//此處省略部分代碼
}
顧名思義,assert_param 是斷言語句,是對(duì)函數(shù)入口參數(shù)的有效性進(jìn)行判斷,所以我們可以從
這個(gè)函數(shù)入手,確定入口參數(shù)范圍。第一行是對(duì)第一個(gè)參數(shù) GPIOx 進(jìn)行有效性判斷,雙擊
“IS_GPIO_ALL_INSTANCE”右鍵點(diǎn)擊“go to defition of…” 定位到了下面的定義:
#define IS_GPIO_ALL_INSTANCE(INSTANCE) (((INSTANCE) == GPIOA) || \\
((INSTANCE) == GPIOB) || \\
((INSTANCE) == GPIOC) || \\
((INSTANCE) == GPIOD) || \\
…//此處省略部分代碼
((INSTANCE) == GPIOJ) || \\
((INSTANCE) == GPIOK))
很明顯可以看出,GPIOx 的取值規(guī)定只允許是 GPIOA~GPIOK。
同樣的辦法,我們雙擊“IS_GPIO_PIN” 右鍵點(diǎn)擊“go to defition of…”,定位到下面的定義:
#define IS_GPIO_PIN(PIN) (((PIN) & GPIO_PIN_MASK ) != (uint32_t)0x00)
同時(shí),宏定義標(biāo)識(shí)符 GPIO_PIN_MASK 的定義為:
#define GPIO_PIN_MASK ((uint32_t)0x0000FFFF)
從上面可以看出,PIN 取值只要低 16 位不為 0 即可。這里需要大家注意,因?yàn)橐唤M IO 口只有
16 個(gè) IO,實(shí)際上 PIN 的值在這里只有低 16 位有效,所以 PIN 的取值范圍為 0x0001~0xFFFF。
那么是不是我們寫代碼初始化就是直接給一個(gè) 16 位的數(shù)字呢?這也是可以的,但是大多數(shù)情況
下,我們不會(huì)直接在入口參數(shù)處設(shè)置一個(gè)簡單的數(shù)字,因?yàn)檫@樣代碼的可讀性太差,HAL 庫會(huì)
將這些數(shù)字的含義 通過宏定義定義出來,這樣可讀性大大增強(qiáng)。我們可以看到在
GPIO_PIN_MASK 宏定義的上面還有數(shù)行宏定義:
#define GPIO_PIN_0 ((uint16_t)0x0001)
#define GPIO_PIN_1 ((uint16_t)0x0002)
#define GPIO_PIN_2 ((uint16_t)0x0004)
…//此處省略部分定義
#define GPIO_PIN_14 ((uint16_t)0x4000)
#define GPIO_PIN_15 ((uint16_t)0x8000)
#define GPIO_PIN_All ((uint16_t)0xFFFF)
這些宏定義 GPIO_PIN_0 ~ GPIO_PIN_All 就是 HAL 庫事先定義好的,我們寫代碼的時(shí)候初始
化結(jié)構(gòu)體 成員變量 Pin 的時(shí)候入口參數(shù)可以是這些宏定義標(biāo)識(shí)符。
同理,對(duì)于成員變量 Pull,我們用同樣的方法,可以找到其取值范圍定義為:
#define IS_GPIO_PULL(PULL) (((PULL) == GPIO_NOPULL)\\
|| ((PULL) == GPIO_PULLUP) || \\ ((PULL) == GPIO_PULLDOWN))
也就是 PULL 的 取 值 范 圍 只 能 是 標(biāo) 識(shí) 符 GPIO_NOPULL , GPIO_PULLUP 以 及
GPIO_PULLDOWN。
對(duì)于其他成員變量 Mode 以及 Alternate,方法都是一樣的,這里基于篇幅考慮我們就不重
復(fù)講解。講到這里,我們基本對(duì) HAL_GPIO_Init 的入口參數(shù)有比較詳細(xì)的了解了。于是我們可
以組織起來下面的代碼:
GPIO_InitTypeDef GPIO_Initure;
GPIO_Initure.Pin=GPIO_PIN_9;
//PA9
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //復(fù)用推挽輸出
GPIO_Initure.Pull=GPIO_PULLUP;
//上拉
GPIO_Initure.Speed=GPIO_SPEED_FAST;
//高速
GPIO_Initure.Alternate=GPIO_AF7_USART1; //復(fù)用為 USART1
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 PA9
接著又有一個(gè)問題會(huì)被提出來,這個(gè)初始化函數(shù)一次只能初始化一個(gè) IO 口嗎?我要同時(shí)
初始化很多個(gè) IO 口,是不是要復(fù)制很多次這樣的初始化代碼呢?
這里又有一個(gè)小技巧了。從上面的 GPIO_PIN_X 的宏定義我們可以看出,這些值是 0,1,2,4
這樣的數(shù)字,所以每個(gè) IO 口選定都是對(duì)應(yīng)著一個(gè)位,16 位的數(shù)據(jù)一共對(duì)應(yīng) 16 個(gè) IO 口。這個(gè)
位為 0 那么這個(gè)對(duì)應(yīng)的 IO 口不選定,這個(gè)位為 1 對(duì)應(yīng)的 IO 口選定。如果多個(gè) IO 口,他們都
是對(duì)應(yīng)同一個(gè) GPIOx,那么我們可以通過|(或)的方式同時(shí)初始化多個(gè) IO 口。這樣操作的前
提是,他們的 Mode,Speed,Pull 和 Alternate 參數(shù)值相同,因?yàn)檫@些參數(shù)并不能一次定義多種。
所以初始化多個(gè)具有相同配置的 IO 口的方式可以是如下:
GPIO_InitTypeDef GPIO_Initure;
GPIO_Initure.Pin=GPIO_PIN_9| GPIO_PIN_10| GPIO_PIN_11; //PA9,PA10,PA11
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //復(fù)用推挽輸出
GPIO_Initure.Pull=GPIO_PULLUP;
//上拉
GPIO_Initure.Speed=GPIO_SPEED_FAST;
//高速
GPIO_Initure.Alternate=GPIO_AF7_USART1; //復(fù)用為 USART1
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 PA9 ,PA10,PA11
對(duì)于那些參數(shù)可以通過|(或)的方式連接,這既有章可循,同時(shí)也靠大家在開發(fā)過程中不斷積累。
大家會(huì)覺得上面講解有點(diǎn)麻煩,每次要去查找 assert_param()這個(gè)函數(shù)去尋找,那么有沒有
更好的辦法呢?大家可以打開 GPIO_InitTypeDef 結(jié)構(gòu)體定義:
typedef struct
{
uint32_t Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
uint32_t Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIO_mode_define */
uint32_t Pull; /*!< Specifies the Pull-up or Pull-Down activation for the selected pins.
This parameter can be a value of @ref GPIO_pull_define */
uint32_t Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIO_speed_define */
uint32_t Alternate; /*!< Peripheral to be connected to the selected pins.
This parameter can be a value of @ref GPIO_Alternate_function_selection */
}GPIO_InitTypeDef;
從上圖的結(jié)構(gòu)體成員后面的注釋我們可以看出 Pin 的意思是“Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define”。
從這段注釋可以看出 Pin 的取值需要參考注釋 GPIO_pins_define,大家可以在 MDK 中搜索注釋
GPIO_pins_define,就可以找到上面我們提到的 Pin 的取值范圍宏定義。如果要確定詳細(xì)的信息
我們就得去查看手冊(cè)了。對(duì)于去查看手冊(cè)的哪個(gè)地方,你可以在函數(shù) HAL_GPIO_Init ()的函數(shù)
體中搜索 Pin 關(guān)鍵字,然后查看庫函數(shù)設(shè)置 Pin 是設(shè)置的哪個(gè)寄存器的哪個(gè)位,然后去中文參
考手冊(cè)查看該寄存器相應(yīng)位的定義以及前后文的描述。
這一節(jié)我們就講解到這里,希望能對(duì)大家的開發(fā)有幫助。
匯付天下pos機(jī)怎樣
匯付天下pos機(jī)比較安全。
匯付天下有央行頒橘冊(cè)發(fā)的支付牌照,所以是正規(guī)的機(jī)構(gòu),旗下的pos收單業(yè)務(wù),只要是通過正常渠道購買的pos機(jī),基本就是安全的,但是注意市面上一些代理,有些是二清機(jī),存在一定風(fēng)險(xiǎn)。
匯付天下的業(yè)務(wù)主要覆蓋兩大板塊,即支付服務(wù)和金融科技服務(wù)。 支付服務(wù) 我們?yōu)閿?shù)百萬小微商戶及垂直行業(yè)的公司譽(yù)賣提供各種支付服務(wù)。我們的解決方案能夠?yàn)榭蛻籼峁o縫、便捷及安全的支付方式。我們的服務(wù)包括 POS 、互聯(lián)網(wǎng)支付、移動(dòng)支付、 移動(dòng)POS 及跨境支付服務(wù)。
擴(kuò)展資料:
機(jī)型慶伍逗分類:
1、手持POS機(jī)。體積較小,移動(dòng)方便,能以單鍵快速操作,不必死記及輸入多位貨號(hào)。國內(nèi)手持POS機(jī)品牌有:拉卡拉、微付通、快錢等。
2、臺(tái)式POS機(jī)。體積較手持POS機(jī)大,功能比手持POS機(jī)齊全。國內(nèi)臺(tái)式POS機(jī)
3、移動(dòng)手機(jī)POS機(jī):按操作方式分類分為手機(jī)外置設(shè)備刷卡機(jī)和手機(jī)專用pos機(jī)。
onu設(shè)備介紹?
朋友雖然不知道160A是華為那款設(shè)備,但我是電信搞設(shè)備維護(hù)的,我們這邊的onu型號(hào)是5606t,一般onu上第一層是主控板,二層是業(yè)務(wù)板或語音板,支持混插,onu的主要作用是管理大樓內(nèi)所有用戶的adsl和普話,他的上層是olt設(shè)備,onu上主要是對(duì)adsl帶寬的配置和限速和語音的分離,減少長距離對(duì)adsl線路的影響,一般onu上業(yè)務(wù)板豐富,可以支持pos機(jī)和窄帶撥號(hào)。
希望對(duì)你有幫助。以上就是關(guān)于pos機(jī)方案開發(fā)板,探索者 STM32F407 開發(fā)板資料連載第四章 F4 開發(fā)基礎(chǔ)知識(shí)入門的知識(shí),后面我們會(huì)繼續(xù)為大家整理關(guān)于pos機(jī)方案開發(fā)板的知識(shí),希望能夠幫助到大家!
