---
# System prepended metadata

title: LED實例看GPIO與HAL庫
tags: [STM32]

---

[TOC]

<font color="red">請先照著步驟設定CubeMX檔，下面會接著對照生成檔案與CubeMX的設定分別出現於何處以及看看HAL庫</font>

# LED閃爍實例

## 功能描述
- 本次以主迴圈方式實現LED的閃爍控制


## 設定CubeMX
- 新建專案時點選**要**initialize
- 在圖形化接腳設定中設定板上的PD12~PD15為輸出，在板上已連接至LED。
    - 可以善用右下角的腳位搜尋
    ![image](https://hackmd.io/_uploads/rkUAPDX2A.png)
- PD12~15的輸出設定皆相同
    ![image](https://hackmd.io/_uploads/Hku0uDQnR.png)
- 綠色代表有做設定，這邊把沒有要用的I2C1和SPI1先disable
    ![image](https://hackmd.io/_uploads/BkT-FR7nA.png)![image](https://hackmd.io/_uploads/B1G_g1NnA.png)

- 時鐘樹的配置先不用動，依照原本預設的即可
- ![image](https://hackmd.io/_uploads/Hyp3YA72A.png)

## 程式添加
- 找到main函式，在main中找到while(1)迴圈，這是我們的主迴圈，要在這添加程式
- 在while內的user begin之間添加如下程式
``` c++=
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1){
    /* USER CODE END WHILE */


    /* USER CODE BEGIN 3 */
    HAL_GPIO_WritePin(GPIOD, LD3_Pin, GPIO_PIN_RESET);
    HAL_Delay(1000);
    HAL_GPIO_WritePin(GPIOD, LD3_Pin, GPIO_PIN_SET);
    HAL_Delay(1000);
}
/* USER CODE END 3 */
```

## 燒錄
- 使用Keil燒錄程式，將會看到LD3的LED閃爍

# 程式碼架構

## CubeMX生成程式
- 除了while之外，其餘的程式碼，包含所有main.c檔案以外的.c檔都是CubeMX生成的
- 直接看到main函式中，在infinite loop之前，呼叫了一些帶有 INIT 尾綴的函式，這些都是初始設定的部分，如果你使用的編輯器有跳轉功能，你可以到那個函式去看看
    - HAL_INIT啟用了中斷群組、系統時鐘和底層硬體
    - SystemClock_Config對應了你在CubeMX時鐘樹的設定
    - MX_GPIO_Init對應了你在CubeMX做的GPIO設定
- 在MX_GPIO_Init中找到有關LED腳位的設定如下
	``` c++=
	/*Configure GPIO pin Output Level */
	HAL_GPIO_WritePin(GPIOD, LD4_Pin|LD3_Pin|LD5_Pin|LD6_Pin
	                          |Audio_RST_Pin, GPIO_PIN_RESET);
	
	/*Configure GPIO pins : PDPin PDPin PDPin PDPin
	                           PDPin */
	  GPIO_InitStruct.Pin = LD4_Pin|LD3_Pin|LD5_Pin|LD6_Pin
	                          |Audio_RST_Pin;
	  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	  GPIO_InitStruct.Pull = GPIO_NOPULL;
	  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	  HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
	```
    - 可以看到這些設定都是在CubeMX中做的設定
    - 其中User Label的部分是讓使用者可以自定義腳位名稱，其實可以在main.h看到以下define，這些腳位(pin)原本在HAL庫的名稱是GPIO_PIN_XX 對應 埠(port，端口) GPIOX 
        ``` c++=
        #define LD4_Pin GPIO_PIN_12
        #define LD4_GPIO_Port GPIOD
        
        #define LD3_Pin GPIO_PIN_13
        #define LD3_GPIO_Port GPIOD
        
        #define LD5_Pin GPIO_PIN_14
		#define LD5_GPIO_Port GPIOD
        
		#define LD6_Pin GPIO_PIN_15
		#define LD6_GPIO_Port GPIOD
        ```
- 這邊帶著大家看軟體設定如何對應到程式，如果你的程式是需要在主迴圈中不斷更改設定的，可以參考設定的程式部分如何更動

## STM 的 GPIO設定
- 可參照STM32 HAL庫開發時戰指南 第7章
- GPIO output設定描述
	- GPIO output level : 初始化後默認的輸出狀態
	- GPIO mode : 推挽或開漏，可見[這篇](https://www.youtube.com/watch?v=dgIyNq3TB_M)
	- GPIO Pull-up/Pull-down : 
        輸出其實沒必要設上下拉電阻，但還是有一種可能是你在主程式中常做GPIO模式的切換，見[討論](https://community.st.com/t5/stm32-mcus-products/output-push-pull-with-pull-up-or-pull-down-capability/td-p/575348)
	- Max. output speed : 
	    [GPIO 速度區別](https://blog.csdn.net/tianshi_1988/article/details/53317429)
    - User Label : 自定義名稱
- GPIO input 描述設定
    - input的話只有上下拉電阻與User label需要自訂
    - 上或下拉電阻用途可見[微處理器基礎](https://hackmd.io/6Rj1vNC5S2ya2y2MNScFbg)的GPIO部分

## STM的GPIO操作
- 首先從本例點亮LED的函式HAL_GPIO_WritePin看起，我們到他所在的.h或.c檔中看看
- 在stm32f4xx_hal_gpio.c可以看到這個函式的程式，其餘跟GPIO相關的函式其實也放在這個檔案，在檔案最上面也有GPIO Peripheral features、How to use this driver 的描述，可以大略知道有哪些函式可以用
- 接著看到函式定義，以HAL_GPIO_WritePin來說，它的使用與定義如下
    ``` c++=
    // 庫定義
    // @stm32f4xx_hal_gpio.c
    
    
    /**
  	* @brief  Sets or clears the selected data port bit.
  	*
  	* @note   This function uses GPIOx_BSRR register to allow atomic read/modify
  	*         accesses. In this way, there is no risk of an IRQ occurring between
  	*         the read and the modify access.
  	*
  	* @param  GPIOx where x can be (A..K) to select the GPIO peripheral for STM32F429X device or
  	*                      x can be (A..I) to select the GPIO peripheral for STM32F40XX and STM32F427X devices.
  	* @param  GPIO_Pin specifies the port bit to be written.
  	*          This parameter can be one of GPIO_PIN_x where x can be (0..15).
  	* @param  PinState specifies the value to be written to the selected bit.
  	*          This parameter can be one of the GPIO_PinState enum values:
  	*            @arg GPIO_PIN_RESET: to clear the port pin
  	*            @arg GPIO_PIN_SET: to set the port pin
  	* @retval None
  	*/
	void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
	{
	  /* Check the parameters */
	  assert_param(IS_GPIO_PIN(GPIO_Pin));
	  assert_param(IS_GPIO_PIN_ACTION(PinState));
	
	  if(PinState != GPIO_PIN_RESET)
	  {
	    GPIOx->BSRR = GPIO_Pin;
	  }
	  else
	  {
	    GPIOx->BSRR = (uint32_t)GPIO_Pin << 16U;
	  }
	}
    ```
    
    ``` C++=
    // 函式使用範例
    HAL_GPIO_WritePin(GPIOD, LD3_Pin, GPIO_PIN_RESET);
    ```
    - 針對HAL庫GPIO所有結構體的命名與內部結構可以看看HAL庫實戰開發內容
    - 可以看到函數註解有告訴我們以下事情
        - GPIOx表示不同的端口
        - GPIO_Pin表示要被更動的端口的某個bit，也就是腳位
        - PinState有兩種，GPIO_PIN_RESET表示clear，也就是使腳位為0(低電位)，GPIO_PIN_SET相反，使該腳位輸出為1(高電位)

- 在stm32f4xx_hal_gpio.c中常用的GPIO操作函式 : 
    - **HAL_GPIO_ReadPin** : 腳位做為輸入時，讀取腳位
    - **HAL_GPIO_WritePin** : 腳位做為輸出時，設定腳位為高或低電平輸出
    - **HAL_GPIO_TogglePin** : 腳位做為輸出時，翻轉腳位輸出(反向運算)

## STM 的Delay
- 延遲使用HAL_Delay做延遲，參數單位為毫秒

# 總結
- HAL庫針對STM各種微處理器功能有一大堆函式可以使用，除了上網查，問GPT外，還是希望各位試著培養閱讀與查找源碼的能力，當上網查不到或是GPT也解決不了時還是只能靠自己!
- 作為微處理器開發用，HAL庫已經非常方便了，幾乎已經不再需要針對各個暫存器的直接操作，但還是需要知道實際操作都是對暫存器做處理的