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连上查看
  • 开发包中的例子. 在官方库的压缩包里, 包含着这个版本各个外设功能的代码例子, 可以直接参考.

Keil MDK STM32系列(四) 基于抽象外设库HAL的STM32F401开发的更多相关文章

  1. Keil MDK STM32系列(六) 基于抽象外设库HAL的ADC模数转换

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  2. Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401开发

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  3. Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  4. Keil MDK STM32系列(三) 基于标准外设库SPL的STM32F407开发

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  5. Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  6. Keil MDK STM32系列(七) STM32F4基于HAL的PWM和定时器

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  7. Keil MDK STM32系列(八) STM32F4基于HAL的PWM和定时器输出音频

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  8. Keil MDK STM32系列(五) 使用STM32CubeMX创建项目基础结构

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  9. AIR32F103(三) Linux环境基于标准外设库的项目模板

    目录 AIR32F103(一) 合宙AIR32F103CBT6开发板上手报告 AIR32F103(二) Linux环境和LibOpenCM3项目模板 AIR32F103(三) Linux环境基于标准外 ...

随机推荐

  1. 微信小程序项目使用npm安装vant-weapp的正确步骤,简单易懂!!

    微信小程序项目使用npm安装vant-weapp的正确步骤 1.在当前小程序项目目录npm init -y 构建npm项目 2.运行命令 npm install vant-weapp -S --pro ...

  2. LuoguP7080 [NWRRC2013]Ballot Analyzing Device 题解

    Content 有 \(n\) 名选手参加一个比赛,有 \(m\) 个人为他们投票.第 \(i\) 个人的投票情况用一个长度为 \(n\),并且仅包含 . 和 X 两个字符的字符串,其中,如果第 \( ...

  3. java 编程基础 Class对象 反射 :参数反射

    方法参数反射 Java8在java.lang.reflect包下新增了Executable抽象基类,该对象代表可执行的类成员,该类派生了Constructor和Method两个子类.Executabl ...

  4. 云小课|DGC数据开发之基础入门篇

    阅识风云是华为云信息大咖,擅长将复杂信息多元化呈现,其出品的一张图(云图说).深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手华为云.更多精彩内容请单击此处. 摘要:欢迎来到DGC数据 ...

  5. HttpServletResponse工具类和HttpServletRequest工具类,前台参数接收方式和后台返回(JSON)数据格式

    RequestUtils.java 操作类 package cn.utils; import org.apache.commons.lang3.StringUtils; import org.slf4 ...

  6. FastAPI 学习之路(六十)打造系统的日志输出

    我们要搭建日志系统,我们使用loguru,挺不错的一个开源的日志系统.可以使用 pip install loguru 我们在common创建log.py使用方式也很简单 import os impor ...

  7. c++设计模式概述之备忘录

    代买写的不够规范,,目的是缩短篇幅,实际中请不要这样做. 1.概述 和这个模式相似的生活场景,比如 office的撤销操作.VS  和 xcode等IDE的撤销操作 . 其实都是恢复到上一个或者下一个 ...

  8. 【LeetCode】5685. 交替合并字符串 Merge Strings Alternately (Python)

    作者: 负雪明烛 id: fuxuemingzhu 公众号:每日算法题 本文关键词:LeetCode,力扣,算法,算法题,交替合并字符串,Merge Strings Alternately,刷题群 目 ...

  9. 写在LeetCode刷题600题

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 从2015年9月刷题至今,我终于在LeetCode刷够600道题了.从刚开始的Java语 ...

  10. 【LeetCode】637. Average of Levels in Binary Tree 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 方法一:DFS 方法二:BFS 日期 题目地址:ht ...