# 【STM32】寄存器筆記(register)
<style>
table {
word-break: normal !important;
}
</style>
###### tags: `Embedded`
# 1.什麼是寄存器?
寄存器是設計用來控制和配置各種外設和核心功能的。
# 2.為什麼重要?
因為所有單片機都是要透過程式**操作寄存器**去控制外設的電路。

以stm32407為例寄存器如下:
| Module | Register | Description |
|--------------------------|----------------|---------------------------------------------------------------------------------------------|
| Core Registers | NVIC | ISERx, ICERx, ISPRx, ICPRx, IABRx, IPRx |
| Core Registers | SCB | CPUID, ICSR, VTOR, AIRCR, SCR, CCR, SHPRx, SHCSR |
| GPIO | GPIOA-GPIOI | MODER, OTYPER, OSPEEDR, PUPDR, IDR, ODR, BSRR, LCKR, AFR[2] |
| Timer | TIM1-TIM14 | CR1, CR2, SMCR, DIER, SR, EGR, CCMR1, CCMR2, CCER, CNT, PSC, ARR, RCR, CCR1-CCR4, BDTR, DCR, DMAR |
| Communication Peripherals| USART1-USART6 | SR, DR, BRR, CR1, CR2, CR3, GTPR |
| Communication Peripherals| SPI1-SPI3 | CR1, CR2, SR, DR, CRCPR, RXCRCR, TXCRCR, I2SCFGR, I2SPR |
| Communication Peripherals| I2C1-I2C3 | CR1, CR2, OAR1, OAR2, DR, SR1, SR2, CCR, TRISE, FLTR |
| Communication Peripherals| CAN1, CAN2 | MCR, MSR, TSR, RF0R, RF1R, IER, ESR, BTR |
| ADC | ADC1-ADC3 | SR, CR1, CR2, SMPR1, SMPR2, JOFR1-JOFR4, HTR, LTR, SQR1, SQR2, SQR3, JSQR, JDR1-JDR4, DR |
| DAC | DAC | CR, SWTRIGR, DHR12R1, DHR12L1, DHR8R1, DHR12R2, DHR12L2, DHR8R2, DHR12RD, DHR12LD, DHR8RD, DOR1, DOR2, SR |
| Power Management | PWR | CR, CSR |
| Clock Control | RCC | CR, PLLCFGR, CFGR, CIR, AHB1RSTR, AHB2RSTR, AHB3RSTR, APB1RSTR, APB2RSTR, AHB1ENR, AHB2ENR, AHB3ENR, APB1ENR, APB2ENR, AHB1LPENR, AHB2LPENR, AHB3LPENR, APB1LPENR, APB2LPENR, BDCR, CSR, SSCGR, PLLI2SCFGR |
| DMA | DMA1, DMA2 | LISR, HISR, LIFCR, HIFCR, S0CR-S7CR, S0NDTR-S7NDTR, S0PAR-S7PAR, S0M0AR-S7M0AR, S0M1AR-S7M1AR, S0FCR-S7FCR |
| SysTick | SysTick | CTRL, LOAD, VAL, CALIB |
| EXTI | EXTI | IMR, EMR, RTSR, FTSR, SWIER, PR |
| RTC | RTC | TR, DR, CR, ISR, PRER, WUTR, ALRMAR, ALRMBR, WPR, SSR, SHIFTR, TSTR, TSDR, TSSSR, CALR, TAFCR, ALRMASSR, ALRMBSSR, BKPxR (x = 0-19) |
那具體寄存器被放到哪些地址上?
# 3.如何操作寄存器控制外部設備
## 一.透過HAL庫
HAL庫的優點
HAL庫將底層的寄存器操作封裝在易於使用的函數中,帶來了以下幾個主要優點:
`簡化開發過程`: 開發者可以專注於功能實現,而不需要直接操作寄存器。
`提高代碼可讀`: 封裝好的函數使得代碼更加直觀易讀。
`提升代碼可維護性`: 使用高階API減少了低階寄存器操作的錯誤風險,提升了代碼的可靠性和可維護性。

## 二. 直接寫程式碼控制寄存器
1. **頭文件**:
為了方便開發者,STM32 提供了豐富的標頭文件(如 `stm32f07xx.h`的位置
),**文件中定義了各個外設寄存器的地址和位段**。
> 根據不同版子可能有不同名稱的頭文件。
`stm32f07xx.h`的位置

2. **定義寄存器地址**:
每個外設都有對應的一組寄存器,這些寄存器在記憶體中都有固定的地址。STM32 微控制器使用的是記憶體映射的 I/O(Memory-mapped I/O),這意味著寄存器的位置可以像存取記憶體一樣被讀寫。在手冊中有明確規定具體寄存器映射地址,在2-3章節有一個**Memory map**表格,如下

在`stm32f07xx.h` 頭文件中有明確定義寄存器的地址,舉GPIO為例子
```c
#define PERIPH_BASE 0x40000000UL /*!< Peripheral base address in the alias region */
```
```c
/*!< Peripheral memory map */
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000UL)
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000UL) //0x40000000 + 0x00020000 = 0x4002000 就是上圖中AHB1的首地址
#define AHB2PERIPH_BASE (PERIPH_BASE + 0x10000000UL)
```
```c
#define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000UL)
#define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400UL)
#define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800UL)
#define GPIOD_BASE (AHB1PERIPH_BASE + 0x0C00UL)
#define GPIOE_BASE (AHB1PERIPH_BASE + 0x1000UL)
#define GPIOF_BASE (AHB1PERIPH_BASE + 0x1400UL)
#define GPIOG_BASE (AHB1PERIPH_BASE + 0x1800UL)
#define GPIOH_BASE (AHB1PERIPH_BASE + 0x1C00UL)
#define GPIOI_BASE (AHB1PERIPH_BASE + 0x2000UL)
```
4. **寄存器結構體**:
在 STM32 的`stm32f407.h`頭文件中,**外設寄存器通常會被定義為結構體**。
有最大的兩個好處
`易於訪問和操作`: 使用結構體可以通過成員變量直接訪問寄存器,** **。
`提高代碼可讀性`: 結構體成員名稱通常與寄存器名稱一致,使得代碼更加清晰,容易理解每個操作對應的硬體功能。這些結構體包含了該外設的所有寄存器。例如,GPIO 外設在手冊中`8.4 GPIO registers`有如下定義:










**Q:為什麼底下偏移量只有0x04 怎麼能表示32位,應該是0x0400?**
Ans: 因為STM32 微控制器中的地址是以**字節(Byte)為單位來尋址的**。這意味著每個地址指向一個字節大小的存儲
空間。所以32位剛好等於4個Bytes。
> 一個Byte 等於8個bits 4個byte 32bits 故等於0x04
底下的寄存器結構體在手冊中哪裡找的到? 手冊中收尋 **register map**

```c
/**
* @brief General Purpose I/O
*/
typedef struct
{
__IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
__IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
__IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
__IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
__IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
__IO uint32_t BSRR; /*!< GPIO port bit set/reset register, Address offset: 0x18 */
__IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */
} GPIO_TypeDef;
```
**寄存器上每個位元的定義和操作**:
底下是GPIO 上ODR 寄存器上的位定義,`stm32f407.h`頭文件中**也定義了每個寄存器上的每一位操作**,所以也導致頭文件中有一萬五千多行。
```cpp
/****************** Bits definition for GPIO_ODR register *******************/
#define GPIO_ODR_OD0_Pos (0U)
#define GPIO_ODR_OD0_Msk (0x1UL << GPIO_ODR_OD0_Pos) /*!< 0x00000001 */
#define GPIO_ODR_OD0 GPIO_ODR_OD0_Msk
#define GPIO_ODR_OD1_Pos (1U)
#define GPIO_ODR_OD1_Msk (0x1UL << GPIO_ODR_OD1_Pos) /*!< 0x00000002 */
#define GPIO_ODR_OD1 GPIO_ODR_OD1_Msk
#define GPIO_ODR_OD2_Pos (2U)
#define GPIO_ODR_OD2_Msk (0x1UL << GPIO_ODR_OD2_Pos) /*!< 0x00000004 */
#define GPIO_ODR_OD2 GPIO_ODR_OD2_Msk
#define GPIO_ODR_OD3_Pos (3U)
#define GPIO_ODR_OD3_Msk (0x1UL << GPIO_ODR_OD3_Pos) /*!< 0x00000008 */
#define GPIO_ODR_OD3 GPIO_ODR_OD3_Msk
#define GPIO_ODR_OD4_Pos (4U)
#define GPIO_ODR_OD4_Msk (0x1UL << GPIO_ODR_OD4_Pos) /*!< 0x00000010 */
#define GPIO_ODR_OD4 GPIO_ODR_OD4_Msk
#define GPIO_ODR_OD5_Pos (5U)
#define GPIO_ODR_OD5_Msk (0x1UL << GPIO_ODR_OD5_Pos) /*!< 0x00000020 */
#define GPIO_ODR_OD5 GPIO_ODR_OD5_Msk
#define GPIO_ODR_OD6_Pos (6U)
#define GPIO_ODR_OD6_Msk (0x1UL << GPIO_ODR_OD6_Pos)
```
5. **寄存器操作**:
stm32f4xx.h頭文件為STM32F4系列微控制器提供了配置選項,定義了裝置型號選擇和CMSIS版本號,包含對寄存器基本操作的宏,並包含選擇性使用HAL庫的支持
通過這些指標,開發者可以很方便地讀寫寄存器。例如,配置 GPIOA 的某個引腳為輸出模式:
```c
GPIOA->MODER |= (1 << (2 * pin_number));
```
6. **位操作**:
為了更精確地控制寄存器中的每一位,常常會使用位操作符。以下是一些常見的操作:
- **設置某位**:`GPIOA->ODR |= (1 << pin_number);`
- **清除某位**:`GPIOA->ODR &= ~(1 << pin_number);`
- **切換某位**:`GPIOA->ODR ^= (1 << pin_number);`
- **讀取某位**:`uint32_t bit_status = (GPIOA->IDR & (1 << pin_number)) >> pin_number;`
透過這種方式,開發者可以精確控制 STM32 微控制器的各種外設功能,進行高效的硬體操作。
# 如何設置寄存器內部的值
## 位操作例子1
補充: stm32f4xx.h 裡面可能會看到以下代碼:
```c
#define SET_BIT(REG, BIT_MASK) ((REG) |= (BIT_MASK))
```
**實際使用**
```c
#define SET_BIT(REG, BIT_MASK) ((REG) |= (BIT_MASK))
uint32_t myRegister = 0x00000000; // 初始值為0
SET_BIT(myRegister, 0x00000004); // 設置第三位(從0開始計數)
```
**步驟如下:**
1. `myRegister` 的初始值是 `0x00000000`,即所有位都為0。
2. `BIT_MASK` 是 `0x00000004`,即**二進制的** `0000 0100`。
3. `SET_BIT(myRegister, 0x00000004)` 展開後是 `myRegister |= 0x00000004`。
4. 按位或操作的結果將是:
```c
myRegister: 0000 0000
BIT_MASK: 0000 0100
---------------------
結果: 0000 0100
```
即 `myRegister` 的第三位被設置為1,其他位保持不變。
結果 `myRegister` 的值將是 `0x00000004`。
### 應用場景
這種宏在操作硬體寄存器時非常有用,特別是設置特定位元時。例如,在嵌入式系統中,你可能需要設置 GPIO 引腳的特定位來控制輸出或配置中斷,這種宏可以簡化操作。
### 總結
`SET_BIT` 宏通過按位或操作設置寄存器 `REG` 中由 `BIT_MASK` 指定的位。這是一種常用的位操作技巧,在嵌入式系統開發中非常常見,用於設置寄存器中特定位元以控制硬體行為。
## 位操作例子2
**實際使用**
```c
#define GPIO_LCKR_LCK9_Pos 9U
#define GPIO_LCKR_LCK9_Msk (0x1UL << GPIO_LCKR_LCK9_Pos)
```
> 上面這兩行代碼從stm32f407xx.h 內部擷取出來
**這會展開為:**
```c
#define GPIO_LCKR_LCK9_Msk (0x1UL << 9)
```
現在,我們來看看這是如何應用的。例如,要設置第 9 位,我們可以使用這樣的操作:
```c
uint32_t reg = 0x00000000; // 初始值
reg |= GPIO_LCKR_LCK9_Msk; // 設置第 9 位
```
讓我們詳細分析這一步:
1. `GPIO_LCKR_LCK9_Msk` 計算結果是 `0x00000200`。
2. `reg |= GPIO_LCKR_LCK9_Msk` 展開為:
```c
reg = reg | 0x00000200;
```
假設 `reg` 的初始值是 `0x00000000`:
```c
reg: 0000 0000 0000 0000 0000 0000 0000 0000
BIT_MASK: 0000 0000 0000 0000 0000 0010 0000 0000
----------------------------------------------
結果: 0000 0000 0000 0000 0000 0010 0000 0000
```
最終 `reg` 的值將是 `0x00000200`,表示第 9 位被設置為 1。
### 總結
這段代碼 `0x1UL << GPIO_LCKR_LCK9_Pos` 用於生成一個位掩碼,這個掩碼只有第 9 位是 1,其餘位都是 0。這種位掩碼常用於設定或清除寄存器中的特定位元,使得我們可以方便地操作硬體寄存器中的單獨位元。
::: warning
記住在單片機中,所有位操作都要將16進制轉換成2進制下去操作
:::
## 位操作例子3 清除某位
以下是一行代碼 `pGPIOx->ODR &= ~(1 << PinNumber);` 的詳細解釋:
### 代碼說明
```c
pGPIOx->ODR &= ~(1 << PinNumber);
```
這行代碼的目的是將指定的 GPIO 引腳設置為低電平(0)。下面是對該代碼的分步解釋和示例。
### 分步解釋
1. `1 << PinNumber`:
- 將數值 `1` 左移 `PinNumber` 位。例如,若 `PinNumber` 為 3,則結果是 `0000 1000`(二進制表示),即數值 8。
2. `~(1 << PinNumber)`:
- 對上述結果進行按位取反。例如,對 `0000 1000` 取反得到 `1111 0111`(二進制表示),即數值 247。
3. `pGPIOx->ODR &= ~(1 << PinNumber)`:
- 對寄存器 ODR 的當前值與上述取反結果進行按位與操作。例如,假設 `pGPIOx->ODR` 的當前值為 `1111 1111`,與 `1111 0111` 進行按位與操作得到 `1111 0111`,即將指定的引腳(第 3 位)設置為 0。
### 示例
假設我們要將 GPIO 引腳 3 設置為低電平,且 `pGPIOx->ODR` 的初始值為 `0xFF`(即所有引腳都是高電平),那麼執行這行代碼的過程如下:
1. **初始值**:
- `pGPIOx->ODR` = `0xFF` = `1111 1111`(二進制表示)
2. **左移操作**:
- `1 << 3` = `0000 1000`
3. **取反操作**:
- `~(0000 1000)` = `1111 0111`
4. **按位與操作**:
- `1111 1111`(原值)
- `1111 0111`(取反值)
- 結果:`1111 0111`
最後,`pGPIOx->ODR` 的值變為 `1111 0111`,即引腳 3 被設置為低電平,其餘引腳保持不變。
## 位操作例子3 設置某位
代碼說明
```c
pGPIOx->ODR |= (1 << PinNumber);
```
這行代碼的目的是將指定的 GPIO 引腳設置為高電平(1)。下面是對該代碼的分步解釋和示例。
分步解釋
```cpp
1 << PinNumber://看到位移符號(前<<後) 一定是 前(值)面往後面(腳位)移
```
將數值 1 左移 PinNumber 位。例如,若 PinNumber 為 3,則結果是 0000 1000(二進制表示),即數值 8。
```cpp
pGPIOx->ODR |= (1 << PinNumber):
```
對寄存器 ODR 的當前值與上述結果進行按位或操作。例如,假設 pGPIOx->ODR 的當前值為 `0000 0000`,與 `0000 1000` 進行按位或操作得到 `0000 1000`,即將指定的引腳(第 3 位)設置為 1。
示例
假設我們要將 GPIO 引腳 3 設置為高電平,且 pGPIOx->ODR 的初始值為 `0x00`(即所有引腳都是低電平),那麼執行這行代碼的過程如下:
初始值:
pGPIOx->ODR = `0x00`= `0000 0000`(二進制表示)
左移操作:
1 << 3 = 0000 1000
按位或操作:
```cpp
0000 0000(原值)
0000 1000(左移結果)
結果:0000 1000
```
最後,pGPIOx->ODR 的值變為 0000 1000,即引腳 3 被設置為高電平,其餘引腳保持不變。
## 位操作例子3 反轉某位
代碼說明
```c
pGPIOx->ODR ^= (1 << PinNumber);
```
這行代碼的目的是反轉指定的 GPIO 引腳的狀態。如果該引腳當前是高電平(1),則將其設置為低電平(0);如果當前是低電平(0),則將其設置為高電平(1)。下面是對該代碼的分步解釋和示例。
### 分步解釋
#### `1 << PinNumber`:
將數值 1 左移 `PinNumber` 位。例如,若 `PinNumber` 為 3,則結果是 `0000 1000`(二進制表示),即數值 8。
#### `pGPIOx->ODR ^= (1 << PinNumber)`:
對寄存器 `ODR` 的當前值與上述結果進行按位異或操作。例如,假設 `pGPIOx->ODR` 的當前值為 `0000 0000`,與 `0000 1000` 進行按位異或操作得到 `0000 1000`,即將指定的引腳(第 3 位)反轉。
### 示例
假設我們要反轉 GPIO 引腳 3 的狀態,且 `pGPIOx->ODR` 的初始值為 `0x08`(即引腳 3 為高電平),那麼執行這行代碼的過程如下:
#### 初始值:
`pGPIOx->ODR` = `0x08` = `0000 1000`(二進制表示)
#### 左移操作:
`1 << 3` = `0000 1000`
#### 按位異或操作:
```cpp
0000 1000(原值)
0000 1000(左移結果)
結果:0000 0000
```
最後,`pGPIOx->ODR` 的值變為 `0000 0000`,即引腳 3 被設置為低電平,其餘引腳保持不變。
#### 另一示例
假設我們再次執行這行代碼,此時 `pGPIOx->ODR` 的初始值為 `0x00`(即引腳 3 為低電平),那麼執行這行代碼的過程如下:
#### 初始值:
`pGPIOx->ODR` = `0x00` = `0000 0000`(二進制表示)
#### 左移操作:
`1 << 3` = `0000 1000`
#### 按位異或操作:
```cpp
0000 0000(原值)
0000 1000(左移結果)
結果:0000 1000
```
最後,`pGPIOx->ODR` 的值變為 `0000 1000`,即引腳 3 被設置為高電平,其餘引腳保持不變。
**總結來說,這行代碼每次執行都會反轉指定引腳的狀態。如果引腳是高電平,則變為低電平;如果是低電平,則變為高電平。**