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红色,延时1SLED_RED;Led_GPIO_Delay(1);//Led绿色,延时1SLED_GREEN;Led_GPIO_Delay(1);//Led蓝色,延时1SLED_BLUE;Led_GPIO_Delay(1);//Led黄色,延时1SLED_YELLOW;Led_GPIO_Delay(1);//Led紫色,延时1SLED_PURPLE;Led_GPIO_Delay(1);//Led青色,延时1SLED_CYAN;Led_GPIO_Delay(1);//Led白色,延时1SLED_WHITE;Led_GPIO_Delay(1);//Led黑色,延时1SLED_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红色,延时1SLED_RED;Led_GPIO_Delay(1);//Led绿色,延时1SLED_GREEN;Led_GPIO_Delay(1);//Led蓝色,延时1SLED_BLUE;Led_GPIO_Delay(1);//Led黄色,延时1SLED_YELLOW;Led_GPIO_Delay(1);//Led紫色,延时1SLED_PURPLE;Led_GPIO_Delay(1);//Led青色,延时1SLED_CYAN;Led_GPIO_Delay(1);//Led白色,延时1SLED_WHITE;Led_GPIO_Delay(1);//Led黑色,延时1SLED_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.mzph.cn/news/730539.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:DatePicker)

日期选择器组件&#xff0c;用于根据指定日期范围创建日期滑动选择器。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 无 接口 DatePicker(options?: DatePickerOptions) 根据指定范…

java及特性的简单介绍

简介&#xff1a; 印度尼西亚有一个盛产咖啡的岛屿java&#xff0c;中文名翻译为爪哇&#xff0c;给这种新语言起码java 寓意端上一杯热咖啡。 特性&#xff1a; 1.面向对象 2.与平台无关 3.稳定安全 4.多线程 面向对象 1.面向对象语言关注的是对象&#xff0c;而不关注过程 2…

Redis缓存穿透-缓存雪崩-缓存击穿

什么叫缓存穿透? 模拟一个场景: 前端用户发送请求获取数据,后端首先会在缓存Redis中查询,如果能查到数据,则直接返回.如果缓存中查不到数据,则要去数据库查询,如果数据库有,将数据保存到Redis缓存中并且返回用户数据.如果数据库没有则返回null; 这个缓存穿透的问题就是这个…

linux安装todesk

xunilToDesk远程桌面软件-免费安全流畅的远程连接电脑手机ToDesk远程控制软件是一款稳定流畅的远程控制电脑手机连接软件,可远程桌面办公,远程协助运维.采用端对端加密,让每一次远程访问都安全可靠。https://www.todesk.com/linux.htmlToDesk远程控制软件是一款稳定流畅的远程控…

【考研数学】李林《880》vs 李永乐《660》完美使用搭配

没有说谁一定好&#xff0c;只有适不适合自身情况&#xff0c;针对自身弱点选择性价比才最高。 两者侧重点不同&#xff0c;660适合强化前期&#xff0c;弥补基础的不足&#xff0c;880适合强化后期&#xff0c;题型全面&#xff0c;提高我们对综合运用知识的能力。 选择习题…

蓝桥杯2023年-买瓜(dfs,类型转换同样耗时)

题目描述 小蓝正在一个瓜摊上买瓜。瓜摊上共有 n 个瓜&#xff0c;每个瓜的重量为 Ai 。 小蓝刀功了得&#xff0c;他可以把任何瓜劈成完全等重的两份&#xff0c;不过每个瓜只能劈一刀。 小蓝希望买到的瓜的重量的和恰好为 m 。 请问小蓝至少要劈多少个瓜才能买到重量恰好…

计算机组成原理之机器:存储器之辅助存储器

计算机组成原理之机器&#xff1a;存储器之辅助存储器 笔记来源&#xff1a;哈尔滨工业大学计算机组成原理&#xff08;哈工大刘宏伟&#xff09; Chapter3&#xff1a;存储器之辅助存储器 3.1 概述 3.2 磁记录原理 通不同方向电流时磁化方向不同&#xff0c;由此区分写入…

BUUCTF:[MRCTF2020]ezmisc

题目地址&#xff1a;https://buuoj.cn/challenges#[MRCTF2020]ezmisc 下载附件打开是一张照片&#xff1a; 放到kali中发现crc校验错误&#xff0c;修改照片宽高&#xff1a; 保存即可发现flag flag为&#xff1a; flag{1ts_vEryyyyyy_ez!}

报错:ModuleNotFoundError: No module named ‘tensorrt’

写在前面 我安装了tensorRT,在运行它自带的模型都没问题。 但是在代码中import tensorrt就报错&#xff1a; ModuleNotFoundError: No module named ‘tensorrt’。 网上搜了一大堆&#xff0c;发现是没有在自己的python环境下安装。 所以特意写这篇文章记录一下。 在进行下一…

webUI自动化测试框架

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

C#中实现接口的一些小知识(C#用abstract或virtual来实现接口成员)

文章目录 不可用的修饰可用的修饰非抽象类实现接口抽象类实现接口抽象类与接口方法同名时一同实现 不可用的修饰 在C#中实现接口时&#xff0c;我们不能直接使用static或const来实现接口成员&#xff0c;因为接口中的成员默认都是实例成员&#xff0c;并且它们表示一种契约&am…

c++的STL(2)-- vector容器

目录 1. 默认构造 代码: 相关知识点: 2. 有参构造函数 以及 使用{}初始化对象 代码: 相关知识点: 3. vector容器在尾部添加和删除元素 代码: 使用push_back()和pop_back()进行尾部元素的添加和删除 相关知识点: 代码: 使用emplace_back在尾部添…

人工蜂群算法

人工蜂群算法 人工蜂群算法&#xff08;Artificial Bee Colony Optimization,ABC&#xff09;是一种基于蜜蜂觅食行为的优化算法&#xff0c;由土耳其学者Karaboga于2005年提出&#xff0c;算法模拟蜜蜂的采蜜行为对优化问题进行求解。 算法原理 ABC算法的核心思想是将优化问…

Mysql学习笔记之事务详解(读未提交、读以提交、可重复读、串行化读)

在这个博主的基础上&#xff0c;增加两种情况的对比&#xff1a;https://blog.csdn.net/llllllkkkkkooooo/article/details/108068919 可重复读中幻读现象&#xff08;未使用MVCC&#xff09; 设置可重复读的隔离级别 set global transaction isolation level repeatable read…

「解析文件流,Java之FileOutputStream助您轻松操作文件!」

&#x1f3c6;本文收录于「滚雪球学Java」专栏&#xff0c;专业攻坚指数级提升&#xff0c;助你一臂之力&#xff0c;带你早日登顶&#x1f680;&#xff0c;欢迎大家关注&&收藏&#xff01;持续更新中&#xff0c;up&#xff01;up&#xff01;up&#xff01;&#xf…

TCP重传机制、滑动窗口、拥塞控制

一、总述 TCP&#xff0c;Transmission Control Protocol&#xff0c;是一个面向连接、基于流式传输的可靠传输协议&#xff0c;考虑到的内容很多&#xff0c;比如数据包的丢失、损坏、分片和乱序等&#xff0c;TCP协议通过多种不同的机制来实现可靠传输。今天&#xff0c;重点…

复试人工智能前沿概念总结

1.大模型相关概念&#xff08;了解即可&#xff09; 1.1 GPT GPT&#xff0c;全称为Generative Pre-training Transformer&#xff0c;是OpenAI开发的一种基于Transformer的大规模自然语言生成模型。GPT模型采用了自监督学习的方式&#xff0c;首先在大量的无标签文本数据上进…

Android使用WebView打开内嵌H5网页

Android打开外部网页链接请参考上一篇文章 https://public.blog.csdn.net/article/details/136384559 继上篇&#xff0c;新建assets文章夹&#xff0c;将H5的网页资源放到此文件夹下 把H5的资源文件都拷进来 这个时候&#xff0c;将添加打开本地网页的代码&#xff1a; //打…

【活动】2024年AI辅助研发:深度变革与无限潜力

作为一名前端程序员&#xff0c;深入探讨2024年AI在软件研发领域的应用趋势及其影响&#xff0c;我们可以看到一场引人注目的转型正在发生。AI辅助研发对于前端开发而言&#xff0c;不仅意味着效率的飞跃&#xff0c;更是在用户体验设计、代码编写、性能优化、项目管理等诸多方…

Java零基础 - 数组的定义和声明

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一个人虽可以走的更快&#xff0c;但一群人可以走的更远。 我是一名后…