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

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

網(wǎng)上有很多關(guān)于pos機方案開發(fā)板,探索者 STM32F407 開發(fā)板資料連載第四章 F4 開發(fā)基礎(chǔ)知識入門的知識,也有很多人為大家解答關(guān)于pos機方案開發(fā)板的問題,今天pos機之家(www.afbey.com)為大家整理了關(guān)于這方面的知識,讓我們一起來看下吧!

本文目錄一覽:

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

2、匯付天下pos機怎樣

3、onu設(shè)備介紹?

pos機方案開發(fā)板

1)實驗平臺:探索者 STM32F407 開發(fā)板

2)摘自《STM32F4 開發(fā)指南(HAL 庫版)》關(guān)注官方微信號公眾號,獲取更多資料:正點原子

第四章 STM32F4 開發(fā)基礎(chǔ)知識入門

這一章,我們將著重 STM32 開發(fā)的一些基礎(chǔ)知識,讓大家對 STM32 開發(fā)有一個初步的了

解,為后面 STM32 的學習做一個鋪墊,方便后面的學習。這一章的內(nèi)容大家第一次看的時候

可以只了解一個大概,后面需要用到這方面的知識的時候再回過頭來仔細看看。這章我們分 7

個小結(jié),

·4.1 MDK 下 C 語言基礎(chǔ)復習

·4.2 STM32F4 系統(tǒng)架構(gòu)

·4.3 STM32F4 時鐘系統(tǒng)

·4.4 IO 引腳復用器和映射

·4.5 STM32F4 NVIC 中斷優(yōu)先級管理

·4.6 MDK 中寄存器地址名稱映射分析

·4.7 MDKHAL 庫快速開發(fā)技巧

4.1 MDK 下 C 語言基礎(chǔ)復習

這一節(jié)我們主要講解一下 C 語言基礎(chǔ)知識。C 語言知識博大精深,也不是我們?nèi)詢烧Z能

講解清楚,同時我們相信學 STM32F4 這種級別 MCU 的用戶,C 語言基礎(chǔ)應(yīng)該都是沒問題的。我

們這里主要是簡單的復習一下幾個 C 語言基礎(chǔ)知識點,引導那些 C 語言基礎(chǔ)知識不是很扎實的

用戶能夠快速開發(fā) STM32 程序。同時希望這些用戶能夠多去復習一下 C 語言基礎(chǔ)知識,C 語言

畢竟是單片機開發(fā)中的必備基礎(chǔ)知識。對于 C 語言基礎(chǔ)比較扎實的用戶,這部分知識可以忽略

不看。

4.1.1 位操作

C 語言位操作相信學過 C 語言的人都不陌生了,簡而言之,就是對基本類型變量可以在位級

別進行操作。這節(jié)的內(nèi)容很多朋友都應(yīng)該很熟練了,我這里也就點到為止,不深入探討。下面

我們先講解幾種位操作符,然后講解位操作使用技巧。

C 語言支持如下 6 種位操作

表 4.1.1 16 種位操作

這些與或非,取反,異或,右移,左移這些到底怎么回事,這里我們就不多做詳細,相信

大家學 C 語言的時候都學習過了。如果不懂的話,可以百度一下,非常多的知識講解這些操作

符。下面我們想著重講解位操作在單片機開發(fā)中的一些實用技巧。

1) 不改變其他位的值的狀況下,對某幾個位進行設(shè)值。

這個場景單片機開發(fā)中經(jīng)常使用,方法就是先對需要設(shè)置的位用&操作符進行清零操作,

然后用|操作符設(shè)值。比如我要改變 GPIOA-> BSRRL 的狀態(tài),可以先對寄存器的值進行&

清零操作

GPIOA-> BSRRL &=0XFF0F; //將第 4-7 位清 0

然后再與需要設(shè)置的值進行|或運算

GPIOA-> BSRRL |=0X0040;//設(shè)置相應(yīng)位的值,不改變其他位的值2) 移位操作提高代碼的可讀性。

移位操作在單片機開發(fā)中也非常重要,我們來看看下面一行代碼

GPIOx->ODR = (((uint32_t)0x01) << pinpos);

這個操作就是將 ODR 寄存器的第 pinpos 位設(shè)置為 1,為什么要通過左移而不是直接設(shè)

置一個固定的值呢?其實,這是為了提高代碼的可讀性以及可重用性。這行代碼可以

很直觀明了的知道,是將第 pinpos 位設(shè)置為 1。如果你寫成

GPIOx->ODR =0x0030;

這樣的代碼就不好看也不好重用了。

3) ~取反操作使用技巧

SR 寄存器的每一位都代表一個狀態(tài),某個時刻我們希望去設(shè)置某一位的值為 0,同時

其他位都保留為 1,簡單的作法是直接給寄存器設(shè)置一個值:

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)

看這個應(yīng)該很容易明白,可以直接從宏定義中看出 TIM_FLAG_Update 就是設(shè)置的第 0 位了,

可讀性非常強。

4.1.2 define 宏定義

define 是 C 語言中的預處理命令,它用于宏定義,可以提高源代碼的可讀性,為編程提供

方便。常見的格式:

#define 標識符 字符串

“標識符”為所定義的宏名。“字符串”可以是常數(shù)、表達式、格式串等。例如:

#define PLL_M 8

定義標識符 PLL_M 的值為 8。

至于 define 宏定義的其他一些知識,比如宏定義帶參數(shù)這里我們就不多講解。

4.1.3 ifdef 條件編譯

單片機程序開發(fā)過程中,經(jīng)常會遇到一種情況,當滿足某條件時對一組語句進行編譯,而

當條件不滿足時則編譯另一組語句。條件編譯命令最常見的形式為:

#ifdef 標識符

程序段 1

#else

程序段 2

#endif

它的作用是:當標識符已經(jīng)被定義過(一般是用#define 命令定義),則對程序段 1 進行編譯,

否則編譯程序段 2。 其中#else 部分也可以沒有,即:

#ifdef

程序段 1

#endif

這個條件編譯在MDK里面是用得很多的,在stm32f4xx.h這個頭文件中經(jīng)常會看到這樣的語句:

#if defined (STM32F40_41xxx)

STM32F40x 系列和 STM32F41x 系列芯片需要的一些變量定義

#end

而(STM32F40_41xxx 則是我們通過#define 來定義的。條件編譯也是 c 語言的基礎(chǔ)知識,這里

也就點到為止吧。

4.1.4 extern 變量申明

C 語言中 extern 可以置于變量或者函數(shù)前,以表示變量或者函數(shù)的定義在別的文件中,提示編

譯器遇到此變量和函數(shù)時在其他模塊中尋找其定義。這里面要注意,對于 extern 申明變量可以多

次,但定義只有一次。在我們的代碼中你會看到看到這樣的語句:

extern u16 USART_RX_STA;

這個語句是申明 USART_RX_STA 變量在其他文件中已經(jīng)定義了,在這里要使用到。所以,你肯定

可以找到在某個地方有變量定義的語句:

u16 USART_RX_STA;

的出現(xiàn)。下面通過一個例子說明一下使用方法。

在 Main.c 定義的全局變量 id,id 的初始化都是在 Main.c 里面進行的。

Main.c 文件

u8 id;//定義只允許一次

main()

{

id=1;

printf("d%",id);//id=1

test();

printf("d%",id);//id=2

}

但是我們希望在main.c的 changeId(void)函數(shù)中使用變量id,這個時候我們就需要在main.c

里面去申明變量 id 是外部定義的了,因為如果不申明,變量 id 的作用域是到不了 main.c 文件

中??聪旅?main.c 中的代碼:

extern u8 id;//申明變量 id 是在外部定義的,申明可以在很多個文件中進行

void test(void){

id=2;

}

在 main.c 中申明變量 id 在外部定義,然后在 main.c 中就可以使用變量 id 了。

對于 extern 申明函數(shù)在外部定義的應(yīng)用,這里我們就不多講解了。

4.1.5 typedef 類型別名

typedef 用于為現(xiàn)有類型創(chuàng)建一個新的名字,或稱為類型別名,用來簡化變量的定義。

typedef 在 MDK 用得最多的就是定義結(jié)構(gòu)體的類型別名和枚舉類型了。

struct _GPIO

{

__IO uint32_t MODER;

__IO uint32_t OTYPER;

};

定義了一個結(jié)構(gòu)體 GPIO,這樣我們定義變量的方式為:

struct _GPIO GPIOA;//定義結(jié)構(gòu)體變量 GPIOA

但是這樣很繁瑣,MDK 中有很多這樣的結(jié)構(gòu)體變量需要定義。這里我們可以為結(jié)體定義一個別

名 GPIO_TypeDef,這樣我們就可以在其他地方通過別名 GPIO_TypeDef 來定義結(jié)構(gòu)體變量了。

方法如下:

typedef struct

{

__IO uint32_t MODER;

__IO uint32_t OTYPER;

} GPIO_TypeDef;

Typedef 為結(jié)構(gòu)體定義一個別名 GPIO_TypeDef,這樣我們可以通過 GPIO_TypeDef 來定義結(jié)構(gòu)體

變量:

GPIO_TypeDef _GPIOA,_GPIOB;

這里的 GPIO_TypeDef 就跟 struct _GPIO 是等同的作用了。 這樣是不是方便很多?

4.1.6 結(jié)構(gòu)體

經(jīng)常很多用戶提到,他們對結(jié)構(gòu)體使用不是很熟悉,但是 MDK 中太多地方使用結(jié)構(gòu)體以及

結(jié)構(gòu)體指針,這讓他們一下子摸不著頭腦,學習 STM32 的積極性大大降低,其實結(jié)構(gòu)體并不是

那么復雜,這里我們稍微提一下結(jié)構(gòu)體的一些知識,還有一些知識我們會在下一節(jié)的“寄存器

地址名稱映射分析”中講到一些。

聲明結(jié)構(gòu)體類型:

Struct 結(jié)構(gòu)體名{

成員列表;

}變量名列表;

例如:

Struct U_TYPE {

Int BaudRate

Int WordLength;

}usart1,usart2;

在結(jié)構(gòu)體申明的時候可以定義變量,也可以申明之后定義,方法是:

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)體指針成員變量引用方法是通過“->”符號實現(xiàn),比如要訪問 usart3 結(jié)構(gòu)體指針指向的結(jié)

構(gòu)體的成員變量 BaudRate,方法是:

Usart3->BaudRate;

上面講解了結(jié)構(gòu)體和結(jié)構(gòu)體指針的一些知識,其他的什么初始化這里就不多講解了。講到這里,

有人會問,結(jié)構(gòu)體到底有什么作用呢?為什么要使用結(jié)構(gòu)體呢?下面我們將簡單的通過一個實

例回答一下這個問題。

在我們單片機程序開發(fā)過程中,經(jīng)常會遇到要初始化一個外設(shè)比如串口,它的初始化狀態(tài)

是由幾個屬性來決定的,比如串口號,波特率,極性,以及模式等。對于這種情況,在我們沒

有學習結(jié)構(gòu)體的時候,我們一般的方法是:

void USART_Init(u8 usartx,u32 u32 BaudRate,u8 parity,u8 mode);

這種方式是有效的同時在一定場合是可取的。但是試想,如果有一天,我們希望往這個函數(shù)里

面再傳入一個參數(shù),那么勢必我們需要修改這個函數(shù)的定義,重新加入字長這個入口參數(shù)。于

是我們的定義被修改為:

void USART_Init (u8 usartx,u32 BaudRate, u8 parity,u8 mode,u8 wordlength );

但是如果我們這個函數(shù)的入口參數(shù)是隨著開發(fā)不斷的增多,那么是不是我們就要不斷的修改函

數(shù)的定義呢?這是不是給我們開發(fā)帶來很多的麻煩?那又怎樣解決這種情況呢?

這樣如果我們使用到結(jié)構(gòu)體就能解決這個問題了。我們可以在不改變?nèi)肟趨?shù)的情況下,

只需要改變結(jié)構(gòu)體的成員變量,就可以達到上面改變?nèi)肟趨?shù)的目的。

結(jié)構(gòu)體就是將多個變量組合為一個有機的整體。上面的函數(shù),BaudRate,wordlength,

Parity,mode,wordlength 這些參數(shù),他們對于串口而言,是一個有機整體,都是來設(shè)置串口參

數(shù)的,所以我們可以將他們通過定義一個結(jié)構(gòu)體來組合在一個。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;

于是,我們在初始化串口的時候入口參數(shù)就可以是 USART_InitTypeDef 類型的變量或者指針變

量了,MDK 中是這樣做的:

void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);

這樣,任何時候,我們只需要修改結(jié)構(gòu)體成員變量,往結(jié)構(gòu)體中間加入新的成員變量,而不需

要修改函數(shù)定義就可以達到修改入口參數(shù)同樣的目的了。這樣的好處是不用修改任何函數(shù)定義

就可以達到增加變量的目的。

理解了結(jié)構(gòu)體在這個例子中間的作用嗎?在以后的開發(fā)過程中,如果你的變量定義過多,

如果某幾個變量是用來描述某一個對象,你可以考慮將這些變量定義在結(jié)構(gòu)體中,這樣也許可

以提高你的代碼的可讀性。

使用結(jié)構(gòu)體組合參數(shù),可以提高代碼的可讀性,不會覺得變量定義混亂。當然結(jié)構(gòu)體的作

用就遠遠不止這個了,同時,MDK 中用結(jié)構(gòu)體來定義外設(shè)也不僅僅只是這個作用,這里我們只

是舉一個例子,通過最常用的場景,讓大家理解結(jié)構(gòu)體的一個作用而已。后面一節(jié)我們還會講

解結(jié)構(gòu)體的一些其他知識。

4.2 STM32F4 總線架構(gòu)

STM32F4 的總線架構(gòu)比 51 單片機就要強大很多了。STM32F4 總線架構(gòu)的知識可以在

《STM32F4XX 中文參考手冊》第二章有講解,這里我們也把這一部分知識抽取出來講解,是

為了大家在學習 STM32F4 之前對系統(tǒng)架構(gòu)有一個初步的了解。這里的內(nèi)容基本也是從中文參

考手冊中參考過來的,讓大家能通過我們手冊也了解到,免除了到處找資料的麻煩吧。如果需

要詳細深入的了解 STM32 的系統(tǒng)架構(gòu),還需要多看看《STM32F4XX 中文參考手冊》或者在網(wǎng)

上搜索其他相關(guān)資料學習。

我們這里所講的 STM32F4 系統(tǒng)架構(gòu)主要針對的 STM32F407 系列芯片。首先我們看看

STM32 的總線架構(gòu)圖:

圖 4.2.1 STM32F407 系統(tǒng)架構(gòu)圖

主系統(tǒng)由 32 位多層 AHB 總線矩陣構(gòu)成??偩€矩陣用于主控總線之間的訪問仲裁管理。仲裁采

取循環(huán)調(diào)度算法??偩€矩陣可實現(xiàn)以下部分互聯(lián):

八條主控總線是:

Cortex-M4 內(nèi)核 I 總線, D 總線和 S 總線;

DMA1 存儲器總線, DMA2 存儲器總線;

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

下面我們具體講解一下圖中幾個總線的知識。

① I 總線(S0):此總線用于將 Cortex-M4 內(nèi)核的指令總線連接到總線矩陣。內(nèi)核通過此總

線獲取指令。此總線訪問的對象是包括代碼的存儲器。

② D 總線(S1):此總線用于將 Cortex-M4 數(shù)據(jù)總線和 64KB CCM 數(shù)據(jù) RAM 連接到總線矩

陣。內(nèi)核通過此總線進行立即數(shù)加載和調(diào)試訪問。

③ S 總線(S2):此總線用于將 Cortex-M4 內(nèi)核的系統(tǒng)總線連接到總線矩陣。此總線用于訪

問位于外設(shè)或 SRAM 中的數(shù)據(jù)。

④ DMA 存儲器總線(S3,S4):此總線用于將 DMA 存儲器總線主接口連接到總線矩陣。

DMA 通過此總線來執(zhí)行存儲器數(shù)據(jù)的傳入和傳出。

⑤ DMA 外設(shè)總線:此總線用于將 DMA 外設(shè)主總線接口連接到總線矩陣。DMA 通過此

總線訪問 AHB 外設(shè)或執(zhí)行存儲器之間的數(shù)據(jù)傳輸。

⑥ 以太網(wǎng) DMA 總線:此總線用于將以太網(wǎng) DMA 主接口連接到總線矩陣。以太網(wǎng) DMA

通過此總線向存儲器存取數(shù)據(jù)。

⑦ USB OTG HS DMA 總線(S7):此總線用于將 USB OTG HS DMA 主接口連接到總線矩

陣。USB OTG HS DMA 通過此總線向存儲器加載/存儲數(shù)據(jù)。

對于系統(tǒng)架構(gòu)的知識,在剛開始學習 STM32 的時候只需要一個大概的了解,大致知道是個

什么情況即可。對于尋址之類的知識,這里就不做深入的講解,中文參考手冊都有很詳細的講

解。

4.3 STM32F4 時鐘系統(tǒng)

STM32F4 時鐘系統(tǒng)的知識在《STM32F4 中文參考手冊》第六章復位和時鐘控制章節(jié)有非

常詳細的講解,網(wǎng)上關(guān)于時鐘系統(tǒng)的講解也基本都是參考的這里,講不出啥特色,不過作為一

個完整的參考手冊,我們必然要提到時鐘系統(tǒng)的知識。這些知識也不是什么原創(chuàng),純粹根據(jù)官

方提供的中文參考手冊和自己的應(yīng)用心得來總結(jié)的,如有不合理之處望大家諒解。

這部分內(nèi)容我們分 3 個小節(jié)來講解:

·4.3.1 STM32F4 時鐘樹概述

·4.3.2 STM32F4 時鐘初始化配置

·4.3.3 STM32F4 時鐘使能和配置

4.3.1 STM32F4 時鐘樹概述

眾所周知,時鐘系統(tǒng)是 CPU 的脈搏,就像人的心跳一樣。所以時鐘系統(tǒng)的重要性就不言而

喻了。 STM32F4 的時鐘系統(tǒng)比較復雜,不像簡單的 51 單片機一個系統(tǒng)時鐘就可以解決一切。

于是有人要問,采用一個系統(tǒng)時鐘不是很簡單嗎?為什么 STM32 要有多個時鐘源呢? 因為首

先 STM32 本身非常復雜,外設(shè)非常的多,但是并不是所有外設(shè)都需要系統(tǒng)時鐘這么高的頻率,

比如看門狗以及 RTC 只需要幾十 k 的時鐘即可。同一個電路,時鐘越快功耗越大,同時抗電磁

干擾能力也會越弱,所以對于較為復雜的 MCU 一般都是采取多時鐘源的方法來解決這些問題。

首先讓我們來看看 STM32F4 的時鐘系統(tǒng)圖:

圖 4.3.1.1STM32 時鐘系統(tǒng)圖

在 STM32F4 中,有 5 個最重要的時鐘源,為 HSI、HSE、LSI、LSE、PLL。其中 PLL 實

際是分為兩個時鐘源,分別為主 PLL 和專用 PLL。從時鐘頻率來分可以分為高速時鐘源和低速

時鐘源,在這 5 個中 HSI,HSE 以及 PLL 是高速時鐘,LSI 和 LSE 是低速時鐘。從來源可分為

外部時鐘源和內(nèi)部時鐘源,外部時鐘源就是從外部通過接晶振的方式獲取時鐘源,其中 HSE 和

LSE 是外部時鐘源,其他的是內(nèi)部時鐘源。下面我們看看 STM32F4 的這 5 個時鐘源,我們講

解順序是按圖中紅圈標示的順序:①、LSI 是低速內(nèi)部時鐘,RC 振蕩器,頻率為 32kHz 左右。供獨立看門狗和自動喚醒單元使用。

②、LSE 是低速外部時鐘,接頻率為 32.768kHz 的石英晶體。這個主要是 RTC 的時鐘源。

③、HSE 是高速外部時鐘,可接石英/陶瓷諧振器,或者接外部時鐘源,頻率范圍為 4MHz~26MHz。

我們的開發(fā)板接的是 8M 的晶振。HSE 也可以直接做為系統(tǒng)時鐘或者 PLL 輸入。

④、HSI 是高速內(nèi)部時鐘,RC 振蕩器,頻率為 16MHz??梢灾苯幼鳛橄到y(tǒng)時鐘或者用作 PLL

輸入。

⑤、PLL 為鎖相環(huán)倍頻輸出。STM32F4 有兩個 PLL:

1) 主 PLL(PLL)由 HSE 或者 HSI 提供時鐘信號,并具有兩個不同的輸出時鐘。

第一個輸出 PLLP 用于生成高速的系統(tǒng)時鐘(最高 168MHz)

第二個輸出 PLLQ 用于生成 USB OTG FS 的時鐘(48MHz),隨機數(shù)發(fā)生器的時鐘和 SDIO

時鐘。

2)專用 PLL(PLLI2S)用于生成精確時鐘,從而在 I2S 接口實現(xiàn)高品質(zhì)音頻性能。

這里我們著重看看主 PLL 時鐘第一個高速時鐘輸出 PLLP 的計算方法。圖 4.3.1.2 是主 PLL 的

時鐘圖。


圖 4.3.1.2 STM32F4 主 PLL 時鐘圖

從圖 4.3.1.2 可以看出。主 PLL 時鐘的時鐘源要先經(jīng)過一個分頻系數(shù)為 M 的分頻器,然后經(jīng)過

倍頻系數(shù)為 N 的倍頻器出來之后的時候還需要經(jīng)過一個分頻系數(shù)為 P(第一個輸出 PLLP)或

者 Q(第二個輸出 PLLQ)的分頻器分頻之后,最后才生成最終的主 PLL 時鐘。

例如我們的外部晶振選擇 8MHz。同時我們設(shè)置相應(yīng)的分頻器 M=8,倍頻器倍頻系數(shù) N=336,

分頻器分頻系數(shù) P=2,那么主 PLL 生成的第一個輸出高速時鐘 PLLP 為:

PLL=8MHz * N/ (M*P)=8MHz* 336 /(8*2) = 168MHz

如果我們選擇HSE為PLL時鐘源,同時SYSCLK時鐘源為PLL,那么SYSCLK時鐘為 168MHz。

這對于我們后面的實驗都是采用這樣的配置。

上面我們簡要概括了 STM32 的時鐘源,那么這 5 個時鐘源是怎么給各個外設(shè)以及系統(tǒng)提

供時鐘的呢?這里我們選擇一些比較常用的時鐘知識來講解。

圖 4.3.1.1 中我們用 A~G 標示我們要講解的地方。

A.

這里是看門狗時鐘輸入。從圖中可以看出,看門狗時鐘源只能是低速的 LSI 時鐘。

B.

這里是 RTC 時鐘源,從圖上可以看出,RTC 的時鐘源可以選擇 LSI,LSE,以及

HSE 分頻后的時鐘,HSE 分頻系數(shù)為 2~31。

C.

這里是 STM32F4 輸出時鐘 MCO1 和 MCO2。MCO1 是向芯片的 PA8 引腳輸出時

鐘。它有四個時鐘來源分別為:HSI,LSE,HSE 和 PLL 時鐘。MCO2 是向芯片的PC9 輸出時鐘,它同樣有四個時鐘來源分別為:HSE,PLL,SYSCLK 以及 PLLI2S

時鐘。MCO 輸出時鐘頻率最大不超過 100MHz。

D.

這里是系統(tǒng)時鐘。從圖 4.3.1 可以看出,SYSCLK 系統(tǒng)時鐘來源有三個方面:

HSI,HSE 和 PLL。在我們實際應(yīng)用中,因為對時鐘速度要求都比較高我們才會選

用 STM32F4 這種級別的處理器,所以一般情況下,都是采用 PLL 作為 SYSCLK

時鐘源。根據(jù)前面的計算公式,大家就可以算出你的系統(tǒng)的 SYSCLK 是多少。

E.

這里我們指的是以太網(wǎng) PTP 時鐘,AHB 時鐘,APB2 高速時鐘,APB1 低速時鐘。

這些時鐘都是來源于 SYSCLK 系統(tǒng)時鐘。其中以太網(wǎng) PTP 時鐘是使用系統(tǒng)時鐘。

AHB,APB2 和 APB1 時鐘是經(jīng)過 SYSCLK 時鐘分頻得來。這里大家記住,AHB

最大時鐘為168MHz, APB2高速時鐘最大頻率為84MHz,而APB1低速時鐘最大頻

率為 42MHz。

F.

這里是指 I2S 時鐘源。從圖 4.3.1 可以看出,I2S 的時鐘源來源于 PLLI2S 或者映

射到 I2S_CKIN 引腳的外部時鐘。I2S 出于音質(zhì)的考慮,對時鐘精度要求很高。探

索者 STM32F4 開發(fā)板使用的是內(nèi)部 PLLI2SCLK。

G.

這是 STM32F4 內(nèi)部以太網(wǎng) MAC 時鐘的來源。對于 MII 接口來說,必須向外部

PHY 芯片提供 25Mhz 的時鐘,這個時鐘,可以由 PHY 芯片外接晶振,或者使用

STM32F4 的 MCO 輸 出 來 提 供 。 然 后 , PHY 芯 片 再 給 STM32F4 提 供

ETH_MII_TX_CLK 和 ETH_MII_RX_CLK 時鐘。對于 RMII 接口來說,外部必須

提供 50Mhz 的時鐘驅(qū)動 PHY 和 STM32F4 的 ETH_RMII_REF_CLK,這個 50Mhz

時鐘可以來自 PHY、有源晶振或者 STM32F4 的 MCO。我們的開發(fā)板使用的是

RMII 接 口 , 使 用 PHY 芯 片 提 供 50Mhz 時 鐘 驅(qū) 動 STM32F4 的

ETH_RMII_REF_CLK。

H.

這里是指外部 PHY 提供的 USB OTG HS(60MHZ)時鐘。

這里還需要說明一下,Cortex 系統(tǒng)定時器 Systick 的時鐘源可以是 AHB 時鐘 HCLK 或

HCLK 的 8 分頻。具體配置請參考 Systick 定時器配置,我們后面會在 5.1 小節(jié)講解 delay 文件

夾代碼的時候講解。

在以上的時鐘輸出中,有很多是帶使能控制的,例如 AHB 總線時鐘、內(nèi)核時鐘、各種 APB1

外設(shè)、APB2 外設(shè)等等。當需要使用某模塊時,記得一定要先使能對應(yīng)的時鐘。后面我們講解

實例的時候會講解到時鐘使能的方法。

4.3.2 STM32F4 時鐘初始化配置

上一小節(jié)我們對 STM32F407 時鐘樹進行了詳細講解,接下來我們來講解通過 STM32F4 的

HAL 庫進行 STM32F407 時鐘系統(tǒng)配置步驟。實際上,STM32F4 的時鐘系統(tǒng)配置也可以通過圖

形化配置工具 STM32CubeMX 來配置生成,這里我們講解初始化代碼,是為了讓大家對 STM32

時鐘系統(tǒng)有更加清晰的理解。圖形化配置工具 STM32CubeMX 在另外的 Cube MX 教程,大家

可以對比參考學習。

前面我們講解過,在系統(tǒng)啟動之后,程序會先執(zhí)行 HAL 庫定義的 SystemInit 函數(shù),進行系

統(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 主要做了如下四個方面工作:

1) FPU 設(shè)置

2) 復位 RCC 時鐘配置為默認復位值(默認開始了 HIS)

3) 外部存儲器配置

4) 中斷向量表地址配置

HAL 庫的 SystemInit 函數(shù)并沒有像標準庫的 SystemInit 函數(shù)一樣進行時鐘的初始化配置。HAL

庫的 SystemInit 函數(shù)除了打開 HSI 之外,沒有任何時鐘相關(guān)配置,所以使用 HAL 庫我們必須編

寫自己的時鐘配置函數(shù)。首先我們打開工程模板看看我們在工程 SYSTEM 分組下面定義的 sys.c

文件中的時鐘初始化函數(shù) Stm32_Clock_Init 的內(nèi)容:

//時鐘系統(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)時鐘頻率//Fusb:USB,SDIO,RNG 等的時鐘頻率//Fs:PLL 輸入時鐘頻率,可以是 HSI,HSE 等. //plln:主 PLL 倍頻系數(shù)(PLL 倍頻),取值范圍:64~432.//pllm:主 PLL 和音頻 PLL 分頻系數(shù)(PLL 之前的分頻),取值范圍:2~63.//pllp:系統(tǒng)時鐘的主 PLL 分頻系數(shù)(PLL 之后的分頻),取值范圍:2,4,6,8.(僅限這 4 個值!)//pllq:USB/SDIO/隨機數(shù)產(chǎn)生器等的主 PLL 分頻系數(shù)(PLL 之后的分頻),取值范圍:2~15.//外部晶振為 8M 的時候,推薦值: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è)置用來設(shè)置調(diào)壓器輸出電壓級別,以便在器件未以最大頻率工作 //時使性能與功耗實現(xiàn)平衡。 __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);//設(shè)置調(diào)壓器輸出電壓級別 1 RCC_OscInitStructure.OscillatorType=RCC_OSCILLATORTYPE_HSE; //時鐘源為 HSE RCC_OscInitStructure.HSEState=RCC_HSE_ON; //打開 HSE RCC_OscInitStructure.PLL.PLLState=RCC_PLL_ON;//打開 PLL RCC_OscInitStructure.PLL.PLLSource=RCC_PLLSOURCE_HSE;//PLL 時鐘源選擇 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)時鐘的主 PLL 分頻系數(shù)(PLL 之后的分頻),取值范圍:2,4,6,8.(僅限這 4 個值!) RCC_OscInitStructure.PLL.PLLQ=pllq; //USB/SDIO/隨機數(shù)產(chǎn)生器等的主 PLL 分頻系數(shù)(PLL 之后的分頻),取值范圍:2~15. ret=HAL_RCC_OscConfig(&RCC_OscInitStructure);//初始化 if(ret!=HAL_OK) while(1); //選中 PLL 作為系統(tǒng)時鐘源并且配置 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)時鐘時鐘源為 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è)置 FLASH 延時周期為 5WS,也就是 6 個 CPU 周期。if(ret!=HAL_OK) while(1);//STM32F405x/407x/415x/417x Z 版本的器件支持預取功能if (HAL_GetREVID() == 0x1001){__HAL_FLASH_PREFETCH_BUFFER_ENABLE(); //使能 flash 預取}}

從函數(shù)注釋可知,函數(shù) Stm32_Clock_Init 的作用是進行時鐘系統(tǒng)配置,除了配置 PLL 相關(guān)

參數(shù)確定 SYSCLK 值之外,還配置了 AHB,APB1 和 APB2 的分頻系數(shù),也就是確定了 HCLK,

PCLK1 和 PCLK2 的時鐘值。我們首先來看看使用 HAL 庫配置 STM32F407 時鐘系統(tǒng)的一般步

驟:

1) 使能 PWR 時鐘:調(diào)用函數(shù)__HAL_RCC_PWR_CLK_ENABLE()。

2) 設(shè)置調(diào)壓器輸出電壓級別:調(diào)用函數(shù)__HAL_PWR_VOLTAGESCALING_CONFIG()。

3) 選擇是否開啟 Over-Driver 功能:調(diào)用函數(shù) HAL_PWREx_EnableOverDrive()。

4) 配置時鐘源相關(guān)參數(shù):調(diào)用函數(shù) HAL_RCC_OscConfig()。

5) 配置系統(tǒng)時鐘源以及 AHB,APB1 和 APB2 的分頻系數(shù):調(diào)用函數(shù) HAL_RCC_ClockConfig()。

步驟 2 和 3,具有一定的關(guān)聯(lián)性,我們放在后面講解。對于步驟 1 之所以要使能 PWR 時鐘,是

因為后面的步驟設(shè)置調(diào)節(jié)器輸出電壓級別以及開啟 Over-Driver 功能都是電源控制相關(guān)配置,所

以必須開啟 PWR 時鐘。接下來我們先著重講解步驟 4 和步驟 5 的內(nèi)容,這也是時鐘系統(tǒng)配置

的關(guān)鍵步驟。

對于步驟 4,使用 HAL 來配置時鐘源相關(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ù)只有一個入口參數(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 校準值 uint32_t LSIState; //LSI 狀態(tài) RCC_PLLInitTypeDef PLL; //PLL 配置}RCC_OscInitTypeDef;

對于這個結(jié)構(gòu)體,前面幾個參數(shù)主要是用來選擇配置的振蕩器類型。比如我們要開啟 HSE,

那么我們會設(shè)置 OscillatorType 的值為 RCC_OSCILLATORTYPE_HSE,然后設(shè)置 HSEState 的值

為 RCC_HSE_ON 開啟 HSE。對于其他時鐘源 HSI,LSI 和 LSE,配置方法類似。這個結(jié)構(gòu)體還

有一個很重要的成員變量是 PLL,它是結(jié)構(gòu)體 RCC_PLLInitTypeDef 類型。它的作用是配置 PLL

相關(guān)參數(shù),我們來看看它的定義:

typedef struct{ uint32_t PLLState; //PLL 狀態(tài) uint32_t PLLSource; //PLL 時鐘源 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 時鐘源以及

相關(guān)分頻倍頻參數(shù)。

這個結(jié)構(gòu)體的定義我們就不做過多講解,接下來我們看看我們的時鐘初始化函數(shù)

Stm32_Clock_Init 中的配置內(nèi)容:

RCC_OscInitStructure.OscillatorType=RCC_OSCILLATORTYPE_HSE; //時鐘源為 HSERCC_OscInitStructure.HSEState=RCC_HSE_ON; //打開 HSERCC_OscInitStructure.PLL.PLLState=RCC_PLL_ON; //打開 PLLRCC_OscInitStructure.PLL.PLLSource=RCC_PLLSOURCE_HSE; //PLL 時鐘源為 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 時鐘源,同時選擇 PLL 時鐘源為 HSE,然后把

Stm32_Clock_Init 的 4 個入口參數(shù)直接設(shè)置作為 PLL 的參數(shù) M,N,P 和 Q 的值,這樣就達到了設(shè)

置 PLL 時鐘源相關(guān)參數(shù)的目的。設(shè)置好 PLL 時鐘源參數(shù)之后,也就是確定了 PLL 的時鐘頻率,

接下來我們就需要設(shè)置系統(tǒng)時鐘,以及 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ù)有兩個入口參數(shù),第一個入口參數(shù) RCC_ClkInitStruct 是結(jié)構(gòu)體 RCC_ClkInitTypeDef

指針類型,用來設(shè)置 SYSCLK 時鐘源以及 AHB,APB1 和 APB2 的分頻系數(shù)。第二個入口參數(shù)

FLatency 用來設(shè)置 FLASH 延遲,這個參數(shù)我們放在后面跟步驟 2 和步驟 3 一起講解。

RCC_ClkInitTypeDef 結(jié)構(gòu)體類型定義非常簡單,這里我們就不列出來,我們來看看

Stm32_Clock_Init 函數(shù)中的配置內(nèi)容:

//選中 PLL 作為系統(tǒng)時鐘源并且配置 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)時鐘源 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ù) ClockType 配置說明我們要配置的是 SYSCLK,HCLK,PCLK1 和 PCLK2 四個時鐘。

第二個參數(shù) SYSCLKSource 配置選擇系統(tǒng)時鐘源為 PLL。

第三個參數(shù) AHBCLKDivider 配置 AHB 分頻系數(shù)為 1。

第四個參數(shù) APB1CLKDivider 配置 APB1 分頻系數(shù)為 4。

第五個參數(shù) APB2CLKDivider 配置 APB2 分頻系數(shù)為 2。

根據(jù)我們在主函數(shù)中調(diào)用 Stm32_Clock_Init(336,8,2,7)時候設(shè)置的入口參數(shù)值,我們可以計

算出,PLL 時鐘為 PLLCLK=HSE*N/M*P=8MHz*336/(8*2)=168MHz,同時我們選擇系統(tǒng)時鐘

源 為 PLL , 所 以 系 統(tǒng) 時 鐘 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)鍵時鐘頻率值:

SYSCLK(系統(tǒng)時鐘)

=168MHz

PLL 主時鐘

=168MHz

AHB 總線時鐘(HCLK=SYSCLK/1)

=168MHz

APB1 總線時鐘(PCLK1=HCLK/4)

=42MHz

APB2 總線時鐘(PCLK2=HCLK/2)

=84MHz

時鐘系統(tǒng)配置相關(guān)知識就給大家講解到這里。

4.4 IO 引腳復用器和映射

STM32F4 有很多的內(nèi)置外設(shè),這些外設(shè)的外部引腳都是與 GPIO 復用的。也就是說,一個 GPIO

如果可以復用為內(nèi)置外設(shè)的功能引腳,那么當這個 GPIO 作為內(nèi)置外設(shè)使用的時候,就叫做復用。

這部分知識在《STM32F4 中文參考手冊》第七章和芯片數(shù)據(jù)手冊有詳細的講解哪些 GPIO 管腳是

可以復用為哪些內(nèi)置外設(shè)。

對于本小節(jié)知識,STM32F4 中文參考手冊講解比較詳細,我們同樣會從中抽取重要的知識點

羅列出來。同時,我們會以串口使用為例給大家講解具體的引腳復用的配置。

STM32F4 系列微控制器 IO 引腳通過一個復用器連接到內(nèi)置外設(shè)或模塊。該復用器一次只允

許一個外設(shè)的復用功能(AF)連接到對應(yīng)的 IO 口。這樣可以確保共用同一個 IO 引腳的外設(shè)之

間不會發(fā)生沖突。

每個 IO 引腳都有一個復用器,該復用器采用 16 路復用功能輸入(AF0 到 AF15),可通過

GPIOx_AFRL(針對引腳 0-7)和 GPIOx_AFRH(針對引腳 8-15)寄存器對這些輸入進行配置,每四

位控制一路復用:

1)完成復位后,所有 IO 都會連接到系統(tǒng)的復用功能 0(AF0)。

2)外設(shè)的復用功能映射到 AF1 到 AF13。

3)Cortex-M4 EVENTOUT 映射到 AF15。

復用器示意圖如下圖 4.4.1:

圖 4.4.1 復用器示意圖

接下來,我們簡單說明一下這個圖要如何看,舉個例子,探索者 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

等復用功能輸出,這么多復用功能,如果這些外設(shè)都開啟了,那么對 STM32F1 來說,那就可

能亂套了,外設(shè)之間可互相干擾,但是 STM32F4,由于有復用功能選擇功能,可以讓 PC11 僅

連接到某個特定的外設(shè),因此不存在互相干擾的情況。

上圖 4.4.1 是針對引腳 0-7,對于引腳 8-15,控制寄存器為 GPIOx_AFRH。從圖中可以看出。

當需要使用復用功能的時候,我們配置相應(yīng)的寄存器 GPIOx_AFRL 或者 GPIOx_AFRH,讓對應(yīng)引

腳通過復用器連接到對應(yīng)的復用功能外設(shè)。這里我們列出 GPIOx_AFRL 寄存器的描述,

GPIOx_AFRH 的作用跟 GPIOx_AFRL 類似,只不過 GPIOx_AFRH 控制的是一組 IO 口的高八位,

GPIOx_AFRL 控制的是一組 IO 口的低八位。


圖 4.4.3 GPIOx_AFRL 寄存器位描述

從表中可以看出,32 位寄存器 GPIOx_AFRL 每四個位控制一個 IO 口,所以每個寄存器控制

32/4=8 個 IO 口。寄存器對應(yīng)四位的值配置決定這個 IO 映射到哪個復用功能 AF。

在微控制器完成復位后,所有 IO 口都會連接到系統(tǒng)復用功能 0(AF0)。這里大家需要注意,

對于系統(tǒng)復用功能 AF0,我們將 IO 口連接到 AF0 之后,還要根據(jù)所用功能進行配置:

1) JTAG/SWD:在器件復位之后,會將這些功能引腳指定為專用引腳。也就是說,這些引腳

在復位后默認就是 JTAG/SWD 功能。如果我們要作為 GPIO 來使用,就需要對對應(yīng)的 IO

口復用器進行配置。

2) RTC_REFIN:此引腳在系統(tǒng)復位之后要使用的話要配置為浮空輸入模式。

3) MCO1 和 MCO2:這些引腳在系統(tǒng)復位之后要使用的話要配置為復用功能模式。

對于外設(shè)復用功能的配置,除了 ADC 和 DAC 要將 IO 配置為模擬通道之外其他外設(shè)功能一律

要配置為復用功能模式,這個配置是在 IO 口對應(yīng)的 GPIOx_MODER 寄存器中配置的。同時要配

置 GPIOx_AFRH 或者 GPIOx_AFRL 寄存器,將 IO 口通過復用器連接到所需要的復用功能對應(yīng)的

AFx。

不是每個 IO 口都可以復用為任意復用功能外設(shè)。到底哪些 IO 可以復用為相關(guān)外設(shè)呢?這

在芯片對應(yīng)的數(shù)據(jù)手冊(請參考光盤目錄:)上面會有詳細的表格列出來。對于 STM32F407,數(shù)

據(jù)手冊里面的 Table 9.Alternate function mapping 表格列出了所有的端口 AF 映射表,因為

表格比較大,所以這里只列出 PORTA 的幾個端口為例方便大家理解:


表 4.4.4 PORTA 部分端口 AF 映射表

上一節(jié)我們講解了時鐘系統(tǒng)配置步驟。在配置好時鐘系統(tǒng)之后,如果我們要使用某些外設(shè),

例如 GPIO,ADC 等,我們還要使能這些外設(shè)時鐘。這里大家必須注意,如果在使用外設(shè)之前

沒有使能外設(shè)時鐘,這個外設(shè)是不可能正常運行的。STM32 的外設(shè)時鐘使能是在 RCC 相關(guān)寄

存器中配置的。因為 RCC 相關(guān)寄存器非常多,有興趣的同學可以直接打開《STM32F4 中文參

考手冊》6.3 小節(jié)查看所有 RCC 相關(guān)寄存器的配置。接下來我們來講解通過 STM32F4 的 HAL

庫使能外設(shè)時鐘的方法。

在 STM32F4 的 HAL 庫中,外設(shè)時鐘使能操作都是在 RCC 相關(guān) HAL 庫文件頭文件

stm32f4xx_hal_rcc.h 定義的。大家打開 stm32f4xx_hal_rcc.h 頭文件可以看到文件中除了少數(shù)幾

個函數(shù)聲明之外大部分都是宏定義標識符。外設(shè)時鐘使能在 HAL 庫中都是通過宏定義標識符

來實現(xiàn)的。首先,我們來看看 GPIOA 的外設(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)

這幾行代碼非常簡單,主要是定義了一個宏定義標識符__HAL_RCC_GPIOA_CLK_ENABLE(),

它的核心操作是通過下面這行代碼實現(xiàn)的:

SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);

這行代碼的作用是,設(shè)置寄存器 RCC->AHB1ENR 的相關(guān)位為 1,至于是哪個位,是由宏定義

標識符 RCC_AHB1ENR_GPIOAEN 的值決定的,而它的值為:

#define RCC_AHB1ENR_GPIOAEN ((uint32_t)0x00000001)

所以,我們很容易理解上面代碼的作用是設(shè)置寄存器 RCC->AHB1ENR 寄存器的最低位為 1。

我們可以從 STM32F4 的中文參考手冊中搜索 AHB1ENR 寄存器定義,最低位的作用是用來使

用 GPIOA 時鐘。AHB1ENR 寄存器的位 0 描述如下:

位 0

GPIOAEN:IO 端口 A 時鐘使能

由軟件置 1 和清零

0:禁止 IO 端口 A 時鐘

1:使能 IO 端口 A 時鐘

那么我們只需要在我們的用戶程序中調(diào)用宏定義標識符__HAL_RCC_GPIOA_CLK_ENABLE()

就可以實現(xiàn) GPIOA 時鐘使能。使用方法為:

__HAL_RCC_GPIOA_CLK_ENABLE();//使能 GPIOA 時鐘

對于其他外設(shè),同樣都是在 stm32f4xx_hal_rcc.h 頭文件中定義,大家只需要找到相關(guān)宏定義標

識符即可,這里我們列出幾個常用使能外設(shè)時鐘的宏定義標識符使用方法:

__HAL_RCC_DMA1_CLK_ENABLE();//使能 DMA1 時鐘

__HAL_RCC_USART2_CLK_ENABLE();//使能串口 2 時鐘

__HAL_RCC_TIM1_CLK_ENABLE();//使能 TIM1 時鐘

我們使用外設(shè)的時候需要使能外設(shè)時鐘,如果我們不需要使用某個外設(shè),同樣我們可以禁

止某個外設(shè)時鐘。禁止外設(shè)時鐘使用方法和使能外設(shè)時鐘非常類似,同樣是頭文件中定義的宏

定義標識符。我們同樣以 GPIOA 為例,宏定義標識符為:

#define __HAL_RCC_GPIOA_CLK_DISABLE() \\

(RCC->AHB1ENR &= ~(RCC_AHB1ENR_GPIOAEN))

同樣,宏定義標識符__HAL_RCC_GPIOA_CLK_DISABLE()的作用是設(shè)置 RCC->AHB1ENR 寄

存器的最低位為 0,也就是禁止 GPIOA 時鐘。具體使用方法我們這里就不做過多講解,我們這

里同樣列出幾個常用的禁止外設(shè)時鐘的宏定義標識符使用方法:

__HAL_RCC_DMA1_CLK_DISABLE();//禁止 DMA1 時鐘

__HAL_RCC_USART2_CLK_DISABLE();//禁止串口 2 時鐘

__HAL_RCC_TIM1_CLK_DISABLE();//禁止 TIM1 時鐘

關(guān)于 STM32F4 的外設(shè)時鐘使能和禁止方法我們就給大家講解到這里。

這類函數(shù)跟前面講解的外設(shè)時鐘函數(shù)使用方法基本一致,不同的是一個是用來使能外設(shè)時

鐘,一個是用來復位對應(yīng)的外設(shè)。這里大家在調(diào)用函數(shù)的時候一定不要混淆。

對于這些時鐘操作函數(shù),我們就不一一列舉出來,大家可以打開 RCC 對應(yīng)的文件仔細了解。

ST32F4 的端口復用和映射就給大家講解到這里,希望大家課余結(jié)合相關(guān)實驗工程和手冊鞏

固本小節(jié)知識。

4.5 STM32 NVIC 中斷優(yōu)先級管理

CM4 內(nèi)核支持 256 個中斷,其中包含了 16 個內(nèi)核中斷和 240 個外部中斷,并且具有 256

級的可編程中斷設(shè)置。但 STM32F407 并沒有使用 CM4 內(nèi)核的全部東西,而是只用了它的一部

分。STM32F4xx 則總共有 101 個中斷,以下僅以 STM32F407xx 為例講解。

STM32F407xx 的 96 個中斷里面,包括 10 個內(nèi)核中斷和 91 個可屏蔽中斷,具有 16 級可編

程的中斷優(yōu)先級,而我們常用的就是這 91 個可屏蔽中斷。在 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 的中斷。下面重點介紹這幾個寄存器:

ISER[8]:ISER 全稱是:Interrupt Set-Enable Registers,這是一個中斷使能寄存器組。上面

說了 CM4 內(nèi)核支持 256 個中斷,這里用 8 個 32 位寄存器來控制,每個位控制一個中斷。但是

STM32F407 的可屏蔽中斷最多只有 91 個,所以對我們來說,有用的就是三個(ISER[0~2]]),

總共可以表示 96 個中斷。而 STM32F407 只用了其中的前 91 個。ISER[0]的 bit0~31 分別對應(yīng)

中斷 0~31;ISER[1]的 bit0~32 對應(yīng)中斷 32~63;ISER[2]的 bit0~26 對應(yīng)中斷 64~90;這樣總共

91 個中斷就分別對應(yīng)上了。你要使能某個中斷,必須設(shè)置相應(yīng)的 ISER 位為 1,使該中斷被使

能(這里僅僅是使能,還要配合中斷分組、屏蔽、IO 口映射等設(shè)置才算是一個完整的中斷設(shè)置)。

具體每一位對應(yīng)哪個中斷,請參考 STM32F407xx.h 里面的第 84 行處。

ICER[8]:全稱是:Interrupt Clear-Enable Registers,是一個中斷除能寄存器組。該寄存器組

與 ISER 的作用恰好相反,是用來清除某個中斷的使能的。其對應(yīng)位的功能,也和 ICER 一樣。

這里要專門設(shè)置一個 ICER 來清除中斷位,而不是向 ISER 寫 0 來清除,是因為 NVIC 的這些寄

存器都是寫 1 有效的,寫 0 是無效的。

ISPR[8]:全稱是:Interrupt Set-Pending Registers,是一個中斷掛起控制寄存器組。每個位

對應(yīng)的中斷和 ISER 是一樣的。通過置 1,可以將正在進行的中斷掛起,而執(zhí)行同級或更高級別

的中斷。寫 0 是無效的。

ICPR[8]:全稱是:Interrupt Clear-Pending Registers,是一個中斷解掛控制寄存器組。其作

用與 ISPR 相反,對應(yīng)位也和 ISER 是一樣的。通過設(shè)置 1,可以將掛起的中斷接掛。寫 0 無效。

IABR[8]:全稱是:Interrupt Active Bit Registers,是一個中斷激活標志位寄存器組。對應(yīng)位

所代表的中斷和 ISER 一樣,如果為 1,則表示該位所對應(yīng)的中斷正在被執(zhí)行。這是一個只讀寄

存器,通過它可以知道當前在執(zhí)行的中斷是哪一個。在中斷執(zhí)行完了由硬件自動清零。

IP[240]:全稱是:Interrupt Priority Registers,是一個中斷優(yōu)先級控制的寄存器組。這個寄

存器組相當重要!STM32F407 的中斷分組與這個寄存器組密切相關(guān)。IP 寄存器組由 240 個 8bit

的寄存器組成,每個可屏蔽中斷占用 8bit,這樣總共可以表示 240 個可屏蔽中斷。而 STM32F407

只用到了其中的 91 個。IP[90]~IP[0]分別對應(yīng)中斷 90~0。而每個可屏蔽中斷占用的 8bit 并沒有

全部使用,而是 只用了高 4 位。這 4 位,又分為搶占優(yōu)先級和子優(yōu)先級。搶占優(yōu)先級在前,子

優(yōu)先級在后。而這兩個優(yōu)先級各占幾個位又要根據(jù) SCB->AIRCR 中的中斷分組設(shè)置來決定。

這里簡單介紹一下 STM32F407 的中斷分組:STM32F407 將中斷分為 5 個組,組 0~4。該

分組的設(shè)置是由 SCB->AIRCR 寄存器的 bit10~8 來定義的。具體的分配關(guān)系如表 4.5.1 所示:

表 4.5.1AIRCR 中斷優(yōu)先級分組設(shè)置表

通過這個表,我們就可以清楚的看到組 0~4 對應(yīng)的配置關(guān)系,例如組設(shè)置為 3,那么此時

所有的 91 個中斷,每個中斷的中斷優(yōu)先寄存器的高四位中的最高 3 位是搶占優(yōu)先級,低 1 位是

響應(yīng)優(yōu)先級。每個中斷,你可以設(shè)置搶占優(yōu)先級為 0~7,響應(yīng)優(yōu)先級為 1 或 0。搶占優(yōu)先級的

級別高于響應(yīng)優(yōu)先級。而數(shù)值越小所代表的優(yōu)先級就越高。

這里需要注意兩點:第一,如果兩個中斷的搶占優(yōu)先級和響應(yīng)優(yōu)先級都是一樣的話,則看

哪個中斷先發(fā)生就先執(zhí)行;第二,高優(yōu)先級的搶占優(yōu)先級是可以打斷正在進行的低搶占優(yōu)先級

中斷的。而搶占優(yōu)先級相同的中斷,高優(yōu)先級的響應(yīng)優(yōu)先級不可以打斷低響應(yīng)優(yōu)先級的中斷。

結(jié)合實例說明一下:假定設(shè)置中斷優(yōu)先級組為 2,然后設(shè)置中斷 3(RTC_WKUP 中斷)的搶

占優(yōu)先級為 2,響應(yīng)優(yōu)先級為 1。中斷 6(外部中斷 0)的搶占優(yōu)先級為 3,響應(yīng)優(yōu)先級為 0。中

斷 7(外部中斷 1)的搶占優(yōu)先級為 2,響應(yīng)優(yōu)先級為 0。那么這 3 個中斷的優(yōu)先級順序為:中

斷 7>中斷 3>中斷 6。

上面例子中的中斷 3 和中斷 7 都可以打斷中斷 6 的中斷。而中斷 7 和中斷 3 卻不可以相互

打斷!

通過以上介紹,我們熟悉了 STM32F407 中斷設(shè)置的大致過程。接下來我們介紹如何使用

HAL 庫實現(xiàn)以上中斷分組設(shè)置以及中斷優(yōu)先級管理,使中斷配置簡單化。NVIC 中斷管理相關(guān)

函數(shù)主要在 HAL 庫關(guān)鍵文件 stm32f4xx_hal_cortex.c 中定義。

首先要講解的是中斷優(yōu)先級分組函數(shù) HAL_NVIC_SetPriorityGrouping,其函數(shù)申明如下:

void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup);

這個函數(shù)的作用是對中斷的優(yōu)先級進行分組,這個函數(shù)在系統(tǒng)中只需要被調(diào)用一次,一旦

分組確定就最好不要更改,否則容易造成程序分組混亂。這個函數(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ù)體以及注釋可以看出,這個函數(shù)是通過調(diào)用函數(shù) NVIC_SetPriorityGrouping 來進行中斷

優(yōu)先級分組設(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)容可以看出,這個函數(shù)主要作用是通過設(shè)置 SCB->AIRCR 寄存器的值來設(shè)置中斷優(yōu)先

級分組,這在前面寄存器講解的過程中已經(jīng)講到。

關(guān)于函數(shù) HAL_NVIC_SetPriorityGrouping 的函數(shù)體內(nèi)容解讀我就給大家介紹到這里。接下

來我們來看看這個函數(shù)的入口參數(shù)。大家繼續(xù)回到函數(shù) HAL_NVIC_SetPriorityGrouping 的定義

可以看到,函數(shù)的最開頭有這樣一行函數(shù):

assert_param(IS_NVIC_PRIORITY_GROUP(PriorityGroup));

其中函數(shù) assert_param 是斷言函數(shù),它的作用主要是對入口參數(shù)的有效性進行判斷。也就是說

我們可以通過這個函數(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))

從這個內(nèi)容可以看出,當 GROUP 的值為 NVIC_PriorityGroup_0~ NVIC_PriorityGroup_4 的時候,

IS_NVIC_PRIORITY_GROUP 的值才為真。這也就是我們上面表 4.5.1 講解的,分組范圍為 0-4,

對應(yīng)的入口參數(shù)為宏定義值 NVIC_PriorityGroup_0~ NVIC_PriorityGroup_4。比如我們設(shè)置整個

系統(tǒng)的中斷優(yōu)先級分組值為 2,那么方法是:

HAL_NVIC_SetPriorityGrouping (NVIC_PriorityGroup_2);

這樣就確定了中斷優(yōu)先級分組為 2,也就是 2 位搶占優(yōu)先級,2 位響應(yīng)優(yōu)先級,搶占優(yōu)先級和響

應(yīng)優(yōu)先級的值的范圍均為 0-3。

講到這里,大家對怎么進行系統(tǒng)的中斷優(yōu)先級分組設(shè)置,以及具體的中斷優(yōu)先級設(shè)置函數(shù)

HAL_NVIC_SetPriorityGrouping 的內(nèi)部函數(shù)實現(xiàn)都有了一個詳細的理解。接下來我們來看看在

HAL 庫里面,是怎樣調(diào)用 HAL_NVIC_SetPriorityGrouping 函數(shù)進行分組設(shè)置的。

打開 stm32f4xx_hal.c 文件可以看到,文件內(nèi)部定義了 HAL 庫初始化函數(shù) HAL_Init,這個

函數(shù)非常重要,其作用主要是對中斷優(yōu)先級分組,F(xiàn)LASH 以及硬件層進行初始化,我們在 3.1

小節(jié)對其進行了比較詳細的講解。這里我們只需要知道,在系統(tǒng)主函數(shù) main 開頭部分,我們都

會首先調(diào)用 HAL_Init 函數(shù)進行一些初始化操作。在 HAL_Init 內(nèi)部,有如下一行代碼:

HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

這行代碼的作用是把系統(tǒng)中斷優(yōu)先級分組設(shè)置為分組 4,這在我們前面已經(jīng)詳細講解。也

就是說,在主函數(shù)中調(diào)用 HAL_Init 函數(shù)之后,在 HAL_Init 函數(shù)內(nèi)部會通過調(diào)用我們前面講解

的 HAL_NVIC_SetPriorityGrouping 函數(shù)來進行系統(tǒng)中斷優(yōu)先級分組設(shè)置。所以,我們要進行中

斷優(yōu)先級分組設(shè)置,只需要修改 HAL_Init 函數(shù)內(nèi)部的這行代碼即可。中斷優(yōu)先級分組的內(nèi)容我

們就給大家講解到這里。

設(shè)置好了系統(tǒng)中斷分組,也就是確定了那么對于每個中斷我們又怎么確定他的搶占優(yōu)先級

和響應(yīng)優(yōu)先級呢?官方 HAL 庫文件 stm32f4xx_hal_cortex.c 中定義了三個單個中斷優(yōu)先級設(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);

第一個函數(shù) HAL_NVIC_SetPriority 是用來設(shè)置單個優(yōu)先級的搶占優(yōu)先級和響應(yīng)優(yōu)先級的值。

第二個函數(shù) HAL_NVIC_EnableIRQ 是用來使能某個中斷通道。

第三個函數(shù) HAL_NVIC_DisableIRQ 是用來清除某個中斷使能的,也就是中斷失能。

這三個函數(shù)的使用都非常簡單,對于具體的調(diào)用方法,大家可以參考我們后面第九章外部中斷

實驗講解。

這里大家還需要注意,中斷優(yōu)先級分組和中斷優(yōu)先級設(shè)置是兩個不同的概念。中斷優(yōu)先級

分組是用來設(shè)置整個系統(tǒng)對于中斷分組設(shè)置為哪個分組,分組號為 0-4,設(shè)置函數(shù)為

HAL_NVIC_SetPriorityGrouping,確定了中斷優(yōu)先級分組號,也就確定了系統(tǒng)對于單個中斷的

搶占優(yōu)先級和響應(yīng)優(yōu)先級設(shè)置各占幾個位(對應(yīng)表 4.5.1)。設(shè)置好中斷優(yōu)先級分組,確定了分

組號之后,接下來我們就是要對單個優(yōu)先級進行中斷優(yōu)先級設(shè)置。也就是這個中斷的搶占優(yōu)先

級和響應(yīng)優(yōu)先級的值,設(shè)置方法就是我們上面講解的三個函數(shù)。

最后我們總結(jié)一下中斷優(yōu)先級設(shè)置的步驟:

①系統(tǒng)運行開始的時候設(shè)置中斷分組。確定組號,也就是確定搶占優(yōu)先級和響應(yīng)優(yōu)先級的

分配位數(shù)。設(shè)置函數(shù)為 HAL_NVIC_PriorityGroupConfig。對于 HAL 庫,在文件 stm32f4xx_hal.c

內(nèi)部定義函數(shù) HAL_Init 中有調(diào)用 HAL_NVIC_PriorityGroupConfig 函數(shù)進行相關(guān)設(shè)置,所以我

們只需要修改 HAL_Init 內(nèi)部對中斷優(yōu)先級分組設(shè)置即可。

① 設(shè)置單個中斷的中斷優(yōu)先級別和使能相應(yīng)中斷通道,使用到的函數(shù)函數(shù)主要為函數(shù)

HAL_NVIC_SetPriority 和函數(shù) HAL_NVIC_EnableIRQ。

4.6 MDK 中寄存器地址名稱映射分析

之所以要講解這部分知識,是因為經(jīng)常會遇到客戶提到不明白 HAL 庫中那些結(jié)構(gòu)體是怎么

與寄存器地址對應(yīng)起來的。這里我們就做一個簡要的分析吧。

首先我們看看 51 中是怎么做的。51 單片機開發(fā)中經(jīng)常會引用一個 reg51.h 的頭文件,下

面我們看看他是怎么把名字和寄存器聯(lián)系起來的:

sfr P0 =0x80;

sfr 也是一種擴充數(shù)據(jù)類型,點用一個內(nèi)存單元,值域為 0~255。利用它可以訪問 51 單片

機內(nèi)部的所有特殊功能寄存器。如用 sfr P1 = 0x90 這一句定義 P1 為 P1 端口在片內(nèi)的寄存

器。然后我們往地址為 0x80 的寄存器設(shè)值的方法是:P0=value;

那么在 STM32 中,是否也可以這樣做呢??答案是肯定的??隙ㄒ部梢酝ㄟ^同樣的方

式來做,但是 STM32 因為寄存器太多太多,如果一一以這樣的方式列出來,那要好大的篇

幅,既不方便開發(fā),也顯得太雜亂無序的感覺。所以 MDK 采用的方式是通過結(jié)構(gòu)體來將

寄存器組織在一起。下面我們就講解 MDK 是怎么把結(jié)構(gòu)體和地址對應(yīng)起來的,為什么我

們修改結(jié)構(gòu)體成員變量的值就可以達到操作對應(yīng)寄存器的值。這些事情都是在 stm32f4xx.h

文件中完成的。我們通過 GPIOA 的幾個寄存器的地址來講解吧。

首先我們可以查看《STM32F4 中文參考手冊》中的寄存器地址映射表(P193)。這里我

們選用 GPIOA 為例來講解。GPIOA 寄存器地址映射如下表 4.6.1:

表 4.6.1 GIPOA 寄存器地址偏移表

從這個表我們可以看出,因為 GIPO 寄存器都是 32 位,所以每組 GPIO 的 10 個寄存器

中,每個寄存器占有 4 個地址,一共占用 40 個地址,地址偏移范圍為(0x00~0x24)。這個

地址偏移是相對 GPIOA 的基地址而言的。GPIOA 的基地址是怎么算出來的呢?因為 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 強制轉(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

下面我們再跟《STM32F 中文參考手冊》比較一下看看 GPIOA 的基地址是不是 0x40020000 。

截圖 P53 存儲器映射表我們可以看到,GPIOA 的起始地址也就是基地址確實是 0x40020000:

圖 4.6.2 GPIO 存儲器地址映射表

同樣的道理,我們可以推算出其他外設(shè)的基地址。

上面我們已經(jīng)知道 GPIOA 的基地址,那么那些 GPIOA 的 10 個寄存器的地址又是怎么

算出來的呢?在上面我們講過 GPIOA 的各個寄存器對于 GPIOA 基地址的偏移地址,所以

我們自然可以算出來每個寄存器的地址。

GPIOA 的寄存器的地址=GPIOA 基地址+寄存器相對 GPIOA 基地址的偏移值

這個偏移值在上面的寄存器地址映像表中可以查到。

那么在結(jié)構(gòu)體里面這些寄存器又是怎么與地址一一對應(yīng)的呢?這里涉及到結(jié)構(gòu)體成員

變量地址對齊方式方面的知識,這方面的知識大家可以在網(wǎng)上查看相關(guān)資料復習一下,這

里我們不做詳細講解。在我們定義好地址對齊方式之后,每個成員變量對應(yīng)的地址就可以

根據(jù)其基地址來計算。對于結(jié)構(gòu)體類型 GPIO_TypeDef,他的所有成員變量都是 32 位,成

員變量地址具有連續(xù)性。所以自然而然我們就可以算出 GPIOA 指向的結(jié)構(gòu)體成員變量對應(yīng)

地址了。

表 4.6.3 GPIOA 各寄存器實際地址表

我們可以把 GPIO_TypeDef 的定義中的成員變量的順序和 GPIOx 寄存器地址映像對比

可以發(fā)現(xiàn),他們的順序是一致的,如果不一致,就會導致地址混亂了。

這就是為什么 HAL 庫里面:GPIOA->BSRR=value;就是設(shè)置地址為 0x40020000

+0x18 (BSRR 偏移量)=0x40020018 的寄存器 BSRR 的值了。它和 51 里面 P0=value 是設(shè)置

地址為 0x80 的 P0 寄存器的值是一樣的道理。

看到這里你是否會學起來踏實一點呢?STM32 使用的方式雖然跟 51 單片機不一樣,

但是原理都是一致的。

4.7 MDK 代碼快速組織代碼技巧

這一節(jié)主要講解在 MDK 中使用 HAL 庫開發(fā)的一些小技巧,僅供初學者參考。這節(jié)的知識

大家可以在學習第一個跑馬燈實驗的時候參考一下,對初學者應(yīng)該很有幫助。我們就用最簡單

的 GPIO 初始化函數(shù)為例。

現(xiàn) 在 我 們 要 初 始 化 某 個 GPIO 端 口 , 我 們 要 怎 樣 快 速 操 作 呢 ? 在 頭 文 件

stm32f4xx_hal_gpio.h 頭文件中,聲明 GPIO 初始化函數(shù)為:

void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);

現(xiàn)在我們想寫初始化函數(shù),那么我們在不參考其他代碼的前提下,怎么快速組織代碼呢?

首先,我們可以看出,函數(shù)的入口參數(shù)是 GPIO_TypeDef 類型指針和 GPIO_InitTypeDef 類型指針,因為 GPIO_TypeDef 入口參數(shù)比較簡單,所以我們 就通過第二個入口參數(shù)

GPIO_InitTypeDef 類型指針來講解。雙擊 GPIO_InitTypeDef 后右鍵選擇“Go to definition of…”,

(前提是打開了“Browse Information”選項,可以參考前面 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;

可以看到這個結(jié)構(gòu)體有 5 個成員變量,這也告訴我們一個信息,一個 GPIO 口的狀態(tài)是由模式

(Mode),速度(Speed)以及上下拉(Pull)來決定的。我們首先要定義一個結(jié)構(gòu)體變量,下面

我們定義:

GPIO_InitTypeDef GPIO_InitStructure;

接著我們要初始化結(jié)構(gòu)體變量 GPIO_InitStructure。首先我們要初始化成員變量 Pin,這個時候我

們就有點迷糊了,這個變量到底可以設(shè)置哪些值呢?這些值的范圍有什么規(guī)定嗎?

這里我們就回到 HAL_GPIO_Init 聲明處,同樣雙擊 HAL_GPIO_Init,右鍵點擊“Go to

definition of …”,這樣光標定位到 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 是斷言語句,是對函數(shù)入口參數(shù)的有效性進行判斷,所以我們可以從

這個函數(shù)入手,確定入口參數(shù)范圍。第一行是對第一個參數(shù) GPIOx 進行有效性判斷,雙擊

“IS_GPIO_ALL_INSTANCE”右鍵點擊“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” 右鍵點擊“go to defition of…”,定位到下面的定義:

#define IS_GPIO_PIN(PIN) (((PIN) & GPIO_PIN_MASK ) != (uint32_t)0x00)

同時,宏定義標識符 GPIO_PIN_MASK 的定義為:

#define GPIO_PIN_MASK ((uint32_t)0x0000FFFF)

從上面可以看出,PIN 取值只要低 16 位不為 0 即可。這里需要大家注意,因為一組 IO 口只有

16 個 IO,實際上 PIN 的值在這里只有低 16 位有效,所以 PIN 的取值范圍為 0x0001~0xFFFF。

那么是不是我們寫代碼初始化就是直接給一個 16 位的數(shù)字呢?這也是可以的,但是大多數(shù)情況

下,我們不會直接在入口參數(shù)處設(shè)置一個簡單的數(shù)字,因為這樣代碼的可讀性太差,HAL 庫會

將這些數(shù)字的含義 通過宏定義定義出來,這樣可讀性大大增強。我們可以看到在

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 庫事先定義好的,我們寫代碼的時候初始

化結(jié)構(gòu)體 成員變量 Pin 的時候入口參數(shù)可以是這些宏定義標識符。

同理,對于成員變量 Pull,我們用同樣的方法,可以找到其取值范圍定義為:

#define IS_GPIO_PULL(PULL) (((PULL) == GPIO_NOPULL)\\

|| ((PULL) == GPIO_PULLUP) || \\ ((PULL) == GPIO_PULLDOWN))

也就是 PULL 的 取 值 范 圍 只 能 是 標 識 符 GPIO_NOPULL , GPIO_PULLUP 以 及

GPIO_PULLDOWN。

對于其他成員變量 Mode 以及 Alternate,方法都是一樣的,這里基于篇幅考慮我們就不重

復講解。講到這里,我們基本對 HAL_GPIO_Init 的入口參數(shù)有比較詳細的了解了。于是我們可

以組織起來下面的代碼:

GPIO_InitTypeDef GPIO_Initure;

GPIO_Initure.Pin=GPIO_PIN_9;

//PA9

GPIO_Initure.Mode=GPIO_MODE_AF_PP; //復用推挽輸出

GPIO_Initure.Pull=GPIO_PULLUP;

//上拉

GPIO_Initure.Speed=GPIO_SPEED_FAST;

//高速

GPIO_Initure.Alternate=GPIO_AF7_USART1; //復用為 USART1

HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 PA9

接著又有一個問題會被提出來,這個初始化函數(shù)一次只能初始化一個 IO 口嗎?我要同時

初始化很多個 IO 口,是不是要復制很多次這樣的初始化代碼呢?

這里又有一個小技巧了。從上面的 GPIO_PIN_X 的宏定義我們可以看出,這些值是 0,1,2,4

這樣的數(shù)字,所以每個 IO 口選定都是對應(yīng)著一個位,16 位的數(shù)據(jù)一共對應(yīng) 16 個 IO 口。這個

位為 0 那么這個對應(yīng)的 IO 口不選定,這個位為 1 對應(yīng)的 IO 口選定。如果多個 IO 口,他們都

是對應(yīng)同一個 GPIOx,那么我們可以通過|(或)的方式同時初始化多個 IO 口。這樣操作的前

提是,他們的 Mode,Speed,Pull 和 Alternate 參數(shù)值相同,因為這些參數(shù)并不能一次定義多種。

所以初始化多個具有相同配置的 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; //復用推挽輸出

GPIO_Initure.Pull=GPIO_PULLUP;

//上拉

GPIO_Initure.Speed=GPIO_SPEED_FAST;

//高速

GPIO_Initure.Alternate=GPIO_AF7_USART1; //復用為 USART1

HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 PA9 ,PA10,PA11

對于那些參數(shù)可以通過|(或)的方式連接,這既有章可循,同時也靠大家在開發(fā)過程中不斷積累。

大家會覺得上面講解有點麻煩,每次要去查找 assert_param()這個函數(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 的取值范圍宏定義。如果要確定詳細的信息

我們就得去查看手冊了。對于去查看手冊的哪個地方,你可以在函數(shù) HAL_GPIO_Init ()的函數(shù)

體中搜索 Pin 關(guān)鍵字,然后查看庫函數(shù)設(shè)置 Pin 是設(shè)置的哪個寄存器的哪個位,然后去中文參

考手冊查看該寄存器相應(yīng)位的定義以及前后文的描述。

這一節(jié)我們就講解到這里,希望能對大家的開發(fā)有幫助。

匯付天下pos機怎樣

匯付天下pos機比較安全。

匯付天下有央行頒橘冊發(fā)的支付牌照,所以是正規(guī)的機構(gòu),旗下的pos收單業(yè)務(wù),只要是通過正常渠道購買的pos機,基本就是安全的,但是注意市面上一些代理,有些是二清機,存在一定風險。

匯付天下的業(yè)務(wù)主要覆蓋兩大板塊,即支付服務(wù)和金融科技服務(wù)。 支付服務(wù) 我們?yōu)閿?shù)百萬小微商戶及垂直行業(yè)的公司譽賣提供各種支付服務(wù)。我們的解決方案能夠為客戶提供無縫、便捷及安全的支付方式。我們的服務(wù)包括 POS 、互聯(lián)網(wǎng)支付、移動支付、 移動POS 及跨境支付服務(wù)。

擴展資料:

機型慶伍逗分類:

1、手持POS機。體積較小,移動方便,能以單鍵快速操作,不必死記及輸入多位貨號。國內(nèi)手持POS機品牌有:拉卡拉、微付通、快錢等。

2、臺式POS機。體積較手持POS機大,功能比手持POS機齊全。國內(nèi)臺式POS機

3、移動手機POS機:按操作方式分類分為手機外置設(shè)備刷卡機和手機專用pos機。

onu設(shè)備介紹?

朋友雖然不知道160A是華為那款設(shè)備,但我是電信搞設(shè)備維護的,我們這邊的onu型號是5606t,一般onu上第一層是主控板,二層是業(yè)務(wù)板或語音板,支持混插,onu的主要作用是管理大樓內(nèi)所有用戶的adsl和普話,他的上層是olt設(shè)備,onu上主要是對adsl帶寬的配置和限速和語音的分離,減少長距離對adsl線路的影響,一般onu上業(yè)務(wù)板豐富,可以支持pos機和窄帶撥號。

希望對你有幫助。

以上就是關(guān)于pos機方案開發(fā)板,探索者 STM32F407 開發(fā)板資料連載第四章 F4 開發(fā)基礎(chǔ)知識入門的知識,后面我們會繼續(xù)為大家整理關(guān)于pos機方案開發(fā)板的知識,希望能夠幫助到大家!

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

你可能會喜歡:

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