Rhythmli's blog Rhythmli's blog
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Rhythmli

知识就是财富
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术文档

  • GitHub技巧

  • Nodejs

  • 博客搭建

  • CSDN迁移

    • Spring IOC
    • Ngnix 阿里云
    • 最长回文子串
    • 面试题xuexixiexue
    • 哈希集合和哈希映射的简单设计
    • leetcode2021.11.03
    • Leetcode2021.11.2
    • JUC学习
    • 869. 重新排序得到 2 的幂
    • Java自动装箱拆箱
    • 55. 跳跃游戏
    • 剑指 Offer II 085. 生成匹配的括号
    • 300. 最长递增子序列
    • Java并发编程之美 01
    • 134. 加油站
    • 139. 单词拆分
    • 岛屿类问题题解
    • 138. 复制带随机指针的链表
    • 347. 前 K 个高频元素
    • 剑指 Offer II 026. 重排链表
    • 剑指 Offer II 025. 链表中的两数相加
    • 剑指 Offer II 014. 字符串中的变位词
    • 剑指 Offer II 010. 和为 k 的子数组
    • 剑指 Offer II 009. 乘积小于 K 的子数组
    • 剑指 Offer II 008. 和大于等于 target 的最短子数组
    • 剑指 Offer II 007. 数组中和为 0 的三个数
    • 剑指 Offer II 006. 排序数组中两个数字之和
    • 剑指 Offer II 002. 二进制加法
    • 129. 求根节点到叶节点数字之和
    • 113.路径总和 II
    • leetcode18. 四数之和
    • 编译OpenCV 以及 openc_contrib 提示缺少boostdesc_bgm.i文件出错的解决
    • fork()浅学习
    • SSM 增删改查
    • springmvc helloworld
    • Spring01 hello实验
    • 树的DFS和BFS
    • leetcode——二分法
    • Halo博客搭建
    • 计算机视觉领域的一些牛人博客,超有实力的研究机构等的网站链接---转载
    • opencv+python+OpenPose姿态实时识别
    • 03.KNN算法 李航统计学习方法
    • 02.感知机 李航统计学习方法
    • 01.最小二乘法拟合 李航统计学习方法
    • Pycharm atplotlib.pyplot图像不显示解决方法
    • 论文阅读01 SVM+kNN图像分类
    • Java简单实现计算器——用数组实现栈
    • 【剑指Offer3】无重复字符的最长子串
    • 【剑指Offer5】最长回文字符串
    • TF-IDF求取文本相似度
    • JAVA_day02
    • JAVA_day01
    • 递归产生回文数
    • 中国象棋QT登录注册以及悔棋功能
    • STM32F4学习笔记(基础介绍篇)
      • leetcode_04 递归,回溯与分治
      • leetcode03_贪心算法
      • leetcode01--链表
      • leetcode_02栈
      • Docker学习入门
      • C/C++编译与链接 程序员的自我修养:链接 装载和库
      • Nginx简单学习
      • JAVA网络编程
      • JVM初步学习
      • Spring简单学习
      • 标准项目格式
      • 设计模式中的几个原则
      • Redis和IDEA简单创建及增删改查
      • Mybatis快速入门01
      • Redis全程学习笔记(附带学习的视频教程)
      • QT入门学习中最基础的那些事儿
      • QT中文输出错误问题:C2001
      • OOP:面向对象编程
      • LINUX常用命令集合(待续)
      • 《C和指针》简单学习笔记
      • 二叉树,栈存储及遍历小程序
      • 数据结构简单学习笔记
      • 扑克牌
      • C++动态内存和智能指针
      • 设计模式之简单工厂模式
      • leetcode_01数组
      • 嵌入式Linux移植应用
      • LINUX 进程与线程 信号量 通信
    • 技术
    • CSDN迁移
    梁山话事人
    最新推荐文章2025-08-22
    目录

    STM32F4学习笔记(基础介绍篇)

    原文链接:https://blog.csdn.net/qq_39355828/article/details/114525402 (opens new window) 来自官网的简介 (opens new window) 这里我们学习的开发板芯片具体型号是STM32F407ZGT6,采用工作频率为168 MHz的Cortex™-M4内核,性能较强。 本篇包含的内容:

    1. 固件库简介
    2. 开发环境的简介
    3. 开发板的基础知识

    # 一、固件库的介绍:

    前言: 在51单片机中,我们经常是直接操作 寄存器:

    P0=0x11;  //通过16进制数赋值0,1直接设置寄存器每一位开启关闭
    
    1

    在STM32中,面对大量的寄存器,很难全部记住并通过直接赋值来操作,开发效率太低且维护起来很麻烦,于是可以通过函数的方式将对寄存器的操作封装起来,我们大多数时候只需要使用函数调用接口(API):

    /**库函数控制电平的翻转
      *void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
      *{
      *	GPIO->BSRRL = GPIO_Pin;  //实际上还是控制寄存器,可以自行查询源码
      *}
      */
    //我们控制BSRRL寄存器实现电平控制只需要如下操作:
    GPIO_SetBits(GPIOF,GPIO_Pin_10);   //LED1对应引脚GPIOF.10,拉高电平表示灯灭
    
    1
    2
    3
    4
    5
    6
    7
    8

    在这里插入图片描述

    STM32官方固件库讲解: 下载连接(提取1234) (opens new window) 在一个工程中,固件库之间的相互关系如下图: 在这里插入图片描述

    (加粗部分为我们经常需要打交道的地方)

    # 二、开发环境的简介

    前言: 我们使用MDK5(提取1234) (opens new window)进行软件的开发: 软件的组成结构如下: 在这里插入图片描述

    1、工程模板的建立: 工程模板的建立是为了代码组织的清晰以及软件维护的方便。 我们先看下ST官方库给出的工程模板包含的文件

    - Template/system_stm32f4xx.c   STM32F4xx 系统时钟配置文件
      - Template/stm32f4xx_conf.h     固件库配置文件
      - Template/stm32f4xx_it.c       中断处理文件
      - Template/stm32f4xx_it.h       中断处理头文件
      - Template/main.c               Main program
      - Template/main.h               Main program header file
    
    1
    2
    3
    4
    5
    6

    我们观察下对应工程模板在MDK5上的头文件路径设置: 在这里插入图片描述

    1、core_cm4.h	//内核功能的定义,比如NVIC相关寄存器的结构体和Systick配置;
    2、core_cm4_simd.h	//包含与编译器相关的处理
    3、core_cmFunc.h	//内核核心功能接口头文件
    4、core_cmInstr.h	//包含一些内核核心专用指令
    
    1
    2
    3
    4

    5、startup_stm32f40_41xxx.s //devices vector table for MDK-ARM toolchain 6、stm32f4xx.h //contains all the peripheral register’s definitions, bits definitions //and memory mapping for STM32F4xx devices 7、system_stm32f4xx.c //contains the system clock configuration for STM32F4xx devices, //关键函数SystemInit(),系统启动文件startup_stm32f40_41xxx.s里面调用 8、stm32f4xx_conf.h //里面包含了我们需要使用的标准库,对于需要使用的库需要在其中加以说明include, //stm32f4xx.h里面可以看到对其include

    9、stm32f4xx_it.c //provides template for all exceptions handler and // peripherals interrupt service routine. 10、main.c //主程序 11、led.c //对于关键使用硬件的驱动配置文件

    对此我们对上面的文件分类存放管理: 在这里插入图片描述

    2、程序的下载调试: 程序的下载方式:USB、串口(通常使用USB转成串口)、JTAG、SWD等。

    • 使用串口下载时,首先需要电脑安装相关的驱动(CH340驱动),然后使用flymcu选择相关hex文件下载到开发板中。
    • 使用JLINK下载:串口下载较为缓慢(每次都要全片擦除)且不能使用调试工具实时跟踪调试,使用JLINK、ULINK、STLINK等可以快速下载并实现程序跟踪调试。使用JLINK下载调试时需要注意设置为SW调试模式(JTAG模式会占用较多I/O口使得部分外设无法使用),相关驱动的安装不做详述。

    程序的调试: (注意:开发板上B0与B1要设置到GND以便代码下载后自动运行) 按下debug键进入仿真调试模式(Load键会直接下载执行而不进入调试模式) 在这里插入图片描述

    # 三、基础知识入门

    1、关于STM32系统总线架构: 首先需要认识什么是总线以及总线的分类(联系计算机基础中学到的DB、CB、AB总线)。 可以简单的认为总线就是信息的传递通道,连接各种外设和芯片,详细的介绍可以看这里 (opens new window) 一般说来,有系统总线AHB和外设总线APB,在STM32F4中,总线矩阵实现8条主控总线和7条被控总线之间的互连并完成相互间访问的仲裁(可以当成交警,指挥疏导交通运输)。不同速率要求的外设会挂载在不同的总线上组成相互之间的通信网络。 在这里插入图片描述 2、时钟系统: 学习之前首先想一下为什么要时钟(看数电)?STM32时钟为什么那么多?时钟树是什么? 首先考虑到不同外设需要的时钟频率以及时钟精度并不一致,我们对于不同的外设使用不同的时钟可以有效降低能耗并提高系统稳定性。 时钟树就是对时钟源进行一系列的倍频和分频得到不同的频率时钟信号(如图一棵树,由根部向上分出枝干)。 可以参考这篇博文学习(CLICK (opens new window))

    2.1 系统时钟SYSCLK的初始化: 系统时钟的配置是在SystemInit()函数中完成的(system_stm32f4xx.c文件),配置完寄存器后该函数会调用SetSysClock函数。 SystemInit()调用是在启动文件(startup_stm32f40_41xxx.s文件)中设置的:系统启动后,进入main之前会调用SystemInit()对系统时钟进行初始化,比如对外部晶振的时钟进行分频倍频得到不同的时钟源初始值。

    2.2 时钟的使能和配置: 系统初始化完成后可能会需要修改某些时钟的默认配置或者使能外设时钟以便使用相关外设,这便需要使用到 stm32f4xx_rcc.h 文件。 对于时钟的很多操作都是定义在 stm32f4xx_rcc.h 文件里,该文件的函数大致分类如下:

    • 时钟使能函数(外设使能与时钟源使能)
    • 外设复位函数(与上面的使能相对应,使用类似)
    • 时钟源选择和分频因子配置函数

    下面对这几类函数做简单讲解: +++ 首先我们从功耗角度考虑假定:任何时钟,你不设置它,默认为关闭。 +++ 然后明确基本知识:

    • 我们有5条总线AHB1、AHB2、AHB3、APB1、APB2,下面分别挂载了不同的外设(具有不同的时钟)。
    • 我们有5大类时钟源(形象点:5个时钟树相互交叉在一起)

    在这里插入图片描述 做个不严谨的比喻:我们可以认为时钟源就是一个大的水源,外设时钟是一个个小水库,有的水库可以有多个水源的水补充(比如系统时钟),水库周围的人(外设)要用水,就要打开水库(使能外设时钟),想喝多少(分频因子设置函数),喝哪里的水(时钟源选择函数)都可以选择。 我们不可能一直开着水闸(浪费,所以设置一堆使能函数); 不同的人不同用途对水的要求不一样(所以可以选择水库的水的来源,给你一堆时钟源配置函数)。 现在你在回头看看这三个函数类别,是不是有点理解了? 挨着顺序举个例: 我们使用串口1,首先就要打开时钟:

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);  //串口1挂载在APB2总线,函数使用要对应其挂载的总线。
    
    1

    然后再对串口1做相关的初始化参数配置就可以使用串口1了(不做展开)。 时钟复位函数用法一致,不做举例。 对于时钟源的开关,我们也有相关函数可以控制(5大类,6个函数):

    void        RCC_HSICmd(FunctionalState NewState);
    void        RCC_LSICmd(FunctionalState NewState);
    void        RCC_PLLCmd(FunctionalState NewState);
    void        RCC_PLLI2SCmd(FunctionalState NewState);
    void        RCC_PLLSAICmd(FunctionalState NewState);
    void        RCC_RTCCLKCmd(FunctionalState NewState);
    
    1
    2
    3
    4
    5
    6

    时钟源的选择(配置):

    void        RCC_SYSCLKConfig(uint32_t RCC_SYSCLKSource);  //配置系统时钟的来源,可以查看相关函数定义了解细节
    
    1

    有的传入参数是分频,有的是时钟源,这样Config函数根据参数一般分为时钟源设置和分频设置或者兼具,具体可以查看函数定义,一般函数定义会有对参数有效性判断的函数,进入这个函数就可以清晰明白函数的真正用途(结合时钟树可以加深理解)。

    void RCC_PCLK1Config(uint32_t RCC_HCLK)
    {
      uint32_t tmpreg = 0;
    
    1
    2
    3

    /* Check the parameters */ assert_param(IS_RCC_PCLK(RCC_HCLK)); //查看这里的IS_RCC_PCL函数定义,如下 … // 省略部分内容 }

    #define RCC_HCLK_Div1 ((uint32_t)0x00000000) #define RCC_HCLK_Div2 ((uint32_t)0x00001000) #define RCC_HCLK_Div4 ((uint32_t)0x00001400) #define RCC_HCLK_Div8 ((uint32_t)0x00001800) #define RCC_HCLK_Div16 ((uint32_t)0x00001C00) #define IS_RCC_PCLK(PCLK) (((PCLK) == RCC_HCLK_Div1) || ((PCLK) == RCC_HCLK_Div2) || ((PCLK) == RCC_HCLK_Div4) || ((PCLK) == RCC_HCLK_Div8) || ((PCLK) == RCC_HCLK_Div16)) //看名字就可以知道这里是提高分频设置的

    3、I/O引脚复用与映射: 一个芯片会有很多引脚,一块板子上面有很多外设,外设是需要与芯片引脚相连实现特定功能的,而我们又需要GPIO的存在(像我们点个灯啊,简单输入输出高低电平啥的),如果各自都单独使用,引脚会存在浪费情况(毕竟不是所有外设同时使用),所以一个引脚我们设置成可以复用为GPIO和多个内置外设的功能引脚。 具体一个引脚作为什么功能用,可以通过寄存器控制参数(这里就讲讲相关设置的函数就行了,具体可以自己查阅相关寄存器功能列表和函数实现)。 举个使用GPIOA.9以及GPIOA.10作为串口1的复用例子(串口需要收发就要两个引脚):

    /*1、如同前面所说,使用一个外设就要首先使能时钟,这里我们使能GPIO时钟以及串口的外设时钟*/
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
    
    1
    2
    3

    /2、初始化GPIOA的Pin9和Pin10引脚,配置为复用功能/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //这里配置为复用功能,其他还有输入输出等选项 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化GPIOA的两个引脚

    /3、使用GPIO_PinAFConfig配置复用功能为串口1,具体是什么功能要看手册或者查源码(编辑器自动查看)/ GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //配置Pin9为TX端 GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //配置Pin10为RX端

    这样我们就完成了GPIO的复用环节了,可能大家注意到我们对GPIOA做了初始化(步骤2),但没有对串口初始化,因为这里只是讲解初始化GPIOA作为复用功能,对于串口的使用(如何配置,后续再做讲解)。

    4、NVIC中断优先级管理: 4.1 一些简单的补充: 中断: 计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。STM32将中断分为内核中断和外部中断,可以就简单区别为内核中断是最高有限级的,不能打断(比如复位,非屏蔽中断,硬件错误等),而外部中断是我们主要操作对象,如外设发出的中断请求(IQR)我们可以设置优先级,可以直接屏蔽等。 优先级: 分为抢占优先级和响应优先级。顾名思义,抢占的意思就是可以强制打断正在执行的中断响应(第一时间得到处理),而响应优先即就是简单的谁级别高谁就优先得到响应(排队排的快),但是不能打断正在执行的中断(别人已经再接收服务,自己来晚了你就不能让别人下来)。 中断向量表: 一个中断向量占据4字节空间,是指中断服务程序入口地址的偏移量与段基值。中断向量表占据一个特定存储空间,它的作用就是按照中断类型号从小到大的顺序存储对应的中断向量。在中断响应过程中,CPU通过从接口电路获取的中断类型号(中断向量号)计算对应中断向量在表中的位置,并从中断向量表中获取中断向量,将程序流程转向中断服务程序的入口地址。

    4.2 言归正传: STM32F40xx/STM32F41xx总共有92个中断,包含10个内核中断和82个可屏蔽中断,具有16级可编程的中断优先级。 Nested Vectored Interrupt Controller (NVIC)相关的寄存器定义在core_cm4.h文件内,可以看到里面很多位是保留的,M4内核提供的中断数量以及相关功能在STM32F40xx/STM32F41xx中只使用到一部分。我们对于中断的控制就是通过对下面这些寄存器操作实现的。

    /** \brief  Structure type to access the Nested Vectored Interrupt Controller (NVIC).
     */
    typedef struct
    {
      __IO uint32_t ISER[8];                 /*!< Offset: 0x000 (R/W)  Interrupt Set Enable Register           */
           uint32_t RESERVED0[24];
      __IO uint32_t ICER[8];                 /*!< Offset: 0x080 (R/W)  Interrupt Clear Enable Register         */
           uint32_t RSERVED1[24];
      __IO uint32_t ISPR[8];                 /*!< Offset: 0x100 (R/W)  Interrupt Set Pending Register          */
           uint32_t RESERVED2[24];
      __IO uint32_t ICPR[8];                 /*!< Offset: 0x180 (R/W)  Interrupt Clear Pending Register        */
           uint32_t RESERVED3[24];
      __IO uint32_t IABR[8];                 /*!< Offset: 0x200 (R/W)  Interrupt Active bit Register           */
           uint32_t RESERVED4[56];
      __IO uint8_t  IP[240];                 /*!< Offset: 0x300 (R/W)  Interrupt Priority Register (8Bit wide) */
           uint32_t RESERVED5[644];
      __O  uint32_t STIR;                    /*!< Offset: 0xE00 ( /W)  Software Trigger Interrupt Register     */
    }  NVIC_Type;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    中断管理函数主要是在misc.c文件里面: 中断优先级分组函数(这里的分组是设置IP[240]里面我们使用的82个可屏蔽中断的抢占和响应优先级各占8 bit中的几位):

    void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
    {
      /* Check the parameters */
      assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
    
    1
    2
    3
    4

    /* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */ SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup; }

    设置好优先级分组后,就需要设置抢占优先级和响应优先级对应各自具体优先级大小(区别前面的分组和这里具体设置优先级的关系): 这就要使用中断初始化函数:

    void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
    typedef struct
    {
      uint8_t NVIC_IRQChannel;                    /*!< Specifies the IRQ channel to be enabled or disabled.
                                                       This parameter can be an enumerator of @ref IRQn_Type
                                                       enumeration (For the complete STM32 Devices IRQ Channels
                                                       list, please refer to stm32f4xx.h file) */
    
    1
    2
    3
    4
    5
    6
    7

    uint8_t NVIC_IRQChannelPreemptionPriority; /*!< Specifies the pre-emption priority for the IRQ channel specified in NVIC_IRQChannel. This parameter can be a value between 0 and 15 as described in the table @ref MISC_NVIC_Priority_Table A lower priority value indicates a higher priority */

    uint8_t NVIC_IRQChannelSubPriority; /*!< Specifies the subpriority level for the IRQ channel specified in NVIC_IRQChannel. This parameter can be a value between 0 and 15 as described in the table @ref MISC_NVIC_Priority_Table A lower priority value indicates a higher priority */

    FunctionalState NVIC_IRQChannelCmd; /*!< Specifies whether the IRQ channel defined in NVIC_IRQChannel will be enabled or disabled. This parameter can be set either to ENABLE or DISABLE */ } NVIC_InitTypeDef;

    NVIC_InitTypeDef   NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;//外部中断0
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;//抢占优先级0
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;//子优先级2
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中断通道
    NVIC_Init(&NVIC_InitStructure);//配置
    
    1
    2
    3
    4
    5
    6

    注: 这里我们只是讲解了中断控制相关文件里面的中断优先级设置,还有一些不常用的函数可以自行查看misc.c文件,这里不做展开。 需要明白的是:前面的是对所有中断都必须的通用的操作,而具体使用某一个外设中断,还需要设置对应的外设中断触发方式,清除中断标准,配置响应函数等等需要去查看对应的外设库文件。

    # 四、总结:

    在STM32库函数开发中,可以看到,对于开发板各个功能的实现还是类似51单片机一样对寄存器的操作。只不过,为了开发的方便快速,代码组织的清晰性,对各个寄存器使用结构体 组织在一起,对寄存器的操作变成了使用各种封装好的函数来对结构体赋值实现对寄存器的各个位的设置。 平时可以多去看看固件库的源码,看看各个函数、结构体是怎么分类组织在各个文件中,各个文件在一个工程项目中的相互关系是怎么样的,可以找一个工程在脑海里思考下这个工程执行的时候各个文件函数是怎么样的执行顺序、调用关系。

    编辑 (opens new window)
    #STM32#订阅专栏#查看详情
    上次更新: 2026/08/11, 13:36:18
    中国象棋QT登录注册以及悔棋功能
    leetcode_04 递归,回溯与分治

    ← 中国象棋QT登录注册以及悔棋功能 leetcode_04 递归,回溯与分治→

    最近更新
    01
    Spring IOC
    03-31
    02
    Git修改分支名
    08-11
    03
    CSS给table的tbody添加滚动条
    06-29
    更多文章>
    Theme by Vdoing | Copyright © 2019-2026 Evan Xu | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式