學習目標:STM32F103C8T6開發板入門學習——寄存器和庫函數介紹
學習內容:
1. 寄存器介紹
1.1 存儲器映射
存儲器本身無固有地址,是具有特定功能的內存單元。它的地址是由芯片廠商或用戶分配,給存儲器分配地址的過程就叫做存儲區映射。給內存單元分配地址之后,就可以通過指針去操作內存地址。
1.2 存儲器映射表
STM32作為32位單片機,地址范圍為232(即4GB)。為降低相同應用場景下的軟件復雜度,其存儲映射按Cortex-M4處理器規則預先定義:
- 部分地址空間由Arm Cortex-M4的系統外設占用,且不可更改;
- 其余地址空間可由芯片供應商定義使用。
1.3 什么是寄存器
寄存器是具有特定功能的內存單元,通過操作這些單元可驅動外設工作。按功能可分為:
- 指令寄存器、地址寄存器、數據寄存器;
- 處理器可通過相互獨立的總線讀取指令、加載/存儲數據。
1.4 寄存器映射
程序存儲器、數據存儲器、寄存器和I/O端口位于同一4GB線性地址空間內:
- 每個寄存器對應不同功能,操作相應寄存器可配置對應功能;
- 控制外設時,可通過起始地址以C語言指針方式訪問內存單元;
- 給已分配地址、有特定功能的內存單元取別名的過程,稱為寄存器映射,該別名即寄存器。
1.5 寄存器重映射
給寄存器再次分配地址的過程稱為寄存器重映射。
1.6 總線基地址
片上外設區域分為三條總線,按外設速度不同掛載相應外設:
- AHB總線:最高時鐘可達72MHz;
- APB1總線:最高時鐘可達36MHz;
- APB2總線:最高時鐘可達72MHz。
根據外設速度的不同,不同的總線掛載著不同的外設。總線的最低地址我們稱為該總線的基地址,總線基地址也是掛載在該總線上的首個外設的地址。
1.7 外設基地址
每條總線上掛載多個外設,每個外設均有自己的地址范圍。
1.8 外設寄存器地址
在外設的地址范圍內,分布著該外設的多個寄存器。以GPIO外設為例:
- GPIO外設地址范圍內有多個寄存器,每個均有特定功能,通過操作對應寄存器可配置GPIO功能;
- 每個寄存器為32位,占4個字節。
1.9 如何操作寄存器
以“讓GPIOA端口的16個引腳都置1”為例,操作步驟如下:
- 確定目標寄存器:需配置端口輸出寄存器GPIOx_ODR;
- 計算寄存器地址:
- 由中文參考手冊第115頁可知,GPIOx_ODR寄存器的地址偏移量為0x0C;
- GPIOA端口的基地址為0x40010800;
- 因此,GPIOx_ODR寄存器的地址為:0x40010800 + 0x0C = 0x4001080C。
通過上圖可以了解到要想使能所有引腳配置為1,只需要將ODRy(y=0…15)對應的位置1即可。也就是配置GPIOA_ODR寄存器的高16位為0,低16位為1,換成十六進制就是0x0000FFFF。
1.通過絕對地址訪問內存單元
// GPIOA 端口的16個引腳全部輸出高電平
*(unsigned int*)(0x4001080C) = 0xFFFF;
說明:
(unsigned int*)
:將立即數0x4001080C
強制轉換為無符號整型指針,告訴編譯器這是一個內存地址;*
:對該地址進行解引用,訪問地址對應的內存空間;- 整體含義:向
0x4001080C
(GPIOA_ODR寄存器)對應的內存空間寫入0xFFFF
,實現GPIOA所有16個引腳輸出高電平(低16位置1,高16位為0)。
2. 通過別名訪問內存單元
為簡化操作并增強可讀性,可給寄存器地址定義別名:
方式一:定義地址指針
// 定義 GPIOA_ODR 地址指針
#define GPIOA_ODR (unsigned int*)(0x4001080C)// GPIOA 端口的16個引腳全部輸出高電平
*GPIOA_ODR = 0xFFFF;
方式二:直接定義解引用的地址(更常用)
// 定義 GPIOA_ODR 并直接解引用
#define GPIOA_ODR (*(unsigned int*)(0x4001080C))// GPIOA 端口的16個引腳全部輸出高電平
GPIOA_ODR = 0xFFFF;
- 優勢:通過
GPIOA_ODR
這樣的別名,可直觀理解操作的是GPIOA的輸出數據寄存器,無需記憶復雜的十六進制地址,提升代碼可讀性和可維護性。
2. 庫函數介紹
2.1 為什么要使用庫函數
從上一節可知,通過寄存器驅動外設雖可行,但STM32寄存器數量極多:
- 僅定義寄存器就需耗費大量時間,還需逐一查找其功能、地址并配置對應值,在開發難度和時間成本上均不劃算。
庫函數的出現正是為解決這一問題,其優勢在于:
- 由STM32官方開發,可直接移植到工程中使用,大幅提升開發效率;
- 無需深入了解硬件底層機制,只需根據需求查找對應函數并調用,降低了開發門檻。
2.2 庫函數簡單介紹
在創建的工程模板中,包含STM32F1x的標準外設庫,可通過打開stm32f10x_gpio.h
和stm32f10x_gpio.c
等文件查看具體實現。
本質上,庫函數是在寄存器操作的基礎上進行了封裝:
- 底層仍通過操作寄存器實現功能,但上層提供了更簡潔的函數接口,使開發操作更簡單。
3. 寄存器和庫函數的區別
寄存器和庫函數最終均通過對地址操作實現功能,但二者存在明顯差異,適用場景也不同:
- 原理與直觀性:寄存器操作更易理解底層原理,過程直觀;庫函數則屏蔽了底層細節,直接面向應用功能。
- 代碼量與冗余:庫函數代碼量相對更大,因函數需考慮多種使用場景,可能存在代碼冗余;寄存器操作代碼更精簡。
- 開發難度與效率:庫函數使用簡單、易上手,可快速開發應用,能顯著提高開發效率;寄存器操作需熟悉硬件細節,開發門檻更高。
- 資源占用與速度:寄存器操作占用內存少、執行速度快,在資源有限(如內存緊張)或對執行速度有高要求的場景下更具優勢。
學習時間:8月25日
1. 寄存器介紹
1.1 存儲器映射
原文核心:存儲器本身無固有地址,分配地址的過程稱為存儲區映射,映射后可通過指針操作內存單元。
個人理解:存儲器就像一堆無編號的儲物箱,“存儲器映射”就是給每個箱子貼上限號(地址)。有了編號后,CPU才能通過“地址指針”準確找到并操作對應的存儲單元。這是硬件與軟件交互的基礎——沒有地址映射,軟件就無法“定位”硬件資源。
1.2 存儲器映射表
原文核心:STM32作為32位單片機,地址范圍為4GB,部分地址由Cortex-M4內核占用(不可改),其余由芯片廠商定義。
個人理解:4GB地址空間是32位處理器的“理論上限”,就像一個巨大的“地址版圖”。其中,Cortex-M4內核預留了部分區域(如系統外設),相當于“國家劃定的保護區”,不可占用;剩下的區域由STM32廠商分配給片上外設(如GPIO、UART等),形成了固定的“地址規劃”。這種標準化的映射表讓不同開發者能基于同一套地址規則操作硬件,降低了兼容性成本。
1.3 什么是寄存器
原文核心:寄存器是具有特定功能的內存單元,按功能分為指令、地址、數據寄存器,處理器通過獨立總線訪問。
個人理解:寄存器是硬件的“控制面板”。比如,指令寄存器存放當前要執行的指令,地址寄存器存放要訪問的內存地址,數據寄存器臨時存儲運算數據。獨立總線設計(如指令總線、數據總線)就像“專用通道”,避免了指令讀取和數據傳輸的沖突,提高了CPU效率。簡單說,寄存器是CPU與外設“對話”的直接窗口——操作寄存器就是在“告訴”硬件該做什么。
1.4 寄存器映射
原文核心:給已分配地址、有特定功能的內存單元取別名的過程,稱為寄存器映射,別名即寄存器。
個人理解:假設某塊內存單元的地址是0x4001080C,其功能是控制GPIOA的輸出,我們給它起個名字“GPIOA_ODR”,這個過程就是寄存器映射。它的本質是“地址→功能別名”的映射,讓開發者不用死記硬背冗長的十六進制地址,而是通過“GPIOA_ODR”這樣直觀的名字操作硬件,大幅降低了代碼的理解難度。
1.6 總線基地址
原文核心:片上外設分為AHB(72MHz)、APB1(36MHz)、APB2(72MHz)三條總線,總線基地址是總線上首個外設的地址。
個人理解:總線就像“高速公路”,外設是“車輛”。AHB和APB2是“快車道”(72MHz),適合高速外設(如GPIO、SPI);APB1是“慢車道”(36MHz),適合低速外設(如I2C、UART)。總線基地址是“高速路的起點”,比如AHB總線基地址是0x40010000,總線上的所有外設地址都從這個起點開始“編號”,方便按總線歸類管理外設。
1.7 外設基地址
原文核心:每條總線上的外設都有自己的地址范圍。
個人理解:如果總線是“高速公路”,外設就是路上的“服務區”,每個服務區有自己的“管轄范圍”(地址范圍)。比如,GPIOA掛在APB2總線上,其基地址是0x40010800,地址范圍從0x40010800到下一個外設的基地址前,這樣CPU就能通過地址范圍準確區分不同外設(如GPIOA和GPIOB)。
1.8 外設寄存器地址
原文核心:外設地址范圍內有多個寄存器,每個32位(4字節),對應特定功能(以GPIO為例)。
個人理解:一個外設(如GPIOA)就像一個“工具箱”,里面的每個“工具”(寄存器)對應不同功能:比如“GPIOA_CRL”控制低8位引腳模式,“GPIOA_ODR”控制輸出電平。每個寄存器占4字節(32位),是因為STM32是32位處理器,一次可處理32位數據,這種設計能高效讀寫寄存器。
1.9 如何操作寄存器(以GPIOA引腳置1為例)
原文核心:步驟為“確定目標寄存器→計算地址→寫入值”,并給出了具體示例。
個人理解:操作寄存器的本質是“地址+值”的精準控制。比如要讓GPIOA所有引腳輸出高電平,需找到“輸出數據寄存器(ODR)”,計算其地址(GPIOA基地址+偏移量0x0C=0x4001080C),再寫入0xFFFF(低16位置1)。這個過程像“按地址找到開關,再撥動開關”,需要對硬件地址和寄存器功能有清晰了解。
1. 通過絕對地址訪問內存單元
原文示例:*(unsigned int*)(0x4001080C) = 0xFFFF;
個人理解:這種方式直接通過地址操作,優點是“直達底層”,缺點是可讀性差——如果不查手冊,沒人知道0x4001080C是什么。適合對硬件極度熟悉的場景,或調試時快速驗證功能。
2. 通過別名訪問內存單元
原文給出兩種方式:定義地址指針(#define GPIOA_ODR (unsigned int*)(0x4001080C)
)或直接定義解引用地址(#define GPIOA_ODR (*(unsigned int*)(0x4001080C))
)。
個人理解:這是實際開發中更推薦的方式。通過“GPIOA_ODR”這樣的別名,代碼可讀性大幅提升,且修改時只需改宏定義,無需逐個修改地址。就像用“家門鑰匙”代替“鑰匙的物理編號”,更符合人類的記憶習慣。
2. 庫函數介紹
2.1 為什么要使用庫函數
原文核心:寄存器數量多,手動定義和配置耗時,庫函數由官方開發,可直接移植,降低門檻、提高效率。
個人理解:STM32的外設和寄存器極其龐大(僅GPIO就有近10個寄存器,全芯片有上百個外設),如果每個寄存器都手動定義地址、查手冊配置值,開發效率會極低。庫函數就像“封裝好的快捷指令”,比如要配置GPIO輸出,直接調用GPIO_Init()
即可,無需關心底層寄存器的地址和具體值,適合快速開發。
2.2 庫函數簡單介紹
原文核心:庫函數基于寄存器操作封裝,在stm32f10x_gpio.h
等文件中實現,提供簡潔接口。
個人理解:庫函數不是“魔法”,其底層依然是操作寄存器。比如GPIO_SetBits(GPIOA, GPIO_Pin_All)
函數,內部其實是對GPIOA_ODR
寄存器寫入0xFFFF。它的價值在于“抽象”——把復雜的寄存器配置邏輯(如時鐘使能、模式設置、輸出控制)打包成函數,讓開發者只需關注“要做什么”,而非“怎么做”。
3. 寄存器和庫函數的區別
原文從原理、代碼量、開發效率、資源占用等方面對比了兩者。
個人感悟:
- 寄存器是“手動擋”:直接操作硬件,代碼精簡、執行快,但需要深入了解硬件細節,開發門檻高,適合底層驅動開發或資源受限場景(如內存極小的設備)。
- 庫函數是“自動擋”:屏蔽底層細節,開發效率高、易上手,但代碼量較大(可能有冗余),執行速度略慢,適合應用層開發或快速原型驗證。
實際開發中,兩者并非對立:簡單功能可用庫函數快速實現,對性能要求高的部分(如中斷處理)可結合寄存器操作優化。初學者建議先通過庫函數入門,熟悉后再深入寄存器,理解底層原理。
學習產出:
- 了解并學習STMSTM32F103C8T6開發板的寄存器和庫函數介紹
- 技術文章的1篇