第3章〓STM32CubeMX和HAL库 本章讲述STM32CubeMX和HAL库,包括安装STM32CubeMX、安装MCU固件包、软件功能与基本使用和HAL库。 3.1安装STM32CubeMX STM32CubeMX软件是ST公司为STM32系列微控制器快速建立工程并初始化使用到的外设、GPIO等而设计的,大大缩短了开发时间。同时,该软件不仅能配置STM32外设,还能进行第三方软件系统的配置,如FreeRTOS、FAT 32、LWIP等。STM32CubeMX还有一个功能,就是进行功耗预估。此外,这款软件可以输出PDF、TXT文档,显示所开发工程中的GPIO等外设的配置信息,供开发者进行原理图设计等。 STM32CubeMX是ST官方推出的一款针对ST的MCU/MPU的跨平台图形化工具,支持Linux、macOS、Windows系统,支持ST全系列产品,目前包括STM32L0、STM32L1、STM32L4、STM32L5、STM32F0、STM32F1、STM32F2、STM32F3、STM32F4、STM32F7、STM32G0、STM32G4、STM32H7、STM32WB、STM32WL、STM32MP1,其对接的底层接口是HAL库,STM32CubeMX除了集成MCU/MPU的硬件抽象层,还集成了RTOS、文件系统、USB、网络、显示、嵌入式AI等中间件,这样开发者就能够很轻松地完成MCU/MPU的底层驱动的配置,留出更多精力开发上层功能逻辑,能够更进一步提高嵌入式开发效率。 STM32CubeMX软件的特点如下。 (1) 集成了ST公司的每款型号的MCU/MPU的可配置的图形界面,能够自动提示I/O冲突并且对于复用I/O可自动分配。 (2) 具有动态验证的时钟树。 (3) 能够很方便地使用所集成的中间件。 (4) 能够估算MCU/MPU在不同主频运行下的功耗。 (5) 能够输出不同编译器的工程,如能够直接生成MDK、EWArm、STM32CubeIDE、MakeFile等工程。 为了使开发人员能够更加快捷有效地进行STM32的开发,ST公司推出了一套完整的STM32Cube开发组件。STM32Cube主要包括两部分: 一是STM32CubeMX图形化配置工具,直接在图形界面简单配置下生成初始化代码,并对外设做进一步的抽象,让开发人员只专注于应用的开发; 二是基于STM32微控制器的固件集STM32Cube软件资料包。 从ST公司官网可下载STM32CubeMX软件最新版本的安装包,本书使用的版本是6.6.1。解压安装包后,运行其中的安装程序,按照安装向导的提示进行安装。安装过程中会出现如图31所示的界面,需要勾选第1个复选框后才可以继续安装,第2个复选框可以不用勾选。 图31STM32CubeMX安装向导 在安装过程中,用户要设置软件安装的目录。安装目录中不能带有汉字、空格和非下画线的符号,因为STM32CubeMX对中文的支持不太好。还需要安装器件的MCU固件包,所以最好将它们安装在同一个根目录下。例如,根目录为C:/Program Files/STMicroelectronics/STM32Cube/,可将STM32CubeMX的安装目录设置为C:/Program Files/STMicroelectronics/STM32Cube/STM32CubeMX。 3.2安装MCU固件包 安装MCU固件包,包括软件库文件夹设置和管理嵌入式软件包两部分。 3.2.1软件库文件夹设置 在安装完STM32CubeMX后,若要进行后续的各种操作,必须在STM32CubeMX中设置一个软件库文件夹(Repository Folder)。在STM32CubeMX中安装MCU固件包和STM32Cube扩展包时,都安装到此目录下。 双击桌面上的STM32CubeMX图标,软件启动后的界面如图32所示。 图32STM32CubeMX界面 界面的最上方有3个主菜单项,执行Help→Updater Settings菜单命令,弹出Updater Settings对话框,如图33所示。首次启动STM32CubeMX后,立刻执行这个菜单命令可能提示软件更新已经在后台运行,需要稍微等待一段时间。 图33Updater Settings 对话框 图33中,Repository Folder 就是需要设置的软件库文件夹,所有MCU固件包和扩展包要安装到此目录下。这个文件夹一经设置并且安装了一个固件包之后就不能再更改。不要使用默认的软件库文件夹,因为默认的是用户工作目录下的文件夹,可能带有汉字或空格,安装后会导致使用出错。设置软件库文件夹为C:/Users/lenovo/STM32Cube/Repository/。 Check and Update Settings用于设置STM32CubeMX软件的更新方式,Data AutoRefresh用于设置在STM32CubeMX启动时是否自动刷新已安装软件库的数据和文档。为了加快软件启动速度,可以将其分别设置为Manual Check(手动检查更新软件)和No AutoRefresh at Application start(不在STM32CubeMX启动时自动刷新)。STM32CubeMX启动后,用户可以通过相应的菜单命令检查STM32CubeMX软件,更新或刷新数据。 Connection Parameters选项卡用于设置网络连接参数。如果没有网络代理,直接选择No Proxy(无代理)即可; 如果有网络代理,就设置自己的网络代理参数。 3.2.2管理嵌入式软件包 设置了软件库文件夹,就可以安装MCU固件包和扩展包了。执行Help→Manage embedded software packages菜单命令,弹出Embedded Software Packages Manager(嵌入式软件包管理器)对话框,如图34所示。这里将STM32Cube MCU固件包和STM32Cube扩展包统称为嵌入式软件包。 图34Embedded Software Packages Manager 对话框 该对话框包含多个选项卡,STM32Cube MCU Packages选项卡管理STM32所有系列MCU的固件包。每个系列对应一个节点,节点展开后是这个系列MCU不同版本的固件包。固件包经常更新,在STM32CubeMX里最好只保留一个最新版本的固件包。如果在STM32CubeMX里打开一个用旧的固件包设计的项目,会有提示将项目迁移到新的固件包版本,一般都能成功自动迁移。 对话框下方有几个按钮,它们可用于完成不同的操作功能。 (1) From Local 按钮:从本地文件安装MCU固件包。如果从ST官网下载了固件包的压缩文件,如en.stm32cubef4_vl84.zip是1.8.4版本的STM32CubeF4固件包压缩文件,那么单击From Local 按钮后,选择这个压缩文件(无须解压),就可以安装这个固件包。但是要注意,这个压缩文件不能存放在软件库根目录下。 (2) From Url按钮:需要输入一个URL网址,从指定网站下载并安装固件包,一般不使用这种方式。 (3) Refresh按钮:刷新目录树,以显示是否有新版本的固件包。应该偶尔刷新一下,以保持更新到最新版本。 (4) Install按钮:在目录树里选择一个版本的固件包,如果这个版本的固件包还没有安装,这个按钮就可用。单击Install按钮,将自动从ST官网下载相应版本的固件包并安装。 (5) Remove按钮:在目录树里选择一个版本的固件包,如果已经安装了这个版本的固件包,这个按钮就可用。单击Remove按钮,将删除这个版本的固件包。 本书示例都是基于STM32F407ZGT6开发的,所以需要安装STM32CubeF4固件包。在图34对话框中选择最新版本的STM32Cube MCU Package for STM32F4Series,然后单击Install按钮,将会联网自动下载和安装STM32CubeF4固件包。固件包自动安装到所设置的软件库目录下,并自动建立一个子目录。将固件包安装后目录下的所有程序称为固件库,如1.26.0版本的STM32CubeF4固件包安装后的固件库目录为C:/Users/lenovo/STM32Cube/Repository/STM32Cube_FW_F4_V1.26.0。 STMicroelectronics选项卡的管理内容如图35所示,其中ST公司提供的一些STM32Cube扩展包,包括人工智能库XCUBEAI、图形用户界面库XCUBETOUCHGFX等,以及一些芯片的驱动程序,如MEMS、BLE、NFC芯片的驱动库。 图35STMicroelectronics选项卡的管理内容 3.3软件功能与基本使用 在设置了软件库文件夹并安装了STM32CubeF4固件包之后,就可以开始用STM32CubeMX创建项目并进行操作了。在开始针对开发板开发实际项目之前,我们需要先熟悉STM32CubeMX的一些界面功能和操作。 3.3.1软件界面 软件界面包含初始主界面和主菜单功能两部分。 1. 初始主界面 启动STM32CubeMX之后的初始界面如图32所示。STM32CubeMX从5.0版本开始使用了一种比较新颖的用户界面,与一般的Windows应用软件界面不太相同,也与4.x版本的STM32CubeMX界面相差很大。 初始界面主要分为3个功能区,具体如下。 1) 主菜单栏 界面最上方是主菜单栏,有3个主菜单项,分别是File、Window和Help。这3个菜单项有下拉菜单,用户可通过下拉菜单项进行一些操作。主菜单栏右侧是一些快捷按钮,单击这些按钮就会用浏览器打开相应的网站,如ST社区、ST官网等。 2) 标签导航栏 主菜单栏下方是标签导航栏。在新建或打开项目后,标签导航栏可以在STM32CubeMX的3个主要视图之间快速切换。这3个视图如下。 (1) Home(主页)视图,即如图32所示的界面。 (2) 新建项目视图,新建项目时显示的一个对话框,用于选择具体型号的 MCU或开发板创建项目。 (3) 项目管理视图,用于对创建或打开的项目进行MCU图形化配置、中间件配置、项目管理等操作。 3) 工作区 窗口其他区域都是工作区。STM32CubeMX使用的是单文档界面,工作区会根据当前操作的内容显示不同的界面。 图32的工作区显示的是Home视图,Home视图的工作区可以分为以下3个功能区域。 (1) Existing Projects 区域,显示最近打开过的项目,单击某个项目就可以打开此项目。 (2) New Project区域,有3个按钮用于新建项目,选择MCU创建项目,选择开发板创建项目,或交叉选择创建项目。 (3) Manage software installations区域,有两个按钮: CHECK FOR UPDATES 按钮用于检查STM32CubeMX和嵌入式软件包的更新信息; INSTALL/REMOVE 按钮用于打开如图34所示的对话框。 Home视图上的这些按钮的功能都可以通过主菜单里的菜单项实现操作。 2. 主菜单功能 STM32CubeMX有3个主菜单项,软件的很多功能操作都是通过这些菜单项实现的。 1) File菜单 (1) New Project(新建项目),用于创建新的项目。STM32CubeMX的项目文件扩展名是.ioc,一个项目只有一个文件。新建项目对话框是软件的3个视图之一,界面功能比较多,在后面具体介绍。 (2) Load Project(加载项目),选择一个已经存在的.ioc项目文件并载入项目。 (3) Import Project(导入项目),选择一个.ioc项目文件并导入其中的MCU设置到当前项目。注意,只有新项目与导入项目的MCU型号一致且新项目没有做任何设置时才可以导入其他项目的设置。 (4) Save Project(保存项目),保存当前项目。如果新建的项目第1次保存,会提示设置项目名称,需要选择一个文件夹,项目会自动以最后一级文件夹的名称作为项目名称。 (5) Save Project As(项目另存为),将当前项目保存为另一个项目文件。 (6) Close Project(关闭项目),关闭当前项目。 (7) Generate Report(生成报告),为当前项目的设置内容生成一个PDF报告文件,PDF报告文件名称与项目名称相同,并自动保存在项目文件所在的文件夹里。 (8) Recent Projects(最近的项目),显示最近打开过的项目列表,用于快速打开项目。 (9) Exit(退出),退出STM32CubeMX。 2) Window菜单 (1) Outputs(输出),一个复选菜单项,被勾选时,在工作区的最下方显示一个输出子窗口,显示一些输出信息。 (2) Font Size(字体大小),有3个子菜单项,用于设置软件界面字体大小,需重启STM32CubeMX后才生效。 3) Help菜单 (1) Help(帮助),显示STM32CubeMX的英文版用户手册PDF文档,文档有300多页,是一个很全面的使用手册。 (2) About(关于),显示关于本软件的对话框。 (3) Docs&Resources(文档和资源),只有在打开或新建一个项目后此菜单项才有效。选择此项会弹出一个对话框,显示与项目所用MCU型号相关的技术文档列表,包括数据手册、参考手册、编程手册、应用笔记等。这些都是ST公司官方的资料文档,单击即可打开PDF文档。首次单击一个文档时会自动从ST官网下载文档并保存到软件库根目录下,如D:/STM32Dev/Repository。这避免了每次查看文档都要从ST公司官网搜索的麻烦,也便于管理。 (4) Refresh Data(刷新数据),会弹出如图36所示的Data Refresh对话框,用于刷新MCU和开发板的数据,或下载所有官方文档。 图36Data Refresh 对话框 (5) User Preferences(用户选项),用于设置用户选项,只有一个需要设置的选项,即是否允许软件收集用户使用习惯。 (6) Check For Updates(检查更新),用于检查STM32CubeMX软件、各系列MCU固件包、STM32Cube扩展包是否有新版本需要更新。 (7) Manage Embedded Software Packages(管理嵌入式软件包),会弹出如图34所示的对话框,对嵌入式软件包进行管理。 (8) Updater Settings(更新设置),会弹出如图33所示的对话框,用于设置软件库文件夹,设置软件检查更新方式和数据刷新方式。 3.3.2新建项目 新建项目包含选择MCU创建项目、选择开发板新建项目和交叉选择MCU新建项目3部分。 1. 选择MCU创建项目 执行File→New Project菜单命令,或单击Home视图上的ACCESS TO MCU SELECTOR 按钮,都可以弹出如图37所示的New Project对话框,用于选择MCU或开发板以新建项目。 STM32CubeMX界面上一些地方使用了MCU/MPU,是为了表示STM32系列MCU和MPU。因为STM32MP系列推出较晚,型号较少,STM32系列一般就是指MCU。除非特殊说明或为与界面上的表示一致,为了表达的简洁,本书后面一般用MCU统一表示MCU和MPU。 New Project对话框中,MCU/MPU Selector选项卡用于选择具体型号的MCU创建项目; Board Selector选项卡用于选择一个开发板创建项目; Cross Selector选项卡用于对比某个STM32 MCU或其他厂家的MCU,选择一个合适的STM32 MCU创建项目。 图37所示为MCU/MPU Selector选项卡,用于选择MCU。 图37New Project对话框 图37的界面有如下几个功能区域。 (1) MCUs/MPUs List,通过筛选或搜索的MCU列表,列出了器件的具体型号、封装、Flash、RAM等参数。在这个区域可以进行如下操作。 ① 单击列表项左侧的星星图标,可以收藏条目(★)或取消收藏(☆)。 ② 单击列表上方的Display similar items按钮,可以将相似的MCU添加到列表中显示,然后按钮标题切换为Hide similar items,再次单击就隐藏相似条目。 ③ 单击列表右上方的Export按钮,可以将列表内容导出为一个Excel文件。 ④ 在列表中双击一个条目时就以所选的MCU新建一个项目,关闭此对话框进入项目管理视图。 ⑤ 在列表中单击一个条目时,将在其上方的资料区域显示该MCU的资料。 (2) MCU资料显示区域,在MCU列表中单击一个条目时,就在此区域显示这个具体型号MCU的资料,有多个选项卡和按钮操作。 ① Features选项卡显示选中型号MCU的基本特性参数,左侧的星星图标表示是否收藏此MCU。 ② Block Diagram选项卡显示MCU的功能模块图,如果是第1次显示某MCU的模块图,会自动从网上下载模块图片并保存到软件库根目录下。 ③ Docs & Resources选项卡显示MCU相关的文档和资源列表,包括数据手册、参考手册、编程手册、应用笔记等。单击某个文档时,如果没有下载,就会自动下载并保存到软件库根目录下; 如果已经下载,就会用PDF阅读器打开文档。 ④ Datasheet按钮: 如果数据手册未下载,会自动下载数据手册然后显示; 否则会用PDF阅读器打开数据手册。数据手册自动保存在软件库根目录下。 ⑤ Buy按钮: 用浏览器打开ST公司网站的购买页面。 ⑥ Start Project按钮: 用选择的MCU创建项目。 (3) MCU/MPU Filters,用于MCU筛选的一些功能操作,上方有一个工具栏,有4个按钮。 ① Show Favorites按钮: 显示收藏的MCU列表。单击MCU列表条目前面的星星图标,可以收藏或取消收藏某个MCU。 ② Save Search按钮: 保存当前搜索条件为某个搜索名称。在设置了某种筛选条件后可以保存为一个搜索名称,然后再单击Load Searches 按钮时选择此搜索名称,就可以快速使用以前用过的搜索条件。 ③ Load Searches按钮: 会显示一个弹出菜单,列出所有保存的搜索名称,单击某一项就可以快速载入以前设置的搜索条件。 ④ Reset All Filters按钮: 复位所有筛选条件。 在此工具栏的下方有一个搜索框,用于设置器件型号进行搜索。可以在搜索框中输入MCU的型号,如STM32F103,就会在MCU列表中看到所有STM32F103xx型号的MCU。 MCU的筛选主要通过以下几组条件进行设置。 ① Core(内核): 筛选内核,选项中列出了STM32支持的所有Cortex内核,如图38所示。 ② Series(系列): 选择内核后会自动更新可选的STM32系列列表,图39只显示了列表的一部分。 图38选择Cortex内核 图39选择STM32系列 ③ Line(产品线): 选择某个STM32 系列后会自动更新产品线列表中的可选范围。例如,选择了STM32F1系列之后,产品线列表中只有STM32F1xx的器件可选。图310所示为产品线列表的一部分。 ④ Package(封装): 根据封装选择器件。用户可以根据已设置的其他条件缩小封装的选择范围。图311所示为封装列表的一部分。 图310选择产品线 图311选择封装 ⑤ Other(其他): 还可以设置价格、I/O引脚数、Flash大小、RAM大小、主频等筛选条件。 MCU筛选的操作非常灵活,并不需要按照条件顺序依次设置,可以根据自己的需要进行设置。例如,如果已知MCU的具体型号,可以直接在器件型号搜索框中输入型号; 如果是根据外设选择MCU,可以直接在外设中进行设置后筛选,如果得到的MCU型号比较多,再根据封装、Flash容量等进一步筛选。设置好的筛选条件可以保存为一个搜索名称,通过 Load Searches按钮选择保存的搜索名称,可以重复执行搜索。 2. 选择开发板新建项目 用户还可以在New Project对话框中选择开发板新建项目,如图312所示。STM32CubeMX目前仅支持ST官方的开发板。 图312选择开发板新建项目 3. 交叉选择MCU新建项目 New Project对话框的Cross Selector选项卡用于交叉选择MCU新建项目,如图313所示。 图313交叉选择MCU新建项目 交叉选择就是针对其他厂家的一个MCU或一个STM32具体型号的MCU,选择一个性能和外设资源相似的MCU。交叉选择对于在一个已有设计基础上选择新的MCU重新设计非常有用。例如,一个原有的设计用的是TI的MSP4305529单片机,需要改用STM32 MCU重新设计,就可以通过交叉选择找到一个性能、功耗、外设资源相似的STM32 MCU。再如,一个原有的设计使用STM32F103,但是发现STM32F103的SRAM和处理速度不够,需要选择一个性能更高且引脚与STM32F103完全兼容的STM32 MCU,就可以使用交叉选择。 Filters区域的Part Number Search部分用于选择原有MCU的厂家和型号,如NXP、Microchip、ST、TI等,选择厂家后会在第2个下拉列表框中列出厂家的MCU型号。选择厂家和MCU型号后,会在下方的Matching ST candidates(500)列表中显示可选的STM32 MCU,并且有一个匹配百分比表示匹配程度。 在候选STM32 MCU列表中可以选择一个或多个MCU,然后在右边的区域会显示原MCU与候选STM32 MCU的具体参数对比。通过这样的对比,用户可以快速地找到能替换原MCU的STM32 MCU。其他一些按钮的功能操作就不具体介绍了,读者可自行尝试使用。 3.3.3MCU图形化配置界面总览 选择一个MCU创建项目后,界面上显示的是项目操作视图。因为本书所用开发板上的MCU型号是STM32F407ZGT6,所以选择STM32F407ZGT6新建一个项目进行操作。这个项目只是用于熟悉STM32CubeMX软件的基本操作,并不需要下载到开发板上,所以可以随意操作。读者选择其他型号的MCU创建项目也是可以的。 如图314所示,MCU图形化配置界面主要由主菜单栏、标签导航栏和工作区3部分组成。 图314MCU图形化配置界面 最上方的主菜单栏一直保持不变,标签导航栏现在有3个层级,最后一个层级显示了当前工作界面的名称。导航栏的最右侧有一个GENERATE CODE按钮,用于图形化配置MCU后生成C语言代码。工作区是一个多页界面,有4个工作界面。 (1) Pinout & Configuration(引脚与配置): 对MCU的系统内核、外设、中间件和引脚进行配置,是主要的工作界面。 (2) Clock Configuration(时钟配置):通过图形化的时钟树对MCU的各个时钟信号频率进行配置。 (3) Project Manager(项目管理): 对项目进行各种设置。 (4) Tools(工具): 进行功耗计算、DDR SDRAM适用性分析(仅用于STM32MP1系列)。 3.3.4MCU配置 引脚与配置界面是MCU图形化配置的主要工作界面,如图314所示。这个界面包括 Component List(组件列表)、Mode and Configuration(模式与配置)、Pinout view(引脚视图)、System view(系统视图)和一个工具栏。 1. 组件列表 位于工作区左侧的是MCU可以配置的系统内核、外设和中间件列表,每项称为一个组件(Component)。组件列表有两种显示方式: 分组显示和按字母顺序显示。单击Categories或A→Z选项卡就可以在这两种显示方式之间切换。 在列表上方的搜索框内输入文字,按Enter键就可以根据输入的文字快速定位某个组件,如搜索RCC。搜索框右侧的图标按钮有两个弹出菜单项,分别是Expand All和Collapse All,在分组显示时可以展开全部分组和收起全部分组。 在分组显示状态下,主要有以下一些分组(每个分组的具体条目与MCU型号有关,这里以STM32F103ZE为例)。 (1) System Core(系统内核),包括DMA、GPIO、IWDG、NVIC、RCC、SYS和WWDG。 (2) Analog(模拟),片上的ADC和DAC。 (3) Timers(定时器),包括RTC和所有定时器。 (4) Connectivity(通信连接),各种外设接口,包括CAN、ETH、FSMC、I2C、SDIO、SPI、UART、USART、USB_OTG_FS、USB_OTG_HS等。 (5) Multimedia(多媒体),各种多媒体接口,包括数字摄像头接口DCMI和数字音频接口I2S。 (6) Security(安全),只有一个RNG(随机数发生器)。 (7) Computing(计算),计算相关的资源,只有一个CRC(循环冗余校验)。 (8) Middleware(中间件),MCU固件库里的各种中间件,主要有FatFS、FreeRTOS、LibJPEG、LwIP、PDM2PCM、USB_Device、USB_Host等。 (9) Additional Software(其他软件),组件列表中默认是没有这个分组的。如果在嵌入式软件管理器中安装了STM32Cube扩展包,那么就可以通过单击Pinout & Configuration工作界面菜单栏的Additional Software按钮打开一个对话框,将该扩展包安装到组件面板的Additional Software分组中。 图315组件的上下文帮助功能 和可用标记 当鼠标指针在组件列表的某个组件上停留时,会显示这个组件的上下文帮助(Contextual Help),如图315所示。上下文帮助显示了组件的简单信息,如果需要知道更详细的信息,可以单击上下文帮助中的details and documentation(细节和文档)超链接,显示其数据手册、参考手册、应用笔记等文档的链接,单击就可以下载并显示PDF文档,而且会自动定位文档中的相应界面。 在初始状态下,组件列表的各项前面没有任何图标,在对MCU的各个组件进行一些设置后,组件列表的各项前面会出现一些图标(见图315),表示组件的可用性信息。因为MCU引脚基本都有复用功能,设置某个组件可用后,其他一些组和可用标记件可能就不能使用了。这些图标的意义如表31所示。 表31组件列表条目前图标的意义 图标示例 意义 CAN1 组件前面没有任何图标,黑色字体,表示这个组件还没有被配置,其可用引脚也没有被占用 √SPI1 表示这个组件的模式和参数已经配置好了 UART1 表示这个组件的可用引脚已经被其他组件占用,不能再配置这个组件了 ▲ADC1 表示这个组件的某些可用引脚或资源被其他组件占用,不能完全随意配置,但还是可以配置的。例如,ADC1有16个可用输入引脚,当部分引脚被占用后不能再被配置为ADC1的输入引脚,就会显示这样的图标 USB_HOST 灰色字体,表示这个组件因为一些限制不能使用。例如,要使用中间件USB_HOST,需要启用USB_OTG接口并配置为Host后才可以使用 2. 模式与配置 在组件列表中单击一个组件后,就会在其右侧显示模式与配置(Mode and Configuration)界面。这个界面分为上、下两部分,上方是模式设置,下方是参数配置,这两部分的显示内容与选择的具体组件有关。 例如,图314显示的是System Core分组中RCC组件的模式与配置。RCC用于设置MCU的两个外部时钟源,模式选择界面中高速外部(High Speed External,HSE)时钟源的下拉列表框中有以下3个选项。 (1) Disable: 禁用外部时钟源。 (2) BYPASS Clock Source: 使用外部有源时钟信号源。 (3) Crystal/Ceramic Resonator: 使用外部晶体振荡器作为时钟源。 当HSE模式设置为Disable时,MCU使用内部高速RC振荡器产生的16MHz信号作为时钟源。其他两项要根据实际的电路进行选择。 低速外部(Low Speed External,LSE)时钟可用作RTC的时钟源,其下拉列表框中的选项与HSE相同。若LSE模式设置为Disable,RTC就使用内部低速RC振荡器产生的32kHz时钟信号。开发板上有外接的 32.768kHz 晶体振荡电路,所以可以将 LSE 设置为 Crystal/Ceramic Resonator。如果设计中不需要使用RTC,不需要提供LSE时钟,就可以将LSE设置为Disable。 参数配置部分分为多个界面,且界面内容与选择的组件有关,一般有如下界面。 (1) Parameter Settings(参数设置): 组件的参数设置。例如,对于USART1,参数设置包括波特率、数据位数(8位或9位)、是否有奇偶校验位等。 (2) NVIC Settings(中断设置): 设置是否启用中断,但不能设置中断的优先级,只能显示中断优先级设置结果。中断的优先级需要在System Core分组的NVIC组件中设置。 (3) DMA Settings(DMA 设置): 是否使用DMA,以及DMA的具体设置。DMA流的中断优先级需要在System Core分组的NVIC组件中设置。 (4) GPIO Settings(GPIO设置):显示组件的GPIO引脚设置结果,不能在此修改GPIO设置。外设的GPIO引脚是自动设置的,GPIO引脚的具体参数,如上拉或下拉、引脚速率等,需要在System Core分组的GPIO组件中设置。 (5) User Constants(用户常量): 用户自定义的一些常量,这些自定义常量可以在STM32CubeMX中使用,生成代码时,这些自定义常量会被定义为宏,放入main.h文件中。 每种组件的模式和参数设置界面都不一样,我们在后续章节介绍各种系统功能和外设时会具体介绍它们的模式和参数设置操作。 3. 引脚视图 图314工作区的右侧显示了MCU的引脚图,直观地表示了各引脚的设置情况。通过组件列表对某个组件进行模式和参数设置后,系统会自动在引脚图上标识出使用的引脚。例如,设置RCC组件的HSE使用外部晶振后,系统会自动将Pin23和Pin24引脚设置为RCC_OSC_IN和RCC_OSC_OUT,这两个名称就是引脚的信号(Signal)。 在MCU的引脚图上,亮黄色的引脚是电源或接地引脚,黄绿色的引脚是只有一种功能的系统引脚,包括系统复位引脚NRST、BOOT0引脚和PDR_ON引脚,这些引脚不能进行配置。其他未配置功能的引脚为灰色,已经配置功能的引脚为绿色。 引脚视图下方有一个工具栏,通过工具栏按钮可以进行放大、缩小、旋转等操作,通过鼠标滚轮也可以缩放,按住鼠标左键可以拖动MCU引脚图。 对引脚功能的分配一般通过组件的模式设置进行,STM32CubeMX 会根据MCU的引脚使用情况自动为组件分配引脚。例如,USART1可以定义在PA9和PA10引脚上,也可以定义在PB6和PB7引脚上。如果PA9和PA10引脚未被占用,定义USART1的模式为Asynchronous(异步)时,就自动定义在PA9和PA10引脚上。如果这两个引脚被其他功能占用了,如定义为GPIO输出引脚用于驱动LED,那么定义USART1为异步模式时就会自动使用PB6和PB7引脚。 所以,如果是在电路的初始设计阶段,可以根据电路的外设需求在组件中设置模式,让软件自动分配引脚,这样可以减少工作量,而且更准确。当然,用户也可以直接在引脚图上定义某个引脚的功能。 在MCU的引脚图上,当鼠标指针停留到某个引脚上时会显示这个引脚的上下文帮助信息,主要显示的是引脚编号和名称。单击引脚,会出现一个引脚功能选择菜单。图316所示为单击PA9引脚时出现的引脚功能选择菜单。这个菜单列出了PA9引脚所有可用的功能,其中的几个解释如下。 (1) Reset_State: 恢复为复位后的初始状态。 (2) GPIO_Input: 作为GPIO输入引脚。 (3) GPIO_Output: 作为GPIO输出引脚。 (4) TIM1_CH2: 作为定时器TIM1的输入通道2。 (5) USART1_TX: 作为USART1的TX引脚。 (6) GPIO_EXTI9: 作为外部中断EXTI9的输入引脚。 引脚功能选择菜单的菜单项由具体的引脚决定,手动选择了功能的引脚上会出现一个图钉图标,表示这是绑定了信号的引脚。不管是软件自动设置的引脚还是手动设置的引脚,都可以重新为引脚手动设置信号。例如,通过设置USART1组件为Asynchronous 模式,软件会自动设置PA9引脚为USART1_TX,PA10引脚为USART1_RX。但是,如果电路设计需要将USART1_RX改用PB7引脚,就可以手动将PB7引脚设置为USART1_RX,这时PA10引脚会自动变为复位初始状态。 手动设置引脚功能时,容易引起引脚功能冲突或设置不全的错误,出现这类错误的引脚会自动用橘黄色显示。例如,直接手动设置PA9和PA10为USART1的两个引脚,但是引脚会显示为橘黄色。这是因为在组件中没有启用USART1并为其选择模式,在组件列表中选择USART1并设置其模式为Asynchronous之后,PA9和PA10引脚就变为绿色了。 用户还可以右击一个引脚,弹出一个快捷菜单,如图317所示。不过,只有设置了功能的引脚,才有该快捷菜单。此快捷菜单有3个菜单项。 图316PA9引脚的引脚功能选择菜单 图317引脚的快捷菜单 (1) Enter User Label(输入用户标签): 用于输入一个用户定义的标签,这个标签将取代原来的引脚信号名称显示在引脚旁边。例如,在将PA10引脚设置为USART1_RX后,可以再为其定义标签GPS_RX,这样在实际的电路中更容易看出引脚的功能。 (2) Signal Pinning(信号绑定): 单击此菜单项后,引脚上将会出现一个图钉图标,表示将这个引脚与功能信号(如USART1_TX)绑定了,这个信号就不会再自动改变引脚,只可以手动改变引脚。对于已经绑定信号的引脚,此菜单项会变为Signal Unpinning,就是解除绑定。对于未绑定信号的引脚,软件在自动分配引脚时可能会重新为此信号分配引脚。 (3) Pin Stacking/Pin Unstacking(引脚叠加/引脚解除叠加): 这个菜单项的功能不明确,手册里没有任何说明,ST官网上也没有明确解答。不要单击此菜单项,否则影响生成的C语言代码。 4. 系统视图 如图314所示,芯片图片的上方有两个按钮: Pinout view(引脚视图)和System view(系统视图),单击这两个按钮可以在引脚视图和系统视图之间切换显示。图318所示为系统视图界面,界面上显示了MCU已经设置的各种组件,便于对MCU已经设置的系统资源和外设有一个总体的了解。 在系统视图界面单击某个组件时,在工作区的组件列表中就会显示此组件,在模式与配置界面中就会显示此组件的设置内容,以便进行查看和修改。 图318系统视图界面 5. Pinout菜单 在引脚视图的上方还有一个工具栏,有两个按钮: Additional Software和Pinout。单击Additional Software按钮会弹出一个对话框,用于选择已安装的STM32Cube扩展包,添加到组件面板的 Additional Software组中。 单击 Pinout按钮会出现一个下拉菜单,如图319所示。 图319引脚视图上方的 Pinout下拉菜单 各菜单项的功能描述如下。 (1) Undo Mode and pinout: 撤销上一次的模式设置和引脚分配操作。 (2) Redo Mode and pinout: 重做上一次的撤销操作。 (3) Keep Current Signals Placement(保持当前信号的配置): 如果勾选此项,将保持当前设置的各个信号的引脚配置,也就是在后续自动配置引脚时,前面配置的引脚不会再改动。这样有时会引起引脚配置困难,如果是在设计电路阶段,可以取消此选项,让软件自动分配各外设的引脚。 (4) Show User Label(显示用户标签): 如果勾选此项,将显示引脚的用户定义标签,否则显示其已设置的信号名称。 (5) Disable All Modes(禁用所有模式): 取消所有外设和中间件的模式设置,复位全部相关引脚。但是,不会改变设置的普通GPIO输入或输出引脚。例如,不会复位用于LED的GPIO输出引脚。 (6) Clear Pinouts(清除引脚分配): 让所有引脚变成复位初始状态。 (7) Clear Single Mapped Signals(清除单边映射的信号): 清除那些定义了引脚的信号,但是没有关联外设的引脚,也就是橘黄色底色标识的引脚。必须先解除信号的绑定后才可以清除,也就是去除引脚上的图钉图标。 (8) Pins/Signals Options(引脚/信号选项): 会弹出一个如图320所示的Pins/Signals Options对话框,显示MCU已经设置的所有引脚名称、关联的信号名称和用户定义标签。可以按住Shift键或Ctrl键选择多行,然后右击弹出快捷菜单,通过菜单项进行引脚与信号的批量绑定或解除绑定。 (9) List Pinout Compatible MCUs(列出引脚分配兼容的MCU): 会弹出一个对话框,显示与当前项目的引脚配置兼容的MCU列表。此功能可用于电路设计阶段选择与电路兼容的不同型号的MCU。例如,可以选择一个与电路完全兼容但Flash更大或主频更高的MCU。 (10) Export pinout with Alt. Functions: 将具有复用功能的引脚的定义导出为一个.csv文件。 (11) Export pinout without Alt. Functions: 将没有复用功能的引脚的定义导出为一个.csv文件。 (12) Set unused GPIOs(设置未使用的GPIO引脚): 弹出如图321所示的Set unused GPIOs对话框,对MCU未使用的GPIO引脚进行设置,可设置为Input、Output或Analog模式。一般设置为Analog,以降低功耗。注意,要进行此项设置,必须在SYS组件中设置了调试引脚,如设置为5线JTAG。 图320Pins/Signals Options 对话框 图321Set unused GPIOs对话框 (13) Reset used GPIOs(复位已用的GPIO引脚): 弹出一个对话框,复位那些通过Set unused GPIOs对话框设置的GPIO引脚,可以选择复位的引脚个数。 (14) Layout reset(布局复位): 将Pinout&Configuration界面的布局恢复为默认状态。 3.3.5时钟配置 MCU图形化设置的第2个工作界面是时钟配置界面。为了充分演示时钟配置的功能,我们先设置RCC的模式,将HSE设置为Crystal/Ceramic Resonator,并且启用Master Clock Output,如图322所示。 图322RCC模式设置 MCO(Master Clock Output)是MCU向外部提供时钟信号的引脚,其中MCO2与音频时钟输入(Audio Clock Input,I2S_CKIN)共用PC9引脚,所以使用MCO2之后就不能再使用I2S_CKIN了。此外,我们需要启用RTC,以便演示设置RTC的时钟源。 在STM32CubeMX的工作区单击Clock Configuration选项卡,它非常直观地显示了MCU的时钟树,使各种时钟信号的配置变得非常简单。 时钟源、时钟信号或选择器的作用如下。 (1) HSE(高速外部)时钟源。当设置RCC的HSE模式为Crystal/Ceramic Resonator时,用户可以设置外部振荡电路的晶振频率。例如,开发板上使用的是8MHz晶振,在其中输入8之后按Enter键,软件就会根据HSE的频率自动计算所有相关时钟频率并刷新显示。注意,HSE的频率设置范围为4~16MHz。 (2) HSI(高速内部)RC振荡器。MCU内部的高速RC振荡器,可产生频率为8MHz的时钟信号。 (3) PLL时钟源选择器和主锁相环。锁相环(PLL)时钟源选择器可以选择HSE或HSI作为锁相环的时钟信号源,PLL的作用是通过倍频和分频产生高频的时钟信号。在Clock Configuration选项卡中带有除号(/)的下拉列表框是分频器,用于将一个频率除以一个系数,产生分频的时钟信号; 带有乘号(×)的下拉列表框是倍频器,用于将一个频率乘以一个系数,产生倍频的时钟信号。 主锁相环(Main PLL)输出两路时钟信号,一路是PLLCLK,进入系统时钟选择器; 另一路输出48MHz时钟信号,USBOTG FS、USBOTG HS、SDIO、RNG都需要使用这个48MHz时钟信号。还有一个专用的锁相环PLLI2S,用于产生精确时钟信号供I2S接口使用,以获得高品质的音效。 (4) 系统时钟选择器。系统时钟 SYSCLK是直接或间接为MCU上的绝大部分组件提供时钟信号的时钟源,系统时钟选择器可以从HSI、HSE、PLLCLK这3个信号中选择一个作为SYSCLK。 系统时钟选择器的下方有一个Enable CSS按钮,CSS(Clock Security System)是时钟安全系统,只有直接或间接使用HSE作为SYSCLK时,此按钮才有效。如果开启了CSS,MCU内部会对HSE时钟信号进行监测,当HSE时钟信号出现故障时,会发出一个CSSI(Clock Security System Interrupt)信号,并自动切换到使用HSI作为系统时钟源。 (5) 系统时钟SYSCLK。STM32F407的SYSCLK最高频率是168MHz,但是在Clock Configuration选项卡中的SYSCLK文本框中不能直接修改SYSCLK的值。可以看出,SYSCLK直接作为以太网精确时间协议(Precision Time Protocol,PTP)的时钟信号,经过AHB Prescaler(AHB预分频器)后生成HCLK时钟信号。 (6) HCLK时钟。SYSCLK经过AHB分频器后生成HCLK时钟,HCLK就是CPU的时钟信号,CPU的频率就由HCLK的频率决定。HCLK还为APB1总线和APB2总线等提供时钟信号。HCLK最高频率为72MHz。用户可以在HCLK文本框中直接输入需要设置的HCLK频率,按Enter键后软件将自动配置计算。 可以看到,HCLK为其右侧的多个部分直接或间接提供时钟信号。 ① HCLK to AHB bus,core,memory and DMA: HCLK 直接为AHB总线、内核、存储器和DMA提供时钟信号。 ② To Cortex System timer: HCLK经过一个分频器后作为Cortex 系统定时器(也就是Systick定时器)的时钟信号。 ③ FCLK Cortex clock: 直接作为Cortex的FCLK(FreeRunning Clock)时钟信号。 ④ APB1 peripheral clocks: HCLK经过APB1分频器后生成外设时钟信号PCLK1,为外设总线APB1上的外设提供时钟信号。 ⑤ APB1 Timer clocks: PCLK1经过2倍频后生成APB1定时器时钟信号,为APB1总线上的定时器提供时钟信号。 ⑥ APB2 peripheral clocks: HCLK经过APB2分频器后生成外设时钟信号PCLK2,为外设总线APB2上的外设提供时钟信号。 ⑦ APB2 timer clocks: PCLK2经过2倍频后生成APB2定时器时钟信号,为APB2总线上的定时器提供时钟信号。 (7) 音频时钟输入。如果在RCC模式设置中勾选了Audio Clock Input(I2S_CKIN)复选框,就可以在此输入一个外部的时钟源,作为I2S接口的时钟信号。 (8) MCO时钟输出和选择器。MCO是MCU为外部设备提供的时钟源,当勾选 Master Clock Output复选框后,就可以在相应引脚输出时钟信号。 Clock Configuration选项卡显示了MCO2的时钟源选择器和输出分频器,MCO1的选择器和输出通道也与此类似,由于幅面限制没有显示出来。MCO2的输出可以从4个时钟信号源中选择,还可以再分频后输出。 (9) LSE(低速外部)时钟源。如果在RCC模式设置中启用LSE,就可以选择LSE作为RTC的时钟源。LSE固定为32.768kHz,因为经过多次分频后,可以得到精确的1Hz信号。 (10) LSI(低速内部)RC振荡器。MCU内部的LSI RC振荡器产生频率为32kHz的时钟信号,它可以作为RTC的时钟信号,也直接作为IWDG(独立看门狗)的时钟信号。 (11) RTC时钟选择器。如果启用RTC,就可以通过RTC时钟选择器为RTC设置一个时钟源。RTC时钟选择器有3个可选的时钟源: LSI、LSE和HSE经分频后的时钟信号HSE_RTC。要使RTC精确度高,应该使用32.768kHz的LSE作为时钟源,因为LSE经过多次分频后可以产生1Hz的精确时钟信号。 弄清楚Clock Configuration选项卡中的这些时钟源和时钟信号的作用后,进行MCU的各种时钟信号的配置就很简单了,因为都是图形化界面的操作,不用像传统编程那样必须弄清楚相关寄存器并计算寄存器的值了,这些底层的寄存器设置将由STM32CubeMX自动完成,并生成代码。 在Clock Configuration选项卡中可以进行以下操作。 (1) 直接在某个时钟信号的文本框中输入数值,按Enter键后由软件自动配置各个选择器、分频器、倍频器。例如,如果希望设置HCLK为50MHz,在HCLK的文本框中输入50后按Enter键即可。 (2) 可以手动修改选择器、分频器、倍频器的设置,以便手动调节某个时钟信号的频率。 (3) 当某个时钟的频率设置错误时,其所在的文本框会以紫色底纹显示。 (4) 在某个时钟信号文本框上右击,会弹出一个快捷菜单,其中包含 Lock和Unlock两个菜单项,用于对时钟频率进行锁定和解锁。如果一个时钟频率被锁定,其文本框会以灰色底纹显示。在软件自动计算频率时,系统尽量不会改变已锁定时钟信号的频率,如果必须改动,会弹出一个对话框提示解锁。 (5) 单击工具栏上的Reset Clock Configuration按钮,会将整棵时钟树复位到初始默认状态。 (6) 工具栏上的其他一些按钮可以进行撤销、重复、缩放等操作。 用户所做的这些时钟配置都涉及寄存器的底层操作,STM32CubeMX在生成代码时会自动生成时钟初始化配置的程序。 3.3.6项目管理 1. 功能概述 对MCU系统功能和各种外设的图形化配置,主要是在引脚配置和时钟配置两个工作界面完成的,完成这些工作后,一个MCU的配置就完成了。STM32CubeMX的重要作用就是将这些图形化的配置结果导出为C语言代码。 STM32CubeMX工作区的第3个工作界面是Project Manager(项目管理器),如图323所示。这是一个多页界面,有以下3个工作界面。 图323项目管理器 (1) Project界面: 用于设置项目名称、保存路径、导出代码的IDE软件等。 (2) Code Generator界面: 用于设置生成C语言代码的一些选项。 (3) Advanced Settings界面: 生成C语言代码的一些高级设置,如外设初始化代码是使用HAL库还是LL库。 2. 项目基本信息设置 新建的STM32CubeMX项目首次保存时会弹出一个选择文件夹的对话框,用户选择一个文件夹后,项目会被保存到文件夹下,并且项目名称与最后一级文件夹的名称相同。 例如,保存项目时选择的文件夹是D:/Demo/MDK/1LED/,那么项目会被保存到此目录下,并且项目文件名为LED.ioc。 对于保存过的项目,就不能再修改图323中的Project Name和Project Location两个文本框中的内容了。项目管理器中还有以下一些设置项。 (1) Application Structure(应用程序结构),有Basic和Advanced两个选项。 ① Basic: 建议用于只使用一个中间件或者不使用中间件的项目。在这种结构中,IDE配置文件夹与源代码文件夹同级,用子目录组织代码。 ② Advanced: 当项目里使用多个中间件时,建议使用这种结构,这样对于中间件的管理容易一些。 (2) Do not generate the main()复选框: 如果勾选此项,导出的代码将不生成main()函数。但是,C语言的程序肯定是需要一个main()函数的,所以不勾选此项。 (3) Toolchain Folder Location,也就是导出的IDE项目所在的文件夹,默认与STM32CubeMX项目文件在同一个文件夹。 图324可选的工具链/ IDE软件列表 (4) Toolchain/IDE: 从一个下拉列表框中选择导出C语言程序的工具链或IDE软件,如图324所示。 本书使用的IDE 软件是Keil MDK,Toolchain/IDE选择 MDKARM。 (5) Linker Settings(链接器设置): 用于设置应用程序的堆(Heap)的最小大小,默认值是0x200和0x400。 (6) Mcu and Firmware Package(MCU和固件包): MCU固件库默认使用已安装的最新固件库版本。如果系统中有一个MCU系列多个版本的固件库,就可以在此重选固件库。如果勾选Use Default Firmware Location 复选框,则表示使用默认的固件库路径,也就是所设置的软件库目录下的相应固件库目录。 3. 代码生成器设置 Code Generator界面如图325所示,用于设置生成代码时的一些特性。 图325Code Generator界面 (1) STM32Cube MCU packages and embedded software packs 选项: 用于设置固件库和嵌入式软件库复制到IDE项目里的方式,有以下3种方式。 ① Copy all used libraries into the project folder: 将所有用到的库都复制到项目文件夹下。 ② Copy only the necessary library files: 只复制必要的库文件,即只复制与用户配置相关的库文件,默认选择这一项。 ③ Add necessary library files as reference in the toolchain project configuration file: 将必要的库文件以引用的方式添加到项目的配置文件中。 (2) Generated files选项: 生成C语言代码文件的一些选项。 ① Generate peripheral initialization as a pair of‘.c/.h’ files per peripheral: 勾选此项后,为每种外设生成的初始化代码将会有.c和.h两个文件。例如,对于GPIO引脚的初始化程序,将有gpio.h和gpio.c两个文件,否则所有外设初始化代码存放在main.c文件里。虽然默认不勾选此项,但推荐勾选,特别是当项目用到的外设比较多时,而且使用.c和.h文件对更方便,也是更好的编程习惯。 ② Backup previously generated files when regenerating: 如果勾选此项,STM32CubeMX在重新生成代码时,就会将前面生成的文件备份到一个名为Backup的子文件夹里,并在.c和.h文件名后面再添加一个.bak扩展名。 ③ Keep User Code when regenerating: 重新生成代码时保留用户代码。这个选项只应用于STM32CubeMX 自动生成文件中代码沙箱段(在后面会具体介绍此概念)的代码,不会影响用户自己创建的文件。 ④ Delete previously generated files when not regenerated: 删除那些以前生成的不需要再重新生成的文件。例如,前一次配置中用到了SDIO,生成的代码中有sdio.h和sdio.c文件,而重新配置时取消了 SDIO,如果勾选了此项,重新生成代码时就会删除前面生成的sdio.h和sdio.c文件。 (3) HAL Settings选项: 用于设置HAL。 ① Set all free pins as analog (to optimize power consumption): 设置所有自由引脚的类型为Analog,这样可以优化功耗。 ② Enable Full Assert: 启用或禁用Full Assert功能。在生成的 stm32f4xx_hal_conf.h文件中有一个宏定义USE_FULL_ASSERT,如果禁用Full Assert 功能,这行宏定义代码就会被注释掉: #defineUSE_FULL_ASSERT1U 如果启用Full Assert 功能,那么HAL库中每个函数都会对函数的输入参数进行检查,如果检查出错,会返回出错代码的文件名和所在行。 (4) Template Settings选项:用于设置自定义代码模板。一般不用此功能,直接使用STM32CubeMX自己的代码模板就很好。 4. 高级设置 Advanced Settings界面如图326所示,分为上、下两个列表。 (1) Driver Selector列表: 用于选择每个组件的驱动库类型。该列表列出了所有已配置的组件,如USART、RCC等,第2列是组件驱动库类型,有HAL和LL两种库可选。 HAL是高级别的驱动程序,MCU上所有组件都有HAL驱动程序。HAL的代码与具体硬件的关联度低,易于在不同系列的器件之间移植。 图326Advanced Settings界面 LL是进行寄存器级别操作的驱动程序,它的性能更加优化,但是需要对MCU的底层和外设比较熟悉,与具体硬件的关联度高,在不同系列之间进行移植时工作量大。并不是MCU上所有组件都有LL驱动程序,软件复杂度高的外设没有LL驱动程序,如SDIO、USBOTG等。 本书完全使用HAL库进行示例程序设计,不会混合使用LL库,以保持总体统一。 (2) Generated Function Calls列表: 对生成函数的调用方法进行设置。图326下方的表格列出了MCU配置的系统功能和外设的初始化函数,列表中的各列如下。 ① Function Name列是生成代码时将要生成的函数名称,这些函数名称是自动确定的,不能修改。 ② Do Not Generate Function Call列: 如果勾选了此项,在main()函数的外设初始化部分不会调用这个函数,但是函数的完整代码还是会生成的,如何调用由编程者自己处理。 ③ Visibility(Static)列用于指定是否在函数原型前面加上static关键字,使函数变为文件内的私有函数。如果在图325中勾选了Generate peripheral initialization as a pair of‘.c/.h’files per peripheral 复选框,则无论是否勾选 Visibility(Static)复选框,外设的初始化函数原型前面都不会加static关键字,因为在.h文件中声明的函数原型对外界就是可见的。 3.3.7生成报告和代码 在对MCU进行各种配置以及对项目进行设置后,用户就可以生成报告和代码。 执行File→Generate Report菜单命令,会在STM32CubeMX项目文件目录下生成一个同名的PDF文件。这个PDF文件里有对项目的基本描述、MCU型号描述、引脚配置图、引脚定义表格、时钟树、各种外设的配置信息等,是对STM32CubeMX项目的一个很好的总结性报告。 保存 STM32CubeMX 项目并在项目管理界面做好生成代码的设置后,用户随时可以单击导航栏右端的GENERATE CODE按钮,为选定的MDKArm软件生成代码。如果是首次生成代码,将自动生成MDKArm项目框架,生成项目所需的所有文件; 如果MDKArm项目已经存在,再次生成代码时只会重新生成初始化代码,不会覆盖用户在沙箱段内编写的代码,也不会删除用户在项目中创建的程序文件。 STM32CubeMX软件的工作区还有一个Tools选项卡,用于进行MCU的功耗计算,这会涉及MCU的低功耗模式。 3.4HAL库 最近兴起的HAL库就是ST公司目前主推的研发方式,其更新速度比较快,可以通过官方推出的STM32CubeMX工具一键生成代码,大大缩短开发周期。使用HAL库的优势主要就是不需要开发工程师再设计所用的MCU型号,只需要专注于功能的软件开发工作即可。 3.4.1HAL库简介 HAL是Hardware Abstraction Layer的缩写,中文名称是硬件抽象层。HAL库是ST公司STM32的MCU最新推出的抽象层嵌入式软件,为更方便地实现跨STM32产品的最大可移植性。和标准外设库(也称标准库)对比起来,STM32的HAL库更加抽象,ST公司最终的目的是要实现在STM32系列MCU之间无缝移植,甚至在其他MCU也能实现快速移植。 ST公司为开发者提供了非常方便的开发库,有SPL库(标准外设库)、HAL库、LL 库(LowLayer,底层库)3种。前者是ST的老库,已经停止更新了,后两者是ST现在主推的开发库。 相比于标准外设库,STM32Cube HAL库表现出更高的抽象整合水平,HAL API集中关注各外设的公共函数功能,这样便于定义一套通用的用户友好的API,从而可以轻松实现从一个STM32产品移植到另一个不同的STM32系列产品。HAL库是ST公司未来主推的库,ST公司新出的芯片已经没有标准外设库了,如F7系列。目前,HAL库已经支持STM32全线产品。 HAL库的特点如下。 (1) 最大可移植性。 (2) 提供了一整套一致的中间件组件,如RTOS、USB、TCP/IP和图形等。 (3) 通用的用户友好的API。 (4) ST公司新出的芯片已经没有标准库。 (5) HAL库已经支持STM32全线产品。 通常新手在入门STM32时,首先都要先选择一种开发方式,不同的开发方式会导致编程的架构完全不同。一般都会选用标准外设库和HAL库,而极少部分人会通过直接配置寄存器进行开发。 1. 直接配置寄存器 不少先学习了MCS51单片机的读者可能会知道,会有一小部分人是通过汇编语言直接操作寄存器实现功能的,这种方法到了STM32就变得不太容易行得通了,因为STM32的寄存器数量是MCS51单片机的数十倍,如此多的寄存器根本无法全部记忆,开发时需要经常翻查芯片的数据手册,此时直接操作寄存器就变得非常费力了。但还是会有很小一部分人喜欢直接操作寄存器,因为这样更接近原理,知其然也知其所以然。 2. 标准外设库 STM32有非常多的寄存器,而导致了开发困难,为此ST公司就为每款芯片都编写了一份库文件。在这些 .c 和.h文件中,包括一些常用量的宏定义,把一些外设也通过结构体变量封装起来,如GPIO等。所以,只需要配置结构体变量成员就可以修改外设的配置寄存器,从而选择不同的功能。这也是目前最多人使用的方式,也是学习STM32接触最多的一种开发方式,这里就不再阐述了。 3. HAL库 HAL库是ST公司目前主推的开发方式,库如其名,很抽象,一眼看上去不太容易知道它的作用是什么。它的出现比标准外设库要晚,但其实和标准外设库一样,都是为了节省程序开发的时间,而且HAL库尤其有效,如果说标准外设库集成了实现功能需要配置的寄存器,那么HAL库的一些函数甚至可以做到某些特定功能的集成。也就是说,同样的功能,标准外设库可能要用几句话,HAL库只需一句话就够了。并且,HAL库也很好地解决了程序移植的问题,不同型号的STM32芯片的标准外设库是不一样的。例如,在STM32F4上开发的程序移植到STM32F1上是不通用的,而使用HAL库,只要使用的是相同的外设,程序基本可以完全复制粘贴。注意是相同外设,也就是不能无中生有,如STM32F7比STM32F1要多几个定时器,不能明明没有这个定时器却非要配置,但其实这种情况不多,一般都可以直接复制粘贴。使用ST公司研发的STMCube软件,可以通过图形化的配置功能,直接生成整个使用HAL库的工程文件。 4. HAL库与标准外设库的区别 STM32的开发中,可以这样操作寄存器: GPIOF->BSRR=0x00000001;//这里是针对STM32F1系列 这种方法当然可以,但是需要掌握每个寄存器的用法,才能正确使用STM32,而对于STM32这种级别的MCU,记忆数百个寄存器又谈何容易。于是ST公司推出了官方标准外设库,标准外设库将这些寄存器底层操作都封装起来,提供一整套API供开发者调用,大多数场合下,不需要知道操作的是哪个寄存器,只需要知道调用哪些函数即可。 例如,控制 BRR 寄存器实现电平控制,官方库封装了一个函数,如下。 void GPIO_ResetBits(GPIO_TypeDef*GPIOx,uint_t GPIO_Pin) { GPIOx->BRR=GPIO_Pin } 这时不需要再直接操作 BRR 寄存器了,只需要知道怎么调用 GPIO_ResetBits()函数就可以了。在对外设的工作原理有一定的了解之后,再去看标准外设库函数,基本上由函数名称就能得知这个函数的功能是什么、该怎么使用,这样开发就方便很多。 标准外设库自推出以来受到广大工程师推崇,现在很多工程师和公司还在使用标准外设库函数开发。不过,ST官方已经不再更新STM32标准外设库,而是力推新的HAL库。 例如,控制BSRR寄存器实现电平控制,官方 HAL 库封装了一个函数,如下。 void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState) { /* Check the parameters */ assert_param(IS_GPIO_PIN(GPIO_Pin)); assert_param(IS_GPIO_PIN_ACTION(PinState)); if (PinState != GPIO_PIN_RESET) { GPIOx->BSRR = GPIO_Pin; } else { GPIOx->BSRR = (uint32_t)GPIO_Pin << 16u; } } 这时不需要再直接操作BSRR寄存器了,只需要知道怎么使用HAL_GPIO_WritePin()这个函数就可以了。 HAL库和标准外设库一样,都是固件库,是由ST官方硬件抽象层而设计的软件函数包,由程序、数据结构和宏组成,包括了STM32所有外设的性能特征。这些固件库为开发者底层硬件提供了中间API,通过使用固件库,无须掌握底层细节,开发者就可以轻松应用每个外设。 HAL库和标准外设库本质上是一样的,都是提供底层硬件操作API,而且在使用上也是大同小异。有标准外设库基础的读者对 HAL 库的使用也很容易入手。ST官方之所以这几年大力推广HAL库,是因为HAL的结构更加容易整合STM32Cube,而STM32CubeMX是ST公司这几年极力推荐的程序生成开发工具。所以,这几年新出的 STM32 芯片,ST公司直接只提供 HAL 库。 在ST公司的官方声明中,HAL库是大势所趋。标准外设库和HAL库虽然都是对外设进行操作的函数库,但由于官方已经停止更新标准外设库,而且标准外设库在STM32创建工程和初始化时不能由STM32CubeMX软件代码生成使用,也就是说STM32CubeMX软件在生成代码时,工程项目和初始化代码就自动生成,这个工程项目和初始化代码里面使用的函数都是基于HAL库的。STM32CubeMX是一个图形化的工具,也是配置和初始化C代码生成器,与STM32CubeMX配合使用的是HAL库。 1) 外设句柄定义 用户代码的第一大部分是对于外设句柄的处理。在结构上,HAL库将每个外设抽象成一个名为ppp_HandleTypeDef的结构体,其中ppp就是每个外设的名字。所有函数都工作在ppp_HandleTypeDef指针之下。 (1) 多实例支持: 每个外设/模块实例都有自己的句柄,因此实例资源是独立的。 (2) 外围进程相互通信: 该句柄用于管理进程例程之间的共享数据资源。 2) 三种编程方式 HAL库对所有函数模型也进行了统一。在HAL库中,支持3种编程模式: 轮询模式、中断模式、DMA模式(如果外设支持),分别对应以下3种类型的函数(以ADC为例)。 HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc); HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef* hadc); HAL_StatusTypeDef HAL_ADC_Start_IT(ADC_HandleTypeDef*hadc); HAL_StatusTypeDef HAL_ADC_Stop_IT(ADC_HandleTypeDef* hadc); HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef*hadc,uint32_t* pData, uint32_t Length); HAL_StatusTypeDef HAL_ADC_Stop_DMA(ADC_HandleTypeDef*hadc); 其中,函数名中带_IT的表示工作在中断模式下; 带_DMA的工作在DMA模式下(注意DMA模式下也是开启中断的); 其他就是轮询模式(没有开启中断的)。至于使用何种方式,就看自己的选择了。 此外,新的HAL库架构下统一采用宏的形式对各种中断等进行配置(原来标准外设库一般都是各种函数)。针对每种外设,主要有以下宏。 __HAL_PPP_ENABLE_IT(__HANDLE__, __INTERRUPT__)//使能一个指定的外设中断 __HAL_PPP_DISABLE_IT(__HANDLE__, __INTERRUPT__)//失能一个指定的外设中断 __HAL_PPP_GET_IT (__HANDLE__, __INTERRUPT__)//获得一个指定的外设中断状态 __HAL_PPP_CLEAR_IT (__HANDLE__, __INTERRUPT__)//清除一个指定的外设的中断状态 __HAL_PPP_GET_FLAG (__HANDLE__, __FLAG__)//获取一个指定的外设的标志状态 __HAL_PPP_CLEAR_FLAG (__HANDLE__, __FLAG__)//清除一个指定的外设的标志状态 __HAL_PPP_ENABLE(__HANDLE__)//使能外设 __HAL_PPP_DISABLE(__HANDLE__)//失能外设 __HAL_PPP_XXXX (__HANDLE__, __PARAM__)//指定外设的宏定义 __HAL_PPP_GET IT_SOURCE (__HANDLE__, __INTERRUPT__)//检查中断源 3) 三大回调函数 在HAL库的源代码中,到处可见一些以__weak开头的函数,而且这些函数有些已经被实现了,如 __weak HAL_Status TypeDef HAL_InitTick(uint32_t TickPriority) { /*Configure the SysTick to have interrupt in 1ms time basis*/ HAL_SYSTICK_Config(SystemCoreClock/1000U); /*Configure the SysTick IRQ priority */ HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,OU); /* Return function status*/ return HAL_OK; } 有些则没有被实现,如 __weak void HAL_SPI_TxCpltCallback(SPI_Handle TypeDef*hspi) { /* Prevent unused argument(s) compilation warning */ UNUSED(hspi); /* NOTE:This function should not be modified, when the callback is needed,the HAL_SPI_TxCpltCallback should be implemented in the user file */ } 在HAL库中,很多回调函数前面使用__weak 修饰符,这里有必要讲解__weak修饰符的作用。 weak 顾名思义是“弱”的意思,所以如果函数名称前面加上__weak 修饰符,一般称这个函数为弱函数。加上了__weak 修饰符的函数,用户可以在用户文件中重新定义一个同名函数,最终编译器编译时,会选择用户定义的函数,如果用户没有重新定义这个函数,那么编译器就会执行__weak 声明的函数,并且编译器不会报错。 所有带有__weak关键字的函数表示,都可以由用户自己实现。如果出现了同名函数,且不带__weak关键字,那么连接器就会采用外部实现的同名函数。通常来说,HAL库负责整体处理和MCU外设的处理逻辑,并将必要部分以回调函数的形式提供给用户,用户只需要在对应的回调函数中修改即可。 HAL库包含以下3种用户级别回调函数(其中PPP为外设名)。 (1) 外设系统级初始化/解除初始化回调函数: HAL_PPP_MspInit()和 HAL_PPP_MspDeInit()。 例如,__weak void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi),在HAL_PPP_Init()函数中被调用,用来初始化底层相关的设备(GPIOs、Clock、DMA和Interrupt) (2) 处理完成回调函数: HAL_PPP_ProcessCpltCallback(),其中Process指具体某种处理,如UART的Tx。 例如,__weak void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi),当外设或DMA工作完成时触发中断,该回调函数会在外设中断处理函数或DMA的中断处理函数中被调用。 (3) 错误处理回调函数: HAL_PPP_ErrorCallback()。 例如,__weak void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi),当外设或DMA出现错误时触发中断,该回调函数会在外设中断处理函数或DMA的中断处理函数中被调用。 绝大多数用户代码均在以上3种回调函数中实现。 HAL库结构中,在每次初始化前(尤其是在多次调用初始化前),先调用对应的解除初始化(DeInit)函数是非常有必要的。某些外设多次初始化时不调用返回会导致初始化失败。完成回调函数有多种,如串口的完成回调函数有HAL_UART_TxCpltCallback()和 HAL_UART_TxHalfCpltCallback()等。 5. HAL库移植使用的基本步骤 HAL库移植使用的基本步骤如下。 (1) 复制stm32f4xx_hal_msp_template.c文件,参照该模板,依次实现用到的外设的HAL_PPP_MspInit()和 HAL_PPP_MspDeInit()函数。 (2) 复制stm32f4xx_hal_conf_template.h文件,用户可以在此文件中自由裁剪,配置HAL库。 (3) 在使用HAL库时,必须先调用HAL_StatusTypeDef HAL_Init(void)函数。该函数在stm32f4xx_hal.c文件中定义,也就意味着必须首先实现HAL_MspInit(void)和HAL_MspDeInit(void)函数。 (4) HAL库与标准外设库不同,HAL库使用RCC中的函数配置系统时钟,用户需要单独写时钟配置函数(标准外设库默认在system_stm32f4xx.c文件中)。 (5) 关于中断,HAL库提供了中断处理函数,只需要调用HAL库提供的中断处理函数。用户自己的代码,不建议先写到中断中,而应该写到HAL库提供的回调函数中。 (6) 对于每个外设,HAL库都提供了回调函数,回调函数用来实现用户自己的代码。整个调用结构由HAL库自己完成。 例如,UART中,HAL库提供了void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)函数,触发中断后,用户只需要调用该函数即可; 同时,自己的代码写在对应的回调函数中即可,如下所示。 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart); void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart); 综上所述,使用HAL库编写程序(针对某个外设)的基本结构(以串口为例)如下。 (1) 配置外设句柄。例如,建立UartConfig.c文件,在其中定义串口句柄 UART_HandleTypeDef huart,接着初始化句柄 HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef huart) (2) 编写MSP函数。例如,建立UartMsp.c文件,在其中实现void HAL_UART_MspInit(UART_HandleTypeDef huart)和void HAL_UART_MspDeInit(UART_HandleTypeDef* huart)函数。 (3) 实现对应的回调函数。例如,建立UartCallBack.c文件,在其中实现三大回调函数中的完成回调函数和错误回调函数。 3.4.2HAL库与标准外设库和LL库的区别 ST公司为开发者提供了非常方便的开发库。到目前为止,有标准外设库、HAL库和LL库3种。其中,标准外设库与HAL库是最常用的库,LL库只是最近新添加的库。 (1) 标准外设库(Standard Peripherals Library)是对STM32芯片的一个完整的封装。以前对芯片外设的操作都是直接操作寄存器,这使得开发非常困难。ST公司为了解决这个难题,给每款芯片都编写了一份库文件,也就是标准外设库。这也是目前使用最多的ST库,几乎均使用C语言实现。但是,标准外设库是针对某一系列芯片而言的,没有可移植性。 (2) HAL库是ST公司目前主推的开发方式。它的出现比标准外设库要晚,但其实和标准外设库一样,都是为了节省程序开发的时间,而且HAL库更有效。如果说标准外设库为了实现对芯片外设的操作功能,集成了需要配置的寄存器,那么HAL库的一些函数甚至做到了某些特定功能的集成。也就是说,同样的功能,标准库可能要用几句话,HAL库只需用一句话就够了,并且HAL库也很好地解决了程序移植的问题。 (3) LL(Low Layer)库是ST公司最近新增的库,与HAL库捆绑发布,文档也是和 HAL 库文档在一起的。例如,在STM32F3x 的 HAL库说明文档中,ST公司新增了LL库这一章节,但是在STM32F2x的HAL库文档中就没有。 LL库更接近硬件层,对需要复杂上层协议栈的外设不适用。LL库直接操作寄存器,支持所有外设。使用方法如下。 ① 独立使用。该库完全独立实现,可以完全抛开HAL库,只用LL库编程完成。在使用STM32CubeMX生成项目时,直接选LL库即可。如果使用了复杂的外设,如USB,则会调用HAL库。 ② 混合使用。与HAL库结合使用。 3.4.3回调函数 下面介绍回调函数的含义、使用,以及回调函数与普通函数的调用区别。 1. 什么是回调函数 回调函数就是一个被作为参数传递的函数。在C语言中,回调函数只能使用函数指针实现,在C++、Python、ECMAScript等更现代的编程语言中还可以使用仿函数或匿名函数。回调函数的使用可以大大提升编程的效率,这使得它在现代编程中应用广泛。同时,有一些需求必须要使用回调函数实现。 函数指针的调用,即是一个通过函数指针调用函数。如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,就说它是回调函数。 也就是说,把一段可执行的代码像传递参数那样传给其他代码,而这段代码会在某个时刻被调用执行,就叫作回调。如果代码立即被执行,就称为同步回调; 如果在之后晚些的某个时间再执行,则称为异步回调。 2. 为什么要使用回调函数 回调函数的作用是“解耦”,普通函数代替不了回调函数的这个作用,这是回调函数最大的特点。 #include #include // 回调函数 int Callback() { func(); return 0; } // 主程序 int main() { Library(Callback); return 0; } 3. 回调函数与普通函数的调用区别 (1) 在主程序中,把回调函数像参数一样传入库函数。这样一来,只要改变传入库函数的参数,就可以实现不同的功能,且不需要修改库函数的实现,变得很灵活,这就是解耦。 (2) 主函数和回调函数是在同一层的,而库函数在另外一层。如果库函数不可见,我们修改不了库函数的实现,也就是说,不能通过修改库函数让库函数调用普通函数,那么就只能通过传入不同的回调函数实现了,这也是在日常工作中常见的情况。 使用回调函数会有间接调用,因此会有一些额外的传参和访存开销,对于对时间要求较高的MCU代码要慎用。 回调函数的使用是对函数指针的应用,函数指针的概念本身很简单,但是把函数指针应用于回调函数就体现了一种解决问题的策略,一种设计系统的思想。 回调函数的缺点如下。 (1) 回调函数固然能解决一部分系统架构问题,但是绝不能在系统内到处都是。如果用户发现系统内到处都是回调函数,那么一定要重构系统。 (2) 回调函数本身是一种破坏系统结构的设计思路,回调函数会绝对地改变系统的运行轨迹、执行顺序和调用顺序。 3.4.4MSP的作用 MSP全称为 MCU Support Package,即MCU支持包。名称中带有MspInit的函数,它们的作用是进行 MCU 级别硬件初始化设置,并且通常会被上一层的初始化函数所调用,这样做的目的是把 MCU 相关的硬件初始化剥夺出来,方便用户代码在不同型号的 MCU 上移植。stm32f4xx_hal_msp.c 文件定义了两个函数: HAL_MspInit()和HAL_MspDeInit()。这两个函数分别被stm32f4xx_hal.c文件中的 HAL_Init()和HAL_DeInit()函数调用。HAL_MspInit()函数的主要作用是进行 MCU相关的硬件初始化操作。例如,要初始化某些硬件,可以将硬件相关的初始化配置写在HAL_MspDeinit()函数中。这样的话,在系统启动调用了HAL_Init()函数之后,会自动调用硬件初始化函数。 实际上,我们在工程模板中直接删掉 stm32f4xx_hal_msp.c 文件也不会对程序运行产生任何影响。 3.4.5HAL库的基本问题 下面介绍HAL库的基本数据类型和一些通用定义。 1. 基本数据类型 对STM32系列MCU编程使用的是C语言或C++语言。C语言整数类型的定义比较多,STM32编程中一般使用简化的定义符号,如表32所示。 表32 STM32编程中的数据类型简化定义符号 数 据 类 型 C语言等效定义 数据长度/B int8_t signed char 1 uint8_t unsigned char 1 int16_t signed short 2 uint16_t unsigned short 2 int32_t signed int 4 uint32_t unsigned int 4 int64_t long long int 8 uint64_t unsigned long long int 8 2. 一些通用定义 在HAL库中,有一些类型或常量是经常用到的,具体如下。 (1) stm32f4xx_hal_def.h文件中表示函数返回值类型的枚举类型HAL_StatusTypeDef,定义如下。 typedef enum { HAL_OK=0x000, HAL_ERROR=0x010, HAL_BUSY=0x02U, HAL_TIMEOUT=0x03U }HAL_StatusTypeDef; 很多函数返回值的类型是HAL_StatusTypeDef,以表示函数运行是否成功或处于其他状态。 (2) stm32f4xx.h文件中几个通用的枚举类型和常量,定义如下。 typedef enum { RESET = 0U, RESET=0U SET=!RESET }FlagStatus,ITStatus;//一般用于判断标志位是否置位 typedef enum { DISABLE=OU, ENABLE=!DISABLE }FunctionalState; //一般用于设置某个逻辑型参数的值 typedef enum { SUCCESS=0U, ERROR=!SUCCESS } ErrorStatus; //一般用于函数返回值,表示成功或失败两种状态