STM32基础--构建自己的固件库

CMSIS 标准及库层次关系

因为基于 Cortex 系列芯片采用的内核都是相同的,区别主要为核外的片上外设的差异,这些差异却导致软件在同内核,不同外设的芯片上移植困难。为了解决不同的芯片厂商生产的 Cortex 微控制器软件的兼容性问题,ARM 与芯片厂商建立了 CMSIS 标准 (Cortex MicroController Software Interface Standard)。所谓 CMSIS 标准,实际是新建了一个软件抽象层。
在这里插入图片描述

CMSIS 标准中最主要的为 CMSIS 核心层,它包括了:
• 内核函数层:其中包含用于访问内核寄存器的名称、地址定义,主要由 ARM 公司提供。
• 设备外设访问层:提供了片上的核外外设的地址和中断定义,主要由芯片生产商提供。
可见 CMSIS 层位于硬件层与操作系统或用户层之间,提供了与芯片生产商无关的硬件抽象层,可以为接口外设、实时操作系统提供简单的处理器软件接口,屏蔽了硬件差异,这对软件的移植是有极大的好处的。STM32 的库,就是按照 CMSIS 标准建立的。

库目录、文件简介

STM32 标准库可以从官网获得。本节讲解的例程全部采用3.5.0 库文件。以下内容请大家打开 STM32 标准库文件配合阅读。
解压库文件后进入其目录:
STM32F10x_StdPeriph_Lib_V3.5.0
软件库各文件夹的内容说明见图 ST 标准库。目录:STM32F10x_StdPeriph_Lib_V3.5.0
在这里插入图片描述

• Libraries:文件夹下是驱动库的源代码及启动文件,这个非常重要,我们要使用的固件库就在这个文件夹里面。。
• Project :文件夹下是用驱动库写的例子和工程模板,其中那些为每个外设写好的例程对我们非常有用,我们在学习的时候就可以参考这里面的例程,非常全面,简直就是穷尽了外设的所有功能。
• Utilities:包含了基于 ST 官方实验板的例程,不需要用到,略过即可。
• stm32f10x_stdperiph_lib_um.chm:库帮助文档,这个很有用,不喜欢直接看源码的可以在合理查询每个外设的函数说明,非常详细。这是一个已经编译好的 HTML 文件,主要讲述如何使用驱动库来编写自己的应用程序。说得形象一点,这个 HTML 就是告诉我们:ST 公司已经为你写好了每个外设的驱动了。不幸的是,这个帮助文档是英文的,这对很多英文不好的朋友来说是一个很大的障碍。但这里要告诉大家,英文仅仅是一种工具,绝对不能让它成为我们学习的障碍。其实这些英文还是很简单的,我们需要的是拿下它的勇气。
在使用库开发时,我们需要把 libraries 目录下的库函数文件添加到工程中,并查阅库帮助文档来了解 ST 提供的库函数,这个文档说明了每一个库函数的使用方法。
进 入Libraries文 件 夹 看 到, 关 于 内 核 与 外 设 的 库 文 件 分 别 存 放 在CMSIS和STM32F10x_StdPeriph_Driver 文件夹中。

CMSIS 文件夹

STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\文件夹展开内容见图 CMSIS 文件夹内容。目录:Libraries\CMSIS
在这里插入图片描述

其中黄色框框住的是我们需要用到的内容,下面我们一一讲解下这几个文件的作用。

内核相关文件

在 CoreSupport 文件夹中有 core_cm3.c 和 core_cm3.h 两个文件。Core_cm3.h 头文件里面实现了内核的寄存器映射,对应外设头文件 stm32f10x.h,区别就是一个针对内核的外设,一个针对片上(内核之外)的外设。core_cm3.c 文件实现了一下操作内核外设寄存器的函数,用的比较少。
我们还需要了解的是 core_cm3.h 头文件中包含了“stdint.h”这个头文件,这是一个 ANSI C 文件,是独立于处理器之外的,就像我们熟知的 C 语言头文件“stdio.h”文件一样。位于 RVMDK 这个软件的安装目录下,主要作用是提供一些类型定义。如下:

/* exact-width signed integer types */
typedef   signed          char int8_t;
typedef   signed short     int int16_t;
typedef   signed           int int32_t;
typedef   signed       __INT64 int64_t;

    /* exact-width unsigned integer types */
typedef unsigned          char uint8_t;
typedef unsigned short     int uint16_t;
typedef unsigned           int uint32_t;
typedef unsigned       __INT64 uint64_t;

这些新类型定义屏蔽了在不同芯片平台时,出现的诸如 int 的大小是 16 位,还是 32 位的差异。所以在我们以后的程序中,都将使用新类型如 uint8_t 、uint16_t 等。在稍旧版的程序中还经常会出现如 u8、u16、u32 这样的类型,分别表示的无符号的 8 位、16 位、32 位整型。初学者碰到这样的旧类型感觉一头雾水,它们定义的位置在 STM32f10x.h 文件中。建议在以后的新程序中尽量使用 uint8_t 、uint16_t 类型的定义。

启动文件

启动文件放在 startup/arm 这个文件夹下面,这里面启动文件有很多个,不同型号的单片机用的启动文件不一样,有关每个启动文件的详细说明见表。
在这里插入图片描述

Stm32f10x.h
这个头文件实现了片上外设的所有寄存器的映射,是一个非常重要的头文件,在内核中与之想对应的头文件是 core_cm3.h。

system_stm32f10x.c
system_stm32f10x.c 文件实现了 STM32 的时钟配置,操作的是片上的 RCC 这个外设。系统在上电之后,首选会执行由汇编编写的启动文件,启动文件中的复位函数中调用的 SystemInit 函数就在这个文件里面定义。调用完之后,系统的时钟就被初始化成 72M。如果后面我们需要重新配置系统时钟,我们就可以参考这个函数重写。为了维持库的完整性,我们不会直接在这个文件里面修改时钟配置函数。

STM32F10x_StdPeriph_Driver 文件夹

文件目录:Libraries\STM32F10x_StdPeriph_Driver
进入 libraries 目录下的 STM32F10x_StdPeriph_Driver 文件夹。
在这里插入图片描述

STM32F10x_StdPeriph_Driver 文件夹下有 inc(include 的缩写)跟 src(source 的简写)这两个文件夹,这里的文件属于 CMSIS 之外的的、芯片片上外设部分。src 里面是每个设备外设的驱动源程序,inc 则是相对应的外设头文件。src 及 inc 文件夹是 ST 标准库的主要内容,甚至不少人直接认为 ST 标准库就是指这些文件,可见其重要性。
在 src 和 inc 文件夹里的就是 ST 公司针对每个 STM32 外设而编写的库函数文件,每个外设对应一个.c 和.h 后缀的文件。我们把这类外设文件统称为:stm32f10x_ppp.c 或 stm32f10x_ppp.h 文件,PPP 表示外设名称。如在上一章中我们自建的 stm32f10x_gpio.c 及 stm32f10x_gpio.h 文件,就属于这一类。
如针对模数转换 (ADC) 外设,在 src 文件夹下有一个 stm32f10x_adc.c 源文件,在 inc 文件夹下有一个 stm32f10x_adc.h 头文件,若我们开发的工程中用到了 STM32 内部的 ADC,则至少要把这两个文件包含到工程里。
在这里插入图片描述

这两个文件夹中,还有一个很特别的 misc.c 文件,这个文件提供了外设对内核中的 NVIC(中断向量控制器) 的访问函数,在配置中断时,我们必须把这个文件添加到工程中。

stm32f10x_it.c、stm32f10x_conf.h 和 system_stm32f10x.c 文件

文件目录:STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Template
在这个文件目录下,存放了官方的一个库工程模板,我们在用库建立一个完整的工程时,还需要添加这个目录下的 stm32f10x_it.c、stm32f10x_it.h、stm32f10x_conf.h 和 system_stm32f10x.c 这四个文件。

stm32f10x_it.c:这个文件是专门用来编写中断服务函数的,在我们修改前,这个文件已经定义了一些系统异常 (特殊中断) 的接口,其它普通中断服务函数由我们自己添加。但是我们怎么知道这些中断服务函数的接口如何写?是不是可以自定义呢?答案当然不是,这些都可以在汇编启动文件中找到,在学习中断和启动文件的时候我们会详细介绍system_stm32f10x.c:这个文件包含了 STM32 芯片上电后初始化系统时钟、扩展外部存储器用的函数,例如我们前两章提到供启动文件调用的“SystemInit”函数,用于上电后初始化时钟,该函数的定义就存储在 system_stm32f10x.c 文件。STM32F103 系列的芯片,调用库的这个 SystemInit函数后,系统时钟被初始化为 72MHz,如有需要可以修改这个文件的内容,设置成自己所需的时钟频率,但鉴于保持库的完整性,我们在做系统时钟配置的时候会另外重写时钟配置函数。

stm32f10x_conf.h:这个文件被包含进 stm32f10x.h 文件。当我们使用固件库编程的时候,如果需要某个外设的驱动库,就需要包含该外设的头文件:stm32f10x_ppp.h,包含一个还好,如果是用了多外设,就需要包含多个头文件,这不仅影响代码美观也不好管理,现我们用一个头文件 stm32f10x_conf.h 把这些外设的头文件都包含在里面,让这个配置头文件统一管理这些外设的头文件,我们在应用程序中只需要包含这个配置头文件即可,我们又知道这个头文件在stm32f10x.h 的最后被包含,所以最终我们只需要包含 stm32f10x.h 这个头文件即可,非常方便。Stm32f10x_conf.h。默认情况下是所以头文件都被包含,没有被注释掉。我们也可以把不要的都注释掉,只留下需要使用的即可。

/* Includes ------------------------------------------------------------------*/
/* Uncomment/Comment the line below to enable/disable peripheral header file inclusion */
#include "stm32f10x_adc.h"
#include "stm32f10x_bkp.h"
#include "stm32f10x_can.h"
#include "stm32f10x_cec.h"
#include "stm32f10x_crc.h"
#include "stm32f10x_dac.h"
#include "stm32f10x_dbgmcu.h"
#include "stm32f10x_dma.h"
#include "stm32f10x_exti.h"
#include "stm32f10x_flash.h"
#include "stm32f10x_fsmc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_i2c.h"
#include "stm32f10x_iwdg.h"
#include "stm32f10x_pwr.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_rtc.h"
#include "stm32f10x_sdio.h"
#include "stm32f10x_spi.h"
#include "stm32f10x_tim.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_wwdg.h"
#include "misc.h" /* High level functions for NVIC and SysTick (add-on to CMSIS functions) */

stm32f10x_conf.h 这个文件还可配置是否使用“断言”编译选项。

/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Uncomment the line below to expanse the "assert_param" macro in the 
   Standard Peripheral Library drivers code */
/* #define USE_FULL_ASSERT    1 */

/* Exported macro ------------------------------------------------------------*/
#ifdef  USE_FULL_ASSERT

/**
  * @brief  The assert_param macro is used for function's parameters check.
  * @param  expr: If expr is false, it calls assert_failed function which reports 
  *         the name of the source file and the source line number of the call 
  *         that failed. If expr is true, it returns no value.
  * @retval None
  */
  #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
  void assert_failed(uint8_t* file, uint32_t line);
#else
  #define assert_param(expr) ((void)0)
#endif /* USE_FULL_ASSERT */

在 ST 标准库的函数中,一般会包含输入参数检查,即上述代码中的“assert_param”宏,当参数不符合要求时,会调用“assert_failed”函数,这个函数默认是空的。
实际开发中使用断言时,先通过定义 USE_FULL_ASSERT 宏来使能断言,然后定义“assert_failed”函数,通常我们会让它调用 printf 函数输出错误说明。使能断言后,程序运行时会检查函数的输入参数,当软件经过测试,可发布时,会取消 USE_FULL_ASSERT 宏来去掉断言功能,使程序全速运行。

库各文件间的关系

前面向大家简单介绍了各个库文件的作用,库文件是直接包含进工程即可,丝毫不用修改,而有的文件就要我们在使用的时候根据具体的需要进行配置。接下来从整体上把握一下各个文件在库工程中的层次或关系,这些文件对应到 CMSIS 标准架构上。
在这里插入图片描述

图库各文件关系 描述了 STM32 库各文件之间的调用关系,在实际的使用库开发工程的过程中,我们把位于 CMSIS 层的文件包含进工程,除了特殊系统时钟需要修改 system_stm32f10x.c,其它文件丝毫不用修改,也不建议修改。对于位于用户层的几个文件,就是我们在使用库的时候,针对不同的应用对库文件进行增删(用条件编译的方法增删)和改动的文件。

使帮助文档

常用官方资料

•《STM32F10X-中文参考手册》
这个文件全方位介绍了 STM32 芯片的各种片上外设,它把 STM32 的时钟、存储器架构、及各种外设、寄存器都描述得清清楚楚。当我们对 STM32 的外设感到困惑时,可查阅这个文档。以直接配置寄存器方式开发的话,查阅这个文档寄存器部分的频率会相当高,但这样效率太低了。
•《STM32 规格书》
本文档相当于 STM32 的 datasheet,包含了 STM32 芯片所有的引脚功能说明及存储器架构、芯片外设架构说明。后面我们使用 STM32 其它外设时,常常需要查找这个手册,了解外设对应到 STM32 的哪个 GPIO 引脚。
•《Cortex™-M3 内核编程手册》
本文档由 ST 公司提供,主要讲解 STM32 内核寄存器相关的说明,例如系统定时器、NVIC 等核外设的寄存器。这部分的内容是《STM32F10X-中文参考手册》没涉及到的内核部分的补充。相对来说,本文档虽然介绍了内核寄存器,但不如以下两个文档详细,要了解内核时,可作为以下两个手册的配合资料使用。
•《Cortex-M3 权威指南》。
这个手册是由 ARM 公司提供的,它详细讲解了 Cortex 内核的架构和特性,要深入了解 Cortex-M 内核,这是首选,经典中的经典。这个手册也被翻译成中文,出版成书。
•《stm32f10x_stdperiph_lib_um.chm》
这个就是本章提到的库的帮助文档,在使用库函数时,我们最好通过查阅此文件来了解标准库提供了哪些外设、函数原型或库函数的调用的方法。也可以直接阅读源码里面的函数的函数说明。(我的建议直接读源码,查api累死个人哦)

初识库函数

所谓库函数,就是 STM32 的库文件中为我们编写好驱动外设的函数接口,我们只要调用这些库函数,就可以对 STM32 进行配置,达到控制目的。我们可以不知道库函数是如何实现的,但我们调用函数必须要知道函数的功能、可传入的参数及其意义、和函数的返回值。于是,有读者就问那么多函数我怎么记呀?我的回答是:会查就行了,哪个人记得了那么多。所以我们学会查阅库帮助文档是很有必要的。
打开库帮助文档《stm32f10x_stdperiph_lib_um.chm》见图库帮助文档
在这里插入图片描述

层层打开文档的目录标签:
标签目录:Modules\STM32F10x_StdPeriph_Driver
可看到 STM32F10x _StdPeriph_Driver 标签下有很多外设驱动文件的名字 MISC、ADC、BKP、CAN等标签。
我们试着查看 GPIO 的“位设置函数 GPIO_SetBits”看看,打开标签:
标签目录:Modules\STM32F10x_StdPeriph_Driver\GPIO\Functions\GPIO_SetBits 见图库帮助文档的函数说明
在这里插入图片描述

利用这个文档,我们即使没有去看它的具体源代码,也知道要怎么利用它了。如 GPIO_SetBits,函数的原型为 void GPIO_SetBits(GPIO_TypeDef * GPIOx , uint16_t GPIO_Pin)。它的功能是:输入一个类型为 GPIO_TypeDef 的指针 GPIOx 参数,选定要控制的 GPIO 端口;输入GPIO_Pin_x 宏,其中 x 指端口的引脚号,指定要控制的引脚。其中输入的参数 GPIOx 为 ST 标准库中定义的自定义数据类型,这两个传入参数均为结构体指针。初学时,我们并不知道如 GPIO_TypeDef 这样的类型是什么意思,可以点击函数原型中带下划线的 GPIO_TypeDef 就可以查看这个类型的声明了。就这样初步了解了一下库函数,读者就可以发现 STM32 的库是写得很优美的。每个函数和数据类型都符合见名知义的原则,当然,这样的名称写起来特别长,而且对于我们来说要输入这么长的英文,很容易出错,所以在开发软件的时候,在用到库函数的地方,直接把库帮助文档中的函数名称复制粘贴到工程文件就可以了。

而且,配合 MDK 软件的代码自动补全功能,可以减少输入量。有的用户觉得使用库文档麻烦,也可以直接查阅 STM32 标准库的源码,库帮助文档的说明都是根据源码生成的,所以直接看源码也可以了解函数功能。

新建工程

新建本地工程文件夹

为了工程目录更加清晰,我们在本地电脑上新建一个“工程模板”文件夹,在它之下再新建 6 个文件夹,具体如下:

名称作用
Doc用来存放程序说明的文件,有写程序的人添加
Libraries存放的库文件
Listing存放编译器编译时产生的C/汇编/链接的列表清单
Output存放编译产生的调试信息、hex文件、预览信息
User用户编写的驱动文件

在这里插入图片描述

在本地新建好文件夹后,把准备好的库文件添加到对应的文件夹下:

名称作用
Doc工程说明.txt
LibrariesCMSIS:里面存放着跟CM3内核有关的库文件、STM32F10x_StdPeriph_Driver:STM32外设库文件
Listing暂时为空
Output暂时为空
Project暂时为空
Userstm32f10x_conf.h:用来配置库的头文件、stm32f10x_it.h与stm32f10x_it.c:中断相关的函数都在这个文件编写,暂时为空、main.c:main函数文件

新建工程

打开 KEIL5,新建一个工程,工程名根据喜好命名,我这里取 Template(中文是模版的意思),保存在 Project(uv5)文件夹下。
在这里插入图片描述

选择 CPU 型号

这个根据你开发板使用的 CPU 具体的型号来选择,M3 旗舰版选 STM32F103ZE 型号。如果这里没有出现你想要的 CPU 型号,或者一个型号都没有,那么肯定是你的 KEIL5 没有添加 device 库,KEIL5 不像 KEIL4 那样自带了很多 MCU 的型号,KEIL5 需要自己添加。
在这里插入图片描述

在线添加库文件

等下我们手动本地添加库文件,这里我们点击关掉在线添加,因为 keil 的服务器在国外,在线添加会非常慢。
在这里插入图片描述

添加组文件夹

在新建的工程中添加 5 个组文件夹,用来存放各种不同的文件,文件从本地建好的工程文件夹下获取,双击组文件夹就会出现添加文件的路径,然后选择文件即可。

名称存放文件
STARTUPstartup_stm32f10x_hd.s
CMSIScore_cm3.c、system_stm32f10x.c
FWLBSTM32FX10x_StdPeriph_Driver\src 文件夹下的全部C文件,即固件库
USER用户编写的文件:main.c:main函数文件,暂时为空
DOC工程说明.txt:程序说明文件,用于说明程序功能和注意事项等

在这里插入图片描述

添加文件(前提先把文件复制或者创建好)(没有文件夹路径的就创建文件夹)

startup_stm32f10x_hd.s:复制整个启动文件夹在Fwlib-Template\Libraries\CMSIS\startup中
core_cm3.c、system_stm32f10x.c 、core_cm3.h、stm32f10x.h、system_stm32f10x.h:复制Fwlib-Template\Libraries\CMSIS中
Fwlib-Template\Libraries\STM32F10x_StdPeriph_Driver中存放inc和src文件夹
Desktop\Fwlib-Template\User存放main.c、stm32f10x_conf.c、stm32f10x_it.c、stm32f10x_conf.h、stm32f10x_it.h
以上除了main.c别的均从上一节说的那个标准模板里面复制,或者去32官网找模板复制,再不行,你下载我上传的。

先把上面提到的文件从 ST 标准库中复制到工程模版对应文件夹的目录下,然后在新建的工程中添加这些文件,双击组文件夹就会出现添加文件的路径,然后选择文件即可。

在这里插入图片描述

配置魔术棒选项卡

这一步的配置工作很重要,很多人串口用不了 printf 函数,编译有问题,下载有问题,都是这个步骤的配置出了错。
(1) Target 中选中微库“Use MicroLib”,为的是在日后编写串口驱动的时候可以使用 printf 函数。
在这里插入图片描述

(2) 在 Output 选项卡中把输出文件夹定位到我们工程目录下的“output”文件夹,如果想在编译的过程中生成 hex 文件,那么那 Create HEX File 选项勾上。
在这里插入图片描述

(3) 在 Listing 选项卡中把输出文件夹定位到我们工程目录下的“Listing”文件夹。
在这里插入图片描述

(4) 在 C/C++ 选项卡中添加处理宏及编译器编译的时候查找的头文件路径。如果头文件路径添加有误,则编译的时候会报错找不到头文件。
在这里插入图片描述

在这里插入图片描述

在这个选项中添加宏,就相当于我们在文件中使用“#define”语句定义宏一样。在编译器中添加宏的好处就是,只要用了这个模版,就不用源文件中修改代码。
• STM32F10X_HD 宏:为了告诉 STM32 标准库,我们使用的芯片类型是 STM32 型号是大容量的,使 STM32 标准库根据我们选定的芯片型号来配置。
• USE_STDPERIPH_DRIVER 宏:为了让 stm32f10x.h 包含 stm32f10x_conf.h 这个头文件。
“Include Paths ”这里添加的是头文件的路径,如果编译的时候提示说找不到头文件,一般就是这里配置出了问题。你把头文件放到了哪个文件夹,就把该文件夹添加到这里即可。(请使用图中的方法用文件浏览器去添加路径,不要直接手打路径,容易出错)

仿真器配置

(这里你看你自己的仿真器是哪个)
Debug 中选择 CMSIS-DAP Debugger
在这里插入图片描述

Utilities 选择 Use Debug Driver
在这里插入图片描述

Settings 选项配置
在这里插入图片描述

选择 CPU 型号

这一步的配置也不是配置一次之后完事,常常会因为各种原因需要重新选择,当你下载的时候,提示说找不到 Device 的时候,请确保该配置是否正确。有时候下载程序之后,不会自动运行,要手动复位的时候,也回来看看这里的“Reset and Run”配置是否失效。STM32F103ZET6用的 STM32 的FLASH 大小是 512kByte,所以这里选择 512k 的容量,如果使用的是其他型号的,要根据实际情况选择。
在这里插入图片描述

一个新的工程模版新建完毕。

补充说明

  1. 如图在 F1 标准库工程组织中的 CMSIS 部分的 core_cm3.c 实际是不需要的,是否留在工程里面没有任何影响,所有例程中都没有使用到它,此文件为官方库保留,已被其他代替。
    在这里插入图片描述

  2. 当要使用 Keil 的 AC6 编译器时,必须去掉 core_cm3.c 文件,因为有不兼容的编译器拓展语法,没有去掉时错误如下
    在这里插入图片描述

将 core_cm3.c 从工程组织去掉即可,其他所有 F1 标准库例程都可以去掉。
在这里插入图片描述


切换 AC5 和 AC6 的位置如下,最新版本 Keil 默认会切换到 AC6,初学者先简单理解为 AC6 比AC5 编译速度更快,但可能输出比较多因为代码不规范的警告,如果不习惯先按教程例程默认用AC5 即可。
在这里插入图片描述

GPIO 输出—使用固件库点亮LED

硬件设计

在本教程中 STM32 芯片与 LED 灯的连接见图LED 硬件原理图 _ ,这是一个 RGB 灯,里面由红蓝绿三个小灯构成,使用 PWM 控制时可以混合成 256256256 种不同的颜色。
在这里插入图片描述

这些 LED 灯的阴极都是连接到 STM32 的 GPIO 引脚,只要我们控制 GPIO 引脚的电平输出状态,即可控制 LED 灯的亮灭。

软件设计

为了使工程更加有条理,我们把 LED 灯控制相关的代码独立分开存储,方便以后移植。在“工程模板”之上新建“bsp_led.c”及“bsp_led.h”文件,其中的“bsp”即 Board Support Packet 的缩写 (板级支持包),这些文件也可根据您的喜好命名,这些文件不属于 STM32 标准库的内容,是由我们自己根据应用需要编写的。

编程要点

  1. 使能 GPIO 端口时钟;
  2. 初始化 GPIO 目标引脚为推挽输出模式;
  3. 编写简单测试程序,控制 GPIO 引脚输出高、低电平。

代码分析

LED 灯引脚宏定义(bsp_led.h中)

在编写应用程序的过程中,要考虑更改硬件环境的情况,例如 LED 灯的控制引脚与当前的不一样,我们希望程序只需要做最小的修改即可在新的环境正常运行。这个时候一般把硬件相关的部分使用宏来封装,若更改了硬件环境,只修改这些硬件相关的宏即可,这些定义一般存储在头文件,即本例子中的“bsp_led.h”文件中。

// R-红色
#define LED1_GPIO_PORT										GPIOB
#define LED1_GPIO_CLK											RCC_APB2Periph_GPIOB
#define LED1_GPIO_PINGPIO_Pin_5
// G-绿色
#define LED2_GPIO_PORT										GPIOB
#define LED2_GPIO_CLK											RCC_APB2Periph_GPIOB
#define LED2_GPIO_PIN											GPIO_Pin_0
// B-蓝色
#define LED3_GPIO_PORT										GPIOB
#define LED3_GPIO_CLK											RCC_APB2Periph_GPIOB
#define LED3_GPIO_PIN											GPIO_Pin_1

以上代码分别把控制 LED 灯的 GPIO 端口、GPIO 引脚号以及 GPIO 端口时钟封装起来了。在实际控制的时候我们就直接用这些宏,以达到应用代码硬件无关的效果。
其中的 GPIO 时钟宏“RCC_APB2Periph_GPIOB”是 STM32 标准库定义的 GPIO 端口时钟相关的宏,它的作用与“GPIO_Pin_x”这类宏类似,是用于指示寄存器位的,方便库函数使用,下面初始化 GPIO 时钟的时候可以看到它的用法。

控制 LED 灯亮灭状态的宏定义

为了方便控制 LED 灯,我们把 LED 灯常用的亮、灭及状态反转的控制也直接定义成宏。

/* 直接操作寄存器的方法控制 IO */
#define digitalHi(p,i)										{p->BSRR=i;}//输出为高电平
#define digitalLo(p,i)										{p->BRR=i;}//输出低电平
#define digitalToggle(p,i)								{p->ODR ^=i;}//输出反转状态

/* 定义控制 IO 的宏 */
#define LED1_TOGGLE												digitalToggle(LED1_GPIO_PORT,LED1_GPIO_PIN)
#define LED1_OFF													digitalHi(LED1_GPIO_PORT,LED1_GPIO_PIN)
#define LED1_ON														digitalLo(LED1_GPIO_PORT,LED1_GPIO_PIN)

#define LED2_TOGGLE												digitalToggle(LED2_GPIO_PORT,LED2_GPIO_PIN)
#define LED2_OFF													digitalHi(LED2_GPIO_PORT,LED2_GPIO_PIN)
#define LED2_ON														digitalLo(LED2_GPIO_PORT,LED2_GPIO_PIN)

#define LED3_TOGGLE												digitalToggle(LED2_GPIO_PORT,LED3_GPIO_PIN)
#define LED3_OFF													digitalHi(LED2_GPIO_PORT,LED3_GPIO_PIN)
#define LED3_ON														digitalLo(LED2_GPIO_PORT,LED3_GPIO_PIN)

/* 基本混色,后面高级用法使用 PWM 可混出全彩颜色, 且效果更好 */

//红
#define LED_RED\
									LED1_ON;\
									LED2_OFF;\
									LED3_OFF

//绿
#define LED_GREEN\
									LED1_OFF;\
									LED2_ON;\
									LED3_OFF

//蓝
#define LED_BLUE\
									LED1_OFF;\
									LED2_OFF;\
									LED3_ON


//黄 (红 + 绿)
#define LED_YELLOW\
									LED1_ON;\
									LED2_ON;\
									LED3_OFF
									
//紫 (红 + 蓝)
#define LED_PURPLE\
									LED1_ON;\
									LED2_OFF;\
									LED3_ON

//青 (绿 + 蓝)
#define LED_CYAN \
									LED1_OFF;\
									LED2_ON;\
									LED3_ON

//白 (红 + 绿 + 蓝)
#define LED_WHITE\
									LED1_ON;\
									LED2_ON;\
									LED3_ON

//黑 (全部关闭)
#define LED_RGBOFF\
										LED1_OFF;\
										LED2_OFF;\
										LED3_OFF

这部分宏控制 LED 亮灭的操作是直接向 BSRR、BRR 和 ODR 这三个寄存器写入控制指令来实现的,对 BSRR 写 1 输出高电平,对 BRR 写 1 输出低电平,对 ODR 寄存器某位进行异或操作可反转位的状态。
RGB 彩灯可以实现混色,如最后一段代码我们控制红灯和绿灯亮而蓝灯灭,可混出黄色效果。
代码中的“\”是 C 语言中的续行符语法,表示续行符的下一行与续行符所在的代码是同一行。代码中因为宏定义关键字“#define”只是对当前行有效,所以我们使用续行符来连接起来,以下的代码是等效的:

define LED_YELLOW LED1_ON; LED2_ON; LED3_OFF

应用续行符的时候要注意,在“\”后面不能有任何字符 (包括注释、空格),只能直接回车。

LED GPIO 初始化函数(bsp_led.c中)

利用上面的宏,编写 LED 灯的初始化函数。

/**
  * @brief  初始化控制LED的IO
  * @param  无
  * @retval 无
  */
	
void static LED_GPIO_Config(void)
{
		/*定义一个GPIO_InitTypeDef类型的结构体*/
		GPIO_InitTypeDef  GPIO_InitStructure;

		/*开启LED相关的GPIO外设时钟*/
		RCC_APB2PeriphClockCmd(LED1_GPIO_CLK | LED2_GPIO_CLK | LED3_GPIO_CLK,ENABLE);
		/*选择要控制的GPIO引脚*/
		GPIO_InitStructure.GPIO_Pin = LED1_GPIO_PIN;

		/*设置引脚模式为通用推挽输出*/
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

		/*设置引脚速率为50MHz */   
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

		/*调用库函数,初始化GPIO*/
		GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure);	
		
		/*选择要控制的GPIO引脚*/
		GPIO_InitStructure.GPIO_Pin = LED2_GPIO_PIN;

		/*调用库函数,初始化GPIO*/
		GPIO_Init(LED2_GPIO_PORT, &GPIO_InitStructure);	
		
		/*选择要控制的GPIO引脚*/
		GPIO_InitStructure.GPIO_Pin = LED3_GPIO_PIN;

		/*调用库函数,初始化GPIOF*/
		GPIO_Init(LED3_GPIO_PORT, &GPIO_InitStructure);	

		/* 关闭所有led灯	*/
		GPIO_SetBits(LED1_GPIO_PORT, LED1_GPIO_PIN);
		GPIO_SetBits(LED2_GPIO_PORT, LED2_GPIO_PIN);
		GPIO_SetBits(LED3_GPIO_PORT, LED3_GPIO_PIN);		


}

/**
  * @brief  LED延时函数(不准,大概1s吧)
  * @param  无
  * @retval 无
  */

void static Led_GPIO_Delay(int time){
	int temp_time=0x8FFFF;
	while(time--){
	temp_time=0x8FFFFF;
		while(temp_time--);
	}
}

/**
  * @brief  LED测试函数
  * @param  无
  * @retval 无
  */
	

void LED_GPIO_Test(void){
	
		//LED灯初始化
		LED_GPIO_Config();
	
		while(1){
							//Led红色,延时1S
							LED_RED;
							Led_GPIO_Delay(1);

							//Led绿色,延时1S
							LED_GREEN;
							Led_GPIO_Delay(1);
						
							//Led蓝色,延时1S
							LED_BLUE;
							Led_GPIO_Delay(1);
						
							//Led黄色,延时1S
							LED_YELLOW;
							Led_GPIO_Delay(1);
						
							//Led紫色,延时1S
							LED_PURPLE;
							Led_GPIO_Delay(1);
							
							//Led青色,延时1S
							LED_CYAN;
							Led_GPIO_Delay(1);
							
							//Led白色,延时1S
							LED_WHITE;
							Led_GPIO_Delay(1);
										
							//Led黑色,延时1S
							LED_RGBOFF;
							Led_GPIO_Delay(1);
						}
	
}


整个函数与“构建库函数雏形”章节中的类似,主要区别是硬件相关的部分使用宏来代替,初始
化 GPIO 端口时钟时也采用了 STM32 库函数,函数执行流程如下:
(1) 使用 GPIO_InitTypeDef 定义 GPIO 初始化结构体变量,以便下面用于存储 GPIO 配置。
(2) 调用库函数 RCC_APB2PeriphClockCmd 来使能 LED 灯的 GPIO 端口时钟,在前面的章节中我们是直接向 RCC 寄存器赋值来使能时钟的,不如这样直观。该函数有两个输入参数,第一个参数用于指示要配置的时钟,如本例中“RCC_APB2Periph_GPIOB”,应用时我们使用“|”操作同时配置 3 个 LED 灯的时钟;函数的第二个参数用于设置状态,可输入“Disable”关闭或“Enable”使能时钟。
(3) 向 GPIO 初始化结构体赋值,把引脚初始化成推挽输出模式,其中的 GPIO_Pin 使用宏“LEDx_GPIO_PIN”来赋值,使函数的实现方便移植。
(4) 使用以上初始化结构体的配置,调用 GPIO_Init 函数向寄存器写入参数,完成 GPIO 的初始化,这里的 GPIO 端口使用“LEDx_GPIO_PORT”宏来赋值,也是为了程序移植方便。
(5) 使用同样的初始化结构体,只修改控制的引脚和端口,初始化其它 LED 灯使用的 GPIO 引脚。
(6) 使用宏控制 RGB 灯默认关闭。
将#include “bsp_led.h” 写入 stm32f10x_conf.c文件中

主函数(main.c中)

编写完 LED 灯的控制函数后,就可以在 main 函数中测试了。

int main(void)	
{
	// 来到这里的时候,系统的时钟已经被配置成72M。

	LED_GPIO_Test();
	while(1);
}

在 main 函数中,调用我们前面定义的 LED_GPIO_Config 初始化好 LED 的控制引脚,然后直接调用各种控制 LED 灯亮灭的宏来实现 LED 灯的控制。
以上,就是一个使用 STM32 标准软件库开发应用的流程。(现象就是不同颜色的灯闪)

STM32 标准库补充知识

SystemInit 函数去哪了?

在前面章节中我们自己建工程的时候需要定义一个 SystemInit 空函数,但是在这个用 STM32 标准库的工程却没有这样做,SystemInit 函数去哪了呢?
这个函数在 STM32 标准库的“system_stm32f10x.c”文件中定义了,而我们的工程已经包含该文件。标准库中的 SystemInit 函数把 STM32 芯片的系统时钟设置成了 72MHz,即此时 AHB 时钟频率为 72MHz,APB2 为 72MHz,APB1 为 36MHz。当 STM32 芯片上电后,执行启动文件中的指令后,会调用该函数,设置系统时钟为以上状态。

断言

细心对比过前几章我们自己定义的 GPIO_Init 函数与 STM32 标准库中同名函数的读者,会发现标准库中的函数内容多了一些乱七八糟的东西,就是断言。

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
uint32_t pinpos = 0x00, pos = 0x00 , currentpin = 0x00;

/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));

基本上每个库函数的开头都会有这样类似的内容,这里的“assert_param”实际是一个宏,在库函数中它用于检查输入参数是否符合要求,若不符合要求则执行某个函数输出警告,“assert_param”的定义如下

#ifdef			USE_FULL_ASSERT
/**
* @briefassert_param 宏用于函数的输入参数检查
* @paramexpr: 若 expr 值为假,则调用 assert_failed 函数
*报告文件名及错误行号
*若 expr 值为真,则不执行操作
*/
#define assert_param(expr)		((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
/* 错误输出函数 ------------------------------------------------------- */
void assert_failed(uint8_t* file, uint32_t line);
#else
#define assert_param(expr) ((void)0)
#endif

这段代码的意思是,假如我们不定义“USE_FULL_ASSERT”宏,那么“assert_param”就是一个空的宏 (#else 与 #endif 之间的语句生效),没有任何操作。从而所有库函数中的 assert_param 实际上都无意义,我们就当看不见好了。假如我们定义了“USE_FULL_ASSERT”宏,那么“assert_param”就是一个有操作的语句 (#if 与#else 之间的语句生效),该宏对参数 expr 使用 C 语言中的问号表达式进行判断,若 expr 值为真,则无操作 (void 0),若表达式的值为假,则调用“assert_failed”函数,且该函数的输入参数为“FILE”及“LINE”,这两个参数分别代表“assert_param”宏被调用时所在的“文件名”及“行号”。
但库文件只对“assert_failed”写了函数声明,没有写函数定义,实际用时需要用户来定义,我们一般会用 printf 函数来输出这些信息,如下:

void assert_failed(uint8_t * file, uint32_t line)
{
printf(“\r\n 输入参数错误,错误文件名 =%s, 行号 =%s”,file,line);
}

注意在我们的这个 LED 工程中,还不支持 printf 函数 (在 USART 外设章节会讲解),想测试 assert_failed 输出的读者,可以在这个函数中做点亮红色 LED 灯的操作,作为警告输出测试。

那 么 为 什 么 函 数 输 入 参 数 不 对 的 时 候,assert_param宏 中 的expr参 数 值 会 是假呢? 这 要 回 到GPIO_Init函 数, 看 它 对assert_param宏 的 调 用, 它 被 调 用 时 分 别 以“IS_GPIO_ALL_PERIPH(GPIOx)” 、 “IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin)”等作为输入参数,也就是说被调用时,expr 实际上是一条针对输入参数的判断表达式。例如“IS_GPIO_PIN”的宏定义:

#define IS_GPIO_PIN(PIN) ((((PIN) & (uint16_t)0x00) == 0x00) && ((PIN) != (uint16_t)0x00))

若它的输入参数 PIN 值为 0,则表达式的值为假,PIN 非 0 时表达式的值为真。我们知道用于选择 GPIO 引脚号的宏“GPIO_Pin_x”的值至少有一个数据位为 1,这样的输入参数才有意义,若 GPIO_InitStruct->GPIO_Pin 的值为 0,输入参数就无效了。配合“IS_GPIO_PIN”这句表达式,“assert_param”就实现了检查输入参数的功能。对 assert_param 宏的其它调用方式类似,大家可以自己看库源码来研究一下。

Doxygen 注释规范(这个玩意我认为是本篇最重要的,没注释的代码,简直了)

在 STM32 标准库以及我们自己编写的“bsp_led.c”文件中,可以看到一些比较特别的注释,类似

/**
* @brief初始化控制 LED 的 IO
* @param无
* @retval 无
*/

这 是 一 种 名 为 “Doxygen” 的 注 释 规 范, 如 果 在 工 程 文 件 中 按 照 这 种 规 范 去 注 释, 可以 使 用 Doxygen 软 件 自 动 根 据 注 释 生 成 帮 助 文 档。 我 们 所 说 非 常 重 要 的 库 帮 助 文 档《stm32f10x_stdperiph_lib_um.chm》,就是由该软件根据库文件的注释生成的。
具体可参考:https://zhuanlan.zhihu.com/p/100223113

防止头文件重复包含

在 STM32 标准库的所有头文件以及我们自己编写的“bsp_led.h”头文件中,可看到类似代码的宏定义。它的功能是防止头文件被重复包含,避免引起编译错误。

#ifndef __LED_H
#define __LED_H

/* 此处省略头文件的具体内容 */

#endif /* end of __LED_H */

在头文件的开头,使用“#ifndef”关键字,判断标号“__LED_H”是否被定义,若没有被定义,则从“#ifndef”至“#endif”关键字之间的内容都有效,也就是说,这个头文件若被其它文件“#include”,它就会被包含到其该文件中了,且头文件中紧接着使用“#define”关键字定义上面判断的标号“__LED_H”。当这个头文件被同一个文件第二次“#include”包含的时候,由于有了第一次包含中的“#define __LED_H”定义,这时再判断“#ifndef__LED_H”,判断的结果就是假了,从“#ifndef”至“#endif”之间的内容都无效,从而防止了同一个头文件被包含多次,编译时就不会出现“redefine重复定义)”的错误了。

一般来说,我们不会直接在 C 的源文件写两个“#include”来包含同一个头文件,但可能因为头文件内部的包含导致重复,这种代码主要是避免这样的问题。如“bsp_led.h”文件中使用了“#include“stm32f10x.h””语句,按习惯,可能我们写主程序的时候会在 main 文件写“#include “bsp_led.h”及 #include “stm32f10x.h””,这个时候“stm32f10x.h”文件就被包含两次了,如果没有这种机制,就会出错。

至于为什么要用两个下划线来定义“__LED_H”标号,其实这只是防止它与其它普通宏定义重复了, 如我们用“GPIO_PIN_0”来代替这个判断标号,就会因为 stm32f10x.h 已经定义了 GPIO_PIN_0,结果导致“bsp_led.h”文件无效了,“bsp_led.h”文件一次都没被包含。

代码

bsp_led.c

/**
 * *****************************************************************************
 * @file        bsp_led.c
 * @brief      	点亮或者熄灭LED灯
 * @author       (六千里)
 * @date        2024-03-08
 * @copyright   无
 * *****************************************************************************
 */

#include "bsp_led.h"


/**
  * @brief  初始化控制LED的IO
  * @param  无
  * @retval 无
  */
	
void static LED_GPIO_Config(void)
{
		/*定义一个GPIO_InitTypeDef类型的结构体*/
		GPIO_InitTypeDef  GPIO_InitStructure;

		/*开启LED相关的GPIO外设时钟*/
		RCC_APB2PeriphClockCmd(LED1_GPIO_CLK | LED2_GPIO_CLK | LED3_GPIO_CLK,ENABLE);
		/*选择要控制的GPIO引脚*/
		GPIO_InitStructure.GPIO_Pin = LED1_GPIO_PIN;

		/*设置引脚模式为通用推挽输出*/
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

		/*设置引脚速率为50MHz */   
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

		/*调用库函数,初始化GPIO*/
		GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure);	
		
		/*选择要控制的GPIO引脚*/
		GPIO_InitStructure.GPIO_Pin = LED2_GPIO_PIN;

		/*调用库函数,初始化GPIO*/
		GPIO_Init(LED2_GPIO_PORT, &GPIO_InitStructure);	
		
		/*选择要控制的GPIO引脚*/
		GPIO_InitStructure.GPIO_Pin = LED3_GPIO_PIN;

		/*调用库函数,初始化GPIOF*/
		GPIO_Init(LED3_GPIO_PORT, &GPIO_InitStructure);	

		/* 关闭所有led灯	*/
		GPIO_SetBits(LED1_GPIO_PORT, LED1_GPIO_PIN);
		GPIO_SetBits(LED2_GPIO_PORT, LED2_GPIO_PIN);
		GPIO_SetBits(LED3_GPIO_PORT, LED3_GPIO_PIN);		


}

/**
  * @brief  LED延时函数(不准,大概1s吧)
  * @param  无
  * @retval 无
  */

void static Led_GPIO_Delay(int time){
	int temp_time=0x8FFFF;
	while(time--){
	temp_time=0x8FFFFF;
		while(temp_time--);
	}
}

/**
  * @brief  LED测试函数
  * @param  无
  * @retval 无
  */
	

void LED_GPIO_Test(void){
	
		//LED灯初始化
		LED_GPIO_Config();
	
		while(1){
							//Led红色,延时1S
							LED_RED;
							Led_GPIO_Delay(1);

							//Led绿色,延时1S
							LED_GREEN;
							Led_GPIO_Delay(1);
						
							//Led蓝色,延时1S
							LED_BLUE;
							Led_GPIO_Delay(1);
						
							//Led黄色,延时1S
							LED_YELLOW;
							Led_GPIO_Delay(1);
						
							//Led紫色,延时1S
							LED_PURPLE;
							Led_GPIO_Delay(1);
							
							//Led青色,延时1S
							LED_CYAN;
							Led_GPIO_Delay(1);
							
							//Led白色,延时1S
							LED_WHITE;
							Led_GPIO_Delay(1);
										
							//Led黑色,延时1S
							LED_RGBOFF;
							Led_GPIO_Delay(1);
						}
		

}

bsp_led.h

#ifndef __BSP_LED_C
#define __BSP_LED_C

/**
 * *****************************************************************************
 * 包含的头文件
 * *****************************************************************************
 */

#include "stm32f10x.h"

/**
 * *****************************************************************************
 * 宏定义
 * *****************************************************************************
 */

// R-红色
#define LED1_GPIO_PORT										GPIOB
#define LED1_GPIO_CLK											RCC_APB2Periph_GPIOB
#define LED1_GPIO_PIN											GPIO_Pin_5
// G-绿色
#define LED2_GPIO_PORT										GPIOB
#define LED2_GPIO_CLK											RCC_APB2Periph_GPIOB
#define LED2_GPIO_PIN											GPIO_Pin_0
// B-蓝色
#define LED3_GPIO_PORT										GPIOB
#define LED3_GPIO_CLK											RCC_APB2Periph_GPIOB
#define LED3_GPIO_PIN											GPIO_Pin_1


/* 直接操作寄存器的方法控制 IO */
#define digitalHi(p,i)										{p->BSRR=i;}//输出为高电平
#define digitalLo(p,i)										{p->BRR=i;}//输出低电平
#define digitalToggle(p,i)								{p->ODR ^=i;}//输出反转状态

/* 定义控制 IO 的宏 */
#define LED1_TOGGLE												digitalToggle(LED1_GPIO_PORT,LED1_GPIO_PIN)
#define LED1_OFF													digitalHi(LED1_GPIO_PORT,LED1_GPIO_PIN)
#define LED1_ON														digitalLo(LED1_GPIO_PORT,LED1_GPIO_PIN)

#define LED2_TOGGLE												digitalToggle(LED2_GPIO_PORT,LED2_GPIO_PIN)
#define LED2_OFF													digitalHi(LED2_GPIO_PORT,LED2_GPIO_PIN)
#define LED2_ON														digitalLo(LED2_GPIO_PORT,LED2_GPIO_PIN)

#define LED3_TOGGLE												digitalToggle(LED2_GPIO_PORT,LED3_GPIO_PIN)
#define LED3_OFF													digitalHi(LED2_GPIO_PORT,LED3_GPIO_PIN)
#define LED3_ON														digitalLo(LED2_GPIO_PORT,LED3_GPIO_PIN)

/* 基本混色,后面高级用法使用 PWM 可混出全彩颜色, 且效果更好 */

//红
#define LED_RED\
									LED1_ON;\
									LED2_OFF;\
									LED3_OFF

//绿
#define LED_GREEN\
									LED1_OFF;\
									LED2_ON;\
									LED3_OFF

//蓝
#define LED_BLUE\
									LED1_OFF;\
									LED2_OFF;\
									LED3_ON


//黄 (红 + 绿)
#define LED_YELLOW\
									LED1_ON;\
									LED2_ON;\
									LED3_OFF
									
//紫 (红 + 蓝)
#define LED_PURPLE\
									LED1_ON;\
									LED2_OFF;\
									LED3_ON

//青 (绿 + 蓝)
#define LED_CYAN \
									LED1_OFF;\
									LED2_ON;\
									LED3_ON

//白 (红 + 绿 + 蓝)
#define LED_WHITE\
									LED1_ON;\
									LED2_ON;\
									LED3_ON

//黑 (全部关闭)
#define LED_RGBOFF\
										LED1_OFF;\
										LED2_OFF;\
										LED3_OFF

/**
 * *****************************************************************************
 * .c文件中包含的函数
 * *****************************************************************************
 */
void static LED_GPIO_Config(void);
void static Led_GPIO_Delay(int time);
void LED_GPIO_Test(void);
#endif  /*__BSP_LED_C*/

main.c

/**
 * *****************************************************************************
 * @file        main.c
 * @brief       主函数
 * @author       (六千里)
 * @date        2024-03-06
 * @copyright   无
 * *****************************************************************************
 */
#include "stm32f10x.h"  

int main(void)	
{
	// 来到这里的时候,系统的时钟已经被配置成72M。

	LED_GPIO_Test();

}

stm32f10x_conf.h

/**
  ******************************************************************************
  * @file    Project/STM32F10x_StdPeriph_Template/stm32f10x_conf.h 
  * @author  MCD Application Team
  * @version V3.5.0
  * @date    08-April-2011
  * @brief   Library configuration file.
  ******************************************************************************
  * @attention
  *
  * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
  * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE
  * TIME. AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY
  * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING
  * FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE
  * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
  *
  * <h2><center>&copy; COPYRIGHT 2011 STMicroelectronics</center></h2>
  ******************************************************************************
  */

/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __STM32F10x_CONF_H
#define __STM32F10x_CONF_H

/* Includes ------------------------------------------------------------------*/
/* Uncomment/Comment the line below to enable/disable peripheral header file inclusion */
#include "stm32f10x_adc.h"
#include "stm32f10x_bkp.h"
#include "stm32f10x_can.h"
#include "stm32f10x_cec.h"
#include "stm32f10x_crc.h"
#include "stm32f10x_dac.h"
#include "stm32f10x_dbgmcu.h"
#include "stm32f10x_dma.h"
#include "stm32f10x_exti.h"
#include "stm32f10x_flash.h"
#include "stm32f10x_fsmc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_i2c.h"
#include "stm32f10x_iwdg.h"
#include "stm32f10x_pwr.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_rtc.h"
#include "stm32f10x_sdio.h"
#include "stm32f10x_spi.h"
#include "stm32f10x_tim.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_wwdg.h"
#include "misc.h" /* High level functions for NVIC and SysTick (add-on to CMSIS functions) */

#include "bsp_led.h"


/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Uncomment the line below to expanse the "assert_param" macro in the 
   Standard Peripheral Library drivers code */
/* #define USE_FULL_ASSERT    1 */

/* Exported macro ------------------------------------------------------------*/
#ifdef  USE_FULL_ASSERT

/**
  * @brief  The assert_param macro is used for function's parameters check.
  * @param  expr: If expr is false, it calls assert_failed function which reports 
  *         the name of the source file and the source line number of the call 
  *         that failed. If expr is true, it returns no value.
  * @retval None
  */
  #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
  void assert_failed(uint8_t* file, uint32_t line);
#else
  #define assert_param(expr) ((void)0)
#endif /* USE_FULL_ASSERT */

#endif /* __STM32F10x_CONF_H */

/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/

参考https://doc.embedfire.com/products/link/zh/latest/index.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/441183.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

API可视化编排,提高API可复用率

在数字化时代&#xff0c;API&#xff08;应用程序编程接口&#xff09;已成为不同软件应用之间沟通的桥梁。然而&#xff0c;如何高效管理、编排和复用这些API&#xff0c;成为了企业和开发者面临的重要挑战。随着技术的不断进步&#xff0c;RestCloud API可视化编排应运而生&…

PCIE的TLP包的封包解包原理

前言&#xff1a;开始pcie项目之前需要知道&#xff0c;本次项目我们是使用现有的框架RIFFA框架去完成设计的&#xff0c;因此比起具体代码的含义&#xff0c;更注重框架的使用。在开始项目之前需要了解PCIE的组建包过程。 一、TLP包的基本格式&#xff1a; 1.1整体包结构概述…

01-DevOps代码上线-git入门及gitlab远程仓库

一、准备学习环境 10.0.0.71-gitlab 2c2g-20GB 10.0.0.72-jenkins 2c2g-20GB 10.0.0.73-sonarqube 1c1g-20GB 10.0.0.74-nexus 1c1g-20GB 10.0.0.75-dm 1c1g-20GB &#xff08;模拟写代码服务器&#xff09; 在centos系统中&…

2024 批量下载公众号文章内容/阅读数/在看数/点赞数/留言数/粉丝数导出pdf文章备份(带留言):公众号记忆承载近1500篇历史文章在线查看,找文章方便了

关于公众号文章批量下载&#xff0c;我之前写过很多文章&#xff1a; 视频更新版&#xff1a;批量下载公众号文章内容/话题/图片/封面/音频/视频&#xff0c;导出html&#xff0c;pdf&#xff0c;excel包含阅读数/点赞数/留言数 2021陶博士2006/caoz的梦呓/刘备我祖/六神读金…

微服务架构 | 多级缓存

INDEX 通用设计概述2 优势3 最佳实践 通用设计概述 通用设计思路如下图 内容分发网络&#xff08;CDN&#xff09; 可以理解为一些服务器的副本&#xff0c;这些副本服务器可以广泛的部署在服务器提供服务的区域内&#xff0c;并存有服务器中的一些数据。 用户访问原始服务器…

HNU-算法设计与分析-甘晴void学习感悟

前言 算法设计与分析&#xff0c;仅就课程而言&#xff0c;似乎是数据结构与算法分析的延续 教材使用&#xff1a; 课程 关于课程&#xff0c;橙学长讲的非常清晰&#xff0c;我深以为然。 HNUCS-大三课程概览-CSDN博客文章浏览阅读1.3k次&#xff0c;点赞5次&#xff0c;收…

JVM-垃圾收集底层算法实现

三色标记 背景描述 在并发标记的过程中&#xff0c;因为标记期间应用线程还在继续跑&#xff0c;对象间的引用可能发生变化&#xff0c;多标和漏标的情况就有可能发生。 如何解决上面的问题&#xff1f; 引入“三色标记” 意思就是&#xff0c;把Gcroots可达性分析遍历对象过程…

【北京迅为】《iTOP-3588开发板网络环境配置手册》第4章 开发板直连电脑配置方法(无线上网)

RK3588是一款低功耗、高性能的处理器&#xff0c;适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用&#xff0c;RK3588支持8K视频编解码&#xff0c;内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…

qtvs2022工程cmakelist.txt添加QCharts模块

find_package(QT NAMES Qt5 COMPONENTS Core Gui Widgets OpenGL Concurrent Charts Sql Network REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Widgets OpenGL Charts Concurrent Sql Network REQUIRED)这里find_package只是设置搜索路径&#xff0c;为…

使用ffmpeg提取视频中的音频并保存为单声道wav

1 原始视频信息 通过ffmpeg -i命令查看视频基本信息 $ ffmpeg -i C0439.MP4 ffmpeg version 6.1-essentials_build-www.gyan.dev Copyright (c) 2000-2023 the FFmpeg developersbuilt with gcc 12.2.0 (Rev10, Built by MSYS2 project)configuration: --enable-gpl --enable…

设计模式学习笔记 - 设计原则 - 10.实战:针对非业务的通用框架开发,如何做需求分析和设计及如何实现一个支持各种统计规则的性能计数器

前言 接下来我们在结合一个支持各种统计规则的性能计数项目&#xff0c;学习针对一个非业务的通用框架开发&#xff0c;如何来做需求分析、设计和实现&#xff0c;同时学习如何灵活应用各种设计原则。 项目背景 设计开发一个小的框架&#xff0c;能够获取接口调用的各种统计信…

Mysql中的MVCC

”真正学会&#xff0c;如你般自由~“ MVCC机制简介 MVCC(Multi-Version-Concurrency-Control)多版本并发控制&#xff0c;MVCC 是一种并发控制的方法&#xff0c;一般在数据库管理系统中&#xff0c;实现对数据库的并发访问&#xff1b;在编程中实现事务内存。 取自 MVCC存在被…

基于Python3的数据结构与算法 - 12 数据结构(列表和栈)

目录 一、引入 二、分类 三、列表 1. C语言中数组的存储方式 2. Python中列表的存储方式 四、栈 1. 栈的应用 -- 括号匹配问题 一、引入 定义&#xff1a;数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成。简单来说&#x…

防御保护 IPSEC VPPN实验

实验背景&#xff1a;FW1和FW2是双机热备 主备备份模式。 实验要求&#xff1a;在FW5和FW3之间建立一条IPSEC通道&#xff0c;保证10.0.2.0/24网段可以正常访问到192.168.1.0/24 IPSEC VPPN实验配置&#xff08;由于是双机热备状态&#xff0c;所以FW1和FW2只需要配置FW1主设…

Cloud-Nacos服务治理-Feign服务调用

构建Cloud 父工程依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.…

设计模式-行为型模式-职责链模式

在软件系统运行时&#xff0c;对象并不是孤立存在的&#xff0c;它们可以通过相互通信协作完成某些功能&#xff0c;一个对象在运行时也将影响到其他对象的运行。行为型模式&#xff08;Behavioral Pattern&#xff09;关注系统中对象之间的交互&#xff0c;研究系统在运行时对…

基于springboot的某大学外卖系统的实现(源码+论文)

文章目录 目录 文章目录 前言 一、功能设计 二、功能实现 1 后台登录 2管理员界面 3员工信息管理 4客户信息管理 三、库表设计 四、论文 前言 如今&#xff0c;信息化不断的高速发展&#xff0c;社会也跟着不断进步&#xff0c;现今的社会&#xff0c;各种工作都离不开信息化技…

蓝桥杯每日一题:烤鸡dfs

这道题考察了dfs的应用&#xff0c;题干十分有趣&#xff0c;思考过程对以后类似题目也有很强的参考性&#xff0c;一起来学习吧&#xff01; 题目&#xff1a; # 烤鸡 ## 题目背景 猪猪 Hanke 得到了一只鸡。 ## 题目描述 猪猪 Hanke 特别喜欢吃烤鸡&#xff08;本是同畜…

图片速览 BitNet: 1-bit LLM

输入数据 模型使用absmax 量化方法进行b比特量化,将输入量化到 [ − Q b , Q b ] ( Q b 2 b − 1 ) \left[-Q_{b},Q_{b}\right](Q_{b}2^{b-1}) [−Qb​,Qb​](Qb​2b−1) x ~ Q u a n t ( x ) C l i p ( x Q b γ , − Q b ϵ , Q b − ϵ ) , Clip ⁡ ( x , a , b ) ma…

供应链管理系统(SCM):得供应链得天下不是空话。

2023-08-26 15:51贝格前端工场 Hi&#xff0c;我是贝格前端工场&#xff0c;优化升级各类管理系统的界面和体验&#xff0c;是我们核心业务之一&#xff0c;欢迎老铁们评论点赞互动&#xff0c;有需求可以私信我们 一、供应链对于企业的重要性 供应链对企业经营的重要性不可…