pos機(jī)方案開發(fā)板,探索者 STM32F407 開發(fā)板資料連載第四章 F4 開發(fā)基礎(chǔ)知識(shí)入門

 新聞資訊  |   2023-03-19 07:49  |  投稿人:pos機(jī)之家

網(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í),讓我們一起來看下吧!

本文目錄一覽:

1、pos機(jī)方案開發(fā)板

2、匯付天下pos機(jī)怎樣

3、onu設(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í),希望能夠幫助到大家!

轉(zhuǎn)發(fā)請(qǐng)帶上網(wǎng)址:http://www.shineka.com/news/10042.html

你可能會(huì)喜歡:

版權(quán)聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻(xiàn),該文觀點(diǎn)僅代表作者本人。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權(quán)/違法違規(guī)的內(nèi)容, 請(qǐng)發(fā)送郵件至 babsan@163.com 舉報(bào),一經(jīng)查實(shí),本站將立刻刪除。