给漫画网站做推广,网页微信注册新号怎么注册,美图网,境外公司在国内建网站CMSIS 标准及库层次关系
因为基于 Cortex 系列芯片采用的内核都是相同的#xff0c;区别主要为核外的片上外设的差异#xff0c;这些差异却导致软件在同内核#xff0c;不同外设的芯片上移植困难。为了解决不同的芯片厂商生产的 Cortex 微控制器软件的兼容性问题#xff0…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 文件夹下有 incinclude 的缩写跟 srcsource 的简写这两个文件夹这里的文件属于 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 functions 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工程说明.txtLibrariesCMSIS:里面存放着跟CM3内核有关的库文件、STM32F10x_StdPeriph_Driver:STM32外设库文件Listing暂时为空Output暂时为空Project暂时为空Userstm32f10x_conf.h:用来配置库的头文件、stm32f10x_it.h与stm32f10x_it.c中断相关的函数都在这个文件编写暂时为空、main.c:main函数文件
新建工程
打开 KEIL5新建一个工程工程名根据喜好命名我这里取 Template中文是模版的意思保存在 Projectuv5文件夹下。
选择 CPU 型号
这个根据你开发板使用的 CPU 具体的型号来选择M3 旗舰版选 STM32F103ZE 型号。如果这里没有出现你想要的 CPU 型号或者一个型号都没有那么肯定是你的 KEIL5 没有添加 device 库KEIL5 不像 KEIL4 那样自带了很多 MCU 的型号KEIL5 需要自己添加。
在线添加库文件
等下我们手动本地添加库文件这里我们点击关掉在线添加因为 keil 的服务器在国外在线添加会非常慢。
添加组文件夹
在新建的工程中添加 5 个组文件夹用来存放各种不同的文件文件从本地建好的工程文件夹下获取双击组文件夹就会出现添加文件的路径然后选择文件即可。
名称存放文件STARTUPstartup_stm32f10x_hd.sCMSIScore_cm3.c、system_stm32f10x.cFWLBSTM32FX10x_StdPeriph_Driver\src 文件夹下的全部C文件,即固件库USER用户编写的文件main.cmain函数文件暂时为空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 的容量如果使用的是其他型号的要根据实际情况选择。
一个新的工程模版新建完毕。
补充说明 如图在 F1 标准库工程组织中的 CMSIS 部分的 core_cm3.c 实际是不需要的是否留在工程里面没有任何影响所有例程中都没有使用到它此文件为官方库保留已被其他代替。 当要使用 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 标准库的内容是由我们自己根据应用需要编写的。
编程要点
使能 GPIO 端口时钟初始化 GPIO 目标引脚为推挽输出模式编写简单测试程序控制 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-BSRRi;}//输出为高电平
#define digitalLo(p,i) {p-BRRi;}//输出低电平
#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_time0x8FFFF;while(time--){temp_time0x8FFFFF;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 时钟频率为 72MHzAPB2 为 72MHzAPB1 为 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_time0x8FFFF;while(time--){temp_time0x8FFFFF;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-BSRRi;}//输出为高电平
#define digitalLo(p,i) {p-BRRi;}//输出低电平
#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.** h2centercopy; 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 functions 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