Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401开发
阅读原文时间:2022年03月18日阅读:1

Keil MDK STM32系列

概述

Windows下使用Keil MDK5进行 STM32F401CCU6 的开发和编译, 配合ST-LINK工具进行烧录, 使用标准外设库SPL.

STM32F4系列MCU介绍

STM32F4系列基于Cortex M4内核, 于2011年发布, 上市已经十年了. 相对于基于Cortex M3的产品, STM32F4最大的优势是新增了硬件FPU单元以及DSP指令, 同时主频也提高到了168Mhz(可获得210DMIPS的处理能力), 这使得STM32F4适用于需要浮点运算或DSP处理的应用.

STM32F4相对于STM32F1,主要优势如下

  1. 采用Cortex M4内核, 带FPU和DSP指令集, 而STM32F1采用的是Cortex M3内核, 不带FPU和DSP指令集
  2. 更多的资源, STM32F4有 192KB 片内SRAM, 带摄像头接口(DCMI), 加密处理器(CRYP), USB高速OTG, 真随机数发生器, OTP存储器等
  3. 增强的外设功能. 对于相同的外设部分, STM32F4具有更快的模数转换速度、更低的ADC/DAC工作电压, 32位定时器, 带日历功能的实时时钟(RTC), IO复用功能大大增强, 4K字节的电池备份SRAM以及更快的USART和SPI通信速度
  4. 更高的性能. STM32F4最高运行频率可达168Mhz, 而STM32F1只能到72Mhz, STM32F4拥有ART自适应实时加速器, 可以达到相当于FLASH零等待周期的性能, STM32F1则需要等待周期, STM32F4的FSMC采用32位多重AHB总线矩阵, 相比STM32F1总线访问速度明显提高
  5. 更低的功耗, STM32F40x的功耗为 238uA/Mhz, 其中低功耗版本的STM32F401更是低到 140uA/Mhz, 而STM32F1则为 421uA/Mhz

主要参数

  • ARM 32-bit Cortex-M4 with FPU
  • 80 MHz maximum frequency
  • 256k flash
  • 64k ram
  • UFQFPN 封装48pin

STM32F401CCU6与STM32F103C8T6相比, 引脚基本兼容, 但是需要注意一个引脚PB11, PB11引脚不能当GPIO使用, 同时必须外接2.2UF电容, 因此USART3也不复存在, 在STM32F401中换成了USART6. 下面的开发板PB口就没有PB11.

市面上常见的开发板有两种, 一种黑色的俗称Black Pill, 另一种是绿色的

Black Pill 开发板

芯片有两种, STM32F401和STM32F411, 开发板的具体说明可以查看 https://stm32-base.org/boards/STM32F401CCU6-WeAct-Black-Pill-V1.2

Black Pill的特点是背面有sop8 flash焊盘, 可以自己加焊flash

Mini F401 开发板

芯片是STM32F401CCU6 和 STM32F401CDU6, 后者flash是384K, 在开发时要选STM32F401CE而不是CC.

开发板的具体说明可以查看 https://stm32-base.org/boards/STM32F401CCU6-STM32-Mini-F401

外观

电路图(不完全一致)

使用普通的4线 ST-Link V2, 也可以使用增加了串口的V2.1

ST-Link 与 stm32核心板的连接需要4根线, 连接关系为

G   -- GND
CLK -- SWCLK
IO  -- SWDIO
V3  -- 3.3V

烧录时, 如果开发板已经外接了电源, 那么st-link的3.3V不要接到开发板.

在观察串口输出时需要使用.

Keil MDK安装说明

STM32F4 官方标准外设库 Standard Peripheral Libraries

ST官方库下载:

  1. 前往https://www.st.com/,
  2. 点击Tools&Software > Embedded Software > MCU & MPU Embedded Software > STM32 Embedded Software > STM32 Standard Peripheral Libraries
  3. (链接),
  4. 下载F4, 解压备用

标准库版本只到1.8.0版本, 已经停止更新. 好处就是现在使用标准库的项目基本上都不用担心库版本的兼容问题.

STM32F4xx_DSP_StdPeriph_Lib_V1.8.0 目录结构

├─Libraries
│  ├─CMSIS
│  │  ├─Device
│  │  │  └─ST
│  │  │      └─STM32F4xx
│  │  │          ├─Include                # 需要
│  │  │          └─Source
│  │  │              └─Templates
│  │  │                  ├─arm            # startup文件, 需要
│  │  │                  ├─gcc_ride7
│  │  │                  ├─iar
│  │  │                  ├─SW4STM32
│  │  │                  ├─TASKING
│  │  │                  └─TrueSTUDIO
│  │  ├─Documentation
│  │  ├─DSP_Lib
│  │  ├─Include                           # 需要
│  │  ├─Lib
│  │  │  ├─ARM
│  │  │  └─GCC
│  │  └─RTOS
│  │      └─Template
│  └─STM32F4xx_StdPeriph_Driver           # 需要. 注意里面的xx_fmc.c和xx_fsmc.c不要包含, 因为这两个
                                          # 用到的变量在F401里面没有, 只在少数几个F4xx型号里有.
│      ├─inc
│      └─src
├─Project
│  ├─STM32F4xx_StdPeriph_Examples         # 官方的代码例子
│  └─STM32F4xx_StdPeriph_Templates        # 需要stm32f4xx_conf.h, stm32f4xx_it.c, stm32f4xx_it.h
│      ├─EWARM
│      ├─MDK-ARM
│      ├─SW4STM32
│      └─TrueSTUDIO
├─Utilities
│  ├─Media
│  ├─ST
│  │  └─STemWin
│  │      └─Software
│  ├─STM32_EVAL
│  │  ├─Common
│  │  ├─STM3240_41_G_EVAL
│  │  ├─STM324x7I_EVAL
│  │  └─STM324x9I_EVAL
│  └─Third_Party
└─_htmresc

按步骤手工创建项目

先组织好库文件和目录, 然后创建项目

以下以名称为test001的项目为例

  1. 创建工作目录 test001
  2. 在工作目录下创建 Drivers, User 2个目录
  3. 从解压后的标准外设库中, 复制 Libraries\CMSIS 目录到 Drivers, CMSIS 这个目录下只需要保留 Device 和 Include 这两个目录, 其他目录不需要
  4. 复制 Libraries\STM32F4xx_StdPeriph_Driver 整个目录到 Drivers
  5. User
    • 复制 Project\STM32F4xx_StdPeriph_Templates 下面的stm32f4xx_conf.h, stm32f4xx_it.c, stm32f4xx_it.h三个文件到这个目录
    • 用于放置用户编写的代码

完成后的目录结构是这样的

test001>
├─Drivers
│  ├─CMSIS
│  │  ├─Device
│  │  │  └─ST
│  │  │      └─STM32F4xx
│  │  │          ├─Include
│  │  │          └─Source
│  │  └─Include
│  └─STM32F4xx_StdPeriph_Driver
│      ├─inc
│      └─src
└─User
  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
    • StdPeriph_Driver
    • Startup
    • User

对每个group, 添加的文件为

  • CMSIS

    • 添加 Drivers\CMSIS\Device\ST\STM32F4xx\Source\Templates\system_stm32f4xx.c
  • StdPeriph_Driver

    • 添加 Drivers\STM32F4xx_StdPeriph_Driver\src 下面的所有c文件, 除了 stm32f4xx_fmc.c 和 stm32f4xx_fsmc.c
  • Startup

    • (F401CCU6)添加 Drivers\CMSIS\Device\ST\STM32F4xx\Source\Templates\arm 下面的 startup_stm32f401xx.s 文件
    • (F407VET6)添加的是 startup_stm32f40_41xxx.s
  • User

    • 添加 user 目录下的所有C文件

修改项目包含路径

在菜单中点击Project -> Options for Target 'test001', 或者直接在图标栏中点击configure target option图标, 在弹出的对话框中

  1. 定位到c/c++标签页

  2. Define: 这个是编译参数, 写入 USE_STDPERIPH_DRIVER, STM32F401xx 因为MDK自动添加的参数是STM32F401xC, 而SPL需要指定的参数是STM32F401xx, 和MDK给的不一致, 所以这里要自行指定

  3. Include Paths: 这里是头文件的包含路径, 如果按上面的目录结构组织的项目, 可以直接复制下面的配置

    .\Drivers\CMSIS\Include;.\Drivers\CMSIS\Device\ST\STM32F4xx\Include;.\Drivers\STM32F4xx_StdPeriph_Driver\inc;.\User

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

--c99 --gnu -c --cpu Cortex-M4.fp -g -O0 --apcs=interwork --split_sections -I ./Drivers/CMSIS/Include -I ./Drivers/CMSIS/Device/ST/STM32F4xx/Include -I ./Drivers/STM32F4xx_StdPeriph_Driver/inc -I ./User
-I./RTE/_test001
-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" -DSTM32F401xC -DUSE_STDPERIPH_DRIVER -DSTM32F401xx
-o .\Objects\*.o --omf_browse .\Objects\*.crf --depend .\Objects\*.d

下面的例子, 使用开发板自带的led灯(PC13)实现间隔1秒的亮灭效果.

在User目录下创建 main.h 和 main.c, 注意通过Keil MDK创建的时候, 要注意文件位置, 默认是放到项目根目录的, 这里要改到User目录下.

main.c

#include "main.h"

static __IO uint32_t uwTimingDelay;
RCC_ClocksTypeDef RCC_Clocks;

static void Delay(__IO uint32_t nTime);

void LED_Init(void)
{
    GPIO_InitTypeDef  aaa;
    // 使能指定的GPIO模块时钟--默认复位后开机时钟不会全部提供给各个模块 使用时需要自己开启
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
    // 初始化引脚
    aaa.GPIO_Pin = GPIO_Pin_13;    // 引脚号选择 PC13: RCC_AHB1Periph_GPIOC, GPIOC, GPIO_Pin_13
    aaa.GPIO_Mode = GPIO_Mode_OUT; // 输出模式
    aaa.GPIO_OType =  GPIO_OType_PP;  // 推挽输出
    aaa.GPIO_Speed = GPIO_High_Speed; // 高速
    GPIO_Init(GPIOC, &aaa);
}

void TIM2_init(void)
{
    TIM_TimeBaseInitTypeDef  aaa;
    // 使能对应模块的时钟 TIM2
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    // 初始化定时器
    aaa.TIM_Prescaler = 8400;  // 通过prescaler将84MHz降为10KHz
    aaa.TIM_Period    = 10000; // 再通过period设置计时器间隔为上面频率的10K个周期, 即1s
    aaa.TIM_CounterMode = TIM_CounterMode_Up; // 选择递增模式
    aaa.TIM_ClockDivision = TIM_CKD_DIV1;     // 1分频 1  2  4
    TIM_TimeBaseInit(TIM2, &aaa);
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);//使能TIM2的更新中断
    TIM_Cmd(TIM2, ENABLE); // 使能TIM2
}

void NVIC_INIT(void)
{
    NVIC_InitTypeDef  ccc;
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断优先级分组  2分组
    ccc.NVIC_IRQChannel = TIM2_IRQn;
    ccc.NVIC_IRQChannelCmd = ENABLE;
    ccc.NVIC_IRQChannelPreemptionPriority = 0;
    ccc.NVIC_IRQChannelSubPriority = 0;
    NVIC_Init(&ccc);
}

void TIM2_IRQHandler(void)
{
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除溢出中断标志位
    GPIO_ToggleBits(GPIOC, GPIO_Pin_13);        // 触发值交替变化
}

int main(void)
{
  LED_Init();//初始化LED
    GPIO_WriteBit(GPIOC, GPIO_Pin_13, 0);
    TIM2_init();
    NVIC_INIT();
    while(1)//卡住
    {
    }
}

/**
  * @brief  Inserts a delay time.
  * @param  nTime: specifies the delay time length, in milliseconds.
  * @retval None
  */
void Delay(__IO uint32_t nTime)
{
  uwTimingDelay = nTime;

  while(uwTimingDelay != 0);
}

/**
  * @brief  Decrements the TimingDelay variable.
  * @param  None
  * @retval None
  */
void TimingDelay_Decrement(void)
{
  if (uwTimingDelay != 0x00)
  {
    uwTimingDelay--;
  }
}

#ifdef  USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line)
{
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */

  /* Infinite loop */
  while (1)
  {
  }
}
#endif

main.h

#ifndef __MAIN_H
#define __MAIN_H

#include "stm32f4xx.h"

void TimingDelay_Decrement(void);

#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, 进行烧录

在官方库的压缩包里, 包含着这个版本各个外设功能的代码例子, 可以直接参考.