---
# System prepended metadata

title: 【STM32】寄存器筆記(register)
tags: [Embedded]

---

# 【STM32】寄存器筆記(register)
<style>
table {
  word-break: normal !important;
}
</style>
###### tags: `Embedded`





# 1.什麼是寄存器？
寄存器是設計用來控制和配置各種外設和核心功能的。


# 2.為什麼重要？
因為所有單片機都是要透過程式**操作寄存器**去控制外設的電路。
![image](https://hackmd.io/_uploads/r1SWZcy_R.png)
以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減少了低階寄存器操作的錯誤風險，提升了代碼的可靠性和可維護性。
![image](https://hackmd.io/_uploads/Bksiyj1_A.png)
## 二. 直接寫程式碼控制寄存器
1. **頭文件**：
   為了方便開發者，STM32 提供了豐富的標頭文件（如 `stm32f07xx.h`的位置
   ），**文件中定義了各個外設寄存器的地址和位段**。
> 根據不同版子可能有不同名稱的頭文件。
`stm32f07xx.h`的位置
![螢幕擷取畫面 2024-07-13 134623](https://hackmd.io/_uploads/BkLVr5k_C.png)

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



![memory_map](https://hackmd.io/_uploads/BJ2Oad0vR.png)


 在`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`有如下定義：
 ![image](https://hackmd.io/_uploads/SJwPWKAvC.png)
![image](https://hackmd.io/_uploads/B1BuZYAvC.png)
![image](https://hackmd.io/_uploads/ByZKWK0vA.png)
![image](https://hackmd.io/_uploads/By-9WFAvC.png)
![image](https://hackmd.io/_uploads/B1BoWYCPR.png)
![image](https://hackmd.io/_uploads/SJ83bK0v0.png)
![image](https://hackmd.io/_uploads/HyZCWKCvR.png)
![image](https://hackmd.io/_uploads/SkiyzYCD0.png)
![image](https://hackmd.io/_uploads/ByteGKCD0.png)
![image](https://hackmd.io/_uploads/H1zZfKAwA.png)

**Q:為什麼底下偏移量只有0x04  怎麼能表示32位，應該是0x0400?**
Ans: 因為STM32 微控制器中的地址是以**字節（Byte）為單位來尋址的**。這意味著每個地址指向一個字節大小的存儲
空間。所以32位剛好等於4個Bytes。
> 一個Byte 等於8個bits 4個byte 32bits 故等於0x04

底下的寄存器結構體在手冊中哪裡找的到? 手冊中收尋 **register map** 
![image](https://hackmd.io/_uploads/H14u8UbuC.png)

```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 被設置為高電平，其餘引腳保持不變。

**總結來說，這行代碼每次執行都會反轉指定引腳的狀態。如果引腳是高電平，則變為低電平；如果是低電平，則變為高電平。**
























