# STM32 HAL Driver Note 這裡會存放 STM32 HAL Driver 的相關程式筆記。 # 目錄 [TOC] # Intro - HAL Library(Hardware Abstraction Layer,硬體抽象層函式庫)。 - 抽離硬體暫存器的概念,可以用於易讀的程式碼方便開發應用,也可以方便的移植程式碼到其他平台。 ![](https://hackmd.io/_uploads/BJylIXmLa.png) # GPIO ## Enum - GPIO_PinState:腳位狀態 ```c typedef enum { GPIO_PIN_RESET = 0, GPIO_PIN_SET } GPIO_PinState; ``` ## Output ```c // 設置該腳位為高電位 HAL_GPIO_WritePin(PORT, PIN, GPIO_PIN_SET); // 設置該腳位為低電位 HAL_GPIO_WritePin(PORT, PIN, GPIO_PIN_RESET); // 反向該腳位的電位 (低 => 高, 高 => 低) HAL_GPIO_TogglePin(PORT, PIN); ``` ## Input ```c // 讀取該腳位電位狀態後反回GPIO_PinState HAL_GPIO_ReadPin(PORT, PIN); ``` ## Interrupt 1. 不建議在中斷向量呼叫任何阻斷系統/GPIO操作的函式,如`HAL_Delay()`。 - 中斷內的程式需要快速完成,不要影響到主程式的運作。 - 可以更改變數值,在主迴圈中實現剩下的邏輯。 2. 若遇到多個向量中斷得同時進行,需要設定優先順序 (Preemption Priority)。 - 數字越高,中斷優先權越高。 ### Steps 1. 於STM32CubeMX中,調整按鈕的腳位為EXIT_XX_XX (外部中斷)。 ![](https://hackmd.io/_uploads/BkJ2Oob-6.png) 2. 到`System Core` -> `GPIO`,設定該腳位的中斷觸發方法。 1. 上升沿觸發 (Rising Edge)。 2. 下降沿觸發 (Falling Edge)。 3. 上升與下降沿同時觸發 (Rising / Falling Edge)。 ![](https://hackmd.io/_uploads/HkqvtsZZa.png) 3. 若未在電路中實作上拉/下拉電路,需配置GPIO的Pull Up/Down。 - 有的話則選`No Pull Up / Pull Down`。 ![](https://hackmd.io/_uploads/ryB_YiZZp.png) 4. 到`System Core` -> `NVIC`,設定該腳位的中斷向量函式。 ![](https://hackmd.io/_uploads/ryUiYiZ-a.png) 5. 重新生成程式。 ## Code - 主程式宣告:main.h ```c /* USER CODE BEGIN EC */ extern GPIO_PinState _btn_status; /* USER CODE END EC */ ``` - 主程式:main.c ```c /* USER CODE BEGIN PV */ GPIO_PinState _btn_status = GPIO_PIN_RESET; /* USER CODE END PV */ // 以下略... /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin); // 當按鈕按下,更換Delay為500ms,再次按下則更換為2000ms。 if (_btn_status == GPIO_PIN_SET) HAL_Delay(500); else HAL_Delay(2000); } /* USER CODE END 3 */ ``` - 中斷:stm32xxx_it.c ```c /** * @brief This function handles EXTI line[15:10] interrupts. */ void EXTI15_10_IRQHandler(void) { /* USER CODE BEGIN EXTI15_10_IRQn 0 */ // reverse flag _btn_status = (_btn_status == GPIO_PIN_SET) ? GPIO_PIN_RESET : GPIO_PIN_SET; /* USER CODE END EXTI15_10_IRQn 0 */ HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13); /* USER CODE BEGIN EXTI15_10_IRQn 1 */ /* USER CODE END EXTI15_10_IRQn 1 */ } ``` # UART ## Steps 1. `Connectivity -> USARTx / UARTx` - `Mode`:Asynchoronous (非同步/異步) - 由各自系統的時鐘計算頻率 - `BaudRate`:傳輸速率,單位為 Bit/s - `Word Length`:字元長度,預設都是8位元 - `Parity`:校驗碼,預設為None - `Stop Bits`:停止位元,預設為1 ![image.png](https://hackmd.io/_uploads/r1ijJHr76.png) 2. `Configuration -> NVIC Settings` - 可以根據需求開啟/關閉NVIC中斷 - 也可以到`System Core -> NVIC`設定啟用與優先級 ![image.png](https://hackmd.io/_uploads/S1tOeHr7a.png) ## Code ### Init ```c /* Initialize all configured peripherals */ MX_USARTx_UART_Init(); ``` ### Write - No Interrupt - 傳輸訊息的Buffer資料型別必須為`uint8_t`陣列。 - 長度的資料型別為`uint16_t`整數。 ```c HAL_UART_Transmit(&huart1, buffer, 4); ``` ### Write - With Interrupt - HAL_UART_TxCpltCallback為UART TX的中斷回調函式。 ```c HAL_UART_Transmit_IT(&huart1, buffer, 4); // Send Measure Message To PC void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { // Do Smth... } ``` # Timer - 分成5個Timer (F1/F3/F4/F7) - **Basic Timer** (基本定時器) - 只有上數模式 (Up) - 可以自動重載 - **Low Power Timer** (低功耗定時器) - **General-Purpose Timers** (通用定時器) - 上數模式、下數模式、上/下數自動重置模式 - 四個獨立通道 1. 輸入捕獲 2. 輸出比較 3. PWM波型產生 4. 一次性波形輸出 - **Advanced-Control Timers** (進階控制定時器) - 部分同通用定時器 - **High-Resolution Timers** (高解析度定時器) - 217ps高解析度 ## 觸發時間計算 $$ T_{out}=\frac{PSC * ARR}{Fclk} $$ - Tout:觸發時間,單位為秒(s)。 - PSC:預分頻係數,最高為65535。 - ARR:重載計數器數值,最高為65535。 - Fclk:系統時鐘頻率,根據OSC而定,單位為Hz。 ## Basic Timer - 系統頻率為32MHz,以每秒5Hz的頻率 (200ms),進入中斷控制LED亮滅 - $0.2s * 32MHz = 6400000$ - $6400000 / 1000 = 6400 (Preload)$ ### Steps 1. 開啟TIM6/TIM7的定時器並設定如下: ![image.png](https://hackmd.io/_uploads/SJLIv2N7a.png) - PSC設定1000 - Counter Perlod(Preload)設定6400 - 開啟auto-reload preload 2. 到NVIC Settings,啟用NVIC TIM6全域中斷 ![image.png](https://hackmd.io/_uploads/ByGo_hEm6.png) ## Code ### Init ```c /* Initialize all configured peripherals */ MX_TIM6_Init(); ``` - 開啟中斷 - 這步沒做,中斷就不會開啟 ```c /* USER CODE BEGIN 2 */ HAL_TIM_Base_Start_IT(&htim); // Start Timer Interrupt /* USER CODE END 2 */ ``` - TIM中斷函數 - HAL_TIM_PeriodElapsedCallback為 Timer 在 Up 模式下,計數器到達Period數字時的中斷回調函式。 - `htim->Instance`:發生中斷的Timer Instance,可以用來判斷是哪個Timer產生了中斷。 ```c /* USER CODE BEGIN 4 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { // Check Callback Is Generated By Timer X if (htim->Instance == TIMX) { // Toggle LED HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } } /* USER CODE END 4 */ ``` # ADC > TODO: 整理得好看一點 ## Steps ![image.png](https://hackmd.io/_uploads/SJUXmHH7p.png) ![image.png](https://hackmd.io/_uploads/rycMXBHQ6.png) ![image.png](https://hackmd.io/_uploads/ByQV7SrX6.png) ## Code ### Init ```c /* Initialize all configured peripherals */ MX_ADC1_Init(); ``` ### Polling ```c HAL_ADC_Start(&hadc1); // Open ADC Conversion HAL_ADC_PollForConversion(&hadc1, 1); // Wait Conversion Complete or Timeout adc_val = HAL_ADC_GetValue(&hadc1); // Get ADC Value ``` ### Interrupt ```c void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { /* * Check Callback Is Generated By Specific ADC Unit */ if (hadc->Instance == ADC1) { adc_val = HAL_ADC_GetValue(hadc); // Get ADC Value adc_volt = ((3.3 / 4096) * adc_val) * 1000; // Calculate Convert Volt (Format: mV, FK407MK2 VDDA is 3.3V) HAL_ADC_Stop_IT(hadc); // Stop ADC Interrupt Convert } } ``` # CAN - 主要應用在車內訊息與IoT物聯網之間的傳輸。 - 有高可靠性與不錯的抗干擾、糾錯能力。 ![image](https://hackmd.io/_uploads/B1mYRnNHle.png) ## CAN 2.0 vs FDCAN | 項目 | CAN2.0 | FDCAN | | ------------ | ----------------- | -------------------------------- | | 最大資料長度 | 8 Bytes | 64 Bytes | | 傳輸速率 | 固定速率 ≤ 1 Mb/s | 仲裁 ≤ 1 Mb/s,資料階段可高達 8× | | CRC | 15-bit | 17/21-bit (以Payload 長度而定) | | Control Byte | RTR/IDE | RTR/IDE/EDL/BRS/ESI | | 過濾機制 | 傳統標準器/mask | 用RAM分割,可靈活配置 | | 硬體特性 | 簡易 | Message RAM、FIFO、延遲補償、CCU | | 相容性 | 不支援 | 向下相容 CAN 2.0 | * EDL:區分是否為 FD 幀 * BRS:控制資料階段速率 * ESI:顯示錯誤狀態(error-active 或 passive) ### Packet Structure - CAN 2.0 ![](https://www.systemaccess.com.tw/upload/web/Technical/4(1).jpg) - FDCAN ![](https://www.systemaccess.com.tw/upload/web/Technical/6(1).jpg) ### Filter Config - CAN 2.0 (HAL) ```c CAN_FilterTypeDef canFilterConfig; canFilterConfig.FilterActivation = CAN_FILTER_ENABLE; canFilterConfig.FilterBank = 0; // which filter bank to use from the assigned ones canFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0; canFilterConfig.FilterIdHigh = 0x0; canFilterConfig.FilterIdLow = 0x0; canFilterConfig.FilterMaskIdHigh = 0x0; canFilterConfig.FilterMaskIdLow = 0x0; canFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; canFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; canFilterConfig.SlaveStartFilterBank = 0; // how many filters to assign to the CAN1 (master can) if (HAL_CAN_ConfigFilter(&HCAN, &canFilterConfig) != HAL_OK) { Error_Handler(); } ``` - FDCAN (HAL) - 要設定HAL_FDCAN_ConfigGlobalFilter (FDCAN全域Filter),否則FDCAN無法正常運作。 - **hfdcan**:要設定的CAN Handle - **StdIdFilter**:標準 ID 如何處理其他非 filter 範圍的 frame - **ExtIdFilter**:延伸 ID 的處理 - **StdRmtFilter**:標準 Remote frame 處理 - **ExtRmtFilter**: 延伸 Remote frame 處理 ```c FDCAN_FilterTypeDef canFilterConfig; canFilterConfig.idType = FDCAN_STANDARD_ID; canFilterConfig.filterIndex = 0; canFilterConfig.filterType = FDCAN_FILTER_MASK; canFilterConfig.filterConfig = FDCAN_FILTER_TO_RXFIFO0; canFilterConfig.filterID1 = 0x00000000; // Filter ID 1 canFilterConfig.filterID2 = 0x00000000; // Filter ID 2 canFilterConfig.rxBufferIndex = 0; // Use RX FIFO 0 HAL_FDCAN_ConfigFilter(&hfdcan1, &canFilterConfig); /** * Standard Filter Configuration, Need To Add This Line, Otherwise, FDCAN will not work properly * FDCAN_ACCEPT_IN_RX_FIFO0: Accepts messages in RX FIFO 0 (Std And Ext Messages) * FDCAN_FILTER_REMOTE: Accepts remote frames (Std And Ext Messages) */ HAL_FDCAN_ConfigGlobalFilter(&hfdcan1, FDCAN_ACCEPT_IN_RX_FIFO0, FDCAN_ACCEPT_IN_RX_FIFO0, FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE); ``` # Misc - HAL_Delay - 採用阻斷式 (讓卡住CPU一段時間),單位為ms。 ```c HAL_Delay(1000); // 延遲1000毫秒 = 1秒 ``` - NOP - 什麼事都不做,方便在Debug模式時設定中斷點。 ```c __NOP(); ```