Keil MDK STM32系列(四) 基於抽象外設庫HAL的STM32F401開發


Keil MDK STM32系列

概述

Windows下使用Keil MDK5進行 STM32F401 的開發和編譯, 配合ST-LINK工具進行燒錄, 使用硬件抽象庫HAL.

STM32F401硬件環境和連接

略, 與SPL環境相同

STM32F4 硬件抽象庫 STM32F4xx_HAL_Driver

直接下載 STM32CubeF4 MCU 固件開發包

  1. 前往 https://github.com/STMicroelectronics/STM32CubeF4
  2. 點擊Code -> Download ZIP
  3. 文件比較大, 有接近300M, 解壓備用

當前版本是v1.26.2.

ST硬件抽象庫HAL結構說明

STM32CubeF4\Drivers 目錄結構

├─BSP
├─CMSIS
│  ├─Core
│  │  ├─Include
│  │  └─Template
│  ├─Core_A
│  │  ├─Include
│  │  └─Source
│  ├─Device                    # 設備文件, 需要
│  │  └─ST
│  │      └─STM32F4xx
│  │          ├─Include
│  │          ├─Source
│  │          │  └─Templates
│  │          │      ├─arm
│  │          │      ├─gcc
│  │          │      └─iar
│  │          └─_htmresc
│  ├─docs
│  ├─DSP
│  ├─Include                   # 頭文件, 需要
│  ├─Lib
│  │  ├─ARM
│  │  ├─GCC
│  │  └─IAR
│  ├─NN
│  ├─RTOS
│  └─RTOS2
└─STM32F4xx_HAL_Driver         # 外設庫, 需要
    ├─Inc
    │  └─Legacy
    ├─Src
    │  └─Legacy
    └─_htmresc

按步驟手工創建項目

先組織好庫文件和目錄, 然后創建項目

創建目錄並填充文件

以下以名稱為test001的項目為例

  1. 創建工作目錄 test001
  2. 在工作目錄下創建 Drivers, User 2個目錄
  3. 從解壓后的標准外設庫中, 復制 Drivers\CMSIS 目錄到 Drivers, CMSIS 這個目錄下只需要保留 Device 和 Include 這兩個目錄, 其他目錄不需要
  4. 復制 Drivers\STM32F4xx_HAL_Driver 整個目錄到 Drivers
  5. User
    • 復制 Projects\STM32F401-Discovery\Templates\Src 下面的 stm32f4xx_hal_msp.c stm32f4xx_it.c 到這個目錄
    • 復制 Projects\STM32F401-Discovery\Templates\Inc 下面的 stm32f4xx_hal_conf.h stm32f4xx_it.h 到這個目錄
    • 修改 stm32f4xx_hal_conf.h 注釋掉不需要的模塊
    • 添加用戶代碼 main.c 和 main.h, 下面有示例代碼. 注意函數SystemClock_Config(), 不正確的配置會導致板子運行卡住,STLink無響應.

完成后的目錄結構是這樣的, 建議在文件系統中, 將Drivers下面的目錄和文件屬性設置成只讀, 避免開發中被誤改

test001>
├─Drivers
│  ├─CMSIS
│  │  ├─Device
│  │  │  └─ST
│  │  │      └─STM32F4xx
│  │  │          ├─Include
│  │  │          ├─Source
│  │  │          │  └─Templates
│  │  │          └─_htmresc
│  │  └─Include
│  └─STM32F4xx_HAL_Driver
│      ├─Inc
│      ├─Src
│      └─_htmresc
└─User

在Keil uVision5中創建項目

  1. Project -> New uVision Project, 選擇工作目錄 test001, 使用名稱test001, 保存
  2. 在彈出的對話框中, 選擇芯片型號, STM32F401CCU6 選擇芯片型號STM32F401CCUx, STM32F401CDU6 選擇 STM32F401CDUx
  3. 在后續的 Manage Run-Time Enviroment 對話框中什么都不選, 因為會在項目里自己管理庫文件

配置項目

在上面的步驟完成后, Keil MDK中就會顯示一個項目的初始結構, 目錄為 Project:test001, 以及一個 Target1

修改 Target 名稱以及添加源文件

在菜單中點擊 Project -> Manage -> Project Items, 或者直接在圖標欄中點擊紅黃綠品字形的圖標, 在彈出的對話框中

  1. 修改 project targets 名稱為 test001, 這個可以隨便改
  2. 編輯並添加 Groups, 最終會有以下 Groups
    • CMSIS
    • STM32F4xx_HAL_Driver
    • Startup
    • User

對每個group, 添加的文件為

  • CMSIS
    • 添加 Drivers\CMSIS\Device\ST\STM32F4xx\Source\Templates\system_stm32f4xx.c
  • STM32F4xx_HAL_Driver
    • 添加 Drivers\STM32F4xx_HAL_Driver\src 下面stm32f4xx_hal開頭的所有C文件, 除了以 _template.c 結尾的那幾個
    • 如果對 stm32f4xx_hal_conf.h 中的配置進行了修改, 對裁剪的部分也需要從這里移除掉
  • Startup
    • (F401CCU6)添加 Drivers\CMSIS\Device\ST\STM32F4xx\Source\Templates\arm 下面的 startup_stm32f401xc.s 文件
    • (F401CDU6)添加 Drivers\CMSIS\Device\ST\STM32F4xx\Source\Templates\arm 下面的 startup_stm32f401xe.s 文件
    • (F407VET6)添加的是 startup_stm32f407xx.s
  • User
    • 添加User\目錄下的C文件

修改項目包含路徑

在菜單中點擊Project -> Options for Target 'test001', 或者直接在圖標欄中點擊configure target option圖標, 在彈出的對話框中

  1. 定位到c/c++標簽頁
  2. Define: 這個是編譯參數, 寫入 USE_HAL_DRIVER 這里可以不指定 STM32F401xC/STM32F401xE , MDK已經自動指定了. 但是如果這里不指定的話, 代碼提示可能會有錯, 所以也可以加上, 加上的話, 與系統添加的一致就行.
  3. Include Paths: 這里是頭文件的包含路徑, 如果按上面的目錄結構組織的項目, 可以直接復制下面的配置
.\Drivers\CMSIS\Include;.\Drivers\CMSIS\Device\ST\STM32F4xx\Include;.\Drivers\STM32F4xx_HAL_Driver\Inc;.\Drivers\STM32F4xx_HAL_Driver\Inc\Legacy;.\User

在下面的 compiler control string 中可以查看完整的命令行

--c99 --gnu -c --cpu Cortex-M4.fp -D__MICROLIB -g -O3 --apcs=interwork --split_sections -I ./Drivers/CMSIS/Include -I ./Drivers/CMSIS/Device/ST/STM32F4xx/Include -I ./Drivers/STM32F4xx_HAL_Driver/Inc -I ./Drivers/STM32F4xx_HAL_Driver/Inc/Legacy -I ./User
-I./RTE/_Target_1
-IC:/Keil_v5/ARM/PACK/Keil/STM32F4xx_DFP/2.15.0/Drivers/CMSIS/Device/ST/STM32F4xx/Include
-IC:/Keil_v5/ARM/CMSIS/Include
-D__UVISION_VERSION="525" -DSTM32F401xE
-o .\Objects\*.o --omf_browse .\Objects\*.crf --depend .\Objects\*.d

調整配置文件

這個文件是.\User\stm32f4xx_hal_conf.h, 在里面可以設置外部振盪源頻率, 以及去掉不需要的外設模塊

設置外部振盪源頻率 改成自己開發板上晶振的頻率

#define HSE_VALUE    25000000U /*!< Value of the External oscillator in Hz */

對外設模塊進行裁剪 主要是把#define HAL_SPI_MODULE_ENABLED, #define HAL_UART_MODULE_ENABLED這部分當中不需要的注釋掉

STM32F401CCU6/STM32F401CDU6 示例代碼

下面的例子, 使用開發板自帶的led燈(PC13)實現間隔1秒的亮滅效果.
在User目錄下創建 main.h 和 main.c, 注意通過Keil MDK創建的時候, 要注意文件位置, 默認是放到項目根目錄的, 這里要改到User目錄下.

main.c

#include "main.h"

#define LED_PIN                        GPIO_PIN_13  // 指定PIN
#define LED_GPIO_PORT                  GPIOC        // 指定IO
#define LED_GPIO_CLK_ENABLE()          __HAL_RCC_GPIOC_CLK_ENABLE() // 指定啟用時鍾的IO

void LED_Init(void);
static void SystemClock_Config(void);
static void Error_Handler(void);

int main(void)
{
  HAL_Init();

  /* Configure the System clock to have a frequency of 84 MHz */
  SystemClock_Config();
  LED_Init();
  while (1)
  {
    HAL_GPIO_TogglePin(LED_GPIO_PORT, LED_PIN);
    HAL_Delay(1000);
  }
}

void LED_Init(void) {
  LED_GPIO_CLK_ENABLE();
  GPIO_InitTypeDef GPIO_InitStruct;
  GPIO_InitStruct.Pin = LED_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
  HAL_GPIO_Init(LED_GPIO_PORT, &GPIO_InitStruct);
}

/**
* 對應STM32Cube 1.26.x 的時間初始化方法 
* *) 使用外部高速晶振(25MHz), 不使用外部低速晶振(32.768KHz)
* *) 不經PLL,不分頻, 直接接入SYSCLK->PHBPrescaler=1->AHB,APB1,APB2...
*/
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSE;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
}

void Error_Handler(void)
{
  __disable_irq();
  while (1);
}

#ifdef  USE_FULL_ASSERT

void assert_failed(uint8_t* file, uint32_t line)
{ 
  while (1);
}
#endif

main.h

#ifndef __MAIN_H
#define __MAIN_H

#include "stm32f4xx_hal.h"

#endif /* __MAIN_H */

編譯

按F7執行編譯

燒錄

在菜單中點擊Project -> Options for Target 'test001', 或者直接在圖標欄中點擊configure target option圖標, 在彈出的對話框中

  1. 定位到 Debug 標簽頁
  2. Use 選擇 ST-Link Debuger, 點擊Settings
  3. 如果 Debug Adapter 里是空白沒有顯示ST-LINK/V2, 去windows設備管理器看下設備是否正常
  4. 切換到 Flash Download 標簽, 勾選Reset and Run
  5. 點擊 Download 按鈕, 或者按F8, 進行燒錄

https://electronics.stackexchange.com/questions/204996/stm32-st-link-cannot-connect-to-mcu-after-successful-programming

if you're using stmcubemx, u need to configure the serial wire on stmcube pinout tab. on pinout tab, click SYS and change debug option to serial wire. it fix my problem, and maybe your problem too.

硬件抽象庫HAL的代碼

1. 裁剪不必要的代碼

完整的外設庫, 完整編譯一次需要時間很長, 在使用中可以排除掉不需要的內容. 一個最小化的開發中如果只啟用SWD, UART, SPI, 需要包含的庫文件有

stm32f4xx_hal.c
stm32f4xx_hal_cortex.c
stm32f4xx_hal_dma.c
stm32f4xx_hal_dma_ex.c
stm32f4xx_hal_exti.c
stm32f4xx_hal_flash.c
stm32f4xx_hal_flash_ex.c
stm32f4xx_hal_flash_ramfunc.c
stm32f4xx_hal_gpio.c
stm32f4xx_hal_pwr.c
stm32f4xx_hal_pwr_ex.c
stm32f4xx_hal_rcc.c
stm32f4xx_hal_rcc_ex.c
stm32f4xx_hal_spi.c
stm32f4xx_hal_tim.c
stm32f4xx_hal_tim_ex.c
stm32f4xx_hal_uart.c

對應的體現在 stm32f4xx_hal_conf.h 里, 只需要啟用下面的外設, 對應的配置為

#define HAL_MODULE_ENABLED
#define HAL_SPI_MODULE_ENABLED
#define HAL_UART_MODULE_ENABLED
// 下面這些基本上是固定必須要的
#define HAL_GPIO_MODULE_ENABLED
#define HAL_EXTI_MODULE_ENABLED
#define HAL_DMA_MODULE_ENABLED
#define HAL_RCC_MODULE_ENABLED
#define HAL_FLASH_MODULE_ENABLED
#define HAL_PWR_MODULE_ENABLED
#define HAL_CORTEX_MODULE_ENABLED

2. 使用不同的時鍾設置

例如使用最高的84MHz作為SYSCLK, 對應的初始化方法為

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);
  // 使用外部晶振, 開啟PPL, 25MHz->25分頻->168倍頻->2分頻->PLLCLK->SYSCLK->APB1 2分頻->APB1外設時鍾
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 25;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

3. 系統和外設的初始化和反初始化

使用HAL庫的代碼, 在main()方法中調用HAL_Init();, 這個方法內部會調用HAL_MspInit(), 這個方法在stm32f4xx_hal_msp.c中定義, 用於初始化時指定硬件初始化

初始化外設時調用HAL_xxxx_Init(*handle), 例如HAL_GPIO_Init(..), HAL_UART_Init(...), HAL_SPI_Init(...)在這些方法內部, 會調用對應的HAL_xxxx_MspInit(...)方法, 這些方法完成的是底層硬件GPIO, CLOCK, NVIC的初始化.

反初始化外設時使用的HAL_xxxx_DeInit(...), 例如HAL_SPI_DeInit(...), HAL_UART_DeInit(...), 這些方法內部會調用對應的HAL_xxxx_MspDeInit(...)方法

HAL_xxxx_MspInit()和 HAL_xxxx_MspDeInit() 方法可以定義在用戶編寫的對應硬件方法文件中, 不需要加入硬件方法的頭文件

參考代碼例子: https://github.com/lupyuen/NB-EK-L476/blob/master/platform/STM32L476RC_NBEK/Src/spi.c

其他

  • 如果不能從絲印判斷自己開發板芯片的型號, 可以用STM32 ST-LINK Utility連上查看
  • 開發包中的例子. 在官方庫的壓縮包里, 包含着這個版本各個外設功能的代碼例子, 可以直接參考.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM