zoukankan      html  css  js  c++  java
  • 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结构说明

    STM32CubeF4Drivers 目录结构

    ├─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. 从解压后的标准外设库中, 复制 DriversCMSIS 目录到 Drivers, CMSIS 这个目录下只需要保留 Device 和 Include 这两个目录, 其他目录不需要
    4. 复制 DriversSTM32F4xx_HAL_Driver 整个目录到 Drivers
    5. User
      • 复制 ProjectsSTM32F401-DiscoveryTemplatesSrc 下面的 stm32f4xx_hal_msp.c stm32f4xx_it.c 到这个目录
      • 复制 ProjectsSTM32F401-DiscoveryTemplatesInc 下面的 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
      • 添加 DriversCMSISDeviceSTSTM32F4xxSourceTemplatessystem_stm32f4xx.c
    • STM32F4xx_HAL_Driver
      • 添加 DriversSTM32F4xx_HAL_Driversrc 下面stm32f4xx_hal开头的所有C文件, 除了以 _template.c 结尾的那几个
      • 如果对 stm32f4xx_hal_conf.h 中的配置进行了修改, 对裁剪的部分也需要从这里移除掉
    • Startup
      • (F401CCU6)添加 DriversCMSISDeviceSTSTM32F4xxSourceTemplatesarm 下面的 startup_stm32f401xc.s 文件
      • (F401CDU6)添加 DriversCMSISDeviceSTSTM32F4xxSourceTemplatesarm 下面的 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: 这里是头文件的包含路径, 如果按上面的目录结构组织的项目, 可以直接复制下面的配置
    .DriversCMSISInclude;.DriversCMSISDeviceSTSTM32F4xxInclude;.DriversSTM32F4xx_HAL_DriverInc;.DriversSTM32F4xx_HAL_DriverIncLegacy;.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
    

    调整配置文件

    这个文件是.Userstm32f4xx_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连上查看
    • 开发包中的例子. 在官方库的压缩包里, 包含着这个版本各个外设功能的代码例子, 可以直接参考.
  • 相关阅读:
    js-数组方法的使用和详谈
    JS中的作用域(一)-详谈
    vue中的axios
    GitHub使用
    nodejs项目文件搭建环境
    我的黑客和渗透测试学习路线
    带你了解后渗透工具Koadic
    黑客专用多功能记事本
    Kali环境使用Metasploit生成木马入侵安卓手机
    邪恶葫芦工具包
  • 原文地址:https://www.cnblogs.com/milton/p/15267548.html
Copyright © 2011-2022 走看看